use super::lexer::Token;
pub fn expand_braces(tokens: Vec<Token>) -> Result<Vec<Token>, String> {
if tokens.is_empty() {
return Ok(tokens);
}
let mut result = Vec::new();
let mut i = 0;
while i < tokens.len() {
match &tokens[i] {
Token::Word(word) => {
if word.contains('{') && word.contains('}') {
let expanded_words = expand_braces_in_word(word)?;
for expanded_word in expanded_words {
result.push(Token::Word(expanded_word));
}
} else {
result.push(tokens[i].clone());
}
}
_ => {
result.push(tokens[i].clone());
}
}
i += 1;
}
Ok(result)
}
fn expand_braces_in_word(word: &str) -> Result<Vec<String>, String> {
if !word.contains('{') || !word.contains('}') {
return Ok(vec![word.to_string()]);
}
let patterns = find_brace_patterns(word)?;
if patterns.is_empty() {
return Ok(vec![word.to_string()]);
}
let expansions = generate_expansions(patterns)?;
Ok(expansions)
}
fn find_brace_patterns(word: &str) -> Result<Vec<(String, Vec<String>, String)>, String> {
let mut patterns = Vec::new();
let chars: Vec<char> = word.chars().collect();
let mut pos = 0;
let mut last_end = 0;
while pos < chars.len() {
if chars[pos] == '{' {
let start = pos;
let mut depth = 0;
let mut end = None;
for (i, &ch) in chars.iter().enumerate().skip(start) {
if ch == '{' {
depth += 1;
} else if ch == '}' {
depth -= 1;
if depth == 0 {
end = Some(i);
break;
}
}
}
if let Some(end_pos) = end {
let prefix: String = if patterns.is_empty() {
chars[..start].iter().collect()
} else {
chars[last_end..start].iter().collect()
};
let content: String = chars[start + 1..end_pos].iter().collect();
let suffix = String::new();
let alternatives = parse_brace_content(&content)?;
patterns.push((prefix, alternatives, suffix));
last_end = end_pos + 1;
pos = end_pos + 1;
} else {
return Err("Unmatched braces in pattern".to_string());
}
} else {
pos += 1;
}
}
if !patterns.is_empty() && last_end < chars.len() {
let final_suffix: String = chars[last_end..].iter().collect();
let last_idx = patterns.len() - 1;
patterns[last_idx].2 = final_suffix;
}
Ok(patterns)
}
fn parse_brace_content(content: &str) -> Result<Vec<String>, String> {
let mut alternatives = Vec::new();
let parts = split_top_level(content, ',')?;
for part in parts {
let part = part.trim();
if part.starts_with('{') && part.ends_with('}') {
let inner_content = &part[1..part.len() - 1];
let nested_alternatives = parse_brace_content(inner_content)?;
alternatives.extend(nested_alternatives);
} else if part.contains("..") {
let range_alternatives = expand_range(part)?;
alternatives.extend(range_alternatives);
} else {
alternatives.push(part.to_string());
}
}
Ok(alternatives)
}
fn split_top_level(input: &str, _delimiter: char) -> Result<Vec<String>, String> {
let mut parts = Vec::new();
let mut current = String::new();
let mut brace_depth = 0;
let chars = input.chars().peekable();
for ch in chars {
match ch {
'{' => {
brace_depth += 1;
current.push(ch);
}
'}' => {
brace_depth -= 1;
current.push(ch);
}
',' if brace_depth == 0 => {
parts.push(current);
current = String::new();
}
_ => {
current.push(ch);
}
}
}
if brace_depth != 0 {
return Err("Unmatched braces in pattern".to_string());
}
if !current.is_empty() {
parts.push(current);
}
Ok(parts)
}
fn expand_range(pattern: &str) -> Result<Vec<String>, String> {
let parts: Vec<&str> = pattern.split("..").collect();
if parts.len() != 2 {
return Err(format!("Invalid range pattern: {}", pattern));
}
let start = parts[0].trim();
let end = parts[1].trim();
if start.len() == 1
&& end.len() == 1
&& let (Some(start_ch), Some(end_ch)) = (start.chars().next(), end.chars().next())
&& start_ch.is_ascii_alphabetic()
&& end_ch.is_ascii_alphabetic()
{
return expand_char_range(start_ch, end_ch);
}
if let (Ok(start_num), Ok(end_num)) = (start.parse::<i64>(), end.parse::<i64>()) {
return expand_numeric_range(start_num, end_num);
}
Ok(vec![start.to_string(), end.to_string()])
}
fn expand_char_range(start: char, end: char) -> Result<Vec<String>, String> {
if !start.is_ascii_alphabetic() || !end.is_ascii_alphabetic() {
return Err(format!("Invalid character range: {}..{}", start, end));
}
let mut result = Vec::new();
let start_byte = start as u8;
let end_byte = end as u8;
if start_byte > end_byte {
return Err(format!("Invalid range: {} > {}", start, end));
}
for byte in start_byte..=end_byte {
if let Some(ch) = char::from_u32(byte as u32) {
result.push(ch.to_string());
}
}
Ok(result)
}
fn expand_numeric_range(start: i64, end: i64) -> Result<Vec<String>, String> {
if start > end {
return Err(format!("Invalid range: {} > {}", start, end));
}
let mut result = Vec::new();
for num in start..=end {
result.push(num.to_string());
}
Ok(result)
}
fn generate_expansions(
patterns: Vec<(String, Vec<String>, String)>,
) -> Result<Vec<String>, String> {
if patterns.is_empty() {
return Ok(Vec::new());
}
if patterns.len() == 1 {
let (prefix, alternatives, suffix) = &patterns[0];
let mut result = Vec::new();
for alt in alternatives {
result.push(format!("{}{}{}", prefix, alt, suffix));
}
return Ok(result);
}
let mut is_consecutive = true;
for i in 0..patterns.len() - 1 {
let (_, _, suffix) = &patterns[i];
let (prefix, _, _) = &patterns[i + 1];
if !suffix.is_empty() && !prefix.is_empty() {
is_consecutive = false;
break;
}
}
if is_consecutive {
let base_prefix = &patterns[0].0;
let mut current_results = vec![base_prefix.clone()];
for (i, (_prefix, alternatives, suffix)) in patterns.iter().enumerate() {
let mut next_results = Vec::new();
for current in ¤t_results {
for alt in alternatives {
let mut new_str = current.clone();
if i > 0 {
}
new_str.push_str(alt);
if i == patterns.len() - 1 {
new_str.push_str(suffix);
}
next_results.push(new_str);
}
}
current_results = next_results;
}
return Ok(current_results);
}
let mut result = Vec::new();
for (prefix, alternatives, suffix) in patterns {
for alt in alternatives {
result.push(format!("{}{}{}", prefix, alt, suffix));
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_top_level_simple() {
let result = split_top_level("a,b,c", ',').unwrap();
assert_eq!(result, vec!["a", "b", "c"]);
}
#[test]
fn test_split_top_level_with_braces() {
let result = split_top_level("a,{b,c},d", ',').unwrap();
assert_eq!(result, vec!["a", "{b,c}", "d"]);
}
#[test]
fn test_split_top_level_nested_braces() {
let result = split_top_level("{a,b},{c,d}", ',').unwrap();
assert_eq!(result, vec!["{a,b}", "{c,d}"]);
}
#[test]
fn test_expand_char_range() {
let result = expand_char_range('a', 'c').unwrap();
assert_eq!(result, vec!["a", "b", "c"]);
}
#[test]
fn test_expand_numeric_range() {
let result = expand_numeric_range(1, 3).unwrap();
assert_eq!(result, vec!["1", "2", "3"]);
}
#[test]
fn test_expand_braces_in_word_simple() {
let result = expand_braces_in_word("a{b,c}d").unwrap();
assert_eq!(result, vec!["abd", "acd"]);
}
#[test]
fn test_expand_braces_in_word_with_ranges() {
let result = expand_braces_in_word("{1..3}").unwrap();
assert_eq!(result, vec!["1", "2", "3"]);
}
#[test]
fn test_expand_braces_in_word_mixed() {
let result = expand_braces_in_word("file{a,b}.txt").unwrap();
assert_eq!(result, vec!["filea.txt", "fileb.txt"]);
}
#[test]
fn test_expand_braces_in_word_nested() {
let result = expand_braces_in_word("{{a,b},{c,d}}").unwrap();
assert_eq!(result, vec!["a", "b", "c", "d"]);
}
#[test]
fn test_parse_brace_content_simple_nested() {
let result = parse_brace_content("{a,b}").unwrap();
assert_eq!(result, vec!["a", "b"]);
}
#[test]
fn test_parse_brace_content_nested() {
let result = parse_brace_content("{a,b},{c,d}").unwrap();
assert_eq!(result, vec!["a", "b", "c", "d"]);
}
#[test]
fn test_expand_braces_no_braces() {
let result = expand_braces_in_word("hello").unwrap();
assert_eq!(result, vec!["hello"]);
}
#[test]
fn test_expand_braces_empty() {
let tokens = vec![];
let result = expand_braces(tokens).unwrap();
assert_eq!(result, vec![]);
}
#[test]
fn test_expand_braces_word_without_braces() {
let tokens = vec![Token::Word("hello".to_string())];
let result = expand_braces(tokens).unwrap();
assert_eq!(result, vec![Token::Word("hello".to_string())]);
}
#[test]
fn test_expand_braces_mixed_tokens() {
let tokens = vec![
Token::Word("{a,b}".to_string()),
Token::Pipe,
Token::Word("cat".to_string()),
];
let result = expand_braces(tokens).unwrap();
assert_eq!(
result,
vec![
Token::Word("a".to_string()),
Token::Word("b".to_string()),
Token::Pipe,
Token::Word("cat".to_string()),
]
);
}
}