Improved logic surrounding expansion of assignments

This commit is contained in:
2025-03-07 19:12:28 -05:00
parent fb0a3af428
commit 972e2ceefa
12 changed files with 154 additions and 92 deletions

View File

@@ -8,7 +8,7 @@ pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
while let Some(arg) = argv_iter.next() { while let Some(arg) = argv_iter.next() {
let arg_raw = shenv.input_slice(arg.span()).to_string(); let arg_raw = shenv.input_slice(arg.span()).to_string();
if let Some((alias,body)) = arg_raw.split_once('=') { 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); shenv.logic_mut().set_alias(alias, &clean_body);
} else { } else {
return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", shenv.get_input(), arg.span().clone())) return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", shenv.get_input(), arg.span().clone()))

View File

@@ -1,6 +1,6 @@
use std::os::fd::AsRawFd; use std::os::fd::AsRawFd;
use crate::prelude::*; use crate::{expand::vars::expand_string, prelude::*};
use shellenv::jobs::{ChildProc, JobBldr}; use shellenv::jobs::{ChildProc, JobBldr};
pub mod shellcmd; pub mod shellcmd;
@@ -274,22 +274,30 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
log!(TRACE, "Command: {:?}", cmd); log!(TRACE, "Command: {:?}", cmd);
let mut assigns = assignments.into_iter(); let mut assigns = assignments.into_iter();
if let Some(cmd) = cmd { if let Some(cmd) = cmd {
while let Some(assign) = assigns.next() { while let Some((var,val)) = assigns.next() {
let assign_raw = assign.as_raw(shenv); let var_raw = var.as_raw(shenv);
if let Some((var,val)) = assign_raw.split_once('=') { let val_raw = val.as_raw(shenv);
shenv.vars_mut().export(var, val); 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) { dispatch_command(*cmd, shenv)?;
exec_builtin(*cmd, shenv)?;
} else {
exec_cmd(*cmd, shenv)?;
}
} else { } else {
while let Some(assign) = assigns.next() { while let Some((var,val)) = assigns.next() {
let assign_raw = assign.as_raw(shenv); let var_raw = var.as_raw(shenv);
if let Some((var,val)) = assign_raw.split_once('=') { let val_raw = val.as_raw(shenv);
shenv.vars_mut().set_var(var, val); 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);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use super::vars::expand_dquote; use super::vars::expand_string;
#[derive(Clone,PartialEq,Debug)] #[derive(Clone,PartialEq,Debug)]
pub enum ExprToken { pub enum ExprToken {
@@ -154,12 +154,15 @@ pub fn eval_rpn(tokens: Vec<ExprToken>) -> ShResult<f64> {
} }
pub fn expand_arithmetic(token: Token, shenv: &mut ShEnv) -> ShResult<Token> { pub fn expand_arithmetic(token: Token, shenv: &mut ShEnv) -> ShResult<Token> {
// I mean hey it works (I think) log!(INFO, "{}", token.as_raw(shenv));
let dummy_token = Token::new(TkRule::DQuote, token.span()); // I mean hey it works
let expanded = expand_dquote(dummy_token, shenv); let token_raw = token.as_raw(shenv);
log!(INFO, token_raw);
let expanded = expand_string(token_raw, shenv);
token.span().borrow_mut().expanded = false; 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)?)?; let expr_tokens = shunting_yard(tokenize_expr(arith_raw)?)?;
log!(DEBUG,expr_tokens); log!(DEBUG,expr_tokens);

View File

@@ -5,7 +5,7 @@ pub mod cmdsub;
pub mod arithmetic; pub mod arithmetic;
use arithmetic::expand_arithmetic; use arithmetic::expand_arithmetic;
use vars::{expand_dquote, expand_var}; use vars::{expand_string, expand_var};
use tilde::expand_tilde; use tilde::expand_tilde;
use crate::prelude::*; use crate::prelude::*;
@@ -25,8 +25,9 @@ pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
let mut processed = vec![]; let mut processed = vec![];
match token.rule() { match token.rule() {
TkRule::DQuote => { TkRule::DQuote => {
let dquote_exp = expand_dquote(token.clone(), shenv); let dquote_exp = expand_string(token.as_raw(shenv), shenv);
processed.push(dquote_exp); let mut expanded = shenv.expand_input(&dquote_exp, token.span());
processed.append(&mut expanded);
} }
TkRule::VarSub => { TkRule::VarSub => {
let mut varsub_exp = expand_var(token.clone(), shenv); let mut varsub_exp = expand_var(token.clone(), shenv);

View File

@@ -8,11 +8,10 @@ pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
shenv.expand_input(&value, var_sub.span()) shenv.expand_input(&value, var_sub.span())
} }
pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { pub fn expand_string(s: String, shenv: &mut ShEnv) -> String {
let dquote_raw = dquote.as_raw(shenv);
let mut result = String::new(); let mut result = String::new();
let mut var_name = String::new(); let mut var_name = String::new();
let mut chars = dquote_raw.chars().peekable(); let mut chars = s.chars().peekable();
let mut in_brace = false; let mut in_brace = false;
while let Some(ch) = chars.next() { 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() { while let Some(ch) = chars.peek() {
if *ch == '"' { if *ch == '"' {
break break
@@ -33,31 +33,42 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token {
in_brace = true; in_brace = true;
} }
'}' if in_brace => { '}' if in_brace => {
let value = shenv.vars().get_var(&var_name);
result.push_str(value);
expanded = true;
break break
} }
_ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => { _ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => {
var_name.push(ch); var_name.push(ch);
let value = shenv.vars().get_var(&var_name);
result.push_str(value);
expanded = true;
break break
} }
'@' | '#' | '*' | '-' | '?' | '!' | '$' if var_name.is_empty() => { '@' | '#' | '*' | '-' | '?' | '!' | '$' if var_name.is_empty() => {
var_name.push(ch); var_name.push(ch);
let value = shenv.vars().get_var(&var_name);
result.push_str(value);
expanded = true;
break break
} }
' ' | '\t' => { ' ' | '\t' => {
let value = shenv.vars().get_var(&var_name);
result.push_str(value);
result.push(ch);
expanded = true;
break break
} }
_ => var_name.push(ch) _ => var_name.push(ch)
} }
} }
log!(TRACE, var_name); if !expanded {
let value = shenv.vars().get_var(&var_name); let value = shenv.vars().get_var(&var_name);
log!(TRACE, value); result.push_str(value);
result.push_str(value); }
} }
_ => result.push(ch) _ => result.push(ch)
} }
} }
result
let token = shenv.expand_input(&result, dquote.span()).pop().unwrap_or(dquote);
token
} }

View File

@@ -2,7 +2,7 @@ use core::fmt::{Debug, Display, Write};
use std::{os::fd::{AsRawFd, BorrowedFd}, str::FromStr}; use std::{os::fd::{AsRawFd, BorrowedFd}, str::FromStr};
use crate::prelude::*; use crate::{parse::lex::EXPANSIONS, prelude::*};
use super::term::StyleSet; use super::term::StyleSet;
@@ -46,7 +46,7 @@ impl ArgVec for Vec<Token> {
let mut argv_iter = self.into_iter(); let mut argv_iter = self.into_iter();
let mut argv_processed = vec![]; let mut argv_processed = vec![];
while let Some(arg) = argv_iter.next() { 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.push(cleaned);
} }
argv_processed argv_processed
@@ -342,12 +342,24 @@ impl CmdRedirs {
} }
} }
pub fn trim_quotes(s: impl ToString) -> String { pub fn check_expansion(s: &str) -> Option<TkRule> {
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(); let s = s.to_string();
if s.starts_with('"') && s.ends_with('"') { if (s.starts_with('"') && s.ends_with('"')) ||
s.trim_matches('"').to_string() (s.starts_with('\'') && s.ends_with('\'')) ||
} else if s.starts_with('\'') && s.ends_with('\'') { (s.starts_with('`') && s.ends_with('`'))
s.trim_matches('\'').to_string() {
s[1..s.len() - 1].to_string()
} else if s.starts_with("$(") && s.ends_with(')') {
s[2..s.len() - 1].to_string()
} else { } else {
s s
} }

View File

@@ -37,6 +37,15 @@ pub const SEPARATORS: [TkRule;7] = [
TkRule::CasePat TkRule::CasePat
]; ];
pub const EXPANSIONS: [TkRule;6] = [
TkRule::DQuote,
TkRule::VarSub,
TkRule::CmdSub,
TkRule::ProcSub,
TkRule::TildeSub,
TkRule::ArithSub
];
pub trait LexRule { pub trait LexRule {
fn try_match(input: &str) -> Option<usize>; fn try_match(input: &str) -> Option<usize>;
} }
@@ -74,6 +83,10 @@ impl<'a> Lexer<'a> {
self.consumed += len; self.consumed += len;
input = &input[len..]; input = &input[len..];
self.tokens.push(token); self.tokens.push(token);
if input.is_empty() {
break
}
} }
if !input.is_empty() { if !input.is_empty() {
log!(WARN, "unconsumed input: {}", input) log!(WARN, "unconsumed input: {}", input)
@@ -81,6 +94,13 @@ impl<'a> Lexer<'a> {
self.tokens self.tokens
} }
} }
pub fn get_rule(s: &str) -> TkRule {
if let Some((rule,_)) = TkRule::try_match(s) {
rule
} else {
TkRule::Ident
}
}
} }
#[derive(Clone)] #[derive(Clone)]
@@ -146,8 +166,14 @@ impl Span {
} }
} }
pub fn shift(&mut self, delta: isize) { pub fn shift(&mut self, delta: isize) {
self.start = self.start.saturating_add_signed(delta); self.shift_start(delta);
self.end = self.end.saturating_add_signed(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, Case,
Esac, Esac,
CasePat, CasePat,
Assign,
Ident, Ident,
Sep, Sep,
} }
@@ -244,7 +269,6 @@ impl TkRule {
try_match!(Subshell,input); try_match!(Subshell,input);
try_match!(CasePat,input); try_match!(CasePat,input);
try_match!(Sep,input); try_match!(Sep,input);
try_match!(Assign,input);
try_match!(If,input); try_match!(If,input);
try_match!(Then,input); try_match!(Then,input);
try_match!(Elif,input); try_match!(Elif,input);
@@ -901,37 +925,6 @@ tkrule_def!(FuncName, |input: &str| {
None 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| { tkrule_def!(BraceGrp, |input: &str| {
// A group of commands inside of braces // A group of commands inside of braces
// Currently just holds a raw string to be re-parsed later // Currently just holds a raw string to be re-parsed later

View File

@@ -85,7 +85,7 @@ pub enum LoopKind {
pub enum NdRule { pub enum NdRule {
Main { cmd_lists: Vec<Node> }, Main { cmd_lists: Vec<Node> },
Command { argv: Vec<Token>, redirs: Vec<Redir> }, Command { argv: Vec<Token>, redirs: Vec<Redir> },
Assignment { assignments: Vec<Token>, cmd: Option<Box<Node>> }, Assignment { assignments: Vec<(Token,Token)>, cmd: Option<Box<Node>> },
FuncDef { name: Token, body: Token }, FuncDef { name: Token, body: Token },
Case { pat: Token, blocks: Vec<(Token,Vec<Node>)>, redirs: Vec<Redir> }, Case { pat: Token, blocks: Vec<(Token,Vec<Node>)>, redirs: Vec<Redir> },
IfThen { cond_blocks: Vec<(Vec<Node>,Vec<Node>)>, else_block: Option<Vec<Node>>, redirs: Vec<Redir> }, IfThen { cond_blocks: Vec<(Vec<Node>,Vec<Node>)>, else_block: Option<Vec<Node>>, redirs: Vec<Redir> },
@@ -1003,7 +1003,6 @@ ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
TkRule::Ident | TkRule::Ident |
TkRule::SQuote | TkRule::SQuote |
TkRule::DQuote | TkRule::DQuote |
TkRule::Assign |
TkRule::TildeSub | TkRule::TildeSub |
TkRule::VarSub => { TkRule::VarSub => {
node_toks.push(token.clone()); node_toks.push(token.clone());
@@ -1139,7 +1138,6 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
TkRule::Ident | TkRule::Ident |
TkRule::SQuote | TkRule::SQuote |
TkRule::DQuote | TkRule::DQuote |
TkRule::Assign |
TkRule::TildeSub | TkRule::TildeSub |
TkRule::ArithSub | TkRule::ArithSub |
TkRule::VarSub => { TkRule::VarSub => {
@@ -1188,10 +1186,38 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
let mut tokens = tokens.into_iter().peekable(); let mut tokens = tokens.into_iter().peekable();
let mut node_toks = vec![]; let mut node_toks = vec![];
let mut assignments = vec![]; let mut assignments = vec![];
while tokens.peek().is_some_and(|tk| tk.rule() == TkRule::Assign) { while let Some(token) = tokens.peek() {
let token = tokens.next().unwrap(); if matches!(token.rule(), TkRule::Ident | TkRule::ArithSub | TkRule::CmdSub | TkRule::DQuote) {
node_toks.push(token.clone()); let raw = token.as_raw(shenv);
assignments.push(token.clone()); // 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() { if assignments.is_empty() {
return Ok(None) return Ok(None)

View File

@@ -90,7 +90,8 @@ pub use crate::{
RedirTarget, RedirTarget,
CmdRedirs, CmdRedirs,
borrow_fd, borrow_fd,
trim_quotes check_expansion,
clean_string
}, },
collections::{ collections::{
VecDequeAliases VecDequeAliases

View File

@@ -96,13 +96,6 @@ impl<'a> Highlighter for SynHelper<'a> {
let styled = raw.styled(Style::Magenta); let styled = raw.styled(Style::Magenta);
result.push_str(&styled); 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 => { TkRule::Ident => {
if in_array || in_case { if in_array || in_case {
if &raw == "in" { if &raw == "in" {
@@ -113,6 +106,11 @@ impl<'a> Highlighter for SynHelper<'a> {
let styled = &raw.styled(Style::Magenta); let styled = &raw.styled(Style::Magenta);
result.push_str(&styled); 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(['"','\'']) { } else if raw.starts_with(['"','\'']) {
let styled = &raw.styled(Style::BrightYellow); let styled = &raw.styled(Style::BrightYellow);
result.push_str(&styled); result.push_str(&styled);

View File

@@ -51,6 +51,11 @@ impl InputMan {
Rc::new(RefCell::new(Span::new(0,0))) Rc::new(RefCell::new(Span::new(0,0)))
} }
} }
pub fn remove_span(&mut self, span: Rc<RefCell<Span>>) {
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<Rc<RefCell<Span>>> { pub fn spans_mut(&mut self) -> &mut Vec<Rc<RefCell<Span>>> {
&mut self.spans &mut self.spans
} }

View File

@@ -51,9 +51,12 @@ impl ShEnv {
if repl_span.borrow().expanded { if repl_span.borrow().expanded {
return vec![]; return vec![];
} }
log!(INFO, repl_span);
log!(INFO, new);
repl_span.borrow_mut().expanded = true; repl_span.borrow_mut().expanded = true;
let saved_spans = self.input_man.spans_mut().clone(); let saved_spans = self.input_man.spans_mut().clone();
let mut new_tokens = Lexer::new(new.to_string(), self).lex(); let mut new_tokens = Lexer::new(new.to_string(), self).lex();
log!(INFO, new_tokens);
*self.input_man.spans_mut() = saved_spans; *self.input_man.spans_mut() = saved_spans;
let offset = repl_span.borrow().start(); let offset = repl_span.borrow().start();
@@ -68,8 +71,9 @@ impl ShEnv {
if let Some(input) = self.input_man.get_input_mut() { if let Some(input) = self.input_man.get_input_mut() {
let old = &input[range.clone()]; let old = &input[range.clone()];
let delta: isize = new.len() as isize - old.len() as isize; let delta: isize = new.len() as isize - old.len() as isize;
log!(INFO, input);
log!(INFO, range);
input.replace_range(range, new); input.replace_range(range, new);
let expanded = input.clone();
for span in self.input_man.spans_mut() { for span in self.input_man.spans_mut() {
let mut span_mut = span.borrow_mut(); let mut span_mut = span.borrow_mut();