From 5c9031548abb26465fc34851d1fa3ef86b5b842d Mon Sep 17 00:00:00 2001 From: pagedmov Date: Fri, 7 Mar 2025 19:12:28 -0500 Subject: [PATCH] Improved logic surrounding expansion of assignments --- src/builtin/alias.rs | 2 +- src/execute/mod.rs | 36 ++++++++++++++--------- src/expand/arithmetic.rs | 15 ++++++---- src/expand/mod.rs | 7 +++-- src/expand/vars.rs | 31 +++++++++++++------- src/libsh/utils.rs | 26 ++++++++++++----- src/parse/lex.rs | 63 ++++++++++++++++++---------------------- src/parse/mod.rs | 40 ++++++++++++++++++++----- src/prelude.rs | 3 +- src/prompt/highlight.rs | 12 ++++---- src/shellenv/input.rs | 5 ++++ src/shellenv/shenv.rs | 6 +++- 12 files changed, 154 insertions(+), 92 deletions(-) diff --git a/src/builtin/alias.rs b/src/builtin/alias.rs index e8df5fa..dab3021 100644 --- a/src/builtin/alias.rs +++ b/src/builtin/alias.rs @@ -8,7 +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('=') { - let clean_body = trim_quotes(&body); + let clean_body = clean_string(&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 aec5b9d..140badb 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -1,6 +1,6 @@ use std::os::fd::AsRawFd; -use crate::prelude::*; +use crate::{expand::vars::expand_string, prelude::*}; use shellenv::jobs::{ChildProc, JobBldr}; pub mod shellcmd; @@ -274,22 +274,30 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> { log!(TRACE, "Command: {:?}", cmd); let mut assigns = assignments.into_iter(); if let Some(cmd) = cmd { - while let Some(assign) = assigns.next() { - let assign_raw = assign.as_raw(shenv); - if let Some((var,val)) = assign_raw.split_once('=') { - shenv.vars_mut().export(var, val); + while let Some((var,val)) = assigns.next() { + let var_raw = var.as_raw(shenv); + let val_raw = val.as_raw(shenv); + if check_expansion(&val_raw).is_some() { + let exp = expand_token(val.clone(), shenv)?; + let val_exp = exp.into_iter().next().unwrap_or(val); + let val_exp_raw = val_exp.as_raw(shenv); + shenv.vars_mut().export(&var_raw, &val_exp_raw); + } else { + shenv.vars_mut().export(&var_raw, &val_raw); } } - if cmd.flags().contains(NdFlag::BUILTIN) { - exec_builtin(*cmd, shenv)?; - } else { - exec_cmd(*cmd, shenv)?; - } + dispatch_command(*cmd, shenv)?; } else { - while let Some(assign) = assigns.next() { - let assign_raw = assign.as_raw(shenv); - if let Some((var,val)) = assign_raw.split_once('=') { - shenv.vars_mut().set_var(var, val); + while let Some((var,val)) = assigns.next() { + let var_raw = var.as_raw(shenv); + let val_raw = val.as_raw(shenv); + if check_expansion(&val_raw).is_some() { + let exp = expand_token(val.clone(), shenv)?; + let val_exp = exp.into_iter().next().unwrap_or(val); + let val_exp_raw = val_exp.as_raw(shenv); + shenv.vars_mut().export(&var_raw, &val_exp_raw); + } else { + shenv.vars_mut().export(&var_raw, &val_raw); } } } diff --git a/src/expand/arithmetic.rs b/src/expand/arithmetic.rs index b95c6b6..b1f5b3b 100644 --- a/src/expand/arithmetic.rs +++ b/src/expand/arithmetic.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -use super::vars::expand_dquote; +use super::vars::expand_string; #[derive(Clone,PartialEq,Debug)] pub enum ExprToken { @@ -154,12 +154,15 @@ pub fn eval_rpn(tokens: Vec) -> ShResult { } 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); + log!(INFO, "{}", token.as_raw(shenv)); + // I mean hey it works + let token_raw = token.as_raw(shenv); + log!(INFO, token_raw); + let expanded = expand_string(token_raw, shenv); token.span().borrow_mut().expanded = false; - let expanded_raw = expanded.as_raw(shenv); - let arith_raw = expanded_raw.trim_matches('`'); + + log!(INFO, expanded); + let arith_raw = expanded.trim_matches('`'); let expr_tokens = shunting_yard(tokenize_expr(arith_raw)?)?; log!(DEBUG,expr_tokens); diff --git a/src/expand/mod.rs b/src/expand/mod.rs index a7c034d..586c965 100644 --- a/src/expand/mod.rs +++ b/src/expand/mod.rs @@ -5,7 +5,7 @@ pub mod cmdsub; pub mod arithmetic; use arithmetic::expand_arithmetic; -use vars::{expand_dquote, expand_var}; +use vars::{expand_string, expand_var}; use tilde::expand_tilde; use crate::prelude::*; @@ -25,8 +25,9 @@ pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult> { let mut processed = vec![]; match token.rule() { TkRule::DQuote => { - let dquote_exp = expand_dquote(token.clone(), shenv); - processed.push(dquote_exp); + let dquote_exp = expand_string(token.as_raw(shenv), shenv); + let mut expanded = shenv.expand_input(&dquote_exp, token.span()); + processed.append(&mut expanded); } TkRule::VarSub => { let mut varsub_exp = expand_var(token.clone(), shenv); diff --git a/src/expand/vars.rs b/src/expand/vars.rs index 955462b..78fa428 100644 --- a/src/expand/vars.rs +++ b/src/expand/vars.rs @@ -8,11 +8,10 @@ pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec { shenv.expand_input(&value, var_sub.span()) } -pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { - let dquote_raw = dquote.as_raw(shenv); +pub fn expand_string(s: String, shenv: &mut ShEnv) -> String { let mut result = String::new(); let mut var_name = String::new(); - let mut chars = dquote_raw.chars().peekable(); + let mut chars = s.chars().peekable(); let mut in_brace = false; while let Some(ch) = chars.next() { @@ -23,6 +22,7 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { } } '$' => { + let mut expanded = false; while let Some(ch) = chars.peek() { if *ch == '"' { break @@ -33,31 +33,42 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { in_brace = true; } '}' if in_brace => { + let value = shenv.vars().get_var(&var_name); + result.push_str(value); + expanded = true; break } _ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => { var_name.push(ch); + let value = shenv.vars().get_var(&var_name); + result.push_str(value); + expanded = true; break } '@' | '#' | '*' | '-' | '?' | '!' | '$' if var_name.is_empty() => { var_name.push(ch); + let value = shenv.vars().get_var(&var_name); + result.push_str(value); + expanded = true; break } ' ' | '\t' => { + let value = shenv.vars().get_var(&var_name); + result.push_str(value); + result.push(ch); + expanded = true; break } _ => var_name.push(ch) } } - log!(TRACE, var_name); - let value = shenv.vars().get_var(&var_name); - log!(TRACE, value); - result.push_str(value); + if !expanded { + let value = shenv.vars().get_var(&var_name); + result.push_str(value); + } } _ => result.push(ch) } } - - let token = shenv.expand_input(&result, dquote.span()).pop().unwrap_or(dquote); - token + result } diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 75e2b6d..466bc4e 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -2,7 +2,7 @@ use core::fmt::{Debug, Display, Write}; use std::{os::fd::{AsRawFd, BorrowedFd}, str::FromStr}; -use crate::prelude::*; +use crate::{parse::lex::EXPANSIONS, prelude::*}; use super::term::StyleSet; @@ -46,7 +46,7 @@ impl ArgVec for Vec { let mut argv_iter = self.into_iter(); let mut argv_processed = vec![]; while let Some(arg) = argv_iter.next() { - let cleaned = trim_quotes(&arg.as_raw(shenv)); + let cleaned = clean_string(&arg.as_raw(shenv)); argv_processed.push(cleaned); } argv_processed @@ -342,12 +342,24 @@ impl CmdRedirs { } } -pub fn trim_quotes(s: impl ToString) -> String { +pub fn check_expansion(s: &str) -> Option { + let rule = Lexer::get_rule(s); + if EXPANSIONS.contains(&rule) { + Some(rule) + } else { + None + } +} + +pub fn clean_string(s: impl ToString) -> String { let s = s.to_string(); - if s.starts_with('"') && s.ends_with('"') { - s.trim_matches('"').to_string() - } else if s.starts_with('\'') && s.ends_with('\'') { - s.trim_matches('\'').to_string() + if (s.starts_with('"') && s.ends_with('"')) || + (s.starts_with('\'') && s.ends_with('\'')) || + (s.starts_with('`') && s.ends_with('`')) + { + s[1..s.len() - 1].to_string() + } else if s.starts_with("$(") && s.ends_with(')') { + s[2..s.len() - 1].to_string() } else { s } diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 31f49cd..80ad3ff 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -37,6 +37,15 @@ pub const SEPARATORS: [TkRule;7] = [ TkRule::CasePat ]; +pub const EXPANSIONS: [TkRule;6] = [ + TkRule::DQuote, + TkRule::VarSub, + TkRule::CmdSub, + TkRule::ProcSub, + TkRule::TildeSub, + TkRule::ArithSub +]; + pub trait LexRule { fn try_match(input: &str) -> Option; } @@ -74,6 +83,10 @@ impl<'a> Lexer<'a> { self.consumed += len; input = &input[len..]; self.tokens.push(token); + + if input.is_empty() { + break + } } if !input.is_empty() { log!(WARN, "unconsumed input: {}", input) @@ -81,6 +94,13 @@ impl<'a> Lexer<'a> { self.tokens } } + pub fn get_rule(s: &str) -> TkRule { + if let Some((rule,_)) = TkRule::try_match(s) { + rule + } else { + TkRule::Ident + } + } } #[derive(Clone)] @@ -146,8 +166,14 @@ impl Span { } } pub fn shift(&mut self, delta: isize) { - self.start = self.start.saturating_add_signed(delta); - self.end = self.end.saturating_add_signed(delta); + self.shift_start(delta); + self.shift_end(delta); + } + pub fn shift_start(&mut self, delta: isize) { + self.start = self.start.saturating_add_signed(delta) + } + pub fn shift_end(&mut self, delta: isize) { + self.end = self.end.saturating_add_signed(delta) } } @@ -215,7 +241,6 @@ pub enum TkRule { Case, Esac, CasePat, - Assign, Ident, Sep, } @@ -244,7 +269,6 @@ impl TkRule { try_match!(Subshell,input); try_match!(CasePat,input); try_match!(Sep,input); - try_match!(Assign,input); try_match!(If,input); try_match!(Then,input); try_match!(Elif,input); @@ -901,37 +925,6 @@ tkrule_def!(FuncName, |input: &str| { None }); -tkrule_def!(Assign, |input: &str| { - let mut chars = input.chars(); - let mut len = 0; - let mut found_equals = false; - - while let Some(ch) = chars.next() { - match ch { - '\\' => { - len += 2; - chars.next(); - } - '=' if len == 0 => return None, - '=' => { - len += 1; - found_equals = true; - } - ' ' | '\t' | ';' | '\n' => { - match len { - _ if found_equals && len > 1 => return Some(len), - _ => return None - } - } - _ => len += 1 - } - } - match len { - _ if found_equals && len > 1 => return Some(len), - _ => return None - } -}); - tkrule_def!(BraceGrp, |input: &str| { // A group of commands inside of braces // Currently just holds a raw string to be re-parsed later diff --git a/src/parse/mod.rs b/src/parse/mod.rs index fb6a040..16d2680 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -85,7 +85,7 @@ pub enum LoopKind { pub enum NdRule { Main { cmd_lists: Vec }, Command { argv: Vec, redirs: Vec }, - Assignment { assignments: Vec, cmd: Option> }, + Assignment { assignments: Vec<(Token,Token)>, 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 }, @@ -1003,7 +1003,6 @@ ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| { TkRule::Ident | TkRule::SQuote | TkRule::DQuote | - TkRule::Assign | TkRule::TildeSub | TkRule::VarSub => { node_toks.push(token.clone()); @@ -1139,7 +1138,6 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| { TkRule::Ident | TkRule::SQuote | TkRule::DQuote | - TkRule::Assign | TkRule::TildeSub | TkRule::ArithSub | TkRule::VarSub => { @@ -1188,10 +1186,38 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| { 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()); + while let Some(token) = tokens.peek() { + if matches!(token.rule(), TkRule::Ident | TkRule::ArithSub | TkRule::CmdSub | TkRule::DQuote) { + let raw = token.as_raw(shenv); + // We are going to deconstruct this Ident into two separate tokens + // This makes expanding it easier later + if let Some((var,val)) = raw.split_once('=') { + const LEN_DELTA: usize = 1; // The distance covered by the '=' that we just split at + let token = tokens.next().unwrap(); + let var_span = shenv.inputman_mut().new_span( + token.span().borrow().start(), + var.len() + ); + let val_span = shenv.inputman_mut().new_span( + var.len() + LEN_DELTA, + token.span().borrow().end() + ); + let var_rule = TkRule::Ident; + let val_rule = if let Some(rule) = check_expansion(&val) { + rule + } else { + TkRule::Ident + }; + let var_token = Token::new(var_rule,var_span); + let val_token = Token::new(val_rule,val_span); + node_toks.push(token.clone()); + assignments.push((var_token.clone(),val_token.clone())); + } else { + break + } + } else { + break + } } if assignments.is_empty() { return Ok(None) diff --git a/src/prelude.rs b/src/prelude.rs index b9aa133..cfa0e94 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -90,7 +90,8 @@ pub use crate::{ RedirTarget, CmdRedirs, borrow_fd, - trim_quotes + check_expansion, + clean_string }, collections::{ VecDequeAliases diff --git a/src/prompt/highlight.rs b/src/prompt/highlight.rs index 90daac0..4aafc25 100644 --- a/src/prompt/highlight.rs +++ b/src/prompt/highlight.rs @@ -96,13 +96,6 @@ impl<'a> Highlighter for SynHelper<'a> { let styled = raw.styled(Style::Magenta); result.push_str(&styled); } - TkRule::Assign => { - let (var,val) = raw.split_once('=').unwrap(); - let var_styled = var.styled(Style::Magenta); - let val_styled = val.styled(Style::Cyan); - let rebuilt = vec![var_styled,val_styled].join("="); - result.push_str(&rebuilt); - } TkRule::Ident => { if in_array || in_case { if &raw == "in" { @@ -113,6 +106,11 @@ impl<'a> Highlighter for SynHelper<'a> { let styled = &raw.styled(Style::Magenta); result.push_str(&styled); } + } else if let Some((var,val)) = raw.split_once('=') { + let var_styled = var.styled(Style::Magenta); + let val_styled = val.styled(Style::Cyan); + let rebuilt = vec![var_styled,val_styled].join("="); + result.push_str(&rebuilt); } else if raw.starts_with(['"','\'']) { let styled = &raw.styled(Style::BrightYellow); result.push_str(&styled); diff --git a/src/shellenv/input.rs b/src/shellenv/input.rs index 3a08b9d..5f05894 100644 --- a/src/shellenv/input.rs +++ b/src/shellenv/input.rs @@ -51,6 +51,11 @@ impl InputMan { Rc::new(RefCell::new(Span::new(0,0))) } } + pub fn remove_span(&mut self, span: Rc>) { + if let Some(idx) = self.spans.iter().position(|iter_span| *iter_span == span) { + self.spans.remove(idx); + } + } pub fn spans_mut(&mut self) -> &mut Vec>> { &mut self.spans } diff --git a/src/shellenv/shenv.rs b/src/shellenv/shenv.rs index bba6784..3690e4c 100644 --- a/src/shellenv/shenv.rs +++ b/src/shellenv/shenv.rs @@ -51,9 +51,12 @@ impl ShEnv { if repl_span.borrow().expanded { return vec![]; } + log!(INFO, repl_span); + log!(INFO, new); 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(); + log!(INFO, new_tokens); *self.input_man.spans_mut() = saved_spans; let offset = repl_span.borrow().start(); @@ -68,8 +71,9 @@ 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!(INFO, input); + log!(INFO, range); input.replace_range(range, new); - let expanded = input.clone(); for span in self.input_man.spans_mut() { let mut span_mut = span.borrow_mut();