From 3034f6c8d2096330b5f652a7116b6e29a8d09ecf Mon Sep 17 00:00:00 2001 From: pagedmov Date: Thu, 6 Mar 2025 15:32:28 -0500 Subject: [PATCH] Implemented case statements --- src/builtin/cd.rs | 2 +- src/builtin/echo.rs | 2 +- src/builtin/pwd.rs | 2 +- src/execute/ifthen.rs | 27 - src/execute/loops.rs | 48 -- src/execute/mod.rs | 14 +- src/execute/shellcmd.rs | 149 +++++ src/expand/mod.rs | 43 +- src/libsh/error.rs | 5 +- src/parse/lex.rs | 99 +++- src/parse/mod.rs | 1179 ++++++++++++++++++++++++++++++++++++++- src/parse/parse.rs | 1014 --------------------------------- src/prelude.rs | 19 +- src/prompt/validate.rs | 15 +- src/shellenv/input.rs | 25 +- src/shellenv/shenv.rs | 20 +- src/shellenv/vars.rs | 3 + 17 files changed, 1528 insertions(+), 1138 deletions(-) delete mode 100644 src/execute/ifthen.rs delete mode 100644 src/execute/loops.rs create mode 100644 src/execute/shellcmd.rs delete mode 100644 src/parse/parse.rs diff --git a/src/builtin/cd.rs b/src/builtin/cd.rs index ca3e431..603cd1d 100644 --- a/src/builtin/cd.rs +++ b/src/builtin/cd.rs @@ -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(); diff --git a/src/builtin/echo.rs b/src/builtin/echo.rs index 7c91454..8b3b85f 100644 --- a/src/builtin/echo.rs +++ b/src/builtin/echo.rs @@ -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(); diff --git a/src/builtin/pwd.rs b/src/builtin/pwd.rs index 1e28d54..e07e3e2 100644 --- a/src/builtin/pwd.rs +++ b/src/builtin/pwd.rs @@ -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(); diff --git a/src/execute/ifthen.rs b/src/execute/ifthen.rs deleted file mode 100644 index c923244..0000000 --- a/src/execute/ifthen.rs +++ /dev/null @@ -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(()) -} diff --git a/src/execute/loops.rs b/src/execute/loops.rs deleted file mode 100644 index 0c40162..0000000 --- a/src/execute/loops.rs +++ /dev/null @@ -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(()) -} diff --git a/src/execute/mod.rs b/src/execute/mod.rs index 9b41f1e..3484634 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -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>(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, 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)?, diff --git a/src/execute/shellcmd.rs b/src/execute/shellcmd.rs new file mode 100644 index 0000000..5093dbe --- /dev/null +++ b/src/execute/shellcmd.rs @@ -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::>() + .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(()) +} diff --git a/src/expand/mod.rs b/src/expand/mod.rs index d563edc..f9612a7 100644 --- a/src/expand/mod.rs +++ b/src/expand/mod.rs @@ -13,25 +13,32 @@ pub fn expand_argv(argv: Vec, shenv: &mut ShEnv) -> Vec { 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 { + 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 diff --git a/src/libsh/error.rs b/src/libsh/error.rs index cc70a17..a236314 100644 --- a/src/libsh/error.rs +++ b/src/libsh/error.rs @@ -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); diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 0e54fc2..6b87954 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -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, _ => { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 7b05ed5..3ef328f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,2 +1,1179 @@ pub mod lex; -pub mod parse; + +use std::{iter::Peekable, str::FromStr}; + +use crate::prelude::*; + +use lex::{Span, TkRule, Token, KEYWORDS}; + +bitflags! { + #[derive(Debug,Clone,Copy,PartialEq,Eq)] + pub struct NdFlag: u32 { + const BACKGROUND = 0b00000000000000000000000000000001; + const FUNCTION = 0b00000000000000000000000000000010; + const BUILTIN = 0b00000000000000000000000000000100; + } +} + + +pub trait ParseRule { + /// Used for cases where a rule is optional + fn try_match(input: &[Token], shenv: &mut ShEnv) -> ShResult>; + /// Used for cases where a rule is assumed based on context + /// For instance, if the "for" keyword is encountered, then it *must* be a for loop + /// And if it isn't, return a parse error + fn assert_match(input: &[Token], shenv: &mut ShEnv) -> ShResult { + Self::try_match(input,shenv)?.ok_or_else(|| + ShErr::simple(ShErrKind::ParseErr, "Parse Error") + ) + } +} + +#[derive(Debug,Clone)] +pub enum CmdGuard { + And, + Or +} + + + +#[derive(Debug,Clone)] +pub struct Node { + node_rule: NdRule, + tokens: Vec, + span: Rc>, + flags: NdFlag, +} + +impl Node { + pub fn len(&self) -> usize { + self.tokens.len() + } + pub fn tokens(&self) -> &Vec { + &self.tokens + } + pub fn rule(&self) -> &NdRule { + &self.node_rule + } + pub fn rule_mut(&mut self) -> &mut NdRule { + &mut self.node_rule + } + pub fn into_rule(self) -> NdRule { + self.node_rule + } + pub fn span(&self) -> Rc> { + self.span.clone() + } + pub fn as_raw(&self, shenv: &mut ShEnv) -> String { + shenv.input_slice(self.span()).to_string() + } + pub fn flags(&self) -> NdFlag { + self.flags + } + pub fn flags_mut(&mut self) -> &mut NdFlag { + &mut self.flags + } +} + +#[derive(Clone,Debug)] +pub enum LoopKind { + While, + Until +} + +#[derive(Clone,Debug)] +pub enum NdRule { + Main { cmd_lists: Vec }, + Command { argv: Vec, redirs: Vec }, + Assignment { assignments: Vec, cmd: Option> }, + FuncDef { name: Token, body: Token }, + Case { pat: Token, blocks: Vec<(Token,Vec)>, redirs: Vec }, + IfThen { cond_blocks: Vec<(Vec,Vec)>, else_block: Option>, redirs: Vec }, + Loop { kind: LoopKind, cond: Vec, body: Vec, redirs: Vec }, + ForLoop { vars: Vec, arr: Vec, body: Vec, redirs: Vec }, + Subshell { body: Token, argv: Vec, redirs: Vec }, + CmdList { cmds: Vec<(Option,Node)> }, + Pipeline { cmds: Vec } +} + +/// Define a Node rule. The body of this macro becomes the implementation for the try_match() method for the rule. +macro_rules! ndrule_def { + ($name:ident,$shenv:ident,$try:expr) => { + #[derive(Debug)] + pub struct $name; + impl ParseRule for $name { + fn try_match(input: &[Token],shenv: &mut ShEnv) -> ShResult> { + $try(input,shenv) + } + } + }; +} + +/// This macro attempts to match all of the given Rules. It returns upon finding the first match, so the order matters +/// Place the most specialized/specific rules first, and the most general rules last +macro_rules! try_rules { + ($tokens:expr,$shenv:expr,$($name:ident),+) => { + $( + let result = $name::try_match($tokens,$shenv)?; + if let Some(node) = result { + return Ok(Some(node)) + } + )+ + return Ok(None) + }; +} + +#[derive(Debug)] +pub struct SynTree { + tree: VecDeque +} + +impl SynTree { + pub fn new() -> Self { + Self { tree: VecDeque::new() } + } + pub fn from_vec(nodes: Vec) -> Self { + Self { tree: VecDeque::from(nodes) } + } + pub fn push_node(&mut self, node: Node) { + self.tree.bpush(node) + } + pub fn next_node(&mut self) -> Option { + self.tree.fpop() + } +} + +pub struct Parser<'a> { + token_stream: Vec, + shenv: &'a mut ShEnv, + ast: SynTree +} + +impl<'a> Parser<'a> { + pub fn new(mut token_stream: Vec, shenv: &'a mut ShEnv) -> Self { + log!(TRACE, "New parser"); + token_stream.retain(|tk| !matches!(tk.rule(), TkRule::Whitespace | TkRule::Comment)); + Self { token_stream, shenv, ast: SynTree::new() } + } + + pub fn parse(mut self) -> ShResult { + log!(TRACE, "Starting parse"); + let mut lists = VecDeque::new(); + let token_slice = &*self.token_stream; + // Get the Main rule + if let Some(mut node) = Main::try_match(token_slice,self.shenv)? { + // Extract the inner lists + if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() { + while let Some(node) = cmd_lists.pop() { + lists.bpush(node) + } + } + } + while let Some(node) = lists.bpop() { + // Push inner command lists to self.ast + self.ast.push_node(node); + } + Ok(self.ast) + } +} + +pub fn get_span(toks: &Vec, shenv: &mut ShEnv) -> ShResult>> { + if toks.is_empty() { + Err(ShErr::simple(ShErrKind::InternalErr, "Get_span was given an empty token list")) + } else { + let start = toks.first().unwrap().span().borrow().start(); + let end = toks.iter().last().unwrap().span().borrow().end(); + let span = shenv.inputman_mut().new_span(start, end); + Ok(span) + } +} + +fn get_lists(mut tokens: &[Token], shenv: &mut ShEnv) -> (usize,Vec) { + let mut lists = vec![]; + let mut tokens_eaten = 0; + while !tokens.is_empty() { + match CmdList::try_match(tokens, shenv) { + Ok(Some(list)) => { + tokens_eaten += list.len(); + tokens = &tokens[list.len()..]; + lists.push(list); + } + Ok(None) | Err(_) => break + } + } + (tokens_eaten,lists) +} + +fn get_redir(token: Token, token_slice: &[Token], shenv: &mut ShEnv) -> ShResult<(usize,Redir)> { + let mut tokens_eaten = 0; + let mut tokens_iter = token_slice.into_iter(); + let redir_raw = shenv.input_slice(token.span()); + let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap(); + // If there isn't an FD target, get the next token and use it as the filename + if redir_bldr.tgt().is_none() { + if let Some(filename) = tokens_iter.next() { + // Make sure it's a word and not an operator or something + if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) { + let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); + let input = shenv.input_slice(token.span()).to_string(); + err.blame(input, token.span()); + return Err(err) + } + tokens_eaten += 1; + // Construct the Path object + let filename_raw = shenv.input_slice(filename.span()).to_string(); + let filename_path = PathBuf::from(filename_raw); + let tgt = RedirTarget::File(filename_path); + // Update the builder + redir_bldr = redir_bldr.with_tgt(tgt); + } else { + let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); + let input = shenv.input_slice(token.span()).to_string(); + err.blame(input, token.span()); + return Err(err) + } + } + Ok((tokens_eaten,redir_bldr.build())) +} + +// TODO: Redirs with FD sources appear to be looping endlessly for some reason + +ndrule_def!(Main, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + log!(TRACE, "Parsing main"); + let mut cmd_lists = vec![]; + let mut node_toks = vec![]; + let mut token_slice = &*tokens; + + while let Some(node) = CmdList::try_match(token_slice,shenv)? { + node_toks.extend(node.tokens().clone()); + token_slice = &token_slice[node.len()..]; + cmd_lists.push(node); + } + + if cmd_lists.is_empty() { + return Ok(None) + } + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::Main { cmd_lists }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + Ok(Some(node)) +}); + +ndrule_def!(CmdList, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + log!(TRACE, "Parsing cmdlist"); + let mut commands: Vec<(Option,Node)> = vec![]; + let mut node_toks = vec![]; + let mut token_slice = &*tokens; + let mut cmd_guard = None; // Operators like '&&' and '||' + + while let Some(mut node) = Expr::try_match(token_slice,shenv)? { + // Add sub-node tokens to our tokens + node_toks.extend(node.tokens().clone()); + // Reflect changes in the token slice + token_slice = &token_slice[node.len()..]; + // Push sub-node + if let NdRule::Command { argv, redirs: _ } = node.rule() { + if let Some(arg) = argv.first() { + let slice = shenv.input_slice(arg.span().clone()); + if BUILTINS.contains(&slice) { + *node.flags_mut() |= NdFlag::BUILTIN; + } + } + } + commands.push((cmd_guard.take(),node)); + + // If the next token is '&&' or '||' then we set cmd_guard and go again + if token_slice.first().is_some_and(|tk| matches!(tk.rule(),TkRule::AndOp | TkRule::OrOp)) { + let token = token_slice.first().unwrap(); + node_toks.push(token.clone()); + match token.rule() { + TkRule::AndOp => cmd_guard = Some(CmdGuard::And), + TkRule::OrOp => cmd_guard = Some(CmdGuard::Or), + _ => unreachable!() + } + token_slice = &token_slice[1..]; + } else { + break + } + } + if node_toks.is_empty() { + return Ok(None) + } + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::CmdList { cmds: commands }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + Ok(Some(node)) +}); + +ndrule_def!(Expr, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + try_rules!(tokens, shenv, + ShellCmd, + Pipeline, + Subshell, + Assignment, + Command + ); +}); +// Used in pipelines to avoid recursion +ndrule_def!(ExprNoPipeline, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + try_rules!(tokens, shenv, + ShellCmd, + Subshell, + Assignment, + Command + ); +}); + +ndrule_def!(ShellCmd, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + try_rules!(tokens, shenv, + Case, + ForLoop, + IfThen, + Loop, + FuncDef + ); +}); + +ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + log!(DEBUG, tokens); + let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { + ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) + }; + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut pat: Option = None; + let mut blocks = vec![]; + let mut redirs = vec![]; + + if let Some(token) = tokens_iter.next() { + if let TkRule::Case = token.rule() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } else { return Ok(None) } + } else { return Ok(None) } + + while let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + match token.rule() { + TkRule::Whitespace => continue, + TkRule::Ident => { + pat = Some(token.clone()); + break + } + _ => return Err(err("Expected an ident in case statement", token.span(), shenv)) + } + } + + if pat.is_none() { + return Err(err("Expected an ident in case statement", node_toks.last().unwrap().span(), shenv)) + } + let pat = pat.unwrap(); + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + match token.rule() { + TkRule::Whitespace => continue, + TkRule::Ident => { + if token.as_raw(shenv) != "in" { + return Err(err("Expected `in` after case statement pattern", token.span(), shenv)) + } else { + closed = true; + } + } + TkRule::Sep => { + if closed { + break + } + } + _ => return Err(err("Expected `in` after case statement pattern", token.span(), shenv)) + } + } + + if tokens_iter.peek().is_none() { + return Err(err("Expected `in` after case statement pattern", node_toks.last().unwrap().span(), shenv)) + } + + let mut closed = false; + loop { + if tokens.is_empty() { + break + } + if let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::CasePat => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + let block_pat = token.clone(); + let (used,lists) = get_lists(tokens, shenv); + let mut lists_iter = lists.iter().peekable(); + log!(DEBUG, used); + log!(DEBUG, lists); + while let Some(list) = lists_iter.next() { + node_toks.extend(list.tokens.clone()); + if lists_iter.peek().is_none() { + log!(DEBUG, list); + for token in list.tokens() { + log!(DEBUG, "{}", token.as_raw(shenv)); + } + } + if let Some(token) = list.tokens().last() { + if lists_iter.peek().is_none() && (token.rule() != TkRule::Sep || token.as_raw(shenv).trim() != ";;") { + log!(ERROR, "{:?}",list.tokens()); + log!(ERROR, token); + log!(ERROR, "{}",token.as_raw(shenv).trim()); + return Err(err("Expected `;;` after case block", token.span(), shenv)) + } + } + } + tokens = &tokens[used..]; + tokens_iter = tokens.iter().peekable(); + blocks.push((block_pat,lists)); + } + TkRule::Esac => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + closed = true; + } + TkRule::Sep => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { + break + } + } + TkRule::RedirOp if closed => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + let (used,redir) = get_redir(token.clone(), tokens, shenv)?; + for _ in 0..used { + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + } + } + tokens = &tokens[used..]; + redirs.push(redir); + } + _ => { + log!(DEBUG, token); + return Err(err("Expected `esac` or a case block here", node_toks.last().unwrap().span(), shenv)) + } + } + } + } + + if !closed { + return Err(err("Expected `esac` after case statement", node_toks.last().unwrap().span(), shenv)) + } + + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::Case { pat, blocks, redirs }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + + Ok(Some(node)) +}); + +ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { + ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) + }; + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut vars = vec![]; + let mut arr = vec![]; + let mut redirs = vec![]; + let body: Vec; + + if let Some(token) = tokens_iter.next() { + if let TkRule::For = token.rule() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } else { return Ok(None) } + } else { return Ok(None) } + + while let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if let TkRule::Ident = token.rule() { + if token.as_raw(shenv) == "in" { break } + vars.push(token.clone()); + } else { + 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)) + } + 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() { + arr.push(token.clone()); + } else { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop array",span,shenv)) + } + } + if arr.is_empty() { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop array",span,shenv)) + } + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + TkRule::Do => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + closed = true; + } + _ => { + if closed { break } + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `do` after loop condition",span,shenv)) + } + } + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + body = lists; + tokens_iter = tokens.iter().peekable(); + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Done => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + closed = true; + } + TkRule::Sep => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + TkRule::RedirOp if closed => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + let (used,redir) = get_redir(token.clone(), tokens, shenv)?; + for _ in 0..used { + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + } + } + tokens = &tokens[used..]; + redirs.push(redir); + } + _ => { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `done` after for loop",span,shenv)) + } + } + } + + if !closed { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `done` after for loop",span,shenv)) + } + + let span = get_span(&node_toks, shenv)?; + let node = Node { + node_rule: NdRule::ForLoop { vars, arr, body, redirs }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + + Ok(Some(node)) +}); + +ndrule_def!(IfThen, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + log!(DEBUG,tokens); + let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { + ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) + }; + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut cond_blocks = vec![]; + let mut redirs = vec![]; + let mut else_block: Option> = None; + + log!(DEBUG,tokens); + if let Some(token) = tokens_iter.next() { + if let TkRule::If = token.rule() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } else { return Ok(None) } + } else { return Ok(None) } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let cond = lists; + tokens_iter = tokens.iter().peekable(); + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Then => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + closed = true; + } + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + _ => { + if closed { break } + log!(ERROR, token); + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `then` after if statement condition",span,shenv)) + } + } + } + + if tokens_iter.peek().is_none() { + let span = get_span(&node_toks,shenv)?; + return Err(err("Failed to parse this if statement",span,shenv)) + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let body = lists; + tokens_iter = tokens.iter().peekable(); + cond_blocks.push((cond,body)); + + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Elif => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let cond = lists; + tokens_iter = tokens.iter().peekable(); + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Then => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + closed = true; + } + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + _ => { + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `then` after if statement condition",span,shenv)) + } + } + } + + if tokens_iter.peek().is_none() { + let span = get_span(&node_toks,shenv)?; + return Err(err("Failed to parse this if statement",span,shenv)) + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let body = lists; + tokens_iter = tokens.iter().peekable(); + cond_blocks.push((cond,body)); + } + TkRule::Else => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + else_block = Some(lists); + tokens_iter = tokens.iter().peekable(); + } + TkRule::Fi => { + closed = true; + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + TkRule::Sep => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + TkRule::RedirOp if closed => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + let (used,redir) = get_redir(token.clone(), tokens, shenv)?; + for _ in 0..used { + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + } + } + tokens = &tokens[used..]; + redirs.push(redir); + } + _ => { + let span = get_span(&node_toks, shenv)?; + return Err(err(&format!("Unexpected token in if statement: {:?}",token.rule()),span,shenv)) + } + } + } + + if !closed { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `fi` to close if statement",span,shenv)) + } + + let span = get_span(&node_toks, shenv)?; + let node = Node { + node_rule: NdRule::IfThen { cond_blocks, else_block, redirs }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + + Ok(Some(node)) +}); + +ndrule_def!(Loop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { + ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) + }; + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut redirs = vec![]; + let kind: LoopKind; + let cond: Vec; + let body: Vec; + + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + match token.rule() { + TkRule::While => { + kind = LoopKind::While + } + TkRule::Until => { + kind = LoopKind::Until + } + _ => return Ok(None) + } + } else { return Ok(None) } + tokens = &tokens[1..]; + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + cond = lists; + tokens_iter = tokens.iter().peekable(); + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + TkRule::Do => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + closed = true; + } + _ => { + if closed { break } + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `do` after loop condition",span,shenv)) + } + } + } + + if tokens_iter.peek().is_none() { + return Ok(None) + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + body = lists; + tokens_iter = tokens.iter().peekable(); + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + TkRule::Done => { + closed = true; + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + TkRule::RedirOp if closed => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + let (used,redir) = get_redir(token.clone(), tokens, shenv)?; + for _ in 0..used { + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + } + } + tokens = &tokens[used..]; + redirs.push(redir); + } + _ => { + let span = get_span(&node_toks,shenv)?; + return Err(err("Unexpected token in loop",span,shenv)) + } + } + } + + if !closed { + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `done` to close loop",span,shenv)) + } + + let span = get_span(&node_toks, shenv)?; + let node = Node { + node_rule: NdRule::Loop { kind, cond, body, redirs }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + + Ok(Some(node)) +}); + +ndrule_def!(FuncDef, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + let mut tokens_iter = tokens.iter(); + let mut node_toks = vec![]; + let name: Token; + let body: Token; + + if let Some(token) = tokens_iter.next() { + if let TkRule::FuncName = token.rule() { + node_toks.push(token.clone()); + name = token.clone(); + } else { + return Ok(None) + } + } else { + return Ok(None) + } + + if let Some(token) = tokens_iter.next() { + if let TkRule::BraceGrp = token.rule() { + node_toks.push(token.clone()); + body = token.clone(); + } else { + return Ok(None) + } + } else { + return Ok(None) + } + + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::FuncDef { name, body }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + Ok(Some(node)) +}); + +ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + let mut tokens_iter = tokens.into_iter(); + let mut node_toks = vec![]; + let mut argv = vec![]; + let mut redirs = vec![]; + if let Some(token) = tokens_iter.next() { + if let TkRule::Subshell = token.rule() { + node_toks.push(token.clone()); + let body = token.clone(); + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::AndOp | + TkRule::OrOp | + TkRule::PipeOp | + TkRule::ErrPipeOp => { + break + } + TkRule::Sep => { + node_toks.push(token.clone()); + break; + } + TkRule::Ident | + TkRule::SQuote | + TkRule::DQuote | + TkRule::Assign | + TkRule::TildeSub | + TkRule::VarSub => { + node_toks.push(token.clone()); + argv.push(token.clone()); + } + TkRule::RedirOp => { + node_toks.push(token.clone()); + let slice = &tokens_iter.clone().map(|tk| tk.clone()).collect::>(); + let (used,redir) = get_redir(token.clone(), slice, shenv)?; + for _ in 0..used { + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + } + } + redirs.push(redir); + } + _ => break + } + } + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::Subshell { body, argv, redirs }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + return Ok(Some(node)) + } else { + return Ok(None) + } + } + Ok(None) +}); + +ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + log!(TRACE, "Parsing pipeline"); + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut cmds = vec![]; + + while let Some(token) = tokens_iter.peek() { + match token.rule() { + TkRule::AndOp | TkRule::OrOp => { + // If there are no commands or only one, this isn't a pipeline + match cmds.len() { + 0 | 1 => return Ok(None), + _ => break + } + } + _ => { /* Keep going */ } + } + if let Some(mut cmd) = ExprNoPipeline::try_match(tokens,shenv)? { + // Add sub-node's tokens to our tokens + node_toks.extend(cmd.tokens().clone()); + + // Reflect changes in tokens and tokens_iter + tokens = &tokens[cmd.len()..]; + for _ in 0..cmd.len() { + tokens_iter.next(); + } + + if let NdRule::Command { argv, redirs: _ } = cmd.rule() { + if let Some(arg) = argv.first() { + let slice = shenv.input_slice(arg.span().clone()); + if BUILTINS.contains(&slice) { + *cmd.flags_mut() |= NdFlag::BUILTIN; + } + } + } + // Push sub-node + cmds.push(cmd); + + if tokens_iter.peek().is_none_or(|tk| !matches!(tk.rule(),TkRule::PipeOp | TkRule::ErrPipeOp)) { + match cmds.len() { + 0 | 1 => { + return Ok(None) + } + _ => break + } + } else { + if tokens_iter.peek().is_some() { + node_toks.push(tokens_iter.next().unwrap().clone()); + tokens = &tokens[1..]; + continue + } else { + match cmds.len() { + 0 | 1 => return Ok(None), + _ => break + } + } + } + } else { + match cmds.len() { + 0 | 1 => return Ok(None), + _ => break + } + } + } + if node_toks.is_empty() { + return Ok(None) + } + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::Pipeline { cmds }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + Ok(Some(node)) +}); + +ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + log!(TRACE, "Parsing command"); + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut argv = vec![]; + let mut redirs = vec![]; + + while let Some(token) = tokens_iter.peek() { + match token.rule() { + TkRule::AndOp | TkRule::OrOp | TkRule::PipeOp | TkRule::ErrPipeOp => { + break + } + _ => { /* Keep going */ } + } + let token = tokens_iter.next().unwrap(); + node_toks.push(token.clone()); + match token.rule() { + TkRule::Ident | + TkRule::SQuote | + TkRule::DQuote | + TkRule::Assign | + TkRule::TildeSub | + TkRule::VarSub => { + argv.push(token.clone()); + } + TkRule::RedirOp => { + let slice = &tokens_iter.clone().map(|tk| tk.clone()).collect::>(); + let (used,redir) = get_redir(token.clone(), slice, shenv)?; + for _ in 0..used { + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + } + } + redirs.push(redir); + } + TkRule::Sep => break, + _ => return Err( + ShErr::full( + ShErrKind::ParseErr, + format!("Unexpected token in command rule: {:?}", token.rule()), + shenv.get_input(), + get_span(&node_toks,shenv)? + ) + ) + } + } + if node_toks.is_empty() { + return Ok(None) + } + let span = get_span(&node_toks,shenv)?; + if !argv.is_empty() { + let node = Node { + node_rule: NdRule::Command { argv, redirs }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + Ok(Some(node)) + } else { + Ok(None) + } +}); + +ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + log!(TRACE, "Parsing assignment"); + let mut tokens = tokens.into_iter().peekable(); + let mut node_toks = vec![]; + let mut assignments = vec![]; + while tokens.peek().is_some_and(|tk| tk.rule() == TkRule::Assign) { + let token = tokens.next().unwrap(); + node_toks.push(token.clone()); + assignments.push(token.clone()); + } + if assignments.is_empty() { + return Ok(None) + } + + if tokens.peek().is_some() { + let tokens_vec: Vec = tokens.into_iter().map(|token| token.clone()).collect(); + let tokens_slice = &tokens_vec; + let cmd = Command::try_match(tokens_slice,shenv)?.map(|cmd| Box::new(cmd)); + if let Some(ref cmd) = cmd { + node_toks.extend(cmd.tokens().clone()); + } + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::Assignment { assignments, cmd }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + return Ok(Some(node)) + } else { + let span = get_span(&node_toks,shenv)?; + let node = Node { + node_rule: NdRule::Assignment { assignments, cmd: None }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + Ok(Some(node)) + } +}); diff --git a/src/parse/parse.rs b/src/parse/parse.rs deleted file mode 100644 index 3b87dd2..0000000 --- a/src/parse/parse.rs +++ /dev/null @@ -1,1014 +0,0 @@ -use std::{iter::Peekable, str::FromStr}; - -use crate::prelude::*; - -use super::lex::{Span, TkRule, Token, KEYWORDS}; - -bitflags! { - #[derive(Debug,Clone,Copy,PartialEq,Eq)] - pub struct NdFlag: u32 { - const BACKGROUND = 0b00000000000000000000000000000001; - const FUNCTION = 0b00000000000000000000000000000010; - const BUILTIN = 0b00000000000000000000000000000100; - } -} - - -pub trait ParseRule { - /// Used for cases where a rule is optional - fn try_match(input: &[Token], shenv: &mut ShEnv) -> ShResult>; - /// Used for cases where a rule is assumed based on context - /// For instance, if the "for" keyword is encountered, then it *must* be a for loop - /// And if it isn't, return a parse error - fn assert_match(input: &[Token], shenv: &mut ShEnv) -> ShResult { - Self::try_match(input,shenv)?.ok_or_else(|| - ShErr::simple(ShErrKind::ParseErr, "Parse Error") - ) - } -} - -#[derive(Debug,Clone)] -pub enum CmdGuard { - And, - Or -} - - - -#[derive(Debug,Clone)] -pub struct Node { - node_rule: NdRule, - tokens: Vec, - span: Rc>, - flags: NdFlag, -} - -impl Node { - pub fn len(&self) -> usize { - self.tokens.len() - } - pub fn tokens(&self) -> &Vec { - &self.tokens - } - pub fn rule(&self) -> &NdRule { - &self.node_rule - } - pub fn rule_mut(&mut self) -> &mut NdRule { - &mut self.node_rule - } - pub fn into_rule(self) -> NdRule { - self.node_rule - } - pub fn span(&self) -> Rc> { - self.span.clone() - } - pub fn as_raw(&self, shenv: &mut ShEnv) -> String { - shenv.input_slice(self.span()).to_string() - } - pub fn flags(&self) -> NdFlag { - self.flags - } - pub fn flags_mut(&mut self) -> &mut NdFlag { - &mut self.flags - } -} - -#[derive(Clone,Debug)] -pub enum LoopKind { - While, - Until -} - -#[derive(Clone,Debug)] -pub enum NdRule { - Main { cmd_lists: Vec }, - Command { argv: Vec, redirs: Vec }, - Assignment { assignments: Vec, cmd: Option> }, - FuncDef { name: Token, body: Token }, - IfThen { cond_blocks: Vec<(Vec,Vec)>, else_block: Option>, redirs: Vec }, - Loop { kind: LoopKind, cond: Vec, body: Vec, redirs: Vec }, - ForLoop { vars: Vec, arr: Vec, body: Vec, redirs: Vec }, - Subshell { body: Token, argv: Vec, redirs: Vec }, - CmdList { cmds: Vec<(Option,Node)> }, - Pipeline { cmds: Vec } -} - -/// Define a Node rule. The body of this macro becomes the implementation for the try_match() method for the rule. -macro_rules! ndrule_def { - ($name:ident,$shenv:ident,$try:expr) => { - #[derive(Debug)] - pub struct $name; - impl ParseRule for $name { - fn try_match(input: &[Token],shenv: &mut ShEnv) -> ShResult> { - $try(input,shenv) - } - } - }; -} - -/// This macro attempts to match all of the given Rules. It returns upon finding the first match, so the order matters -/// Place the most specialized/specific rules first, and the most general rules last -macro_rules! try_rules { - ($tokens:expr,$shenv:expr,$($name:ident),+) => { - $( - let result = $name::try_match($tokens,$shenv)?; - if let Some(node) = result { - return Ok(Some(node)) - } - )+ - return Ok(None) - }; -} - -#[derive(Debug)] -pub struct SynTree { - tree: VecDeque -} - -impl SynTree { - pub fn new() -> Self { - Self { tree: VecDeque::new() } - } - pub fn from_vec(nodes: Vec) -> Self { - Self { tree: VecDeque::from(nodes) } - } - pub fn push_node(&mut self, node: Node) { - self.tree.bpush(node) - } - pub fn next_node(&mut self) -> Option { - self.tree.fpop() - } -} - -pub struct Parser<'a> { - token_stream: Vec, - shenv: &'a mut ShEnv, - ast: SynTree -} - -impl<'a> Parser<'a> { - pub fn new(mut token_stream: Vec, shenv: &'a mut ShEnv) -> Self { - log!(TRACE, "New parser"); - token_stream.retain(|tk| !matches!(tk.rule(), TkRule::Whitespace | TkRule::Comment)); - Self { token_stream, shenv, ast: SynTree::new() } - } - - pub fn parse(mut self) -> ShResult { - log!(TRACE, "Starting parse"); - let mut lists = VecDeque::new(); - let token_slice = &*self.token_stream; - // Get the Main rule - if let Some(mut node) = Main::try_match(token_slice,self.shenv)? { - // Extract the inner lists - if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() { - while let Some(node) = cmd_lists.pop() { - lists.bpush(node) - } - } - } - while let Some(node) = lists.bpop() { - // Push inner command lists to self.ast - self.ast.push_node(node); - } - Ok(self.ast) - } -} - -pub fn get_span(toks: &Vec, shenv: &mut ShEnv) -> ShResult>> { - if toks.is_empty() { - Err(ShErr::simple(ShErrKind::InternalErr, "Get_span was given an empty token list")) - } else { - let start = toks.first().unwrap().span().borrow().start(); - let end = toks.iter().last().unwrap().span().borrow().end(); - let span = shenv.inputman_mut().new_span(start, end); - Ok(span) - } -} - -fn get_lists(mut tokens: &[Token], shenv: &mut ShEnv) -> (usize,Vec) { - let mut lists = vec![]; - let mut tokens_eaten = 0; - while !tokens.is_empty() { - match CmdList::try_match(tokens, shenv) { - Ok(Some(list)) => { - tokens_eaten += list.len(); - tokens = &tokens[list.len()..]; - lists.push(list); - } - Ok(None) | Err(_) => break - } - } - (tokens_eaten,lists) -} - -fn get_redir(token: Token, token_slice: &[Token], shenv: &mut ShEnv) -> ShResult<(usize,Redir)> { - let mut tokens_eaten = 0; - let mut tokens_iter = token_slice.into_iter(); - let redir_raw = shenv.input_slice(token.span()); - let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap(); - // If there isn't an FD target, get the next token and use it as the filename - if redir_bldr.tgt().is_none() { - if let Some(filename) = tokens_iter.next() { - // Make sure it's a word and not an operator or something - if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) { - let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); - let input = shenv.input_slice(token.span()).to_string(); - err.blame(input, token.span()); - return Err(err) - } - tokens_eaten += 1; - // Construct the Path object - let filename_raw = shenv.input_slice(filename.span()).to_string(); - let filename_path = PathBuf::from(filename_raw); - let tgt = RedirTarget::File(filename_path); - // Update the builder - redir_bldr = redir_bldr.with_tgt(tgt); - } else { - let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); - let input = shenv.input_slice(token.span()).to_string(); - err.blame(input, token.span()); - return Err(err) - } - } - Ok((tokens_eaten,redir_bldr.build())) -} - -// TODO: Redirs with FD sources appear to be looping endlessly for some reason - -ndrule_def!(Main, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - log!(TRACE, "Parsing main"); - let mut cmd_lists = vec![]; - let mut node_toks = vec![]; - let mut token_slice = &*tokens; - - while let Some(node) = CmdList::try_match(token_slice,shenv)? { - node_toks.extend(node.tokens().clone()); - token_slice = &token_slice[node.len()..]; - cmd_lists.push(node); - } - - if cmd_lists.is_empty() { - return Ok(None) - } - let span = get_span(&node_toks,shenv)?; - let node = Node { - node_rule: NdRule::Main { cmd_lists }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - Ok(Some(node)) -}); - -ndrule_def!(CmdList, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - log!(TRACE, "Parsing cmdlist"); - let mut commands: Vec<(Option,Node)> = vec![]; - let mut node_toks = vec![]; - let mut token_slice = &*tokens; - let mut cmd_guard = None; // Operators like '&&' and '||' - - while let Some(mut node) = Expr::try_match(token_slice,shenv)? { - // Add sub-node tokens to our tokens - node_toks.extend(node.tokens().clone()); - // Reflect changes in the token slice - log!(DEBUG, token_slice); - token_slice = &token_slice[node.len()..]; - log!(DEBUG, token_slice); - // Push sub-node - if let NdRule::Command { argv, redirs: _ } = node.rule() { - if let Some(arg) = argv.first() { - let slice = shenv.input_slice(arg.span().clone()); - if BUILTINS.contains(&slice) { - *node.flags_mut() |= NdFlag::BUILTIN; - } - } - } - commands.push((cmd_guard.take(),node)); - - // If the next token is '&&' or '||' then we set cmd_guard and go again - if token_slice.first().is_some_and(|tk| matches!(tk.rule(),TkRule::AndOp | TkRule::OrOp)) { - let token = token_slice.first().unwrap(); - node_toks.push(token.clone()); - match token.rule() { - TkRule::AndOp => cmd_guard = Some(CmdGuard::And), - TkRule::OrOp => cmd_guard = Some(CmdGuard::Or), - _ => unreachable!() - } - token_slice = &token_slice[1..]; - } else { - break - } - } - if node_toks.is_empty() { - return Ok(None) - } - let span = get_span(&node_toks,shenv)?; - let node = Node { - node_rule: NdRule::CmdList { cmds: commands }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - Ok(Some(node)) -}); - -ndrule_def!(Expr, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - try_rules!(tokens, shenv, - ShellCmd, - Pipeline, - Subshell, - Assignment, - Command - ); -}); -// Used in pipelines to avoid recursion -ndrule_def!(ExprNoPipeline, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - try_rules!(tokens, shenv, - ShellCmd, - Subshell, - Assignment, - Command - ); -}); - -ndrule_def!(ShellCmd, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - try_rules!(tokens, shenv, - IfThen, - Loop, - FuncDef - ); -}); - -ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { - let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { - ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) - }; - let mut tokens_iter = tokens.iter().peekable(); - let mut node_toks = vec![]; - let mut vars = vec![]; - let mut arr = vec![]; - let mut redirs = vec![]; - let body: Vec; - - if let Some(token) = tokens_iter.next() { - if let TkRule::For = token.rule() { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } else { return Ok(None) } - } else { return Ok(None) } - - while let Some(token) = tokens_iter.next() { - if let TkRule::Ident = token.rule() { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - if token.as_raw(shenv) == "in" { break } - vars.push(token.clone()); - } else { - 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)) - } - while let Some(token) = tokens_iter.next() { - if let TkRule::Ident = token.rule() { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - if token.rule() == TkRule::Sep { break } - arr.push(token.clone()); - } else { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected an ident in for loop array",span,shenv)) - } - } - if arr.is_empty() { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected an ident in for loop array",span,shenv)) - } - - if let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - if token.rule() != TkRule::Do { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected `do` after for loop array",span,shenv)) - } - } else { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected `do` after for loop array",span,shenv)) - } - - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - body = lists; - tokens_iter = tokens.iter().peekable(); - - let mut closed = false; - while let Some(token) = tokens_iter.next() { - match token.rule() { - TkRule::Done => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - closed = true; - } - TkRule::Sep => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - if closed { break } - } - TkRule::RedirOp if closed => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - let (used,redir) = get_redir(token.clone(), tokens, shenv)?; - for _ in 0..used { - if let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - } - } - tokens = &tokens[used..]; - redirs.push(redir); - } - _ => { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected `done` after for loop",span,shenv)) - } - } - } - - if !closed { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected `done` after for loop",span,shenv)) - } - - let span = get_span(&node_toks, shenv)?; - let node = Node { - node_rule: NdRule::ForLoop { vars, arr, body, redirs }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - - Ok(Some(node)) -}); - -ndrule_def!(IfThen, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { - let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { - ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) - }; - let mut tokens_iter = tokens.iter().peekable(); - let mut node_toks = vec![]; - let mut cond_blocks = vec![]; - let mut redirs = vec![]; - let mut else_block: Option> = None; - - if let Some(token) = tokens_iter.next() { - if let TkRule::If = token.rule() { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } else { return Ok(None) } - } else { return Ok(None) } - - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - let cond = lists; - tokens_iter = tokens.iter().peekable(); - - while let Some(token) = tokens_iter.next() { - match token.rule() { - TkRule::Then => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - break - } - TkRule::Sep | TkRule::Whitespace => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } - _ => { - let span = get_span(&node_toks,shenv)?; - return Err(err("Expected `then` after if statement condition",span,shenv)) - } - } - } - - if tokens_iter.peek().is_none() { - let span = get_span(&node_toks,shenv)?; - return Err(err("Failed to parse this if statement",span,shenv)) - } - - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - let body = lists; - tokens_iter = tokens.iter().peekable(); - cond_blocks.push((cond,body)); - - - let mut closed = false; - while let Some(token) = tokens_iter.next() { - match token.rule() { - TkRule::Elif => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - let cond = lists; - tokens_iter = tokens.iter().peekable(); - - while let Some(token) = tokens_iter.next() { - match token.rule() { - TkRule::Then => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - break - } - TkRule::Sep | TkRule::Whitespace => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } - _ => { - let span = get_span(&node_toks,shenv)?; - return Err(err("Expected `then` after if statement condition",span,shenv)) - } - } - } - - if tokens_iter.peek().is_none() { - let span = get_span(&node_toks,shenv)?; - return Err(err("Failed to parse this if statement",span,shenv)) - } - - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - let body = lists; - tokens_iter = tokens.iter().peekable(); - cond_blocks.push((cond,body)); - } - TkRule::Else => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - else_block = Some(lists); - tokens_iter = tokens.iter().peekable(); - } - TkRule::Fi => { - closed = true; - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } - TkRule::Whitespace => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } - TkRule::Sep => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - if closed { break } - } - TkRule::RedirOp if closed => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - let (used,redir) = get_redir(token.clone(), tokens, shenv)?; - for _ in 0..used { - if let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - } - } - tokens = &tokens[used..]; - log!(DEBUG,redir); - log!(DEBUG,tokens); - redirs.push(redir); - } - _ => { - let span = get_span(&node_toks, shenv)?; - return Err(err(&format!("Unexpected token in if statement: {:?}",token.rule()),span,shenv)) - } - } - } - - if !closed { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected `fi` to close if statement",span,shenv)) - } - - let span = get_span(&node_toks, shenv)?; - let node = Node { - node_rule: NdRule::IfThen { cond_blocks, else_block, redirs }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - - Ok(Some(node)) -}); - -ndrule_def!(Loop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { - let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { - ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) - }; - let mut tokens_iter = tokens.iter().peekable(); - let mut node_toks = vec![]; - let mut redirs = vec![]; - let kind: LoopKind; - let cond: Vec; - let body: Vec; - - if let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - match token.rule() { - TkRule::While => { - kind = LoopKind::While - } - TkRule::Until => { - kind = LoopKind::Until - } - _ => return Ok(None) - } - } else { return Ok(None) } - tokens = &tokens[1..]; - - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - cond = lists; - tokens_iter = tokens.iter().peekable(); - - while let Some(token) = tokens_iter.next() { - match token.rule() { - TkRule::Sep | TkRule::Whitespace => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } - TkRule::Do => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - break - } - _ => { - let span = get_span(&node_toks,shenv)?; - return Err(err("Expected `do` after loop condition",span,shenv)) - } - } - } - - if tokens_iter.peek().is_none() { - return Ok(None) - } - - let (used,lists) = get_lists(tokens, shenv); - for list in &lists { - node_toks.extend(list.tokens().clone()); - } - tokens = &tokens[used..]; - body = lists; - tokens_iter = tokens.iter().peekable(); - - let mut closed = false; - while let Some(token) = tokens_iter.next() { - match token.rule() { - TkRule::Sep | TkRule::Whitespace => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - if closed { break } - } - TkRule::Done => { - closed = true; - node_toks.push(token.clone()); - tokens = &tokens[1..]; - } - TkRule::RedirOp if closed => { - node_toks.push(token.clone()); - tokens = &tokens[1..]; - let (used,redir) = get_redir(token.clone(), tokens, shenv)?; - for _ in 0..used { - if let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - } - } - tokens = &tokens[used..]; - redirs.push(redir); - } - _ => { - let span = get_span(&node_toks,shenv)?; - return Err(err("Unexpected token in loop",span,shenv)) - } - } - } - - if !closed { - let span = get_span(&node_toks,shenv)?; - return Err(err("Expected `done` to close loop",span,shenv)) - } - - let span = get_span(&node_toks, shenv)?; - let node = Node { - node_rule: NdRule::Loop { kind, cond, body, redirs }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - - log!(DEBUG, node); - Ok(Some(node)) -}); - -ndrule_def!(FuncDef, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - let mut tokens_iter = tokens.iter(); - let mut node_toks = vec![]; - let name: Token; - let body: Token; - - if let Some(token) = tokens_iter.next() { - if let TkRule::FuncName = token.rule() { - node_toks.push(token.clone()); - name = token.clone(); - } else { - return Ok(None) - } - } else { - return Ok(None) - } - - if let Some(token) = tokens_iter.next() { - if let TkRule::BraceGrp = token.rule() { - node_toks.push(token.clone()); - body = token.clone(); - } else { - return Ok(None) - } - } else { - return Ok(None) - } - - let span = get_span(&node_toks,shenv)?; - let node = Node { - node_rule: NdRule::FuncDef { name, body }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - Ok(Some(node)) -}); - -ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - let mut tokens_iter = tokens.into_iter(); - let mut node_toks = vec![]; - let mut argv = vec![]; - let mut redirs = vec![]; - if let Some(token) = tokens_iter.next() { - if let TkRule::Subshell = token.rule() { - node_toks.push(token.clone()); - let body = token.clone(); - while let Some(token) = tokens_iter.next() { - match token.rule() { - TkRule::AndOp | - TkRule::OrOp | - TkRule::PipeOp | - TkRule::ErrPipeOp => { - break - } - TkRule::Sep => { - node_toks.push(token.clone()); - break; - } - TkRule::Ident | - TkRule::SQuote | - TkRule::DQuote | - TkRule::Assign | - TkRule::TildeSub | - TkRule::VarSub => { - node_toks.push(token.clone()); - argv.push(token.clone()); - } - TkRule::RedirOp => { - node_toks.push(token.clone()); - let slice = &tokens_iter.clone().map(|tk| tk.clone()).collect::>(); - let (used,redir) = get_redir(token.clone(), slice, shenv)?; - for _ in 0..used { - if let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - } - } - redirs.push(redir); - } - _ => break - } - } - let span = get_span(&node_toks,shenv)?; - let node = Node { - node_rule: NdRule::Subshell { body, argv, redirs }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - return Ok(Some(node)) - } else { - return Ok(None) - } - } - Ok(None) -}); - -ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { - log!(TRACE, "Parsing pipeline"); - let mut tokens_iter = tokens.iter().peekable(); - let mut node_toks = vec![]; - let mut cmds = vec![]; - - while let Some(token) = tokens_iter.peek() { - match token.rule() { - TkRule::AndOp | TkRule::OrOp => { - // If there are no commands or only one, this isn't a pipeline - match cmds.len() { - 0 | 1 => return Ok(None), - _ => break - } - } - _ => { /* Keep going */ } - } - if let Some(mut cmd) = ExprNoPipeline::try_match(tokens,shenv)? { - // Add sub-node's tokens to our tokens - node_toks.extend(cmd.tokens().clone()); - - // Reflect changes in tokens and tokens_iter - tokens = &tokens[cmd.len()..]; - for _ in 0..cmd.len() { - tokens_iter.next(); - } - - if let NdRule::Command { argv, redirs: _ } = cmd.rule() { - if let Some(arg) = argv.first() { - let slice = shenv.input_slice(arg.span().clone()); - if BUILTINS.contains(&slice) { - *cmd.flags_mut() |= NdFlag::BUILTIN; - } - } - } - // Push sub-node - cmds.push(cmd); - - if tokens_iter.peek().is_none_or(|tk| !matches!(tk.rule(),TkRule::PipeOp | TkRule::ErrPipeOp)) { - match cmds.len() { - 0 | 1 => { - return Ok(None) - } - _ => break - } - } else { - if tokens_iter.peek().is_some() { - node_toks.push(tokens_iter.next().unwrap().clone()); - tokens = &tokens[1..]; - continue - } else { - match cmds.len() { - 0 | 1 => return Ok(None), - _ => break - } - } - } - } else { - match cmds.len() { - 0 | 1 => return Ok(None), - _ => break - } - } - } - if node_toks.is_empty() { - return Ok(None) - } - let span = get_span(&node_toks,shenv)?; - let node = Node { - node_rule: NdRule::Pipeline { cmds }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - Ok(Some(node)) -}); - -ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - log!(TRACE, "Parsing command"); - let mut tokens_iter = tokens.iter().peekable(); - let mut node_toks = vec![]; - let mut argv = vec![]; - let mut redirs = vec![]; - - while let Some(token) = tokens_iter.peek() { - match token.rule() { - TkRule::AndOp | TkRule::OrOp | TkRule::PipeOp | TkRule::ErrPipeOp => { - break - } - _ => { /* Keep going */ } - } - let token = tokens_iter.next().unwrap(); - node_toks.push(token.clone()); - match token.rule() { - TkRule::Ident | - TkRule::SQuote | - TkRule::DQuote | - TkRule::Assign | - TkRule::TildeSub | - TkRule::VarSub => { - argv.push(token.clone()); - } - TkRule::RedirOp => { - let slice = &tokens_iter.clone().map(|tk| tk.clone()).collect::>(); - let (used,redir) = get_redir(token.clone(), slice, shenv)?; - for _ in 0..used { - if let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - } - } - redirs.push(redir); - } - TkRule::Sep => break, - _ => return Err( - ShErr::full( - ShErrKind::ParseErr, - format!("Unexpected token in command rule: {:?}", token.rule()), - shenv.get_input(), - get_span(&node_toks,shenv)? - ) - ) - } - } - if node_toks.is_empty() { - return Ok(None) - } - let span = get_span(&node_toks,shenv)?; - if !argv.is_empty() { - let node = Node { - node_rule: NdRule::Command { argv, redirs }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - Ok(Some(node)) - } else { - Ok(None) - } -}); - -ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| { - log!(TRACE, "Parsing assignment"); - let mut tokens = tokens.into_iter().peekable(); - let mut node_toks = vec![]; - let mut assignments = vec![]; - while tokens.peek().is_some_and(|tk| tk.rule() == TkRule::Assign) { - let token = tokens.next().unwrap(); - node_toks.push(token.clone()); - assignments.push(token.clone()); - } - if assignments.is_empty() { - return Ok(None) - } - - if tokens.peek().is_some() { - let tokens_vec: Vec = tokens.into_iter().map(|token| token.clone()).collect(); - let tokens_slice = &tokens_vec; - let cmd = Command::try_match(tokens_slice,shenv)?.map(|cmd| Box::new(cmd)); - if let Some(ref cmd) = cmd { - node_toks.extend(cmd.tokens().clone()); - } - let span = get_span(&node_toks,shenv)?; - let node = Node { - node_rule: NdRule::Assignment { assignments, cmd }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - return Ok(Some(node)) - } else { - let span = get_span(&node_toks,shenv)?; - let node = Node { - node_rule: NdRule::Assignment { assignments, cmd: None }, - tokens: node_toks, - span, - flags: NdFlag::empty() - }; - Ok(Some(node)) - } -}); diff --git a/src/prelude.rs b/src/prelude.rs index f4a4519..ea59526 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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, diff --git a/src/prompt/validate.rs b/src/prompt/validate.rs index ff3a8a1..911e35f 100644 --- a/src/prompt/validate.rs +++ b/src/prompt/validate.rs @@ -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) { diff --git a/src/shellenv/input.rs b/src/shellenv/input.rs index 8d31c51..3a08b9d 100644 --- a/src/shellenv/input.rs +++ b/src/shellenv/input.rs @@ -1,15 +1,16 @@ - use crate::prelude::*; -#[derive(Clone,Debug)] +#[derive(Clone,Debug,PartialEq)] pub struct InputMan { input: Option, + saved_input: Option, spans: Vec>>, + saved_spans: Vec } 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> { if let Some(_input) = &self.input { let span = Rc::new(RefCell::new(Span::new(start, end))); diff --git a/src/shellenv/shenv.rs b/src/shellenv/shenv.rs index 027059c..ceee364 100644 --- a/src/shellenv/shenv.rs +++ b/src/shellenv/shenv.rs @@ -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>) -> Vec { + 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) -> ShResult { 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) -> ShResult { 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 { diff --git a/src/shellenv/vars.rs b/src/shellenv/vars.rs index 352ee16..2e44a8f 100644 --- a/src/shellenv/vars.rs +++ b/src/shellenv/vars.rs @@ -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);