Skip to main content

yash_syntax/parser/
simple_command.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2020 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Syntax parser for simple command
18
19use super::core::Parser;
20use super::core::Rec;
21use super::core::Result;
22use super::error::Error;
23use super::error::SyntaxError;
24use super::lex::Operator::{CloseParen, Newline, OpenParen};
25use super::lex::TokenId::{Operator, Token};
26use super::lex::is_portable_name;
27use crate::syntax::Array;
28use crate::syntax::Assign;
29use crate::syntax::ExpansionMode;
30use crate::syntax::MaybeLiteral as _;
31use crate::syntax::Redir;
32use crate::syntax::Scalar;
33use crate::syntax::SimpleCommand;
34use crate::syntax::TextUnit::Literal;
35use crate::syntax::Unquote as _;
36use crate::syntax::Word;
37use crate::syntax::WordUnit::Unquoted;
38
39/// Determines the expansion mode of a word.
40///
41/// This function converts a raw token into a word-mode pair assuming that the
42/// token is a command argument word for a declaration utility.
43///
44/// This function tests if the word is in the form of `name=value`. If it is,
45/// the expansion mode is `ExpansionMode::Single`, and tilde expansions are
46/// parsed after the equal sign. Otherwise, the expansion mode is
47/// `ExpansionMode::Multiple`, and the word is returned as is.
48fn determine_expansion_mode(word: Word) -> (Word, ExpansionMode) {
49    if let Some(eq) = word.units.iter().position(|u| *u == Unquoted(Literal('=')))
50        && let Some(name) = word.units[..eq].to_string_if_literal()
51        && !name.is_empty()
52    {
53        let mut word = word;
54        word.parse_tilde_everywhere_after(eq + 1);
55        return (word, ExpansionMode::Single);
56    }
57    (word, ExpansionMode::Multiple)
58}
59
60/// Tests if a word ends with a `:`, but is not equal to `:`.
61///
62/// This is used to determine if a word is a reserved word in the portable
63/// parsing mode. POSIX reserves words ending with a `:` for possible future
64/// use, so we reject one used as a command name.
65///
66/// Technically, a single `:` is reserved, but it is also a built-in utility,
67/// so we don't reject it as a command name.
68fn ends_with_colon(word: &Word) -> bool {
69    if word.units.len() <= 1 {
70        // Since we allow a single `:`, the word must have at least two units to end with a `:`.
71        return false;
72    }
73
74    if let Some(unit) = word.units.last()
75        && unit != &Unquoted(Literal(':'))
76    {
77        // The word does not end with a `:`.
78        return false;
79    }
80
81    // Quoted words are not reserved
82    !word.unquote().1
83}
84
85/// Simple command builder
86#[derive(Default)]
87struct Builder {
88    assigns: Vec<Assign>,
89    words: Vec<(Word, ExpansionMode)>,
90    redirs: Vec<Redir>,
91}
92
93impl Builder {
94    fn is_empty(&self) -> bool {
95        self.assigns.is_empty() && self.words.is_empty() && self.redirs.is_empty()
96    }
97}
98
99impl From<Builder> for SimpleCommand {
100    fn from(builder: Builder) -> Self {
101        SimpleCommand {
102            assigns: builder.assigns,
103            words: builder.words,
104            redirs: builder.redirs.into(),
105        }
106    }
107}
108
109impl Parser<'_, '_> {
110    /// Parses the value of an array assignment.
111    ///
112    /// This function first consumes a `(` token, then any number of words
113    /// separated by blanks and/or newlines, and finally a `)`.
114    /// If the first token is not `(`, the result is `Ok(None)`.
115    /// If the last `)` is missing, the result is
116    /// `Err(ErrorCause::Syntax(SyntaxError::UnclosedArrayValue(_)))`.
117    pub async fn array_values(&mut self) -> Result<Option<Vec<Word>>> {
118        if self.peek_token().await?.id != Operator(OpenParen) {
119            return Ok(None);
120        }
121
122        let opening_location = self.take_token_raw().await?.word.location;
123        let mut words = vec![];
124
125        loop {
126            let next = self.take_token_auto(&[]).await?;
127            match next.id {
128                Operator(Newline) => continue,
129                Operator(CloseParen) => break,
130                Token(_keyword) => words.push(next.word),
131                _ => {
132                    return Err(Error {
133                        cause: SyntaxError::UnclosedArrayValue { opening_location }.into(),
134                        location: next.word.location,
135                    });
136                }
137            }
138        }
139
140        Ok(Some(words))
141    }
142
143    /// Parses a simple command.
144    ///
145    /// If there is no valid command at the current position, this function
146    /// returns `Ok(Rec::Parsed(None))`.
147    pub async fn simple_command(&mut self) -> Result<Rec<Option<SimpleCommand>>> {
148        let mut is_declaration_utility = None;
149        let mut result = Builder::default();
150
151        loop {
152            // Parse redirection
153            if let Some(redir) = self.redirection().await? {
154                result.redirs.push(redir);
155                continue;
156            }
157
158            // Filter token type
159            match self.peek_token().await?.id {
160                Token(Some(_keyword)) if result.is_empty() => break,
161                Token(_) => (),
162                _ => break,
163            }
164
165            // Apply alias substitution
166            let token = match self.take_token_manual(result.words.is_empty()).await? {
167                Rec::AliasSubstituted => {
168                    if result.is_empty() {
169                        return Ok(Rec::AliasSubstituted);
170                    } else {
171                        continue;
172                    }
173                }
174                Rec::Parsed(token) => token,
175            };
176
177            // Handle command argument word
178            if let Some(is_declaration_utility) = is_declaration_utility {
179                // The word determined (not) to be a declaration utility
180                // must already be in the words list.
181                debug_assert!(!result.words.is_empty());
182
183                result.words.push(if is_declaration_utility {
184                    determine_expansion_mode(token.word)
185                } else {
186                    (token.word, ExpansionMode::Multiple)
187                });
188                continue;
189            }
190
191            // Tell assignment from word
192            let assign_or_word = if result.words.is_empty() {
193                // We don't have any words yet, so this token may be an assignment or a word.
194                Assign::try_from(token.word)
195            } else {
196                // We already have some words, so remaining tokens are all words.
197                Err(token.word)
198            };
199            let mut assign = match assign_or_word {
200                Ok(assign) => assign,
201                Err(word) => {
202                    debug_assert!(is_declaration_utility.is_none());
203
204                    // POSIX reserves words ending with a `:` for possible future
205                    // use, so reject one used as a command name (the first token
206                    // of the command, where a reserved word would be recognized)
207                    // in the portable parsing mode. Like the keyword check above,
208                    // `result.is_empty()` restricts this to that leading
209                    // position. Unlike keywords (which are known at the peek
210                    // above from the token ID), we test the word only here, after
211                    // `Assign::try_from`, to keep an assignment whose value ends
212                    // with `:` (such as `PATH=/bin:`) from being regarded as a
213                    // colon-suffixed command name.
214                    if self.mode().portable && result.is_empty() && ends_with_colon(&word) {
215                        return Err(Error {
216                            cause: SyntaxError::ColonSuffixedCommandName.into(),
217                            location: word.location,
218                        });
219                    }
220
221                    is_declaration_utility = self.word_names_declaration_utility(&word);
222                    result.words.push((word, ExpansionMode::Multiple));
223                    continue;
224                }
225            };
226
227            if self.mode().portable && !is_portable_name(&assign.name) {
228                return Err(Error {
229                    cause: SyntaxError::NonPortableAssignmentName.into(),
230                    location: assign.location,
231                });
232            }
233
234            let units = match &assign.value {
235                Scalar(Word { units, .. }) => units,
236                _ => panic!(
237                    "Assign::try_from produced a non-scalar value {:?}",
238                    assign.value
239                ),
240            };
241
242            // Tell array assignment from scalar assignment
243            if units.is_empty()
244                && !self.has_blank().await?
245                && let Some(words) = self.array_values().await?
246            {
247                if self.mode().portable {
248                    let mut location = assign.location;
249                    location.range.end = self.location().await?.range.start;
250                    return Err(Error {
251                        cause: SyntaxError::ArrayAssignment.into(),
252                        location,
253                    });
254                }
255                assign.value = Array(words);
256            }
257
258            result.assigns.push(assign);
259        }
260
261        Ok(Rec::Parsed((!result.is_empty()).then(|| result.into())))
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::super::error::ErrorCause;
268    use super::super::lex::Lexer;
269    use super::super::lex::TokenId::EndOfInput;
270    use super::*;
271    use crate::decl_util::EmptyGlossary;
272    use crate::source::Source;
273    use crate::syntax::RedirBody;
274    use crate::syntax::RedirOp;
275    use crate::syntax::TextUnit;
276    use crate::syntax::WordUnit;
277    use assert_matches::assert_matches;
278    use futures_util::FutureExt as _;
279
280    #[test]
281    fn determine_expansion_mode_empty_name() {
282        let in_word = "=".parse::<Word>().unwrap();
283        let (out_word, mode) = determine_expansion_mode(in_word.clone());
284        assert_eq!(out_word, in_word);
285        assert_eq!(mode, ExpansionMode::Multiple);
286    }
287
288    #[test]
289    fn determine_expansion_mode_nonempty_name() {
290        let in_word = "foo=".parse::<Word>().unwrap();
291        let (out_word, mode) = determine_expansion_mode(in_word.clone());
292        assert_eq!(out_word, in_word);
293        assert_eq!(mode, ExpansionMode::Single);
294    }
295
296    #[test]
297    fn determine_expansion_mode_non_literal_name() {
298        let in_word = "${X}=".parse::<Word>().unwrap();
299        let (out_word, mode) = determine_expansion_mode(in_word.clone());
300        assert_eq!(out_word, in_word);
301        assert_eq!(mode, ExpansionMode::Multiple);
302    }
303
304    #[test]
305    fn determine_expansion_mode_tilde_expansions_after_equal() {
306        let word = "~=~:~b".parse().unwrap();
307        let (word, mode) = determine_expansion_mode(word);
308        assert_eq!(
309            word.units,
310            [
311                WordUnit::Unquoted(TextUnit::Literal('~')),
312                WordUnit::Unquoted(TextUnit::Literal('=')),
313                WordUnit::Tilde {
314                    name: "".to_string(),
315                    followed_by_slash: false
316                },
317                WordUnit::Unquoted(TextUnit::Literal(':')),
318                WordUnit::Tilde {
319                    name: "b".to_string(),
320                    followed_by_slash: false
321                },
322            ]
323        );
324        assert_eq!(mode, ExpansionMode::Single);
325    }
326
327    #[test]
328    fn parser_array_values_no_open_parenthesis() {
329        let mut lexer = Lexer::with_code(")");
330        let mut parser = Parser::new(&mut lexer);
331        let result = parser.array_values().now_or_never().unwrap().unwrap();
332        assert_eq!(result, None);
333    }
334
335    #[test]
336    fn parser_array_values_empty() {
337        let mut lexer = Lexer::with_code("()");
338        let mut parser = Parser::new(&mut lexer);
339        let result = parser.array_values().now_or_never().unwrap();
340        let words = result.unwrap().unwrap();
341        assert_eq!(words, []);
342
343        let next = parser.peek_token().now_or_never().unwrap().unwrap();
344        assert_eq!(next.id, EndOfInput);
345    }
346
347    #[test]
348    fn parser_array_values_many() {
349        let mut lexer = Lexer::with_code("(a b c)");
350        let mut parser = Parser::new(&mut lexer);
351        let result = parser.array_values().now_or_never().unwrap();
352        let words = result.unwrap().unwrap();
353        assert_eq!(words.len(), 3);
354        assert_eq!(words[0].to_string(), "a");
355        assert_eq!(words[1].to_string(), "b");
356        assert_eq!(words[2].to_string(), "c");
357    }
358
359    #[test]
360    fn parser_array_values_newlines_and_comments() {
361        let mut lexer = Lexer::with_code(
362            "(
363            a # b
364            c d
365        )",
366        );
367        let mut parser = Parser::new(&mut lexer);
368        let result = parser.array_values().now_or_never().unwrap();
369        let words = result.unwrap().unwrap();
370        assert_eq!(words.len(), 3);
371        assert_eq!(words[0].to_string(), "a");
372        assert_eq!(words[1].to_string(), "c");
373        assert_eq!(words[2].to_string(), "d");
374    }
375
376    #[test]
377    fn parser_array_values_unclosed() {
378        let mut lexer = Lexer::with_code("(a b");
379        let mut parser = Parser::new(&mut lexer);
380        let e = parser.array_values().now_or_never().unwrap().unwrap_err();
381        assert_matches!(e.cause,
382             ErrorCause::Syntax(SyntaxError::UnclosedArrayValue { opening_location }) => {
383            assert_eq!(*opening_location.code.value.borrow(), "(a b");
384            assert_eq!(opening_location.code.start_line_number.get(), 1);
385            assert_eq!(*opening_location.code.source, Source::Unknown);
386            assert_eq!(opening_location.range, 0..1);
387        });
388        assert_eq!(*e.location.code.value.borrow(), "(a b");
389        assert_eq!(e.location.code.start_line_number.get(), 1);
390        assert_eq!(*e.location.code.source, Source::Unknown);
391        assert_eq!(e.location.range, 4..4);
392    }
393
394    #[test]
395    fn parser_array_values_invalid_word() {
396        let mut lexer = Lexer::with_code("(a;b)");
397        let mut parser = Parser::new(&mut lexer);
398        let e = parser.array_values().now_or_never().unwrap().unwrap_err();
399        assert_matches!(e.cause,
400            ErrorCause::Syntax(SyntaxError::UnclosedArrayValue { opening_location }) => {
401            assert_eq!(*opening_location.code.value.borrow(), "(a;b)");
402            assert_eq!(opening_location.code.start_line_number.get(), 1);
403            assert_eq!(*opening_location.code.source, Source::Unknown);
404            assert_eq!(opening_location.range, 0..1);
405        });
406        assert_eq!(*e.location.code.value.borrow(), "(a;b)");
407        assert_eq!(e.location.code.start_line_number.get(), 1);
408        assert_eq!(*e.location.code.source, Source::Unknown);
409        assert_eq!(e.location.range, 2..3);
410    }
411
412    #[test]
413    fn parser_simple_command_eof() {
414        let mut lexer = Lexer::with_code("");
415        let mut parser = Parser::new(&mut lexer);
416
417        let result = parser.simple_command().now_or_never().unwrap();
418        assert_eq!(result, Ok(Rec::Parsed(None)));
419    }
420
421    #[test]
422    fn parser_simple_command_keyword() {
423        let mut lexer = Lexer::with_code("then");
424        let mut parser = Parser::new(&mut lexer);
425
426        let result = parser.simple_command().now_or_never().unwrap();
427        assert_eq!(result, Ok(Rec::Parsed(None)));
428    }
429
430    #[test]
431    fn parser_simple_command_one_assignment() {
432        let mut lexer = Lexer::with_code("my=assignment");
433        let mut parser = Parser::new(&mut lexer);
434
435        let result = parser.simple_command().now_or_never().unwrap();
436        let sc = result.unwrap().unwrap().unwrap();
437        assert_eq!(sc.words, []);
438        assert_eq!(*sc.redirs, []);
439        assert_eq!(sc.assigns.len(), 1);
440        assert_eq!(sc.assigns[0].name, "my");
441        assert_eq!(sc.assigns[0].value.to_string(), "assignment");
442        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "my=assignment");
443        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
444        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
445        assert_eq!(sc.assigns[0].location.range, 0..13);
446    }
447
448    #[test]
449    fn parser_simple_command_many_assignments() {
450        let mut lexer = Lexer::with_code("a= b=! c=X");
451        let mut parser = Parser::new(&mut lexer);
452
453        let result = parser.simple_command().now_or_never().unwrap();
454        let sc = result.unwrap().unwrap().unwrap();
455        assert_eq!(sc.words, []);
456        assert_eq!(*sc.redirs, []);
457        assert_eq!(sc.assigns.len(), 3);
458        assert_eq!(sc.assigns[0].name, "a");
459        assert_eq!(sc.assigns[0].value.to_string(), "");
460        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "a= b=! c=X");
461        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
462        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
463        assert_eq!(sc.assigns[0].location.range, 0..2);
464        assert_eq!(sc.assigns[1].name, "b");
465        assert_eq!(sc.assigns[1].value.to_string(), "!");
466        assert_eq!(*sc.assigns[1].location.code.value.borrow(), "a= b=! c=X");
467        assert_eq!(sc.assigns[1].location.code.start_line_number.get(), 1);
468        assert_eq!(*sc.assigns[1].location.code.source, Source::Unknown);
469        assert_eq!(sc.assigns[1].location.range, 3..6);
470        assert_eq!(sc.assigns[2].name, "c");
471        assert_eq!(sc.assigns[2].value.to_string(), "X");
472        assert_eq!(*sc.assigns[2].location.code.value.borrow(), "a= b=! c=X");
473        assert_eq!(sc.assigns[2].location.code.start_line_number.get(), 1);
474        assert_eq!(*sc.assigns[2].location.code.source, Source::Unknown);
475        assert_eq!(sc.assigns[2].location.range, 7..10);
476    }
477
478    #[test]
479    fn parser_simple_command_one_word() {
480        let mut lexer = Lexer::with_code("word");
481        let mut parser = Parser::new(&mut lexer);
482
483        let result = parser.simple_command().now_or_never().unwrap();
484        let sc = result.unwrap().unwrap().unwrap();
485        assert_eq!(sc.assigns, []);
486        assert_eq!(*sc.redirs, []);
487        assert_eq!(sc.words.len(), 1);
488        assert_eq!(sc.words[0].0.to_string(), "word");
489        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
490    }
491
492    #[test]
493    fn parser_simple_command_many_words() {
494        let mut lexer = Lexer::with_code(": if then");
495        let mut parser = Parser::new(&mut lexer);
496
497        let result = parser.simple_command().now_or_never().unwrap();
498        let sc = result.unwrap().unwrap().unwrap();
499        assert_eq!(sc.assigns, []);
500        assert_eq!(*sc.redirs, []);
501        assert_eq!(sc.words.len(), 3);
502        assert_eq!(sc.words[0].0.to_string(), ":");
503        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
504        assert_eq!(sc.words[1].0.to_string(), "if");
505        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
506        assert_eq!(sc.words[2].0.to_string(), "then");
507        assert_eq!(sc.words[2].1, ExpansionMode::Multiple);
508    }
509
510    #[test]
511    fn parser_simple_command_one_redirection() {
512        let mut lexer = Lexer::with_code("<foo");
513        let mut parser = Parser::new(&mut lexer);
514
515        let result = parser.simple_command().now_or_never().unwrap();
516        let sc = result.unwrap().unwrap().unwrap();
517        assert_eq!(sc.assigns, []);
518        assert_eq!(sc.words, []);
519        assert_eq!(sc.redirs.len(), 1);
520        assert_eq!(sc.redirs[0].fd, None);
521        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
522            assert_eq!(operator, &RedirOp::FileIn);
523            assert_eq!(operand.to_string(), "foo")
524        });
525
526        let next = parser.peek_token().now_or_never().unwrap().unwrap();
527        assert_eq!(next.id, EndOfInput);
528    }
529
530    #[test]
531    fn parser_simple_command_many_redirections() {
532        let mut lexer = Lexer::with_code("<one >two >>three");
533        let mut parser = Parser::new(&mut lexer);
534
535        let result = parser.simple_command().now_or_never().unwrap();
536        let sc = result.unwrap().unwrap().unwrap();
537        assert_eq!(sc.assigns, []);
538        assert_eq!(sc.words, []);
539        assert_eq!(sc.redirs.len(), 3);
540        assert_eq!(sc.redirs[0].fd, None);
541        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
542            assert_eq!(operator, &RedirOp::FileIn);
543            assert_eq!(operand.to_string(), "one")
544        });
545        assert_eq!(sc.redirs[1].fd, None);
546        assert_matches!(sc.redirs[1].body, RedirBody::Normal { ref operator, ref operand } => {
547            assert_eq!(operator, &RedirOp::FileOut);
548            assert_eq!(operand.to_string(), "two")
549        });
550        assert_eq!(sc.redirs[2].fd, None);
551        assert_matches!(sc.redirs[2].body, RedirBody::Normal { ref operator, ref operand } => {
552            assert_eq!(operator, &RedirOp::FileAppend);
553            assert_eq!(operand.to_string(), "three")
554        });
555
556        let next = parser.peek_token().now_or_never().unwrap().unwrap();
557        assert_eq!(next.id, EndOfInput);
558    }
559
560    #[test]
561    fn parser_simple_command_assignment_word() {
562        let mut lexer = Lexer::with_code("if=then else");
563        let mut parser = Parser::new(&mut lexer);
564
565        let result = parser.simple_command().now_or_never().unwrap();
566        let sc = result.unwrap().unwrap().unwrap();
567        assert_eq!(*sc.redirs, []);
568        assert_eq!(sc.assigns.len(), 1);
569        assert_eq!(sc.words.len(), 1);
570        assert_eq!(sc.assigns[0].name, "if");
571        assert_eq!(sc.assigns[0].value.to_string(), "then");
572        assert_eq!(sc.words[0].0.to_string(), "else");
573        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
574    }
575
576    #[test]
577    fn parser_simple_command_word_redirection() {
578        let mut lexer = Lexer::with_code("word <redirection");
579        let mut parser = Parser::new(&mut lexer);
580
581        let result = parser.simple_command().now_or_never().unwrap();
582        let sc = result.unwrap().unwrap().unwrap();
583        assert_eq!(sc.assigns, []);
584        assert_eq!(sc.words.len(), 1);
585        assert_eq!(sc.redirs.len(), 1);
586        assert_eq!(sc.words[0].0.to_string(), "word");
587        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
588        assert_eq!(sc.redirs[0].fd, None);
589        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
590            assert_eq!(operator, &RedirOp::FileIn);
591            assert_eq!(operand.to_string(), "redirection")
592        });
593    }
594
595    #[test]
596    fn parser_simple_command_redirection_assignment() {
597        let mut lexer = Lexer::with_code("<foo a=b");
598        let mut parser = Parser::new(&mut lexer);
599
600        let result = parser.simple_command().now_or_never().unwrap();
601        let sc = result.unwrap().unwrap().unwrap();
602        assert_eq!(sc.words, []);
603        assert_eq!(sc.assigns.len(), 1);
604        assert_eq!(sc.redirs.len(), 1);
605        assert_eq!(sc.assigns[0].name, "a");
606        assert_eq!(sc.assigns[0].value.to_string(), "b");
607        assert_eq!(sc.redirs[0].fd, None);
608        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
609            assert_eq!(operator, &RedirOp::FileIn);
610            assert_eq!(operand.to_string(), "foo")
611        });
612    }
613
614    #[test]
615    fn parser_simple_command_assignment_redirection_word() {
616        let mut lexer = Lexer::with_code("if=then <foo else");
617        let mut parser = Parser::new(&mut lexer);
618
619        let result = parser.simple_command().now_or_never().unwrap();
620        let sc = result.unwrap().unwrap().unwrap();
621        assert_eq!(sc.assigns.len(), 1);
622        assert_eq!(sc.words.len(), 1);
623        assert_eq!(sc.redirs.len(), 1);
624        assert_eq!(sc.assigns[0].name, "if");
625        assert_eq!(sc.assigns[0].value.to_string(), "then");
626        assert_eq!(sc.words[0].0.to_string(), "else");
627        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
628        assert_eq!(sc.redirs[0].fd, None);
629        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
630            assert_eq!(operator, &RedirOp::FileIn);
631            assert_eq!(operand.to_string(), "foo")
632        });
633    }
634
635    #[test]
636    fn parser_simple_command_array_assignment() {
637        let mut lexer = Lexer::with_code("a=()");
638        let mut parser = Parser::new(&mut lexer);
639
640        let result = parser.simple_command().now_or_never().unwrap();
641        let sc = result.unwrap().unwrap().unwrap();
642        assert_eq!(sc.assigns.len(), 1);
643        assert_eq!(sc.words, []);
644        assert_eq!(*sc.redirs, []);
645        assert_eq!(sc.assigns[0].name, "a");
646        assert_matches!(&sc.assigns[0].value, Array(words) => {
647            assert_eq!(words, &[]);
648        });
649
650        let next = parser.peek_token().now_or_never().unwrap().unwrap();
651        assert_eq!(next.id, EndOfInput);
652    }
653
654    #[test]
655    fn parser_simple_command_empty_assignment_followed_by_blank_and_parenthesis() {
656        let mut lexer = Lexer::with_code("a= ()");
657        let mut parser = Parser::new(&mut lexer);
658
659        let result = parser.simple_command().now_or_never().unwrap();
660        let sc = result.unwrap().unwrap().unwrap();
661        assert_eq!(sc.assigns.len(), 1);
662        assert_eq!(sc.words, []);
663        assert_eq!(*sc.redirs, []);
664        assert_eq!(sc.assigns[0].name, "a");
665        assert_eq!(sc.assigns[0].value.to_string(), "");
666        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "a= ()");
667        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
668        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
669        assert_eq!(sc.assigns[0].location.range, 0..2);
670
671        let next = parser.peek_token().now_or_never().unwrap().unwrap();
672        assert_eq!(next.id, Operator(OpenParen));
673    }
674
675    #[test]
676    fn parser_simple_command_non_empty_assignment_followed_by_parenthesis() {
677        let mut lexer = Lexer::with_code("a=b()");
678        let mut parser = Parser::new(&mut lexer);
679
680        let result = parser.simple_command().now_or_never().unwrap();
681        let sc = result.unwrap().unwrap().unwrap();
682        assert_eq!(sc.assigns.len(), 1);
683        assert_eq!(sc.words, []);
684        assert_eq!(*sc.redirs, []);
685        assert_eq!(sc.assigns[0].name, "a");
686        assert_eq!(sc.assigns[0].value.to_string(), "b");
687        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "a=b()");
688        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
689        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
690        assert_eq!(sc.assigns[0].location.range, 0..3);
691
692        let next = parser.peek_token().now_or_never().unwrap().unwrap();
693        assert_eq!(next.id, Operator(OpenParen));
694    }
695
696    #[test]
697    fn word_with_single_expansion_mode_in_declaration_utility() {
698        // "export" is a declaration utility, so the expansion mode of the word
699        // "a=b" should be single.
700        let mut lexer = Lexer::with_code("export a=b");
701        let mut parser = Parser::new(&mut lexer);
702
703        let result = parser.simple_command().now_or_never().unwrap();
704        let sc = result.unwrap().unwrap().unwrap();
705        assert_eq!(sc.assigns, []);
706        assert_eq!(sc.words.len(), 2);
707        assert_eq!(*sc.redirs, []);
708        assert_eq!(sc.words[0].0.to_string(), "export");
709        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
710        assert_eq!(sc.words[1].0.to_string(), "a=b");
711        assert_eq!(sc.words[1].1, ExpansionMode::Single);
712    }
713
714    #[test]
715    fn word_with_multiple_expansion_mode_in_declaration_utility() {
716        // The expansion mode of the word "foo" should be multiple because it
717        // cannot be parsed as an assignment.
718        let mut lexer = Lexer::with_code("export foo");
719        let mut parser = Parser::new(&mut lexer);
720
721        let result = parser.simple_command().now_or_never().unwrap();
722        let sc = result.unwrap().unwrap().unwrap();
723        assert_eq!(sc.assigns, []);
724        assert_eq!(sc.words.len(), 2);
725        assert_eq!(*sc.redirs, []);
726        assert_eq!(sc.words[0].0.to_string(), "export");
727        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
728        assert_eq!(sc.words[1].0.to_string(), "foo");
729        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
730    }
731
732    #[test]
733    fn word_with_multiple_expansion_mode_in_non_declaration_utility() {
734        // "foo" is not a declaration utility, so the expansion mode of the word
735        // "a=b" should be multiple.
736        let mut lexer = Lexer::with_code("foo a=b");
737        let mut parser = Parser::new(&mut lexer);
738
739        let result = parser.simple_command().now_or_never().unwrap();
740        let sc = result.unwrap().unwrap().unwrap();
741        assert_eq!(sc.assigns, []);
742        assert_eq!(sc.words.len(), 2);
743        assert_eq!(*sc.redirs, []);
744        assert_eq!(sc.words[0].0.to_string(), "foo");
745        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
746        assert_eq!(sc.words[1].0.to_string(), "a=b");
747        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
748    }
749
750    #[test]
751    fn declaration_utility_determined_by_non_first_word() {
752        // "command" delegates to the next word to determine whether it is a
753        // declaration utility.
754        let mut lexer = Lexer::with_code("command command export foo a=b");
755        let mut parser = Parser::new(&mut lexer);
756        let result = parser.simple_command().now_or_never().unwrap();
757        let sc = result.unwrap().unwrap().unwrap();
758        assert_eq!(sc.words[4].0.to_string(), "a=b");
759        assert_eq!(sc.words[4].1, ExpansionMode::Single);
760
761        let mut lexer = Lexer::with_code("command command foo export a=b");
762        let mut parser = Parser::new(&mut lexer);
763        let result = parser.simple_command().now_or_never().unwrap();
764        let sc = result.unwrap().unwrap().unwrap();
765        assert_eq!(sc.words[4].0.to_string(), "a=b");
766        assert_eq!(sc.words[4].1, ExpansionMode::Multiple);
767    }
768
769    #[test]
770    fn no_declaration_utilities_with_empty_glossary() {
771        // "export" is not a declaration utility in the empty glossary.
772        let mut lexer = Lexer::with_code("export a=b");
773        let mut parser = Parser::config()
774            .declaration_utilities(&EmptyGlossary)
775            .input(&mut lexer);
776
777        let result = parser.simple_command().now_or_never().unwrap();
778        let sc = result.unwrap().unwrap().unwrap();
779        assert_eq!(sc.assigns, []);
780        assert_eq!(sc.words.len(), 2);
781        assert_eq!(*sc.redirs, []);
782        assert_eq!(sc.words[0].0.to_string(), "export");
783        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
784        assert_eq!(sc.words[1].0.to_string(), "a=b");
785        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
786    }
787
788    #[test]
789    fn custom_declaration_utility_glossary() {
790        // "foo" is a declaration utility in the custom glossary.
791        #[derive(Debug)]
792        struct CustomGlossary;
793        impl crate::decl_util::Glossary for CustomGlossary {
794            fn is_declaration_utility(&self, name: &str) -> Option<bool> {
795                Some(name == "foo")
796            }
797        }
798
799        let mut lexer = Lexer::with_code("foo a=b");
800        let mut parser = Parser::config()
801            .declaration_utilities(&CustomGlossary)
802            .input(&mut lexer);
803
804        let result = parser.simple_command().now_or_never().unwrap();
805        let sc = result.unwrap().unwrap().unwrap();
806        assert_eq!(sc.assigns, []);
807        assert_eq!(sc.words.len(), 2);
808        assert_eq!(*sc.redirs, []);
809        assert_eq!(sc.words[0].0.to_string(), "foo");
810        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
811        assert_eq!(sc.words[1].0.to_string(), "a=b");
812        assert_eq!(sc.words[1].1, ExpansionMode::Single);
813    }
814
815    fn portable_mode() -> yash_env::parser::Mode {
816        let mut mode = yash_env::parser::Mode::default();
817        mode.portable = true;
818        mode
819    }
820
821    #[test]
822    fn command_name_ending_with_colon_rejected_in_portable_mode() {
823        let mut lexer = Lexer::with_code("$foo:");
824        lexer.set_mode(portable_mode());
825        let mut parser = Parser::new(&mut lexer);
826
827        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
828        assert_eq!(
829            e.cause,
830            ErrorCause::Syntax(SyntaxError::ColonSuffixedCommandName)
831        );
832        assert_eq!(*e.location.code.value.borrow(), "$foo:");
833        assert_eq!(e.location.range, 0..5);
834    }
835
836    #[test]
837    fn lone_colon_command_name_accepted_in_portable_mode() {
838        // The `:` built-in is not a word reserved by POSIX.
839        let mut lexer = Lexer::with_code(":");
840        lexer.set_mode(portable_mode());
841        let mut parser = Parser::new(&mut lexer);
842
843        let result = parser.simple_command().now_or_never().unwrap();
844        let sc = result.unwrap().unwrap().unwrap();
845        assert_eq!(sc.words.len(), 1);
846        assert_eq!(sc.words[0].0.to_string(), ":");
847    }
848
849    #[test]
850    fn command_name_ending_with_colon_accepted_without_portable_mode() {
851        let mut lexer = Lexer::with_code("$foo:");
852        let mut parser = Parser::new(&mut lexer);
853
854        let result = parser.simple_command().now_or_never().unwrap();
855        let sc = result.unwrap().unwrap().unwrap();
856        assert_eq!(sc.words.len(), 1);
857        assert_eq!(sc.words[0].0.to_string(), "$foo:");
858    }
859
860    #[test]
861    fn argument_ending_with_colon_accepted_in_portable_mode() {
862        // Only the command name is a reserved-word recognition position.
863        let mut lexer = Lexer::with_code("echo foo:");
864        lexer.set_mode(portable_mode());
865        let mut parser = Parser::new(&mut lexer);
866
867        let result = parser.simple_command().now_or_never().unwrap();
868        let sc = result.unwrap().unwrap().unwrap();
869        assert_eq!(sc.words.len(), 2);
870        assert_eq!(sc.words[0].0.to_string(), "echo");
871        assert_eq!(sc.words[1].0.to_string(), "foo:");
872    }
873
874    #[test]
875    fn command_name_ending_with_colon_after_assignment_accepted_in_portable_mode() {
876        // A word after an assignment is not a reserved-word recognition position.
877        let mut lexer = Lexer::with_code("a=b foo:");
878        lexer.set_mode(portable_mode());
879        let mut parser = Parser::new(&mut lexer);
880
881        let result = parser.simple_command().now_or_never().unwrap();
882        let sc = result.unwrap().unwrap().unwrap();
883        assert_eq!(sc.assigns.len(), 1);
884        assert_eq!(sc.words.len(), 1);
885        assert_eq!(sc.words[0].0.to_string(), "foo:");
886    }
887
888    #[test]
889    fn quoted_colon_suffix_command_name_accepted_in_portable_mode() {
890        // A quoted trailing colon is not recognized as a reserved word.
891        let mut lexer = Lexer::with_code(r"foo\:");
892        lexer.set_mode(portable_mode());
893        let mut parser = Parser::new(&mut lexer);
894
895        let result = parser.simple_command().now_or_never().unwrap();
896        let sc = result.unwrap().unwrap().unwrap();
897        assert_eq!(sc.words.len(), 1);
898        assert_eq!(sc.words[0].0.to_string(), r"foo\:");
899    }
900
901    #[test]
902    fn assignment_value_ending_with_colon_accepted_in_portable_mode() {
903        // An assignment whose value ends with `:` is not a command name.
904        let mut lexer = Lexer::with_code("a=b:");
905        lexer.set_mode(portable_mode());
906        let mut parser = Parser::new(&mut lexer);
907
908        let result = parser.simple_command().now_or_never().unwrap();
909        let sc = result.unwrap().unwrap().unwrap();
910        assert_eq!(sc.assigns.len(), 1);
911        assert_eq!(sc.assigns[0].name, "a");
912        assert_eq!(sc.assigns[0].value.to_string(), "b:");
913    }
914
915    #[test]
916    fn assignment_name_starting_with_digit_rejected_in_portable_mode() {
917        let mut lexer = Lexer::with_code("1a=b");
918        lexer.set_mode(portable_mode());
919        let mut parser = Parser::new(&mut lexer);
920
921        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
922        assert_eq!(
923            e.cause,
924            ErrorCause::Syntax(SyntaxError::NonPortableAssignmentName)
925        );
926        assert_eq!(*e.location.code.value.borrow(), "1a=b");
927        assert_eq!(e.location.range, 0..4);
928    }
929
930    #[test]
931    fn assignment_name_with_non_portable_character_rejected_in_portable_mode() {
932        let mut lexer = Lexer::with_code("a.b=c");
933        lexer.set_mode(portable_mode());
934        let mut parser = Parser::new(&mut lexer);
935
936        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
937        assert_eq!(
938            e.cause,
939            ErrorCause::Syntax(SyntaxError::NonPortableAssignmentName)
940        );
941    }
942
943    #[test]
944    fn array_assignment_name_starting_with_digit_rejected_in_portable_mode() {
945        let mut lexer = Lexer::with_code("1a=(b c)");
946        lexer.set_mode(portable_mode());
947        let mut parser = Parser::new(&mut lexer);
948
949        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
950        assert_eq!(
951            e.cause,
952            ErrorCause::Syntax(SyntaxError::NonPortableAssignmentName)
953        );
954    }
955
956    #[test]
957    fn assignment_portable_name_allowed_in_portable_mode() {
958        let mut lexer = Lexer::with_code("_Az9=b");
959        lexer.set_mode(portable_mode());
960        let mut parser = Parser::new(&mut lexer);
961
962        let result = parser.simple_command().now_or_never().unwrap();
963        let sc = result.unwrap().unwrap().unwrap();
964        assert_eq!(sc.assigns.len(), 1);
965        assert_eq!(sc.assigns[0].name, "_Az9");
966        assert_eq!(sc.assigns[0].value.to_string(), "b");
967    }
968
969    #[test]
970    fn assignment_non_portable_name_allowed_without_portable_mode() {
971        let mut lexer = Lexer::with_code("1a=b");
972        let mut parser = Parser::new(&mut lexer);
973
974        let result = parser.simple_command().now_or_never().unwrap();
975        let sc = result.unwrap().unwrap().unwrap();
976        assert_eq!(sc.assigns.len(), 1);
977        assert_eq!(sc.assigns[0].name, "1a");
978        assert_eq!(sc.assigns[0].value.to_string(), "b");
979    }
980
981    #[test]
982    fn array_assignment_rejected_in_portable_mode() {
983        let mut lexer = Lexer::with_code("a=(b c)");
984        lexer.set_mode(portable_mode());
985        let mut parser = Parser::new(&mut lexer);
986
987        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
988        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::ArrayAssignment));
989        assert_eq!(*e.location.code.value.borrow(), "a=(b c)");
990        assert_eq!(e.location.range, 0..7);
991    }
992
993    #[test]
994    fn empty_array_assignment_rejected_in_portable_mode() {
995        let mut lexer = Lexer::with_code("a=()");
996        lexer.set_mode(portable_mode());
997        let mut parser = Parser::new(&mut lexer);
998
999        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
1000        assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::ArrayAssignment));
1001        assert_eq!(*e.location.code.value.borrow(), "a=()");
1002        assert_eq!(e.location.range, 0..4);
1003    }
1004
1005    #[test]
1006    fn array_assignment_allowed_without_portable_mode() {
1007        let mut lexer = Lexer::with_code("a=(b c)");
1008        let mut parser = Parser::new(&mut lexer);
1009
1010        let result = parser.simple_command().now_or_never().unwrap();
1011        let sc = result.unwrap().unwrap().unwrap();
1012        assert_eq!(sc.assigns.len(), 1);
1013        assert_eq!(sc.assigns[0].name, "a");
1014        assert_matches!(&sc.assigns[0].value, Array(words) => {
1015            assert_eq!(words.len(), 2);
1016            assert_eq!(words[0].to_string(), "b");
1017            assert_eq!(words[1].to_string(), "c");
1018        });
1019    }
1020
1021    #[test]
1022    fn assignment_is_not_considered_for_declaration_utility() {
1023        #[derive(Debug)]
1024        struct CustomGlossary;
1025        impl crate::decl_util::Glossary for CustomGlossary {
1026            fn is_declaration_utility(&self, _name: &str) -> Option<bool> {
1027                unreachable!("is_declaration_utility should not be called for assignments");
1028            }
1029        }
1030
1031        let mut lexer = Lexer::with_code("a=b");
1032        let mut parser = Parser::config()
1033            .declaration_utilities(&CustomGlossary)
1034            .input(&mut lexer);
1035
1036        let result = parser.simple_command().now_or_never().unwrap();
1037        let sc = result.unwrap().unwrap().unwrap();
1038        assert_eq!(sc.assigns.len(), 1);
1039        assert_eq!(sc.words, []);
1040        assert_eq!(*sc.redirs, [])
1041    }
1042}