From 2edb6cb8cebd29a54efdfb59c89a3a021baba699 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Fri, 7 Mar 2025 04:30:17 -0500 Subject: [PATCH] Implemented arithmetic substitution --- src/builtin/alias.rs | 4 - src/execute/mod.rs | 32 ++++---- src/execute/shellcmd.rs | 2 +- src/expand/alias.rs | 1 - src/expand/arithmetic.rs | 171 +++++++++++++++++++++++++++++++++++++++ src/expand/mod.rs | 16 ++-- src/expand/vars.rs | 4 - src/parse/lex.rs | 1 + src/parse/mod.rs | 13 +-- src/shellenv/jobs.rs | 3 - src/shellenv/shenv.rs | 9 --- 11 files changed, 201 insertions(+), 55 deletions(-) create mode 100644 src/expand/arithmetic.rs diff --git a/src/builtin/alias.rs b/src/builtin/alias.rs index e64cf32..e8df5fa 100644 --- a/src/builtin/alias.rs +++ b/src/builtin/alias.rs @@ -8,11 +8,7 @@ pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> { while let Some(arg) = argv_iter.next() { let arg_raw = shenv.input_slice(arg.span()).to_string(); if let Some((alias,body)) = arg_raw.split_once('=') { - log!(DEBUG, "{:?}",arg.span()); - log!(DEBUG, arg_raw); - log!(DEBUG, body); let clean_body = trim_quotes(&body); - log!(DEBUG, clean_body); shenv.logic_mut().set_alias(alias, &clean_body); } else { return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", shenv.get_input(), arg.span().clone())) diff --git a/src/execute/mod.rs b/src/execute/mod.rs index c309c2d..aec5b9d 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -14,8 +14,8 @@ pub fn exec_input>(input: S, shenv: &mut ShEnv) -> ShResult<()> let token_stream = expand_aliases(token_stream, shenv); for token in &token_stream { - log!(DEBUG, token); - log!(DEBUG, "{}",token.as_raw(shenv)); + log!(TRACE, token); + log!(TRACE, "{}",token.as_raw(shenv)); } let syn_tree = Parser::new(token_stream,shenv).parse()?; @@ -43,7 +43,7 @@ impl<'a> Executor<'a> { } pub fn walk(&mut self) -> ShResult<()> { self.shenv.inputman_mut().save_state(); - log!(DEBUG, "Starting walk"); + log!(TRACE, "Starting walk"); while let Some(node) = self.ast.next_node() { if let NdRule::CmdList { cmds } = node.clone().into_rule() { log!(TRACE, "{:?}", cmds); @@ -51,13 +51,13 @@ impl<'a> Executor<'a> { } else { unreachable!() } } self.shenv.inputman_mut().load_state(); - log!(DEBUG, "passed"); + log!(TRACE, "passed"); Ok(()) } } fn exec_list(list: Vec<(Option, Node)>, shenv: &mut ShEnv) -> ShResult<()> { - log!(DEBUG, "Executing list"); + log!(TRACE, "Executing list"); let mut list = VecDeque::from(list); while let Some(cmd_info) = list.fpop() { let guard = cmd_info.0; @@ -104,7 +104,7 @@ fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> { let mut is_func = false; let mut is_subsh = false; if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() { - *argv = expand_argv(argv.to_vec(), shenv); + *argv = expand_argv(argv.to_vec(), shenv)?; let cmd = argv.first().unwrap().as_raw(shenv); if shenv.logic().get_function(&cmd).is_some() { is_func = true; @@ -112,7 +112,7 @@ fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> { is_builtin = true; } } else if let NdRule::Subshell { body: _, ref mut argv, redirs: _ } = node.rule_mut() { - *argv = expand_argv(argv.to_vec(), shenv); + *argv = expand_argv(argv.to_vec(), shenv)?; is_subsh = true; } else { unreachable!() } @@ -239,7 +239,7 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(DEBUG, "Executing builtin"); + log!(TRACE, "Executing builtin"); let command = if let NdRule::Command { argv, redirs: _ } = node.rule() { argv.first().unwrap().as_raw(shenv) } else { unreachable!() }; @@ -267,7 +267,7 @@ fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(DEBUG, "Executing assignment"); + log!(TRACE, "Executing assignment"); let rule = node.into_rule(); if let NdRule::Assignment { assignments, cmd } = rule { log!(TRACE, "Assignments: {:?}", assignments); @@ -298,7 +298,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(DEBUG, "Executing pipeline"); + log!(TRACE, "Executing pipeline"); let rule = node.into_rule(); if let NdRule::Pipeline { cmds } = rule { let mut prev_rpipe: Option = None; @@ -384,7 +384,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(DEBUG, "Executing command"); + log!(TRACE, "Executing command"); let blame = node.span(); let rule = node.into_rule(); @@ -393,11 +393,11 @@ fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let command = argv.first().unwrap().to_string(); if get_bin_path(&command, shenv).is_some() { - log!(DEBUG, "{:?}",shenv.ctx().flags()); + log!(TRACE, "{:?}",shenv.ctx().flags()); if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { log!(TRACE, "Not forking"); shenv.collect_redirs(redirs); - log!(DEBUG, "{:?}",shenv.ctx().redirs()); + log!(TRACE, "{:?}",shenv.ctx().redirs()); if let Err(e) = shenv.ctx_mut().activate_rdrs() { eprintln!("{:?}",e); exit(1); @@ -411,7 +411,7 @@ fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { log!(TRACE, "Forking"); match unsafe { fork()? } { Child => { - log!(DEBUG, redirs); + log!(TRACE, redirs); shenv.collect_redirs(redirs); if let Err(e) = shenv.ctx_mut().activate_rdrs() { eprintln!("{:?}",e); @@ -441,9 +441,9 @@ fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } fn prep_execve(argv: Vec, shenv: &mut ShEnv) -> (Vec, Vec) { - log!(DEBUG, "Preparing execvpe args"); + log!(TRACE, "Preparing execvpe args"); let argv_s = argv.as_strings(shenv); - log!(DEBUG, argv_s); + log!(TRACE, argv_s); let mut envp = vec![]; let mut env_vars = shenv.vars().env().iter(); diff --git a/src/execute/shellcmd.rs b/src/execute/shellcmd.rs index 5093dbe..0e236f4 100644 --- a/src/execute/shellcmd.rs +++ b/src/execute/shellcmd.rs @@ -119,7 +119,7 @@ pub fn exec_case(node: Node, shenv: &mut ShEnv) -> ShResult<()> { 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) + let pat_raw = expand_token(pat, shenv)? .iter() .map(|tk| tk.as_raw(shenv)) .collect::>() diff --git a/src/expand/alias.rs b/src/expand/alias.rs index 27560a0..5f674f0 100644 --- a/src/expand/alias.rs +++ b/src/expand/alias.rs @@ -51,7 +51,6 @@ pub fn expand_aliases(tokens: Vec, shenv: &mut ShEnv) -> Vec { TkRule::Ident if is_command => { is_command = false; let mut alias_tokens = expand_alias(token.clone(), shenv); - log!(DEBUG, alias_tokens); if !alias_tokens.is_empty() { processed.append(&mut alias_tokens); } else { diff --git a/src/expand/arithmetic.rs b/src/expand/arithmetic.rs new file mode 100644 index 0000000..b95c6b6 --- /dev/null +++ b/src/expand/arithmetic.rs @@ -0,0 +1,171 @@ +use crate::prelude::*; + +use super::vars::expand_dquote; + +#[derive(Clone,PartialEq,Debug)] +pub enum ExprToken { + Number(f64), + Operator(Op), + OpenParen, + CloseParen +} + +#[derive(Clone,PartialEq,Debug)] +pub enum Op { + Add, + Sub, + Mul, + Div, + Mod, + Pow +} + +impl Op { + pub fn precedence(&self) -> u8 { + match self { + Op::Add | Op::Sub => 1, + Op::Mul | Op::Div | Op::Mod => 2, + Op::Pow => 3 + } + } + pub fn is_left_associative(&self) -> bool { + *self != Op::Pow + } +} + +fn tokenize_expr(expr: &str) -> ShResult> { + let mut chars = expr.chars().peekable(); + let mut tokens = vec![]; + + while let Some(ch) = chars.next() { + match ch { + '+' => tokens.push(ExprToken::Operator(Op::Add)), + '-' => tokens.push(ExprToken::Operator(Op::Sub)), + '*' => { + if chars.peek() == Some(&'*') { + chars.next(); + tokens.push(ExprToken::Operator(Op::Pow)); + } else { + tokens.push(ExprToken::Operator(Op::Mul)); + } + } + '/' => tokens.push(ExprToken::Operator(Op::Div)), + '%' => tokens.push(ExprToken::Operator(Op::Mod)), + '(' => tokens.push(ExprToken::OpenParen), + ')' => tokens.push(ExprToken::CloseParen), + '0'..='9' => { + let mut number = ch.to_string(); + while let Some(next_ch) = chars.peek() { + if next_ch.is_ascii_digit() { + number.push(chars.next().unwrap()); + } else { + break; + } + } + let value = number.parse::().unwrap(); + tokens.push(ExprToken::Number(value)); + } + ' ' | '\t' => continue, // Skip whitespace + _ => return Err(ShErr::simple(ShErrKind::ParseErr, format!("Unexpected character in arithmetic expansion: {}",ch))), // Handle unexpected characters + } + } + + Ok(tokens) +} + +fn shunting_yard(tokens: Vec) -> ShResult> { + let mut sorted = vec![]; + let mut operators = vec![]; + + for token in tokens { + match token { + ExprToken::Number(_) => sorted.push(token.clone()), + ExprToken::Operator(ref op) => { + while let Some(top) = operators.last() { + if let ExprToken::Operator(top_op) = top { + if (op.is_left_associative() && op.precedence() <= top_op.precedence()) + || (!op.is_left_associative() && op.precedence() < top_op.precedence()) + { + sorted.push(operators.pop().unwrap()) + } else { + break + } + } else { + break + } + } + operators.push(token.clone()) + } + ExprToken::OpenParen => operators.push(token.clone()), + ExprToken::CloseParen => { + while let Some(top) = operators.pop() { + if matches!(top, ExprToken::OpenParen) { + break; + } + sorted.push(top); + } + } + } + } + + while let Some(op) = operators.pop() { + if matches!(op, ExprToken::OpenParen | ExprToken::CloseParen) { + return Err(ShErr::simple(ShErrKind::ParseErr, "Mismatched parenthesis in arithmetic expansion")) + } + sorted.push(op); + } + + Ok(sorted) +} + +pub fn eval_rpn(tokens: Vec) -> ShResult { + let mut stack = vec![]; + + for token in tokens { + match token { + ExprToken::Number(num) => stack.push(num), + ExprToken::Operator(op) => { + if stack.len() < 2 { + return Err(ShErr::simple(ShErrKind::ParseErr, "Not enough operands in arithmetic expansion")) + } + let rhs = stack.pop().unwrap(); + let lhs = stack.pop().unwrap(); + let result = match op { + Op::Add => lhs + rhs, + Op::Sub => lhs - rhs, + Op::Mul => lhs * rhs, + Op::Mod => lhs % rhs, + Op::Pow => lhs.powf(rhs), + Op::Div => { + if rhs == 0.0 { + return Err(ShErr::simple(ShErrKind::ParseErr, "Attempt to divide by zero in arithmetic expansion")) + } + lhs / rhs + } + }; + stack.push(result); + } + ExprToken::OpenParen => todo!(), + ExprToken::CloseParen => todo!(), + } + } + + Ok(stack.pop().unwrap()) +} + +pub fn expand_arithmetic(token: Token, shenv: &mut ShEnv) -> ShResult { + // I mean hey it works (I think) + let dummy_token = Token::new(TkRule::DQuote, token.span()); + let expanded = expand_dquote(dummy_token, shenv); + token.span().borrow_mut().expanded = false; + let expanded_raw = expanded.as_raw(shenv); + let arith_raw = expanded_raw.trim_matches('`'); + + let expr_tokens = shunting_yard(tokenize_expr(arith_raw)?)?; + log!(DEBUG,expr_tokens); + let result = eval_rpn(expr_tokens)?.to_string(); + + let mut final_expansion = shenv.expand_input(&result, token.span()); + + Ok(final_expansion.pop().unwrap_or(token)) +} diff --git a/src/expand/mod.rs b/src/expand/mod.rs index f9612a7..a7c034d 100644 --- a/src/expand/mod.rs +++ b/src/expand/mod.rs @@ -2,24 +2,26 @@ pub mod vars; pub mod tilde; pub mod alias; pub mod cmdsub; +pub mod arithmetic; +use arithmetic::expand_arithmetic; use vars::{expand_dquote, expand_var}; use tilde::expand_tilde; use crate::prelude::*; -pub fn expand_argv(argv: Vec, shenv: &mut ShEnv) -> Vec { +pub fn expand_argv(argv: Vec, shenv: &mut ShEnv) -> ShResult> { let mut processed = vec![]; for arg in argv { log!(DEBUG, "{}",arg.as_raw(shenv)); log!(DEBUG, processed); - let mut expanded = expand_token(arg, shenv); + let mut expanded = expand_token(arg, shenv)?; processed.append(&mut expanded); } - processed + Ok(processed) } -pub fn expand_token(token: Token, shenv: &mut ShEnv) -> Vec { +pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult> { let mut processed = vec![]; match token.rule() { TkRule::DQuote => { @@ -34,6 +36,10 @@ pub fn expand_token(token: Token, shenv: &mut ShEnv) -> Vec { let tilde_exp = expand_tilde(token.clone(), shenv); processed.push(tilde_exp); } + TkRule::ArithSub => { + let arith_exp = expand_arithmetic(token.clone(), shenv)?; + processed.push(arith_exp); + } _ => { if token.rule() != TkRule::Ident { log!(WARN, "found this in expand_token: {:?}", token.rule()); @@ -41,5 +47,5 @@ pub fn expand_token(token: Token, shenv: &mut ShEnv) -> Vec { processed.push(token.clone()) } } - processed + Ok(processed) } diff --git a/src/expand/vars.rs b/src/expand/vars.rs index dcd1c53..955462b 100644 --- a/src/expand/vars.rs +++ b/src/expand/vars.rs @@ -58,10 +58,6 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { } } - log!(DEBUG, result); - - log!(DEBUG, "{:?}",dquote.span()); let token = shenv.expand_input(&result, dquote.span()).pop().unwrap_or(dquote); - log!(DEBUG, "{}",token.as_raw(shenv)); token } diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 96718ec..31f49cd 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -371,6 +371,7 @@ tkrule_def!(ArithSub, |input: &str| { } } } + ' ' | '\t' | ';' | '\n' => return None, _ => { /* Continue */ } } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 810e8c9..fb6a040 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -343,7 +343,6 @@ ndrule_def!(ShellCmd, shenv, |tokens: &[Token], shenv: &mut ShEnv| { }); 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) }; @@ -411,7 +410,6 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { } } } - log!(DEBUG,tokens); tokens_iter = tokens.iter().peekable(); if tokens_iter.peek().is_none() { @@ -431,14 +429,10 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { 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() { @@ -482,7 +476,6 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { redirs.push(redir); } _ => { - log!(DEBUG, token); return Err(err("Expected `esac` or a case block here", node_toks.last().unwrap().span(), shenv)) } } @@ -523,7 +516,6 @@ ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { } else { return Ok(None) } while let Some(token) = tokens_iter.next() { - log!(DEBUG, token); node_toks.push(token.clone()); tokens = &tokens[1..]; match token.rule() { @@ -656,7 +648,6 @@ ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { }); 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) }; @@ -666,7 +657,6 @@ ndrule_def!(IfThen, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { 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()); @@ -1151,6 +1141,7 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| { TkRule::DQuote | TkRule::Assign | TkRule::TildeSub | + TkRule::ArithSub | TkRule::VarSub => { argv.push(token.clone()); } @@ -1228,13 +1219,11 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| { }; return Ok(Some(node)) } else { - log!(DEBUG, tokens); if let Some(token) = tokens.next() { if token.rule() == TkRule::Sep { node_toks.push(token.clone()); } } - log!(DEBUG, node_toks); let span = get_span(&node_toks,shenv)?; let node = Node { diff --git a/src/shellenv/jobs.rs b/src/shellenv/jobs.rs index 83ebbbe..f826c42 100644 --- a/src/shellenv/jobs.rs +++ b/src/shellenv/jobs.rs @@ -97,7 +97,6 @@ impl<'a> ChildProc { } else { WtStat::Exited(pid, 0) }; - log!(DEBUG, command); let mut child = Self { pgid: pid, pid, command, stat }; if let Some(pgid) = pgid { child.set_pgid(pgid).ok(); @@ -505,7 +504,6 @@ impl JobTab { self.fg.as_mut() } pub fn new_fg<'a>(&mut self, job: Job) -> ShResult> { - log!(DEBUG, "New fg job: {:?}", job); let pgid = job.pgid(); self.fg = Some(job); attach_tty(pgid)?; @@ -519,7 +517,6 @@ impl JobTab { } take_term()?; let fg = std::mem::take(&mut self.fg); - log!(DEBUG, "Moving foreground job to background"); if let Some(mut job) = fg { job.set_stats(stat); self.insert_job(job, false)?; diff --git a/src/shellenv/shenv.rs b/src/shellenv/shenv.rs index 9d5b2fa..bba6784 100644 --- a/src/shellenv/shenv.rs +++ b/src/shellenv/shenv.rs @@ -33,7 +33,6 @@ impl ShEnv { } pub fn source_file(&mut self, path: PathBuf) -> ShResult<()> { if path.is_file() { - log!(DEBUG, "sourcing {}", path.to_str().unwrap()); let mut file = std::fs::File::open(path)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; @@ -43,14 +42,12 @@ impl ShEnv { Ok(()) } pub fn source_rc(&mut self) -> ShResult<()> { - log!(DEBUG, "sourcing rc"); let path_raw = std::env::var("FERN_RC")?; let path = PathBuf::from(path_raw); self.source_file(path)?; Ok(()) } pub fn expand_input(&mut self, new: &str, repl_span: Rc>) -> Vec { - log!(DEBUG,repl_span); if repl_span.borrow().expanded { return vec![]; } @@ -71,10 +68,8 @@ impl ShEnv { 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); for span in self.input_man.spans_mut() { let mut span_mut = span.borrow_mut(); @@ -87,7 +82,6 @@ impl ShEnv { } } self.input_man.clamp_all(); - log!(DEBUG, new_tokens); if new_tokens.is_empty() { let empty = Token::new( TkRule::Ident, @@ -156,11 +150,8 @@ impl ShEnv { let stderr = ctx.masks().stderr().get_fd(); let saved_in = dup(stdin)?; - log!(DEBUG, saved_in); let saved_out = dup(stdout)?; - log!(DEBUG, saved_out); let saved_err = dup(stderr)?; - log!(DEBUG, saved_err); let saved_io = shellenv::exec_ctx::SavedIo::save(saved_in, saved_out, saved_err); *ctx.saved_io() = Some(saved_io);