use super::super::*;
use crate::state::ShellState;
fn expand_tokens(tokens: Vec<Token>, shell_state: &mut ShellState) -> Vec<Token> {
let mut result = Vec::new();
for token in tokens {
match token {
Token::Word(word) => {
let expanded = crate::executor::expand_variables_in_string(&word, shell_state);
if !expanded.is_empty() || !word.starts_with("$(") {
result.push(Token::Word(expanded));
}
}
other => result.push(other),
}
}
result
}
#[test]
fn test_variable_expansion() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "expanded_value".to_string());
let tokens = lex("echo $TEST_VAR", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("expanded_value".to_string())
]
);
}
#[test]
fn test_variable_expansion_nonexistent() {
let shell_state = ShellState::new();
let result = lex("echo $TEST_VAR2", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("$TEST_VAR2".to_string())
]
);
}
#[test]
fn test_empty_variable() {
let shell_state = ShellState::new();
let result = lex("echo $", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("$".to_string())
]
);
}
#[test]
fn test_mixed_quotes_and_variables() {
let mut shell_state = ShellState::new();
shell_state.set_var("USER", "alice".to_string());
let tokens = lex("echo \"Hello $USER\"", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("Hello alice".to_string())
]
);
}
#[test]
fn test_variable_in_quotes_with_pipe() {
let mut shell_state = ShellState::new();
shell_state.set_var("PATH", "/usr/bin:/bin".to_string());
let tokens = lex("echo \"$PATH\" | tr ':' '\\n'", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("/usr/bin:/bin".to_string()),
Token::Pipe,
Token::Word("tr".to_string()),
Token::Word(":".to_string()),
Token::Word("\\n".to_string())
]
);
}
#[test]
fn test_command_substitution_dollar_paren() {
let shell_state = ShellState::new();
let result = lex("echo $(pwd)", &shell_state).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], Token::Word("echo".to_string()));
assert!(matches!(result[1], Token::Word(_)));
}
#[test]
fn test_command_substitution_backticks() {
let shell_state = ShellState::new();
let result = lex("echo `pwd`", &shell_state).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], Token::Word("echo".to_string()));
assert!(matches!(result[1], Token::Word(_)));
}
#[test]
fn test_command_substitution_with_arguments() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $(echo hello world)", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello world".to_string())
]
);
}
#[test]
fn test_command_substitution_backticks_with_arguments() {
let mut shell_state = ShellState::new();
let tokens = lex("echo `echo hello world`", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello world".to_string())
]
);
}
#[test]
fn test_command_substitution_failure_fallback() {
let shell_state = ShellState::new();
let result = lex("echo $(nonexistent_command)", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("$(nonexistent_command)".to_string())
]
);
}
#[test]
fn test_command_substitution_backticks_failure_fallback() {
let shell_state = ShellState::new();
let result = lex("echo `nonexistent_command`", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("`nonexistent_command`".to_string())
]
);
}
#[test]
fn test_command_substitution_with_variables() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "test_value".to_string());
let tokens = lex("echo $(echo $TEST_VAR)", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("test_value".to_string())
]
);
}
#[test]
fn test_command_substitution_in_assignment() {
let mut shell_state = ShellState::new();
let tokens = lex("MY_VAR=$(echo hello)", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(result, vec![Token::Word("MY_VAR=hello".to_string())]);
}
#[test]
fn test_command_substitution_backticks_in_assignment() {
let mut shell_state = ShellState::new();
let tokens = lex("MY_VAR=`echo hello`", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("MY_VAR=".to_string()),
Token::Word("hello".to_string())
]
);
}
#[test]
fn test_command_substitution_with_quotes() {
let mut shell_state = ShellState::new();
let tokens = lex("echo \"$(echo hello world)\"", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello world".to_string())
]
);
}
#[test]
fn test_command_substitution_backticks_with_quotes() {
let mut shell_state = ShellState::new();
let tokens = lex("echo \"`echo hello world`\"", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello world".to_string())
]
);
}
#[test]
fn test_command_substitution_empty_output() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $(true)", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(result, vec![Token::Word("echo".to_string())]);
}
#[test]
fn test_command_substitution_multiple_spaces() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $(echo 'hello world')", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello world".to_string())
]
);
}
#[test]
fn test_command_substitution_with_newlines() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $(printf 'hello\nworld')", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello\nworld".to_string())
]
);
}
#[test]
fn test_command_substitution_special_characters() {
let shell_state = ShellState::new();
let result = lex("echo $(echo '$#@^&*()')", &shell_state).unwrap();
println!("Special chars test result: {:?}", result);
assert_eq!(result.len(), 2);
assert_eq!(result[0], Token::Word("echo".to_string()));
assert!(matches!(result[1], Token::Word(_)));
}
#[test]
fn test_nested_command_substitution() {
let shell_state = ShellState::new();
let result = lex("echo $(echo $(pwd))", &shell_state).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], Token::Word("echo".to_string()));
assert!(matches!(result[1], Token::Word(_)));
}
#[test]
fn test_command_substitution_in_pipeline() {
let shell_state = ShellState::new();
let result = lex("$(echo hello) | cat", &shell_state).unwrap();
println!("Pipeline test result: {:?}", result);
assert_eq!(result.len(), 3);
assert!(matches!(result[0], Token::Word(_)));
assert_eq!(result[1], Token::Pipe);
assert_eq!(result[2], Token::Word("cat".to_string()));
}
#[test]
fn test_command_substitution_with_redirection() {
let shell_state = ShellState::new();
let result = lex("$(echo hello) > output.txt", &shell_state).unwrap();
assert_eq!(result.len(), 3);
assert!(matches!(result[0], Token::Word(_)));
assert_eq!(result[1], Token::RedirOut);
assert_eq!(result[2], Token::Word("output.txt".to_string()));
}
#[test]
fn test_arithmetic_expansion_simple() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $((2 + 3))", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("5".to_string())
]
);
}
#[test]
fn test_arithmetic_expansion_with_variables() {
let mut shell_state = ShellState::new();
shell_state.set_var("x", "10".to_string());
shell_state.set_var("y", "20".to_string());
let tokens = lex("echo $((x + y * 2))", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("50".to_string()) ]
);
}
#[test]
fn test_arithmetic_expansion_comparison() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $((5 > 3))", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("1".to_string()) ]
);
}
#[test]
fn test_arithmetic_expansion_complex() {
let mut shell_state = ShellState::new();
shell_state.set_var("a", "3".to_string());
let tokens = lex("echo $((a * 2 + 5))", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("11".to_string()) ]
);
}
#[test]
fn test_arithmetic_expansion_unmatched_parentheses() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $((2 + 3", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(result.len(), 2);
assert_eq!(result[0], Token::Word("echo".to_string()));
let second_token = &result[1];
if let Token::Word(s) = second_token {
assert!(
s.starts_with("$((") && s.contains("2") && s.contains("3"),
"Expected unmatched arithmetic to be kept as literal, got: {}",
s
);
} else {
panic!("Expected Word token");
}
}
#[test]
fn test_arithmetic_expansion_division_by_zero() {
let mut shell_state = ShellState::new();
let tokens = lex("echo $((5 / 0))", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(result.len(), 2);
assert_eq!(result[0], Token::Word("echo".to_string()));
if let Token::Word(s) = &result[1] {
assert!(
s.contains("Division by zero"),
"Expected division by zero error, got: {}",
s
);
} else {
panic!("Expected Word token");
}
}
#[test]
fn test_parameter_expansion_simple() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello world".to_string());
let result = lex("echo ${TEST_VAR}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello world".to_string())
]
);
}
#[test]
fn test_parameter_expansion_unset_variable() {
let shell_state = ShellState::new();
let result = lex("echo ${UNSET_VAR}", &shell_state).unwrap();
assert_eq!(
result,
vec![Token::Word("echo".to_string()), Token::Word("".to_string())]
);
}
#[test]
fn test_parameter_expansion_default() {
let shell_state = ShellState::new();
let result = lex("echo ${UNSET_VAR:-default}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("default".to_string())
]
);
}
#[test]
fn test_parameter_expansion_default_set_variable() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "value".to_string());
let result = lex("echo ${TEST_VAR:-default}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("value".to_string())
]
);
}
#[test]
fn test_parameter_expansion_assign_default() {
let shell_state = ShellState::new();
let result = lex("echo ${UNSET_VAR:=default}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("default".to_string())
]
);
}
#[test]
fn test_parameter_expansion_alternative() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "value".to_string());
let result = lex("echo ${TEST_VAR:+replacement}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("replacement".to_string())
]
);
}
#[test]
fn test_parameter_expansion_alternative_unset() {
let shell_state = ShellState::new();
let result = lex("echo ${UNSET_VAR:+replacement}", &shell_state).unwrap();
assert_eq!(
result,
vec![Token::Word("echo".to_string()), Token::Word("".to_string())]
);
}
#[test]
fn test_parameter_expansion_substring() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello world".to_string());
let result = lex("echo ${TEST_VAR:6}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("world".to_string())
]
);
}
#[test]
fn test_parameter_expansion_substring_with_length() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello world".to_string());
let result = lex("echo ${TEST_VAR:0:5}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello".to_string())
]
);
}
#[test]
fn test_parameter_expansion_length() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello".to_string());
let result = lex("echo ${#TEST_VAR}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("5".to_string())
]
);
}
#[test]
fn test_parameter_expansion_remove_shortest_prefix() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "prefix_hello".to_string());
let result = lex("echo ${TEST_VAR#prefix_}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello".to_string())
]
);
}
#[test]
fn test_parameter_expansion_remove_longest_prefix() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "prefix_prefix_hello".to_string());
let result = lex("echo ${TEST_VAR##prefix_}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("prefix_hello".to_string())
]
);
}
#[test]
fn test_parameter_expansion_remove_shortest_suffix() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello_suffix".to_string());
let result = lex("echo ${TEST_VAR%suffix}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello_".to_string()) ]
);
}
#[test]
fn test_parameter_expansion_remove_longest_suffix() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello_suffix_suffix".to_string());
let result = lex("echo ${TEST_VAR%%suffix}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello_suffix_".to_string()) ]
);
}
#[test]
fn test_parameter_expansion_substitute() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello world".to_string());
let result = lex("echo ${TEST_VAR/world/universe}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello universe".to_string())
]
);
}
#[test]
fn test_parameter_expansion_substitute_all() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello world world".to_string());
let result = lex("echo ${TEST_VAR//world/universe}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("hello universe universe".to_string())
]
);
}
#[test]
fn test_parameter_expansion_mixed_with_regular_variables() {
let mut shell_state = ShellState::new();
shell_state.set_var("VAR1", "value1".to_string());
shell_state.set_var("VAR2", "value2".to_string());
let tokens = lex("echo $VAR1 and ${VAR2}", &shell_state).unwrap();
let result = expand_tokens(tokens, &mut shell_state);
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("value1".to_string()),
Token::Word("and".to_string()),
Token::Word("value2".to_string())
]
);
}
#[test]
fn test_parameter_expansion_in_double_quotes() {
let mut shell_state = ShellState::new();
shell_state.set_var("TEST_VAR", "hello".to_string());
let result = lex("echo \"Value: ${TEST_VAR}\"", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("Value: hello".to_string())
]
);
}
#[test]
fn test_parameter_expansion_error_unset() {
let shell_state = ShellState::new();
let result = lex("echo ${UNSET_VAR:?error message}", &shell_state);
assert!(result.is_ok());
let tokens = result.unwrap();
assert_eq!(tokens.len(), 3);
assert_eq!(tokens[0], Token::Word("echo".to_string()));
assert_eq!(tokens[1], Token::Word("${UNSET_VAR:?error}".to_string()));
assert_eq!(tokens[2], Token::Word("message}".to_string()));
}
#[test]
fn test_parameter_expansion_complex_expression() {
let mut shell_state = ShellState::new();
shell_state.set_var("PATH", "/usr/bin:/bin:/usr/local/bin".to_string());
let result = lex("echo ${PATH#/usr/bin:}", &shell_state).unwrap();
assert_eq!(
result,
vec![
Token::Word("echo".to_string()),
Token::Word("/bin:/usr/local/bin".to_string())
]
);
}