rush_sh/
parser.rs

1use super::lexer::Token;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum Ast {
5    Pipeline(Vec<ShellCommand>),
6    Sequence(Vec<Ast>),
7    Assignment {
8        var: String,
9        value: String,
10    },
11    LocalAssignment {
12        var: String,
13        value: String,
14    },
15    If {
16        branches: Vec<(Box<Ast>, Box<Ast>)>, // (condition, then_branch)
17        else_branch: Option<Box<Ast>>,
18    },
19    Case {
20        word: String,
21        cases: Vec<(Vec<String>, Ast)>,
22        default: Option<Box<Ast>>,
23    },
24    For {
25        variable: String,
26        items: Vec<String>,
27        body: Box<Ast>,
28    },
29    While {
30        condition: Box<Ast>,
31        body: Box<Ast>,
32    },
33    FunctionDefinition {
34        name: String,
35        body: Box<Ast>,
36    },
37    FunctionCall {
38        name: String,
39        args: Vec<String>,
40    },
41    Return {
42        value: Option<String>,
43    },
44    And {
45        left: Box<Ast>,
46        right: Box<Ast>,
47    },
48    Or {
49        left: Box<Ast>,
50        right: Box<Ast>,
51    },
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ShellCommand {
56    pub args: Vec<String>,
57    pub input: Option<String>,
58    pub output: Option<String>,
59    pub append: Option<String>,
60}
61
62pub fn parse(tokens: Vec<Token>) -> Result<Ast, String> {
63    // First, try to detect and parse function definitions that span multiple lines
64    if tokens.len() >= 4
65        && let (Token::Word(_), Token::LeftParen, Token::RightParen, Token::LeftBrace) =
66            (&tokens[0], &tokens[1], &tokens[2], &tokens[3])
67        {
68            // Look for the matching RightBrace
69            // Start from the opening brace (token 3) and find its match
70            let mut brace_depth = 1; // We've already seen the opening brace at position 3
71            let mut function_end = tokens.len();
72            let mut j = 4; // Start after the opening brace
73
74            while j < tokens.len() {
75                match &tokens[j] {
76                    Token::LeftBrace => {
77                        brace_depth += 1;
78                        j += 1;
79                    },
80                    Token::RightBrace => {
81                        brace_depth -= 1;
82                        if brace_depth == 0 {
83                            function_end = j + 1; // Include the closing brace
84                            break;
85                        }
86                        j += 1;
87                    }
88                    Token::If => {
89                        // Skip to matching fi to avoid confusion
90                        let mut if_depth = 1;
91                        j += 1;
92                        while j < tokens.len() && if_depth > 0 {
93                            match tokens[j] {
94                                Token::If => if_depth += 1,
95                                Token::Fi => if_depth -= 1,
96                                _ => {}
97                            }
98                            j += 1;
99                        }
100                    }
101                    Token::For | Token::While => {
102                        // Skip to matching done
103                        let mut for_depth = 1;
104                        j += 1;
105                        while j < tokens.len() && for_depth > 0 {
106                            match tokens[j] {
107                                Token::For | Token::While => for_depth += 1,
108                                Token::Done => for_depth -= 1,
109                                _ => {}
110                            }
111                            j += 1;
112                        }
113                    }
114                    Token::Case => {
115                        // Skip to matching esac
116                        j += 1;
117                        while j < tokens.len() {
118                            if tokens[j] == Token::Esac {
119                                j += 1;
120                                break;
121                            }
122                            j += 1;
123                        }
124                    }
125                    _ => {
126                        j += 1;
127                    }
128                }
129            }
130
131            if brace_depth == 0 && function_end <= tokens.len() {
132                // We found the complete function definition
133                let function_tokens = &tokens[0..function_end];
134                let remaining_tokens = &tokens[function_end..];
135
136                let function_ast = parse_function_definition(function_tokens)?;
137
138                if remaining_tokens.is_empty() {
139                    return Ok(function_ast);
140                } else {
141                    // There are more commands after the function
142                    let remaining_ast = parse_commands_sequentially(remaining_tokens)?;
143                    return Ok(Ast::Sequence(vec![function_ast, remaining_ast]));
144                }
145            }
146        }
147
148    // Also check for legacy function definition format (word with parentheses followed by brace)
149    if tokens.len() >= 2
150        && let Token::Word(ref word) = tokens[0]
151            && let Some(paren_pos) = word.find('(')
152                && word.ends_with(')') && paren_pos > 0
153                    && tokens[1] == Token::LeftBrace {
154                        return parse_function_definition(&tokens);
155                    }
156
157    // Fall back to normal parsing
158    parse_commands_sequentially(&tokens)
159}
160
161fn parse_slice(tokens: &[Token]) -> Result<Ast, String> {
162    if tokens.is_empty() {
163        return Err("No commands found".to_string());
164    }
165
166    // Check if it's an assignment
167    if tokens.len() == 2 {
168        // Check for pattern: VAR= VALUE
169        if let (Token::Word(var_eq), Token::Word(value)) = (&tokens[0], &tokens[1])
170            && let Some(eq_pos) = var_eq.find('=')
171                && eq_pos > 0 && eq_pos < var_eq.len() - 1 {
172                    let var = var_eq[..eq_pos].to_string();
173                    let full_value = format!("{}{}", &var_eq[eq_pos + 1..], value);
174                    // Basic validation: variable name should start with letter or underscore
175                    if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
176                        return Ok(Ast::Assignment {
177                            var,
178                            value: full_value,
179                        });
180                    }
181                }
182    }
183
184    // Check if it's an assignment (VAR= VALUE)
185    if tokens.len() == 2
186        && let (Token::Word(var_eq), Token::Word(value)) = (&tokens[0], &tokens[1])
187            && let Some(eq_pos) = var_eq.find('=')
188                && eq_pos > 0 && eq_pos == var_eq.len() - 1 {
189                    let var = var_eq[..eq_pos].to_string();
190                    // Basic validation: variable name should start with letter or underscore
191                    if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
192                        return Ok(Ast::Assignment {
193                            var,
194                            value: value.clone(),
195                        });
196                    }
197                }
198
199    // Check if it's a local assignment (local VAR VALUE or local VAR= VALUE)
200    if tokens.len() == 3
201        && let (Token::Local, Token::Word(var), Token::Word(value)) = (&tokens[0], &tokens[1], &tokens[2]) {
202            // Strip trailing = if present (handles "local var= value" format)
203            let clean_var = if var.ends_with('=') {
204                &var[..var.len() - 1]
205            } else {
206                var
207            };
208            // Basic validation: variable name should start with letter or underscore
209            if clean_var.chars().next().unwrap().is_alphabetic() || clean_var.starts_with('_') {
210                return Ok(Ast::LocalAssignment {
211                    var: clean_var.to_string(),
212                    value: value.clone(),
213                });
214            }
215        }
216
217    // Check if it's a return statement
218    if !tokens.is_empty() && tokens.len() <= 2
219        && let Token::Return = &tokens[0] {
220            if tokens.len() == 1 {
221                // return (with no value, defaults to 0)
222                return Ok(Ast::Return { value: None });
223            } else if let Token::Word(word) = &tokens[1] {
224                // return value
225                return Ok(Ast::Return {
226                    value: Some(word.clone()),
227                });
228            }
229        }
230
231    // Check if it's a local assignment (local VAR=VALUE)
232    if tokens.len() == 2
233        && let (Token::Local, Token::Word(var_eq)) = (&tokens[0], &tokens[1])
234            && let Some(eq_pos) = var_eq.find('=')
235                && eq_pos > 0 && eq_pos < var_eq.len() - 1 {
236                    let var = var_eq[..eq_pos].to_string();
237                    let value = var_eq[eq_pos + 1..].to_string();
238                    // Basic validation: variable name should start with letter or underscore
239                    if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
240                        return Ok(Ast::LocalAssignment { var, value });
241                    }
242                }
243
244    // Check if it's an assignment (single token with =)
245    if tokens.len() == 1
246        && let Token::Word(ref word) = tokens[0]
247            && let Some(eq_pos) = word.find('=')
248                && eq_pos > 0 && eq_pos < word.len() - 1 {
249                    let var = word[..eq_pos].to_string();
250                    let value = word[eq_pos + 1..].to_string();
251                    // Basic validation: variable name should start with letter or underscore
252                    if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
253                        return Ok(Ast::Assignment { var, value });
254                    }
255                }
256
257    // Check if it's an if statement
258    if let Token::If = tokens[0] {
259        return parse_if(tokens);
260    }
261
262    // Check if it's a case statement
263    if let Token::Case = tokens[0] {
264        return parse_case(tokens);
265    }
266
267    // Check if it's a for loop
268    if let Token::For = tokens[0] {
269        return parse_for(tokens);
270    }
271
272    // Check if it's a while loop
273    if let Token::While = tokens[0] {
274        return parse_while(tokens);
275    }
276
277    // Check if it's a function definition
278    // Pattern: Word LeftParen RightParen LeftBrace
279    if tokens.len() >= 4
280        && let (Token::Word(word), Token::LeftParen, Token::RightParen, Token::LeftBrace) =
281            (&tokens[0], &tokens[1], &tokens[2], &tokens[3])
282            && (word.chars().next().unwrap().is_alphabetic() || word.starts_with('_')) {
283                return parse_function_definition(tokens);
284            }
285
286    // Also check for function definition with parentheses in the word (legacy support)
287    if tokens.len() >= 2
288        && let Token::Word(ref word) = tokens[0]
289            && let Some(paren_pos) = word.find('(')
290                && word.ends_with(')') && paren_pos > 0 {
291                    let func_name = &word[..paren_pos];
292                    if (func_name.chars().next().unwrap().is_alphabetic() || func_name.starts_with('_'))
293                        && tokens[1] == Token::LeftBrace {
294                            return parse_function_definition(tokens);
295                        }
296                }
297
298    // Check if it's a function call (word followed by arguments)
299    // For Phase 1, we'll parse as regular pipeline and handle function calls in executor
300
301    // Otherwise, parse as pipeline
302    parse_pipeline(tokens)
303}
304
305fn parse_commands_sequentially(tokens: &[Token]) -> Result<Ast, String> {
306    let mut i = 0;
307    let mut commands = Vec::new();
308
309    while i < tokens.len() {
310        // Skip whitespace and comments
311        while i < tokens.len() {
312            match &tokens[i] {
313                Token::Newline => {
314                    i += 1;
315                }
316                Token::Word(word) if word.starts_with('#') => {
317                    // Skip comment line
318                    while i < tokens.len() && tokens[i] != Token::Newline {
319                        i += 1;
320                    }
321                    if i < tokens.len() {
322                        i += 1; // Skip the newline
323                    }
324                }
325                _ => break,
326            }
327        }
328
329        if i >= tokens.len() {
330            break;
331        }
332
333        // Find the end of this command
334        let start = i;
335
336        // Special handling for compound commands
337        if tokens[i] == Token::If {
338            // For if statements, find the matching fi
339            let mut depth = 0;
340            while i < tokens.len() {
341                match tokens[i] {
342                    Token::If => depth += 1,
343                    Token::Fi => {
344                        depth -= 1;
345                        if depth == 0 {
346                            i += 1; // Include the fi
347                            break;
348                        }
349                    }
350                    _ => {}
351                }
352                i += 1;
353            }
354
355            // If we didn't find a matching fi, include all remaining tokens
356            // This handles the case where the if statement is incomplete
357        } else if tokens[i] == Token::For {
358            // For for loops, find the matching done
359            let mut depth = 1; // Start at 1 because we're already inside the for
360            i += 1; // Move past the 'for' token
361            while i < tokens.len() {
362                match tokens[i] {
363                    Token::For | Token::While => depth += 1,
364                    Token::Done => {
365                        depth -= 1;
366                        if depth == 0 {
367                            i += 1; // Include the done
368                            break;
369                        }
370                    }
371                    _ => {}
372                }
373                i += 1;
374            }
375        } else if tokens[i] == Token::While {
376            // For while loops, find the matching done
377            let mut depth = 1; // Start at 1 because we're already inside the while
378            i += 1; // Move past the 'while' token
379            while i < tokens.len() {
380                match tokens[i] {
381                    Token::While | Token::For => depth += 1,
382                    Token::Done => {
383                        depth -= 1;
384                        if depth == 0 {
385                            i += 1; // Include the done
386                            break;
387                        }
388                    }
389                    _ => {}
390                }
391                i += 1;
392            }
393        } else if tokens[i] == Token::Case {
394            // For case statements, find the matching esac
395            while i < tokens.len() {
396                if tokens[i] == Token::Esac {
397                    i += 1; // Include the esac
398                    break;
399                }
400                i += 1;
401            }
402        } else if i + 3 < tokens.len()
403            && matches!(tokens[i], Token::Word(_))
404            && tokens[i + 1] == Token::LeftParen
405            && tokens[i + 2] == Token::RightParen
406            && tokens[i + 3] == Token::LeftBrace
407        {
408            // This is a function definition - find the matching closing brace
409            let mut brace_depth = 1;
410            i += 4; // Skip to after opening brace
411            while i < tokens.len() && brace_depth > 0 {
412                match tokens[i] {
413                    Token::LeftBrace => brace_depth += 1,
414                    Token::RightBrace => brace_depth -= 1,
415                    _ => {}
416                }
417                i += 1;
418            }
419        } else {
420            // For simple commands, stop at newline, semicolon, &&, or ||
421            // But check if the next token after newline is a control flow keyword
422            while i < tokens.len() {
423                if tokens[i] == Token::Newline || tokens[i] == Token::Semicolon
424                    || tokens[i] == Token::And || tokens[i] == Token::Or {
425                    // Look ahead to see if the next non-newline token is else/elif/fi
426                    let mut j = i + 1;
427                    while j < tokens.len() && tokens[j] == Token::Newline {
428                        j += 1;
429                    }
430                    // If we find else/elif/fi, this is likely part of an if statement that wasn't properly detected
431                    if j < tokens.len()
432                        && (tokens[j] == Token::Else
433                            || tokens[j] == Token::Elif
434                            || tokens[j] == Token::Fi)
435                    {
436                        // Skip this token and continue - it will be handled as a parse error
437                        i = j + 1;
438                        continue;
439                    }
440                    break;
441                }
442                i += 1;
443            }
444        }
445
446        let command_tokens = &tokens[start..i];
447        if !command_tokens.is_empty() {
448            // Don't try to parse orphaned else/elif/fi tokens
449            if command_tokens.len() == 1 {
450                match command_tokens[0] {
451                    Token::Else | Token::Elif | Token::Fi => {
452                        // Skip orphaned control flow tokens
453                        if i < tokens.len()
454                            && (tokens[i] == Token::Newline || tokens[i] == Token::Semicolon)
455                        {
456                            i += 1;
457                        }
458                        continue;
459                    }
460                    _ => {}
461                }
462            }
463
464            let ast = parse_slice(command_tokens)?;
465            
466            // Check if the next token is && or ||
467            if i < tokens.len() && (tokens[i] == Token::And || tokens[i] == Token::Or) {
468                let operator = tokens[i].clone();
469                i += 1; // Skip the operator
470                
471                // Skip any newlines after the operator
472                while i < tokens.len() && tokens[i] == Token::Newline {
473                    i += 1;
474                }
475                
476                // Parse the right side recursively
477                let remaining_tokens = &tokens[i..];
478                let right_ast = parse_commands_sequentially(remaining_tokens)?;
479                
480                // Create And or Or node
481                let combined_ast = match operator {
482                    Token::And => Ast::And {
483                        left: Box::new(ast),
484                        right: Box::new(right_ast),
485                    },
486                    Token::Or => Ast::Or {
487                        left: Box::new(ast),
488                        right: Box::new(right_ast),
489                    },
490                    _ => unreachable!(),
491                };
492                
493                commands.push(combined_ast);
494                break; // We've consumed the rest of the tokens
495            } else {
496                commands.push(ast);
497            }
498        }
499
500        if i < tokens.len() && (tokens[i] == Token::Newline || tokens[i] == Token::Semicolon) {
501            i += 1;
502        }
503    }
504
505    if commands.is_empty() {
506        return Err("No commands found".to_string());
507    }
508
509    if commands.len() == 1 {
510        Ok(commands.into_iter().next().unwrap())
511    } else {
512        Ok(Ast::Sequence(commands))
513    }
514}
515
516fn parse_pipeline(tokens: &[Token]) -> Result<Ast, String> {
517    let mut commands = Vec::new();
518    let mut current_cmd = ShellCommand {
519        args: Vec::new(),
520        input: None,
521        output: None,
522        append: None,
523    };
524
525    let mut i = 0;
526    while i < tokens.len() {
527        let token = &tokens[i];
528        match token {
529            Token::Word(word) => {
530                current_cmd.args.push(word.clone());
531            }
532            Token::Pipe => {
533                if !current_cmd.args.is_empty() {
534                    commands.push(current_cmd.clone());
535                    current_cmd = ShellCommand {
536                        args: Vec::new(),
537                        input: None,
538                        output: None,
539                        append: None,
540                    };
541                }
542            }
543            Token::RedirIn => {
544                i += 1;
545                if i < tokens.len()
546                    && let Token::Word(ref file) = tokens[i] {
547                        current_cmd.input = Some(file.clone());
548                    }
549            }
550            Token::RedirOut => {
551                i += 1;
552                if i < tokens.len()
553                    && let Token::Word(ref file) = tokens[i] {
554                        current_cmd.output = Some(file.clone());
555                    }
556            }
557            Token::RedirAppend => {
558                i += 1;
559                if i < tokens.len()
560                    && let Token::Word(ref file) = tokens[i] {
561                        current_cmd.append = Some(file.clone());
562                    }
563            }
564            Token::RightParen => {
565                // Check if this looks like a function call pattern: Word LeftParen ... RightParen
566                // If so, treat it as a function call even if the function doesn't exist
567                if !current_cmd.args.is_empty() && i > 0
568                    && let Token::LeftParen = tokens[i-1] {
569                        // This looks like a function call pattern, treat as function call
570                        // For now, we'll handle this in the executor by checking if it's a function
571                        // If not a function, the executor will handle the error gracefully
572                        break;
573                    }
574                return Err("Unexpected ) in pipeline".to_string());
575            }
576            Token::Newline => {
577                // Newlines are handled at the sequence level, skip them in pipelines
578                i += 1;
579                continue;
580            }
581            Token::Do | Token::Done | Token::Then | Token::Else | Token::Elif | Token::Fi | Token::Esac => {
582                // These are control flow keywords that should be handled at a higher level
583                // If we encounter them here, it means we've reached the end of the current command
584                break;
585            }
586            _ => {
587                return Err(format!("Unexpected token in pipeline: {:?}", token));
588            }
589        }
590        i += 1;
591    }
592
593    if !current_cmd.args.is_empty() {
594        commands.push(current_cmd);
595    }
596
597    if commands.is_empty() {
598        return Err("No commands found".to_string());
599    }
600
601    Ok(Ast::Pipeline(commands))
602}
603
604fn parse_if(tokens: &[Token]) -> Result<Ast, String> {
605    let mut i = 1; // Skip 'if'
606    let mut branches = Vec::new();
607
608    loop {
609        // Parse condition until ; or newline or then
610        let mut cond_tokens = Vec::new();
611        while i < tokens.len()
612            && tokens[i] != Token::Semicolon
613            && tokens[i] != Token::Newline
614            && tokens[i] != Token::Then
615        {
616            cond_tokens.push(tokens[i].clone());
617            i += 1;
618        }
619
620        // Skip ; or newline if present
621        if i < tokens.len() && (tokens[i] == Token::Semicolon || tokens[i] == Token::Newline) {
622            i += 1;
623        }
624
625        // Skip any additional newlines
626        while i < tokens.len() && tokens[i] == Token::Newline {
627            i += 1;
628        }
629
630        if i >= tokens.len() || tokens[i] != Token::Then {
631            return Err("Expected then after if/elif condition".to_string());
632        }
633        i += 1; // Skip then
634
635        // Skip any newlines after then
636        while i < tokens.len() && tokens[i] == Token::Newline {
637            i += 1;
638        }
639
640        // Parse then branch - collect all tokens until we hit else/elif/fi
641        // We need to handle nested structures properly
642        let mut then_tokens = Vec::new();
643        let mut depth = 0;
644        while i < tokens.len() {
645            match &tokens[i] {
646                Token::If => {
647                    depth += 1;
648                    then_tokens.push(tokens[i].clone());
649                }
650                Token::Fi => {
651                    if depth > 0 {
652                        depth -= 1;
653                        then_tokens.push(tokens[i].clone());
654                    } else {
655                        break; // This fi closes our if
656                    }
657                }
658                Token::Else | Token::Elif if depth == 0 => {
659                    break; // These belong to our if, not nested ones
660                }
661                Token::Newline => {
662                    // Skip newlines but check what comes after
663                    let mut j = i + 1;
664                    while j < tokens.len() && tokens[j] == Token::Newline {
665                        j += 1;
666                    }
667                    if j < tokens.len()
668                        && depth == 0
669                        && (tokens[j] == Token::Else
670                            || tokens[j] == Token::Elif
671                            || tokens[j] == Token::Fi)
672                    {
673                        i = j; // Skip to the keyword
674                        break;
675                    }
676                    // Otherwise it's just a newline in the middle of commands
677                    then_tokens.push(tokens[i].clone());
678                }
679                _ => {
680                    then_tokens.push(tokens[i].clone());
681                }
682            }
683            i += 1;
684        }
685
686        // Skip any trailing newlines
687        while i < tokens.len() && tokens[i] == Token::Newline {
688            i += 1;
689        }
690
691        let then_ast = if then_tokens.is_empty() {
692            // Empty then branch - create a no-op
693            Ast::Pipeline(vec![ShellCommand {
694                args: vec!["true".to_string()],
695                input: None,
696                output: None,
697                append: None,
698            }])
699        } else {
700            parse_commands_sequentially(&then_tokens)?
701        };
702
703        let condition = parse_slice(&cond_tokens)?;
704        branches.push((Box::new(condition), Box::new(then_ast)));
705
706        // Check next
707        if i < tokens.len() && tokens[i] == Token::Elif {
708            i += 1; // Skip elif, continue loop
709        } else {
710            break;
711        }
712    }
713
714    let else_ast = if i < tokens.len() && tokens[i] == Token::Else {
715        i += 1; // Skip else
716
717        // Skip any newlines after else
718        while i < tokens.len() && tokens[i] == Token::Newline {
719            i += 1;
720        }
721
722        let mut else_tokens = Vec::new();
723        let mut depth = 0;
724        while i < tokens.len() {
725            match &tokens[i] {
726                Token::If => {
727                    depth += 1;
728                    else_tokens.push(tokens[i].clone());
729                }
730                Token::Fi => {
731                    if depth > 0 {
732                        depth -= 1;
733                        else_tokens.push(tokens[i].clone());
734                    } else {
735                        break; // This fi closes our if
736                    }
737                }
738                Token::Newline => {
739                    // Skip newlines but check what comes after
740                    let mut j = i + 1;
741                    while j < tokens.len() && tokens[j] == Token::Newline {
742                        j += 1;
743                    }
744                    if j < tokens.len() && depth == 0 && tokens[j] == Token::Fi {
745                        i = j; // Skip to fi
746                        break;
747                    }
748                    // Otherwise it's just a newline in the middle of commands
749                    else_tokens.push(tokens[i].clone());
750                }
751                _ => {
752                    else_tokens.push(tokens[i].clone());
753                }
754            }
755            i += 1;
756        }
757
758        let else_ast = if else_tokens.is_empty() {
759            // Empty else branch - create a no-op
760            Ast::Pipeline(vec![ShellCommand {
761                args: vec!["true".to_string()],
762                input: None,
763                output: None,
764                append: None,
765            }])
766        } else {
767            parse_commands_sequentially(&else_tokens)?
768        };
769
770        Some(Box::new(else_ast))
771    } else {
772        None
773    };
774
775    if i >= tokens.len() || tokens[i] != Token::Fi {
776        return Err("Expected fi".to_string());
777    }
778
779    Ok(Ast::If {
780        branches,
781        else_branch: else_ast,
782    })
783}
784
785fn parse_case(tokens: &[Token]) -> Result<Ast, String> {
786    let mut i = 1; // Skip 'case'
787
788    // Parse word
789    if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
790        return Err("Expected word after case".to_string());
791    }
792    let word = if let Token::Word(ref w) = tokens[i] {
793        w.clone()
794    } else {
795        unreachable!()
796    };
797    i += 1;
798
799    if i >= tokens.len() || tokens[i] != Token::In {
800        return Err("Expected in after case word".to_string());
801    }
802    i += 1;
803
804    let mut cases = Vec::new();
805    let mut default = None;
806
807    loop {
808        // Skip newlines
809        while i < tokens.len() && tokens[i] == Token::Newline {
810            i += 1;
811        }
812
813        if i >= tokens.len() {
814            return Err("Unexpected end in case statement".to_string());
815        }
816
817        if tokens[i] == Token::Esac {
818            break;
819        }
820
821        // Parse patterns
822        let mut patterns = Vec::new();
823        while i < tokens.len() && tokens[i] != Token::RightParen {
824            if let Token::Word(ref p) = tokens[i] {
825                // Split pattern on |
826                for pat in p.split('|') {
827                    patterns.push(pat.to_string());
828                }
829            } else if tokens[i] == Token::Pipe {
830                // Skip | separator
831            } else if tokens[i] == Token::Newline {
832                // Skip newlines in patterns
833            } else {
834                return Err(format!("Expected pattern, found {:?}", tokens[i]));
835            }
836            i += 1;
837        }
838
839        if i >= tokens.len() || tokens[i] != Token::RightParen {
840            return Err("Expected ) after patterns".to_string());
841        }
842        i += 1;
843
844        // Parse commands
845        let mut commands_tokens = Vec::new();
846        while i < tokens.len() && tokens[i] != Token::DoubleSemicolon && tokens[i] != Token::Esac {
847            commands_tokens.push(tokens[i].clone());
848            i += 1;
849        }
850
851        let commands_ast = parse_slice(&commands_tokens)?;
852
853        if i >= tokens.len() {
854            return Err("Unexpected end in case statement".to_string());
855        }
856
857        if tokens[i] == Token::DoubleSemicolon {
858            i += 1;
859            // Check if this is the default case (*)
860            if patterns.len() == 1 && patterns[0] == "*" {
861                default = Some(Box::new(commands_ast));
862            } else {
863                cases.push((patterns, commands_ast));
864            }
865        } else if tokens[i] == Token::Esac {
866            // Last case without ;;
867            if patterns.len() == 1 && patterns[0] == "*" {
868                default = Some(Box::new(commands_ast));
869            } else {
870                cases.push((patterns, commands_ast));
871            }
872            break;
873        } else {
874            return Err("Expected ;; or esac after commands".to_string());
875        }
876    }
877
878    Ok(Ast::Case {
879        word,
880        cases,
881        default,
882    })
883}
884
885fn parse_for(tokens: &[Token]) -> Result<Ast, String> {
886    let mut i = 1; // Skip 'for'
887
888    // Parse variable name
889    if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
890        return Err("Expected variable name after for".to_string());
891    }
892    let variable = if let Token::Word(ref v) = tokens[i] {
893        v.clone()
894    } else {
895        unreachable!()
896    };
897    i += 1;
898
899    // Expect 'in'
900    if i >= tokens.len() || tokens[i] != Token::In {
901        return Err("Expected 'in' after for variable".to_string());
902    }
903    i += 1;
904
905    // Parse items until we hit 'do' or semicolon/newline
906    let mut items = Vec::new();
907    while i < tokens.len() {
908        match &tokens[i] {
909            Token::Do => break,
910            Token::Semicolon | Token::Newline => {
911                i += 1;
912                // Check if next token is 'do'
913                if i < tokens.len() && tokens[i] == Token::Do {
914                    break;
915                }
916            }
917            Token::Word(word) => {
918                items.push(word.clone());
919                i += 1;
920            }
921            _ => {
922                return Err(format!("Unexpected token in for items: {:?}", tokens[i]));
923            }
924        }
925    }
926
927    // Skip any newlines before 'do'
928    while i < tokens.len() && tokens[i] == Token::Newline {
929        i += 1;
930    }
931
932    // Expect 'do'
933    if i >= tokens.len() || tokens[i] != Token::Do {
934        return Err("Expected 'do' in for loop".to_string());
935    }
936    i += 1;
937
938    // Skip any newlines after 'do'
939    while i < tokens.len() && tokens[i] == Token::Newline {
940        i += 1;
941    }
942
943    // Parse body until 'done'
944    let mut body_tokens = Vec::new();
945    let mut depth = 0;
946    while i < tokens.len() {
947        match &tokens[i] {
948            Token::For => {
949                depth += 1;
950                body_tokens.push(tokens[i].clone());
951            }
952            Token::Done => {
953                if depth > 0 {
954                    depth -= 1;
955                    body_tokens.push(tokens[i].clone());
956                } else {
957                    break; // This done closes our for loop
958                }
959            }
960            Token::Newline => {
961                // Skip newlines but check what comes after
962                let mut j = i + 1;
963                while j < tokens.len() && tokens[j] == Token::Newline {
964                    j += 1;
965                }
966                if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
967                    i = j; // Skip to done
968                    break;
969                }
970                // Otherwise it's just a newline in the middle of commands
971                body_tokens.push(tokens[i].clone());
972            }
973            _ => {
974                body_tokens.push(tokens[i].clone());
975            }
976        }
977        i += 1;
978    }
979
980    if i >= tokens.len() || tokens[i] != Token::Done {
981        return Err("Expected 'done' to close for loop".to_string());
982    }
983
984    // Parse the body
985    let body_ast = if body_tokens.is_empty() {
986        // Empty body - create a no-op
987        Ast::Pipeline(vec![ShellCommand {
988            args: vec!["true".to_string()],
989            input: None,
990            output: None,
991            append: None,
992        }])
993    } else {
994        parse_commands_sequentially(&body_tokens)?
995    };
996
997    Ok(Ast::For {
998        variable,
999        items,
1000        body: Box::new(body_ast),
1001    })
1002}
1003
1004fn parse_while(tokens: &[Token]) -> Result<Ast, String> {
1005    let mut i = 1; // Skip 'while'
1006
1007    // Parse condition until we hit 'do' or semicolon/newline
1008    let mut cond_tokens = Vec::new();
1009    while i < tokens.len() {
1010        match &tokens[i] {
1011            Token::Do => break,
1012            Token::Semicolon | Token::Newline => {
1013                i += 1;
1014                // Check if next token is 'do'
1015                if i < tokens.len() && tokens[i] == Token::Do {
1016                    break;
1017                }
1018            }
1019            _ => {
1020                cond_tokens.push(tokens[i].clone());
1021                i += 1;
1022            }
1023        }
1024    }
1025
1026    if cond_tokens.is_empty() {
1027        return Err("Expected condition after while".to_string());
1028    }
1029
1030    // Skip any newlines before 'do'
1031    while i < tokens.len() && tokens[i] == Token::Newline {
1032        i += 1;
1033    }
1034
1035    // Expect 'do'
1036    if i >= tokens.len() || tokens[i] != Token::Do {
1037        return Err("Expected 'do' in while loop".to_string());
1038    }
1039    i += 1;
1040
1041    // Skip any newlines after 'do'
1042    while i < tokens.len() && tokens[i] == Token::Newline {
1043        i += 1;
1044    }
1045
1046    // Parse body until 'done'
1047    let mut body_tokens = Vec::new();
1048    let mut depth = 0;
1049    while i < tokens.len() {
1050        match &tokens[i] {
1051            Token::While | Token::For => {
1052                depth += 1;
1053                body_tokens.push(tokens[i].clone());
1054            }
1055            Token::Done => {
1056                if depth > 0 {
1057                    depth -= 1;
1058                    body_tokens.push(tokens[i].clone());
1059                } else {
1060                    break; // This done closes our while loop
1061                }
1062            }
1063            Token::Newline => {
1064                // Skip newlines but check what comes after
1065                let mut j = i + 1;
1066                while j < tokens.len() && tokens[j] == Token::Newline {
1067                    j += 1;
1068                }
1069                if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
1070                    i = j; // Skip to done
1071                    break;
1072                }
1073                // Otherwise it's just a newline in the middle of commands
1074                body_tokens.push(tokens[i].clone());
1075            }
1076            _ => {
1077                body_tokens.push(tokens[i].clone());
1078            }
1079        }
1080        i += 1;
1081    }
1082
1083    if i >= tokens.len() || tokens[i] != Token::Done {
1084        return Err("Expected 'done' to close while loop".to_string());
1085    }
1086
1087    // Parse the condition
1088    let condition_ast = parse_slice(&cond_tokens)?;
1089
1090    // Parse the body
1091    let body_ast = if body_tokens.is_empty() {
1092        // Empty body - create a no-op
1093        Ast::Pipeline(vec![ShellCommand {
1094            args: vec!["true".to_string()],
1095            input: None,
1096            output: None,
1097            append: None,
1098        }])
1099    } else {
1100        parse_commands_sequentially(&body_tokens)?
1101    };
1102
1103    Ok(Ast::While {
1104        condition: Box::new(condition_ast),
1105        body: Box::new(body_ast),
1106    })
1107}
1108
1109fn parse_function_definition(tokens: &[Token]) -> Result<Ast, String> {
1110    if tokens.len() < 2 {
1111        return Err("Function definition too short".to_string());
1112    }
1113
1114    // Extract function name from first token
1115    let func_name = if let Token::Word(word) = &tokens[0] {
1116        // Handle legacy format with parentheses in the word (e.g., "legacyfunc()")
1117        if let Some(paren_pos) = word.find('(') {
1118            if word.ends_with(')') && paren_pos > 0 {
1119                word[..paren_pos].to_string()
1120            } else {
1121                word.clone()
1122            }
1123        } else {
1124            word.clone()
1125        }
1126    } else {
1127        return Err("Function name must be a word".to_string());
1128    };
1129
1130    // Find the opening brace and body
1131    let brace_pos = if tokens.len() >= 4 && tokens[1] == Token::LeftParen && tokens[2] == Token::RightParen {
1132        // Standard format: name() {
1133        if tokens[3] != Token::LeftBrace {
1134            return Err("Expected { after function name".to_string());
1135        }
1136        3
1137    } else if tokens.len() >= 2 && tokens[1] == Token::LeftBrace {
1138        // Legacy format: name() {
1139        1
1140    } else {
1141        return Err("Expected ( after function name or { for legacy format".to_string());
1142    };
1143
1144    // Find the matching closing brace, accounting for nested function definitions and control structures
1145    let mut brace_depth = 0;
1146    let mut body_end = 0;
1147    let mut found_closing = false;
1148    let mut i = brace_pos + 1;
1149
1150    while i < tokens.len() {
1151        // Check if this is the start of a nested function definition
1152        // Pattern: Word LeftParen RightParen LeftBrace
1153        if i + 3 < tokens.len()
1154            && matches!(&tokens[i], Token::Word(_))
1155            && tokens[i + 1] == Token::LeftParen
1156            && tokens[i + 2] == Token::RightParen
1157            && tokens[i + 3] == Token::LeftBrace
1158        {
1159            // This is a nested function - skip over it entirely
1160            // Skip to after the opening brace of nested function
1161            i += 4;
1162            let mut nested_depth = 1;
1163            while i < tokens.len() && nested_depth > 0 {
1164                match tokens[i] {
1165                    Token::LeftBrace => nested_depth += 1,
1166                    Token::RightBrace => nested_depth -= 1,
1167                    _ => {}
1168                }
1169                i += 1;
1170            }
1171            // Don't increment i again - continue from current position
1172            continue;
1173        }
1174        
1175        match &tokens[i] {
1176            Token::LeftBrace => {
1177                brace_depth += 1;
1178                i += 1;
1179            }
1180            Token::RightBrace => {
1181                if brace_depth == 0 {
1182                    // This is our matching closing brace
1183                    body_end = i;
1184                    found_closing = true;
1185                    break;
1186                } else {
1187                    brace_depth -= 1;
1188                    i += 1;
1189                }
1190            }
1191            Token::If => {
1192                // Skip to matching fi
1193                let mut if_depth = 1;
1194                i += 1;
1195                while i < tokens.len() && if_depth > 0 {
1196                    match tokens[i] {
1197                        Token::If => if_depth += 1,
1198                        Token::Fi => if_depth -= 1,
1199                        _ => {}
1200                    }
1201                    i += 1;
1202                }
1203            }
1204            Token::For | Token::While => {
1205                // Skip to matching done
1206                let mut for_depth = 1;
1207                i += 1;
1208                while i < tokens.len() && for_depth > 0 {
1209                    match tokens[i] {
1210                        Token::For | Token::While => for_depth += 1,
1211                        Token::Done => for_depth -= 1,
1212                        _ => {}
1213                    }
1214                    i += 1;
1215                }
1216            }
1217            Token::Case => {
1218                // Skip to matching esac
1219                i += 1;
1220                while i < tokens.len() {
1221                    if tokens[i] == Token::Esac {
1222                        i += 1;
1223                        break;
1224                    }
1225                    i += 1;
1226                }
1227            }
1228            _ => {
1229                i += 1;
1230            }
1231        }
1232    }
1233
1234    if !found_closing {
1235        return Err("Missing closing } for function definition".to_string());
1236    }
1237
1238    // Extract body tokens (everything between { and })
1239    let body_tokens = &tokens[brace_pos + 1..body_end];
1240
1241    // Parse the function body using the existing parser
1242    let body_ast = if body_tokens.is_empty() {
1243        // Empty function body
1244        Ast::Pipeline(vec![ShellCommand {
1245            args: vec!["true".to_string()],
1246            input: None,
1247            output: None,
1248            append: None,
1249        }])
1250    } else {
1251        parse_commands_sequentially(body_tokens)?
1252    };
1253
1254    Ok(Ast::FunctionDefinition {
1255        name: func_name,
1256        body: Box::new(body_ast),
1257    })
1258}
1259
1260#[cfg(test)]
1261mod tests {
1262    use super::super::lexer::Token;
1263    use super::*;
1264
1265    #[test]
1266    fn test_single_command() {
1267        let tokens = vec![Token::Word("ls".to_string())];
1268        let result = parse(tokens).unwrap();
1269        assert_eq!(
1270            result,
1271            Ast::Pipeline(vec![ShellCommand {
1272                args: vec!["ls".to_string()],
1273                input: None,
1274                output: None,
1275                append: None,
1276            }])
1277        );
1278    }
1279
1280    #[test]
1281    fn test_command_with_args() {
1282        let tokens = vec![
1283            Token::Word("ls".to_string()),
1284            Token::Word("-la".to_string()),
1285        ];
1286        let result = parse(tokens).unwrap();
1287        assert_eq!(
1288            result,
1289            Ast::Pipeline(vec![ShellCommand {
1290                args: vec!["ls".to_string(), "-la".to_string()],
1291                input: None,
1292                output: None,
1293                append: None,
1294            }])
1295        );
1296    }
1297
1298    #[test]
1299    fn test_pipeline() {
1300        let tokens = vec![
1301            Token::Word("ls".to_string()),
1302            Token::Pipe,
1303            Token::Word("grep".to_string()),
1304            Token::Word("txt".to_string()),
1305        ];
1306        let result = parse(tokens).unwrap();
1307        assert_eq!(
1308            result,
1309            Ast::Pipeline(vec![
1310                ShellCommand {
1311                    args: vec!["ls".to_string()],
1312                    input: None,
1313                    output: None,
1314                    append: None,
1315                },
1316                ShellCommand {
1317                    args: vec!["grep".to_string(), "txt".to_string()],
1318                    input: None,
1319                    output: None,
1320                    append: None,
1321                }
1322            ])
1323        );
1324    }
1325
1326    #[test]
1327    fn test_input_redirection() {
1328        let tokens = vec![
1329            Token::Word("cat".to_string()),
1330            Token::RedirIn,
1331            Token::Word("input.txt".to_string()),
1332        ];
1333        let result = parse(tokens).unwrap();
1334        assert_eq!(
1335            result,
1336            Ast::Pipeline(vec![ShellCommand {
1337                args: vec!["cat".to_string()],
1338                input: Some("input.txt".to_string()),
1339                output: None,
1340                append: None,
1341            }])
1342        );
1343    }
1344
1345    #[test]
1346    fn test_output_redirection() {
1347        let tokens = vec![
1348            Token::Word("printf".to_string()),
1349            Token::Word("hello".to_string()),
1350            Token::RedirOut,
1351            Token::Word("output.txt".to_string()),
1352        ];
1353        let result = parse(tokens).unwrap();
1354        assert_eq!(
1355            result,
1356            Ast::Pipeline(vec![ShellCommand {
1357                args: vec!["printf".to_string(), "hello".to_string()],
1358                input: None,
1359                output: Some("output.txt".to_string()),
1360                append: None,
1361            }])
1362        );
1363    }
1364
1365    #[test]
1366    fn test_append_redirection() {
1367        let tokens = vec![
1368            Token::Word("printf".to_string()),
1369            Token::Word("hello".to_string()),
1370            Token::RedirAppend,
1371            Token::Word("output.txt".to_string()),
1372        ];
1373        let result = parse(tokens).unwrap();
1374        assert_eq!(
1375            result,
1376            Ast::Pipeline(vec![ShellCommand {
1377                args: vec!["printf".to_string(), "hello".to_string()],
1378                input: None,
1379                output: None,
1380                append: Some("output.txt".to_string()),
1381            }])
1382        );
1383    }
1384
1385    #[test]
1386    fn test_complex_pipeline_with_redirections() {
1387        let tokens = vec![
1388            Token::Word("cat".to_string()),
1389            Token::RedirIn,
1390            Token::Word("input.txt".to_string()),
1391            Token::Pipe,
1392            Token::Word("grep".to_string()),
1393            Token::Word("pattern".to_string()),
1394            Token::Pipe,
1395            Token::Word("sort".to_string()),
1396            Token::RedirOut,
1397            Token::Word("output.txt".to_string()),
1398        ];
1399        let result = parse(tokens).unwrap();
1400        assert_eq!(
1401            result,
1402            Ast::Pipeline(vec![
1403                ShellCommand {
1404                    args: vec!["cat".to_string()],
1405                    input: Some("input.txt".to_string()),
1406                    output: None,
1407                    append: None,
1408                },
1409                ShellCommand {
1410                    args: vec!["grep".to_string(), "pattern".to_string()],
1411                    input: None,
1412                    output: None,
1413                    append: None,
1414                },
1415                ShellCommand {
1416                    args: vec!["sort".to_string()],
1417                    input: None,
1418                    output: Some("output.txt".to_string()),
1419                    append: None,
1420                }
1421            ])
1422        );
1423    }
1424
1425    #[test]
1426    fn test_empty_tokens() {
1427        let tokens = vec![];
1428        let result = parse(tokens);
1429        assert!(result.is_err());
1430        assert_eq!(result.unwrap_err(), "No commands found");
1431    }
1432
1433    #[test]
1434    fn test_only_pipe() {
1435        let tokens = vec![Token::Pipe];
1436        let result = parse(tokens);
1437        assert!(result.is_err());
1438        assert_eq!(result.unwrap_err(), "No commands found");
1439    }
1440
1441    #[test]
1442    fn test_redirection_without_file() {
1443        // Parser doesn't check for missing file, just skips if no token after
1444        let tokens = vec![Token::Word("cat".to_string()), Token::RedirIn];
1445        let result = parse(tokens).unwrap();
1446        assert_eq!(
1447            result,
1448            Ast::Pipeline(vec![ShellCommand {
1449                args: vec!["cat".to_string()],
1450                input: None,
1451                output: None,
1452                append: None,
1453            }])
1454        );
1455    }
1456
1457    #[test]
1458    fn test_multiple_redirections() {
1459        let tokens = vec![
1460            Token::Word("cat".to_string()),
1461            Token::RedirIn,
1462            Token::Word("file1.txt".to_string()),
1463            Token::RedirOut,
1464            Token::Word("file2.txt".to_string()),
1465        ];
1466        let result = parse(tokens).unwrap();
1467        assert_eq!(
1468            result,
1469            Ast::Pipeline(vec![ShellCommand {
1470                args: vec!["cat".to_string()],
1471                input: Some("file1.txt".to_string()),
1472                output: Some("file2.txt".to_string()),
1473                append: None,
1474            }])
1475        );
1476    }
1477
1478    #[test]
1479    fn test_parse_if() {
1480        let tokens = vec![
1481            Token::If,
1482            Token::Word("true".to_string()),
1483            Token::Semicolon,
1484            Token::Then,
1485            Token::Word("printf".to_string()),
1486            Token::Word("yes".to_string()),
1487            Token::Semicolon,
1488            Token::Fi,
1489        ];
1490        let result = parse(tokens).unwrap();
1491        if let Ast::If {
1492            branches,
1493            else_branch,
1494        } = result
1495        {
1496            assert_eq!(branches.len(), 1);
1497            let (condition, then_branch) = &branches[0];
1498            if let Ast::Pipeline(cmds) = &**condition {
1499                assert_eq!(cmds[0].args, vec!["true"]);
1500            } else {
1501                panic!("condition not pipeline");
1502            }
1503            if let Ast::Pipeline(cmds) = &**then_branch {
1504                assert_eq!(cmds[0].args, vec!["printf", "yes"]);
1505            } else {
1506                panic!("then_branch not pipeline");
1507            }
1508            assert!(else_branch.is_none());
1509        } else {
1510            panic!("not if");
1511        }
1512    }
1513
1514    #[test]
1515    fn test_parse_if_elif() {
1516        let tokens = vec![
1517            Token::If,
1518            Token::Word("false".to_string()),
1519            Token::Semicolon,
1520            Token::Then,
1521            Token::Word("printf".to_string()),
1522            Token::Word("no".to_string()),
1523            Token::Semicolon,
1524            Token::Elif,
1525            Token::Word("true".to_string()),
1526            Token::Semicolon,
1527            Token::Then,
1528            Token::Word("printf".to_string()),
1529            Token::Word("yes".to_string()),
1530            Token::Semicolon,
1531            Token::Fi,
1532        ];
1533        let result = parse(tokens).unwrap();
1534        if let Ast::If {
1535            branches,
1536            else_branch,
1537        } = result
1538        {
1539            assert_eq!(branches.len(), 2);
1540            // First branch: false -> printf no
1541            let (condition1, then1) = &branches[0];
1542            if let Ast::Pipeline(cmds) = &**condition1 {
1543                assert_eq!(cmds[0].args, vec!["false"]);
1544            }
1545            if let Ast::Pipeline(cmds) = &**then1 {
1546                assert_eq!(cmds[0].args, vec!["printf", "no"]);
1547            }
1548            // Second branch: true -> printf yes
1549            let (condition2, then2) = &branches[1];
1550            if let Ast::Pipeline(cmds) = &**condition2 {
1551                assert_eq!(cmds[0].args, vec!["true"]);
1552            }
1553            if let Ast::Pipeline(cmds) = &**then2 {
1554                assert_eq!(cmds[0].args, vec!["printf", "yes"]);
1555            }
1556            assert!(else_branch.is_none());
1557        } else {
1558            panic!("not if");
1559        }
1560    }
1561
1562    #[test]
1563    fn test_parse_assignment() {
1564        let tokens = vec![Token::Word("MY_VAR=test_value".to_string())];
1565        let result = parse(tokens).unwrap();
1566        if let Ast::Assignment { var, value } = result {
1567            assert_eq!(var, "MY_VAR");
1568            assert_eq!(value, "test_value");
1569        } else {
1570            panic!("not assignment");
1571        }
1572    }
1573
1574    #[test]
1575    fn test_parse_assignment_quoted() {
1576        let tokens = vec![Token::Word("MY_VAR=hello world".to_string())];
1577        let result = parse(tokens).unwrap();
1578        if let Ast::Assignment { var, value } = result {
1579            assert_eq!(var, "MY_VAR");
1580            assert_eq!(value, "hello world");
1581        } else {
1582            panic!("not assignment");
1583        }
1584    }
1585
1586    #[test]
1587    fn test_parse_assignment_invalid() {
1588        // Variable name starting with number should not be parsed as assignment
1589        let tokens = vec![Token::Word("123VAR=value".to_string())];
1590        let result = parse(tokens).unwrap();
1591        if let Ast::Pipeline(cmds) = result {
1592            assert_eq!(cmds[0].args, vec!["123VAR=value"]);
1593        } else {
1594            panic!("should be parsed as pipeline");
1595        }
1596    }
1597
1598    #[test]
1599    fn test_parse_function_definition() {
1600        let tokens = vec![
1601            Token::Word("myfunc".to_string()),
1602            Token::LeftParen,
1603            Token::RightParen,
1604            Token::LeftBrace,
1605            Token::Word("echo".to_string()),
1606            Token::Word("hello".to_string()),
1607            Token::RightBrace,
1608        ];
1609        let result = parse(tokens).unwrap();
1610        if let Ast::FunctionDefinition { name, body } = result {
1611            assert_eq!(name, "myfunc");
1612            // Body should be a pipeline with echo hello
1613            if let Ast::Pipeline(cmds) = *body {
1614                assert_eq!(cmds[0].args, vec!["echo", "hello"]);
1615            } else {
1616                panic!("function body should be a pipeline");
1617            }
1618        } else {
1619            panic!("should be parsed as function definition");
1620        }
1621    }
1622
1623    #[test]
1624    fn test_parse_function_definition_empty() {
1625        let tokens = vec![
1626            Token::Word("emptyfunc".to_string()),
1627            Token::LeftParen,
1628            Token::RightParen,
1629            Token::LeftBrace,
1630            Token::RightBrace,
1631        ];
1632        let result = parse(tokens).unwrap();
1633        if let Ast::FunctionDefinition { name, body } = result {
1634            assert_eq!(name, "emptyfunc");
1635            // Empty body should default to true command
1636            if let Ast::Pipeline(cmds) = *body {
1637                assert_eq!(cmds[0].args, vec!["true"]);
1638            } else {
1639                panic!("function body should be a pipeline");
1640            }
1641        } else {
1642            panic!("should be parsed as function definition");
1643        }
1644    }
1645
1646    #[test]
1647    fn test_parse_function_definition_legacy_format() {
1648        // Test backward compatibility with parentheses in the function name
1649        let tokens = vec![
1650            Token::Word("legacyfunc()".to_string()),
1651            Token::LeftBrace,
1652            Token::Word("echo".to_string()),
1653            Token::Word("hello".to_string()),
1654            Token::RightBrace,
1655        ];
1656        let result = parse(tokens).unwrap();
1657        if let Ast::FunctionDefinition { name, body } = result {
1658            assert_eq!(name, "legacyfunc");
1659            // Body should be a pipeline with echo hello
1660            if let Ast::Pipeline(cmds) = *body {
1661                assert_eq!(cmds[0].args, vec!["echo", "hello"]);
1662            } else {
1663                panic!("function body should be a pipeline");
1664            }
1665        } else {
1666            panic!("should be parsed as function definition");
1667        }
1668    }
1669
1670    #[test]
1671    fn test_parse_local_assignment() {
1672        let tokens = vec![
1673            Token::Local,
1674            Token::Word("MY_VAR=test_value".to_string()),
1675        ];
1676        let result = parse(tokens).unwrap();
1677        if let Ast::LocalAssignment { var, value } = result {
1678            assert_eq!(var, "MY_VAR");
1679            assert_eq!(value, "test_value");
1680        } else {
1681            panic!("should be parsed as local assignment");
1682        }
1683    }
1684
1685    #[test]
1686    fn test_parse_local_assignment_separate_tokens() {
1687        let tokens = vec![
1688            Token::Local,
1689            Token::Word("MY_VAR".to_string()),
1690            Token::Word("test_value".to_string()),
1691        ];
1692        let result = parse(tokens).unwrap();
1693        if let Ast::LocalAssignment { var, value } = result {
1694            assert_eq!(var, "MY_VAR");
1695            assert_eq!(value, "test_value");
1696        } else {
1697            panic!("should be parsed as local assignment");
1698        }
1699    }
1700
1701    #[test]
1702    fn test_parse_local_assignment_invalid_var_name() {
1703        // Variable name starting with number should not be parsed as local assignment
1704        let tokens = vec![
1705            Token::Local,
1706            Token::Word("123VAR=value".to_string()),
1707        ];
1708        let result = parse(tokens);
1709        // Should return an error since 123VAR is not a valid variable name
1710        assert!(result.is_err());
1711    }
1712}