use super::ExpandedField;
use super::{arith, command_sub, param};
use crate::env::ShellEnv;
use crate::parser::ast::{ParamExpr, SpecialParam, Word, WordPart};
pub(super) fn expand_word_to_fields(
env: &mut ShellEnv,
word: &Word,
) -> crate::error::Result<Vec<ExpandedField>> {
let mut fields = vec![ExpandedField::new()];
for part in &word.parts {
expand_part_to_fields(env, part, &mut fields, false)?;
}
Ok(fields)
}
fn expand_part_to_fields(
env: &mut ShellEnv,
part: &WordPart,
fields: &mut Vec<ExpandedField>,
in_double_quote: bool,
) -> crate::error::Result<()> {
match part {
WordPart::Literal(s) => expand_part_literal(s, fields, in_double_quote),
WordPart::EscapedLiteral(s)
| WordPart::SingleQuoted(s)
| WordPart::DollarSingleQuoted(s) => expand_part_quoted_literal(s, fields),
WordPart::DoubleQuoted(parts) => expand_part_double_quoted(env, parts, fields)?,
WordPart::Tilde(user) => expand_part_tilde(env, user.as_deref(), fields),
WordPart::Parameter(p) => expand_part_parameter(env, p, fields, in_double_quote)?,
WordPart::CommandSub(program) => {
expand_part_command_sub(env, program, fields, in_double_quote)
}
WordPart::ArithSub(expr) => expand_part_arith_sub(env, expr, fields, in_double_quote)?,
}
Ok(())
}
fn expand_part_literal(s: &str, fields: &mut [ExpandedField], in_double_quote: bool) {
if in_double_quote {
fields.last_mut().unwrap().push_quoted(s);
} else {
fields.last_mut().unwrap().push_unquoted(s);
}
}
fn expand_part_quoted_literal(s: &str, fields: &mut [ExpandedField]) {
fields.last_mut().unwrap().push_quoted(s);
}
fn expand_part_double_quoted(
env: &mut ShellEnv,
parts: &[WordPart],
fields: &mut Vec<ExpandedField>,
) -> crate::error::Result<()> {
fields.last_mut().unwrap().was_quoted = true;
for inner in parts {
expand_part_to_fields(env, inner, fields, true)?;
}
Ok(())
}
fn expand_part_tilde(env: &mut ShellEnv, user: Option<&str>, fields: &mut [ExpandedField]) {
let result = match user {
None => env
.vars
.get("HOME")
.map(|s| s.to_string())
.unwrap_or_else(|| "~".to_string()),
Some(name) => super::tilde::expand_tilde_user(name),
};
fields.last_mut().unwrap().push_quoted(&result);
}
fn expand_part_parameter(
env: &mut ShellEnv,
param: &ParamExpr,
fields: &mut Vec<ExpandedField>,
in_double_quote: bool,
) -> crate::error::Result<()> {
expand_param_to_fields(env, param, fields, in_double_quote)
}
fn expand_part_command_sub(
env: &mut ShellEnv,
program: &crate::parser::ast::Program,
fields: &mut [ExpandedField],
in_double_quote: bool,
) {
let output = command_sub::execute(env, program);
if in_double_quote {
fields.last_mut().unwrap().push_quoted(&output);
} else {
fields.last_mut().unwrap().push_unquoted(&output);
}
}
fn expand_part_arith_sub(
env: &mut ShellEnv,
expr: &str,
fields: &mut [ExpandedField],
in_double_quote: bool,
) -> crate::error::Result<()> {
match arith::evaluate(env, expr) {
Ok(result) => {
if in_double_quote {
fields.last_mut().unwrap().push_quoted(&result);
} else {
fields.last_mut().unwrap().push_unquoted(&result);
}
Ok(())
}
Err(msg) => Err(crate::error::ShellError::expansion(
crate::error::ExpansionErrorKind::InvalidArithmetic,
msg,
)),
}
}
fn expand_param_to_fields(
env: &mut ShellEnv,
param: &ParamExpr,
fields: &mut Vec<ExpandedField>,
in_double_quote: bool,
) -> crate::error::Result<()> {
match param {
ParamExpr::Special(SpecialParam::At) if in_double_quote => {
let params = env.vars.positional_params().to_vec();
if params.is_empty() {
if fields.last().map(|f| f.is_empty()).unwrap_or(false) {
fields.pop();
}
return Ok(());
}
for (i, p) in params.iter().enumerate() {
if i == 0 {
fields.last_mut().unwrap().push_quoted(p);
} else {
fields.push(ExpandedField::new());
fields.last_mut().unwrap().push_quoted(p);
}
}
}
ParamExpr::Special(SpecialParam::Star) if in_double_quote => {
let sep = ifs_first_char(env);
let joined = env.vars.positional_params().join(&sep.to_string());
fields.last_mut().unwrap().push_quoted(&joined);
}
ParamExpr::Special(SpecialParam::At) if !in_double_quote => {
let params = env.vars.positional_params().to_vec();
if params.is_empty() {
return Ok(());
}
for (i, p) in params.iter().enumerate() {
if i == 0 {
fields.last_mut().unwrap().push_unquoted(p);
} else {
fields.push(ExpandedField::new());
fields.last_mut().unwrap().push_unquoted(p);
}
}
}
_ => {
let value = param::expand(env, param)?;
if in_double_quote {
fields.last_mut().unwrap().push_quoted(&value);
} else {
fields.last_mut().unwrap().push_unquoted(&value);
}
}
}
Ok(())
}
fn ifs_first_char(env: &ShellEnv) -> char {
env.vars
.get("IFS")
.and_then(|s| s.chars().next())
.unwrap_or(' ')
}
#[cfg(test)]
mod tests {
use super::*;
use crate::env::ShellEnv;
use crate::parser::ast::{ParamExpr, SpecialParam, Word, WordPart};
#[test]
fn test_unquoted_dollar_at_splits_per_param() {
let mut env = ShellEnv::new(
"yosh",
vec!["a".to_string(), "b".to_string(), "c".to_string()],
);
let word = Word {
parts: vec![WordPart::Parameter(ParamExpr::Special(SpecialParam::At))],
};
let fields = expand_word_to_fields(&mut env, &word).unwrap();
assert_eq!(fields.len(), 3, "expected 3 fields, got {:?}", fields);
assert_eq!(fields[0].value, "a");
assert_eq!(fields[1].value, "b");
assert_eq!(fields[2].value, "c");
assert!((0..fields[0].value.len()).all(|i| !fields[0].is_quoted(i)));
assert!((0..fields[1].value.len()).all(|i| !fields[1].is_quoted(i)));
assert!((0..fields[2].value.len()).all(|i| !fields[2].is_quoted(i)));
}
#[test]
fn test_unquoted_dollar_at_empty_produces_nothing() {
let mut env = ShellEnv::new("yosh", vec![]);
let word = Word {
parts: vec![WordPart::Parameter(ParamExpr::Special(SpecialParam::At))],
};
let fields = expand_word_to_fields(&mut env, &word).unwrap();
assert!(
fields.len() <= 1,
"expected 0 or 1 fields, got {:?}",
fields
);
}
}