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