use super::*;
pub(super) fn skip_newlines(tokens: &[Token], i: &mut usize) {
while *i < tokens.len() && tokens[*i] == Token::Newline {
*i += 1;
}
}
pub(super) fn skip_to_matching_fi(tokens: &[Token], i: &mut usize) {
let mut if_depth = 1;
*i += 1; while *i < tokens.len() && if_depth > 0 {
match tokens[*i] {
Token::If => if_depth += 1,
Token::Fi => if_depth -= 1,
_ => {}
}
*i += 1;
}
}
pub(super) fn skip_to_matching_done(tokens: &[Token], i: &mut usize) {
let mut loop_depth = 1;
*i += 1; while *i < tokens.len() && loop_depth > 0 {
match tokens[*i] {
Token::For | Token::While | Token::Until => loop_depth += 1,
Token::Done => loop_depth -= 1,
_ => {}
}
*i += 1;
}
}
pub(super) fn skip_to_matching_esac(tokens: &[Token], i: &mut usize) {
*i += 1; while *i < tokens.len() {
if tokens[*i] == Token::Esac {
*i += 1;
break;
}
*i += 1;
}
}
pub(super) fn parse_if(tokens: &[Token]) -> Result<Ast, String> {
let mut i = 1; let mut branches = Vec::new();
loop {
let mut cond_tokens = Vec::new();
while i < tokens.len()
&& tokens[i] != Token::Semicolon
&& tokens[i] != Token::Newline
&& tokens[i] != Token::Then
{
cond_tokens.push(tokens[i].clone());
i += 1;
}
if i < tokens.len() && (tokens[i] == Token::Semicolon || tokens[i] == Token::Newline) {
i += 1;
}
skip_newlines(tokens, &mut i);
if i >= tokens.len() || tokens[i] != Token::Then {
return Err("Expected then after if/elif condition".to_string());
}
i += 1;
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
let mut then_tokens = Vec::new();
let mut depth = 0;
while i < tokens.len() {
match &tokens[i] {
Token::If => {
depth += 1;
then_tokens.push(tokens[i].clone());
}
Token::Fi => {
if depth > 0 {
depth -= 1;
then_tokens.push(tokens[i].clone());
} else {
break; }
}
Token::Else | Token::Elif if depth == 0 => {
break; }
Token::Newline => {
let mut j = i + 1;
while j < tokens.len() && tokens[j] == Token::Newline {
j += 1;
}
if j < tokens.len()
&& depth == 0
&& (tokens[j] == Token::Else
|| tokens[j] == Token::Elif
|| tokens[j] == Token::Fi)
{
i = j; break;
}
then_tokens.push(tokens[i].clone());
}
_ => {
then_tokens.push(tokens[i].clone());
}
}
i += 1;
}
skip_newlines(tokens, &mut i);
let then_ast = if then_tokens.is_empty() {
create_empty_body_ast()
} else {
parse_commands_sequentially(&then_tokens)?
};
let (condition, _) = parse_next_command(&cond_tokens)?;
branches.push((Box::new(condition), Box::new(then_ast)));
if i < tokens.len() && tokens[i] == Token::Elif {
i += 1; } else {
break;
}
}
let else_ast = if i < tokens.len() && tokens[i] == Token::Else {
i += 1;
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
let mut else_tokens = Vec::new();
let mut depth = 0;
while i < tokens.len() {
match &tokens[i] {
Token::If => {
depth += 1;
else_tokens.push(tokens[i].clone());
}
Token::Fi => {
if depth > 0 {
depth -= 1;
else_tokens.push(tokens[i].clone());
} else {
break; }
}
Token::Newline => {
let mut j = i + 1;
while j < tokens.len() && tokens[j] == Token::Newline {
j += 1;
}
if j < tokens.len() && depth == 0 && tokens[j] == Token::Fi {
i = j; break;
}
else_tokens.push(tokens[i].clone());
}
_ => {
else_tokens.push(tokens[i].clone());
}
}
i += 1;
}
let else_ast = if else_tokens.is_empty() {
create_empty_body_ast()
} else {
parse_commands_sequentially(&else_tokens)?
};
Some(Box::new(else_ast))
} else {
None
};
if i >= tokens.len() || tokens[i] != Token::Fi {
return Err("Expected fi".to_string());
}
Ok(Ast::If {
branches,
else_branch: else_ast,
})
}
pub(super) fn parse_case(tokens: &[Token]) -> Result<Ast, String> {
let mut i = 1;
if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
return Err("Expected word after case".to_string());
}
let word = if let Token::Word(ref w) = tokens[i] {
w.clone()
} else {
unreachable!()
};
i += 1;
if i >= tokens.len() || tokens[i] != Token::In {
return Err("Expected in after case word".to_string());
}
i += 1;
let mut cases = Vec::new();
let mut default = None;
loop {
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
if i >= tokens.len() {
return Err("Unexpected end in case statement".to_string());
}
if tokens[i] == Token::Esac {
break;
}
let mut patterns = Vec::new();
while i < tokens.len() && tokens[i] != Token::RightParen {
if let Token::Word(ref p) = tokens[i] {
for pat in p.split('|') {
patterns.push(pat.to_string());
}
} else if tokens[i] == Token::Pipe {
} else if tokens[i] == Token::Newline {
} else {
return Err(format!("Expected pattern, found {:?}", tokens[i]));
}
i += 1;
}
if i >= tokens.len() || tokens[i] != Token::RightParen {
return Err("Expected ) after patterns".to_string());
}
i += 1;
let mut commands_tokens = Vec::new();
while i < tokens.len() && tokens[i] != Token::DoubleSemicolon && tokens[i] != Token::Esac {
commands_tokens.push(tokens[i].clone());
i += 1;
}
let (commands_ast, _) = parse_next_command(&commands_tokens)?;
if i >= tokens.len() {
return Err("Unexpected end in case statement".to_string());
}
if tokens[i] == Token::DoubleSemicolon {
i += 1;
if patterns.len() == 1 && patterns[0] == "*" {
default = Some(Box::new(commands_ast));
} else {
cases.push((patterns, commands_ast));
}
} else if tokens[i] == Token::Esac {
if patterns.len() == 1 && patterns[0] == "*" {
default = Some(Box::new(commands_ast));
} else {
cases.push((patterns, commands_ast));
}
break;
} else {
return Err("Expected ;; or esac after commands".to_string());
}
}
Ok(Ast::Case {
word,
cases,
default,
})
}
pub(super) fn parse_for(tokens: &[Token]) -> Result<Ast, String> {
let mut i = 1;
if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
return Err("Expected variable name after for".to_string());
}
let variable = if let Token::Word(ref v) = tokens[i] {
v.clone()
} else {
unreachable!()
};
i += 1;
if i >= tokens.len() || tokens[i] != Token::In {
return Err("Expected 'in' after for variable".to_string());
}
i += 1;
let mut items = Vec::new();
while i < tokens.len() {
match &tokens[i] {
Token::Do => break,
Token::Semicolon | Token::Newline => {
i += 1;
if i < tokens.len() && tokens[i] == Token::Do {
break;
}
}
Token::Word(word) => {
items.push(word.clone());
i += 1;
}
_ => {
return Err(format!("Unexpected token in for items: {:?}", tokens[i]));
}
}
}
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
if i >= tokens.len() || tokens[i] != Token::Do {
return Err("Expected 'do' in for loop".to_string());
}
i += 1;
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
let mut body_tokens = Vec::new();
let mut depth = 0;
while i < tokens.len() {
match &tokens[i] {
Token::For => {
depth += 1;
body_tokens.push(tokens[i].clone());
}
Token::Done => {
if depth > 0 {
depth -= 1;
body_tokens.push(tokens[i].clone());
} else {
break; }
}
Token::Newline => {
let mut j = i + 1;
while j < tokens.len() && tokens[j] == Token::Newline {
j += 1;
}
if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
i = j; break;
}
body_tokens.push(tokens[i].clone());
}
_ => {
body_tokens.push(tokens[i].clone());
}
}
i += 1;
}
if i >= tokens.len() || tokens[i] != Token::Done {
return Err("Expected 'done' to close for loop".to_string());
}
let body_ast = if body_tokens.is_empty() {
create_empty_body_ast()
} else {
parse_commands_sequentially(&body_tokens)?
};
Ok(Ast::For {
variable,
items,
body: Box::new(body_ast),
})
}
pub(super) fn parse_while(tokens: &[Token]) -> Result<Ast, String> {
let mut i = 1;
let mut cond_tokens = Vec::new();
while i < tokens.len() {
match &tokens[i] {
Token::Do => break,
Token::Semicolon | Token::Newline => {
i += 1;
if i < tokens.len() && tokens[i] == Token::Do {
break;
}
}
_ => {
cond_tokens.push(tokens[i].clone());
i += 1;
}
}
}
if cond_tokens.is_empty() {
return Err("Expected condition after while".to_string());
}
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
if i >= tokens.len() || tokens[i] != Token::Do {
return Err("Expected 'do' in while loop".to_string());
}
i += 1;
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
let mut body_tokens = Vec::new();
let mut depth = 0;
while i < tokens.len() {
match &tokens[i] {
Token::While | Token::For | Token::Until => {
depth += 1;
body_tokens.push(tokens[i].clone());
}
Token::Done => {
if depth > 0 {
depth -= 1;
body_tokens.push(tokens[i].clone());
} else {
break; }
}
Token::Newline => {
let mut j = i + 1;
while j < tokens.len() && tokens[j] == Token::Newline {
j += 1;
}
if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
i = j; break;
}
body_tokens.push(tokens[i].clone());
}
_ => {
body_tokens.push(tokens[i].clone());
}
}
i += 1;
}
if i >= tokens.len() || tokens[i] != Token::Done {
return Err("Expected 'done' to close while loop".to_string());
}
let (condition_ast, _) = parse_next_command(&cond_tokens)?;
let body_ast = if body_tokens.is_empty() {
create_empty_body_ast()
} else {
parse_commands_sequentially(&body_tokens)?
};
Ok(Ast::While {
condition: Box::new(condition_ast),
body: Box::new(body_ast),
})
}
pub(super) fn parse_until(tokens: &[Token]) -> Result<Ast, String> {
let mut i = 1;
let mut cond_tokens = Vec::new();
while i < tokens.len() {
match &tokens[i] {
Token::Do => break,
Token::Semicolon | Token::Newline => {
i += 1;
if i < tokens.len() && tokens[i] == Token::Do {
break;
}
}
_ => {
cond_tokens.push(tokens[i].clone());
i += 1;
}
}
}
if cond_tokens.is_empty() {
return Err("Expected condition after until".to_string());
}
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
if i >= tokens.len() || tokens[i] != Token::Do {
return Err("Expected 'do' in until loop".to_string());
}
i += 1;
while i < tokens.len() && tokens[i] == Token::Newline {
i += 1;
}
let mut body_tokens = Vec::new();
let mut depth = 0;
while i < tokens.len() {
match &tokens[i] {
Token::While | Token::For | Token::Until => {
depth += 1;
body_tokens.push(tokens[i].clone());
}
Token::Done => {
if depth > 0 {
depth -= 1;
body_tokens.push(tokens[i].clone());
} else {
break; }
}
Token::Newline => {
let mut j = i + 1;
while j < tokens.len() && tokens[j] == Token::Newline {
j += 1;
}
if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
i = j; break;
}
body_tokens.push(tokens[i].clone());
}
_ => {
body_tokens.push(tokens[i].clone());
}
}
i += 1;
}
if i >= tokens.len() || tokens[i] != Token::Done {
return Err("Expected 'done' to close until loop".to_string());
}
let (condition_ast, _) = parse_next_command(&cond_tokens)?;
let body_ast = if body_tokens.is_empty() {
create_empty_body_ast()
} else {
parse_commands_sequentially(&body_tokens)?
};
Ok(Ast::Until {
condition: Box::new(condition_ast),
body: Box::new(body_ast),
})
}
pub(super) fn parse_function_definition(tokens: &[Token]) -> Result<Ast, String> {
if tokens.len() < 2 {
return Err("Function definition too short".to_string());
}
let func_name = if let Token::Word(word) = &tokens[0] {
if let Some(paren_pos) = word.find('(') {
if word.ends_with(')') && paren_pos > 0 {
word[..paren_pos].to_string()
} else {
word.clone()
}
} else {
word.clone()
}
} else {
return Err("Function name must be a word".to_string());
};
let brace_pos =
if tokens.len() >= 4 && tokens[1] == Token::LeftParen && tokens[2] == Token::RightParen {
if tokens[3] != Token::LeftBrace {
return Err("Expected { after function name".to_string());
}
3
} else if tokens.len() >= 2 && tokens[1] == Token::LeftBrace {
1
} else {
return Err("Expected ( after function name or { for legacy format".to_string());
};
let mut brace_depth = 0;
let mut body_end = 0;
let mut found_closing = false;
let mut i = brace_pos + 1;
while i < tokens.len() {
if i + 3 < tokens.len()
&& matches!(&tokens[i], Token::Word(_))
&& tokens[i + 1] == Token::LeftParen
&& tokens[i + 2] == Token::RightParen
&& tokens[i + 3] == Token::LeftBrace
{
i += 4;
let mut nested_depth = 1;
while i < tokens.len() && nested_depth > 0 {
match tokens[i] {
Token::LeftBrace => nested_depth += 1,
Token::RightBrace => nested_depth -= 1,
_ => {}
}
i += 1;
}
continue;
}
match &tokens[i] {
Token::LeftBrace => {
brace_depth += 1;
i += 1;
}
Token::RightBrace => {
if brace_depth == 0 {
body_end = i;
found_closing = true;
break;
} else {
brace_depth -= 1;
i += 1;
}
}
Token::If => {
skip_to_matching_fi(tokens, &mut i);
}
Token::For | Token::While | Token::Until => {
skip_to_matching_done(tokens, &mut i);
}
Token::Case => {
skip_to_matching_esac(tokens, &mut i);
}
_ => {
i += 1;
}
}
}
if !found_closing {
return Err("Missing closing } for function definition".to_string());
}
let body_tokens = &tokens[brace_pos + 1..body_end];
let body_ast = if body_tokens.is_empty() {
create_empty_body_ast()
} else {
parse_commands_sequentially(body_tokens)?
};
Ok(Ast::FunctionDefinition {
name: func_name,
body: Box::new(body_ast),
})
}