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            // TODO no array assignment in POSIXly-correct mode
244            if units.is_empty()
245                && !self.has_blank().await?
246                && let Some(words) = self.array_values().await?
247            {
248                assign.value = Array(words);
249            }
250
251            result.assigns.push(assign);
252        }
253
254        Ok(Rec::Parsed((!result.is_empty()).then(|| result.into())))
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::super::error::ErrorCause;
261    use super::super::lex::Lexer;
262    use super::super::lex::TokenId::EndOfInput;
263    use super::*;
264    use crate::decl_util::EmptyGlossary;
265    use crate::source::Source;
266    use crate::syntax::RedirBody;
267    use crate::syntax::RedirOp;
268    use crate::syntax::TextUnit;
269    use crate::syntax::WordUnit;
270    use assert_matches::assert_matches;
271    use futures_util::FutureExt as _;
272
273    #[test]
274    fn determine_expansion_mode_empty_name() {
275        let in_word = "=".parse::<Word>().unwrap();
276        let (out_word, mode) = determine_expansion_mode(in_word.clone());
277        assert_eq!(out_word, in_word);
278        assert_eq!(mode, ExpansionMode::Multiple);
279    }
280
281    #[test]
282    fn determine_expansion_mode_nonempty_name() {
283        let in_word = "foo=".parse::<Word>().unwrap();
284        let (out_word, mode) = determine_expansion_mode(in_word.clone());
285        assert_eq!(out_word, in_word);
286        assert_eq!(mode, ExpansionMode::Single);
287    }
288
289    #[test]
290    fn determine_expansion_mode_non_literal_name() {
291        let in_word = "${X}=".parse::<Word>().unwrap();
292        let (out_word, mode) = determine_expansion_mode(in_word.clone());
293        assert_eq!(out_word, in_word);
294        assert_eq!(mode, ExpansionMode::Multiple);
295    }
296
297    #[test]
298    fn determine_expansion_mode_tilde_expansions_after_equal() {
299        let word = "~=~:~b".parse().unwrap();
300        let (word, mode) = determine_expansion_mode(word);
301        assert_eq!(
302            word.units,
303            [
304                WordUnit::Unquoted(TextUnit::Literal('~')),
305                WordUnit::Unquoted(TextUnit::Literal('=')),
306                WordUnit::Tilde {
307                    name: "".to_string(),
308                    followed_by_slash: false
309                },
310                WordUnit::Unquoted(TextUnit::Literal(':')),
311                WordUnit::Tilde {
312                    name: "b".to_string(),
313                    followed_by_slash: false
314                },
315            ]
316        );
317        assert_eq!(mode, ExpansionMode::Single);
318    }
319
320    #[test]
321    fn parser_array_values_no_open_parenthesis() {
322        let mut lexer = Lexer::with_code(")");
323        let mut parser = Parser::new(&mut lexer);
324        let result = parser.array_values().now_or_never().unwrap().unwrap();
325        assert_eq!(result, None);
326    }
327
328    #[test]
329    fn parser_array_values_empty() {
330        let mut lexer = Lexer::with_code("()");
331        let mut parser = Parser::new(&mut lexer);
332        let result = parser.array_values().now_or_never().unwrap();
333        let words = result.unwrap().unwrap();
334        assert_eq!(words, []);
335
336        let next = parser.peek_token().now_or_never().unwrap().unwrap();
337        assert_eq!(next.id, EndOfInput);
338    }
339
340    #[test]
341    fn parser_array_values_many() {
342        let mut lexer = Lexer::with_code("(a b c)");
343        let mut parser = Parser::new(&mut lexer);
344        let result = parser.array_values().now_or_never().unwrap();
345        let words = result.unwrap().unwrap();
346        assert_eq!(words.len(), 3);
347        assert_eq!(words[0].to_string(), "a");
348        assert_eq!(words[1].to_string(), "b");
349        assert_eq!(words[2].to_string(), "c");
350    }
351
352    #[test]
353    fn parser_array_values_newlines_and_comments() {
354        let mut lexer = Lexer::with_code(
355            "(
356            a # b
357            c d
358        )",
359        );
360        let mut parser = Parser::new(&mut lexer);
361        let result = parser.array_values().now_or_never().unwrap();
362        let words = result.unwrap().unwrap();
363        assert_eq!(words.len(), 3);
364        assert_eq!(words[0].to_string(), "a");
365        assert_eq!(words[1].to_string(), "c");
366        assert_eq!(words[2].to_string(), "d");
367    }
368
369    #[test]
370    fn parser_array_values_unclosed() {
371        let mut lexer = Lexer::with_code("(a b");
372        let mut parser = Parser::new(&mut lexer);
373        let e = parser.array_values().now_or_never().unwrap().unwrap_err();
374        assert_matches!(e.cause,
375             ErrorCause::Syntax(SyntaxError::UnclosedArrayValue { opening_location }) => {
376            assert_eq!(*opening_location.code.value.borrow(), "(a b");
377            assert_eq!(opening_location.code.start_line_number.get(), 1);
378            assert_eq!(*opening_location.code.source, Source::Unknown);
379            assert_eq!(opening_location.range, 0..1);
380        });
381        assert_eq!(*e.location.code.value.borrow(), "(a b");
382        assert_eq!(e.location.code.start_line_number.get(), 1);
383        assert_eq!(*e.location.code.source, Source::Unknown);
384        assert_eq!(e.location.range, 4..4);
385    }
386
387    #[test]
388    fn parser_array_values_invalid_word() {
389        let mut lexer = Lexer::with_code("(a;b)");
390        let mut parser = Parser::new(&mut lexer);
391        let e = parser.array_values().now_or_never().unwrap().unwrap_err();
392        assert_matches!(e.cause,
393            ErrorCause::Syntax(SyntaxError::UnclosedArrayValue { opening_location }) => {
394            assert_eq!(*opening_location.code.value.borrow(), "(a;b)");
395            assert_eq!(opening_location.code.start_line_number.get(), 1);
396            assert_eq!(*opening_location.code.source, Source::Unknown);
397            assert_eq!(opening_location.range, 0..1);
398        });
399        assert_eq!(*e.location.code.value.borrow(), "(a;b)");
400        assert_eq!(e.location.code.start_line_number.get(), 1);
401        assert_eq!(*e.location.code.source, Source::Unknown);
402        assert_eq!(e.location.range, 2..3);
403    }
404
405    #[test]
406    fn parser_simple_command_eof() {
407        let mut lexer = Lexer::with_code("");
408        let mut parser = Parser::new(&mut lexer);
409
410        let result = parser.simple_command().now_or_never().unwrap();
411        assert_eq!(result, Ok(Rec::Parsed(None)));
412    }
413
414    #[test]
415    fn parser_simple_command_keyword() {
416        let mut lexer = Lexer::with_code("then");
417        let mut parser = Parser::new(&mut lexer);
418
419        let result = parser.simple_command().now_or_never().unwrap();
420        assert_eq!(result, Ok(Rec::Parsed(None)));
421    }
422
423    #[test]
424    fn parser_simple_command_one_assignment() {
425        let mut lexer = Lexer::with_code("my=assignment");
426        let mut parser = Parser::new(&mut lexer);
427
428        let result = parser.simple_command().now_or_never().unwrap();
429        let sc = result.unwrap().unwrap().unwrap();
430        assert_eq!(sc.words, []);
431        assert_eq!(*sc.redirs, []);
432        assert_eq!(sc.assigns.len(), 1);
433        assert_eq!(sc.assigns[0].name, "my");
434        assert_eq!(sc.assigns[0].value.to_string(), "assignment");
435        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "my=assignment");
436        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
437        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
438        assert_eq!(sc.assigns[0].location.range, 0..13);
439    }
440
441    #[test]
442    fn parser_simple_command_many_assignments() {
443        let mut lexer = Lexer::with_code("a= b=! c=X");
444        let mut parser = Parser::new(&mut lexer);
445
446        let result = parser.simple_command().now_or_never().unwrap();
447        let sc = result.unwrap().unwrap().unwrap();
448        assert_eq!(sc.words, []);
449        assert_eq!(*sc.redirs, []);
450        assert_eq!(sc.assigns.len(), 3);
451        assert_eq!(sc.assigns[0].name, "a");
452        assert_eq!(sc.assigns[0].value.to_string(), "");
453        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "a= b=! c=X");
454        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
455        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
456        assert_eq!(sc.assigns[0].location.range, 0..2);
457        assert_eq!(sc.assigns[1].name, "b");
458        assert_eq!(sc.assigns[1].value.to_string(), "!");
459        assert_eq!(*sc.assigns[1].location.code.value.borrow(), "a= b=! c=X");
460        assert_eq!(sc.assigns[1].location.code.start_line_number.get(), 1);
461        assert_eq!(*sc.assigns[1].location.code.source, Source::Unknown);
462        assert_eq!(sc.assigns[1].location.range, 3..6);
463        assert_eq!(sc.assigns[2].name, "c");
464        assert_eq!(sc.assigns[2].value.to_string(), "X");
465        assert_eq!(*sc.assigns[2].location.code.value.borrow(), "a= b=! c=X");
466        assert_eq!(sc.assigns[2].location.code.start_line_number.get(), 1);
467        assert_eq!(*sc.assigns[2].location.code.source, Source::Unknown);
468        assert_eq!(sc.assigns[2].location.range, 7..10);
469    }
470
471    #[test]
472    fn parser_simple_command_one_word() {
473        let mut lexer = Lexer::with_code("word");
474        let mut parser = Parser::new(&mut lexer);
475
476        let result = parser.simple_command().now_or_never().unwrap();
477        let sc = result.unwrap().unwrap().unwrap();
478        assert_eq!(sc.assigns, []);
479        assert_eq!(*sc.redirs, []);
480        assert_eq!(sc.words.len(), 1);
481        assert_eq!(sc.words[0].0.to_string(), "word");
482        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
483    }
484
485    #[test]
486    fn parser_simple_command_many_words() {
487        let mut lexer = Lexer::with_code(": if then");
488        let mut parser = Parser::new(&mut lexer);
489
490        let result = parser.simple_command().now_or_never().unwrap();
491        let sc = result.unwrap().unwrap().unwrap();
492        assert_eq!(sc.assigns, []);
493        assert_eq!(*sc.redirs, []);
494        assert_eq!(sc.words.len(), 3);
495        assert_eq!(sc.words[0].0.to_string(), ":");
496        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
497        assert_eq!(sc.words[1].0.to_string(), "if");
498        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
499        assert_eq!(sc.words[2].0.to_string(), "then");
500        assert_eq!(sc.words[2].1, ExpansionMode::Multiple);
501    }
502
503    #[test]
504    fn parser_simple_command_one_redirection() {
505        let mut lexer = Lexer::with_code("<foo");
506        let mut parser = Parser::new(&mut lexer);
507
508        let result = parser.simple_command().now_or_never().unwrap();
509        let sc = result.unwrap().unwrap().unwrap();
510        assert_eq!(sc.assigns, []);
511        assert_eq!(sc.words, []);
512        assert_eq!(sc.redirs.len(), 1);
513        assert_eq!(sc.redirs[0].fd, None);
514        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
515            assert_eq!(operator, &RedirOp::FileIn);
516            assert_eq!(operand.to_string(), "foo")
517        });
518
519        let next = parser.peek_token().now_or_never().unwrap().unwrap();
520        assert_eq!(next.id, EndOfInput);
521    }
522
523    #[test]
524    fn parser_simple_command_many_redirections() {
525        let mut lexer = Lexer::with_code("<one >two >>three");
526        let mut parser = Parser::new(&mut lexer);
527
528        let result = parser.simple_command().now_or_never().unwrap();
529        let sc = result.unwrap().unwrap().unwrap();
530        assert_eq!(sc.assigns, []);
531        assert_eq!(sc.words, []);
532        assert_eq!(sc.redirs.len(), 3);
533        assert_eq!(sc.redirs[0].fd, None);
534        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
535            assert_eq!(operator, &RedirOp::FileIn);
536            assert_eq!(operand.to_string(), "one")
537        });
538        assert_eq!(sc.redirs[1].fd, None);
539        assert_matches!(sc.redirs[1].body, RedirBody::Normal { ref operator, ref operand } => {
540            assert_eq!(operator, &RedirOp::FileOut);
541            assert_eq!(operand.to_string(), "two")
542        });
543        assert_eq!(sc.redirs[2].fd, None);
544        assert_matches!(sc.redirs[2].body, RedirBody::Normal { ref operator, ref operand } => {
545            assert_eq!(operator, &RedirOp::FileAppend);
546            assert_eq!(operand.to_string(), "three")
547        });
548
549        let next = parser.peek_token().now_or_never().unwrap().unwrap();
550        assert_eq!(next.id, EndOfInput);
551    }
552
553    #[test]
554    fn parser_simple_command_assignment_word() {
555        let mut lexer = Lexer::with_code("if=then else");
556        let mut parser = Parser::new(&mut lexer);
557
558        let result = parser.simple_command().now_or_never().unwrap();
559        let sc = result.unwrap().unwrap().unwrap();
560        assert_eq!(*sc.redirs, []);
561        assert_eq!(sc.assigns.len(), 1);
562        assert_eq!(sc.words.len(), 1);
563        assert_eq!(sc.assigns[0].name, "if");
564        assert_eq!(sc.assigns[0].value.to_string(), "then");
565        assert_eq!(sc.words[0].0.to_string(), "else");
566        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
567    }
568
569    #[test]
570    fn parser_simple_command_word_redirection() {
571        let mut lexer = Lexer::with_code("word <redirection");
572        let mut parser = Parser::new(&mut lexer);
573
574        let result = parser.simple_command().now_or_never().unwrap();
575        let sc = result.unwrap().unwrap().unwrap();
576        assert_eq!(sc.assigns, []);
577        assert_eq!(sc.words.len(), 1);
578        assert_eq!(sc.redirs.len(), 1);
579        assert_eq!(sc.words[0].0.to_string(), "word");
580        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
581        assert_eq!(sc.redirs[0].fd, None);
582        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
583            assert_eq!(operator, &RedirOp::FileIn);
584            assert_eq!(operand.to_string(), "redirection")
585        });
586    }
587
588    #[test]
589    fn parser_simple_command_redirection_assignment() {
590        let mut lexer = Lexer::with_code("<foo a=b");
591        let mut parser = Parser::new(&mut lexer);
592
593        let result = parser.simple_command().now_or_never().unwrap();
594        let sc = result.unwrap().unwrap().unwrap();
595        assert_eq!(sc.words, []);
596        assert_eq!(sc.assigns.len(), 1);
597        assert_eq!(sc.redirs.len(), 1);
598        assert_eq!(sc.assigns[0].name, "a");
599        assert_eq!(sc.assigns[0].value.to_string(), "b");
600        assert_eq!(sc.redirs[0].fd, None);
601        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
602            assert_eq!(operator, &RedirOp::FileIn);
603            assert_eq!(operand.to_string(), "foo")
604        });
605    }
606
607    #[test]
608    fn parser_simple_command_assignment_redirection_word() {
609        let mut lexer = Lexer::with_code("if=then <foo else");
610        let mut parser = Parser::new(&mut lexer);
611
612        let result = parser.simple_command().now_or_never().unwrap();
613        let sc = result.unwrap().unwrap().unwrap();
614        assert_eq!(sc.assigns.len(), 1);
615        assert_eq!(sc.words.len(), 1);
616        assert_eq!(sc.redirs.len(), 1);
617        assert_eq!(sc.assigns[0].name, "if");
618        assert_eq!(sc.assigns[0].value.to_string(), "then");
619        assert_eq!(sc.words[0].0.to_string(), "else");
620        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
621        assert_eq!(sc.redirs[0].fd, None);
622        assert_matches!(sc.redirs[0].body, RedirBody::Normal { ref operator, ref operand } => {
623            assert_eq!(operator, &RedirOp::FileIn);
624            assert_eq!(operand.to_string(), "foo")
625        });
626    }
627
628    #[test]
629    fn parser_simple_command_array_assignment() {
630        let mut lexer = Lexer::with_code("a=()");
631        let mut parser = Parser::new(&mut lexer);
632
633        let result = parser.simple_command().now_or_never().unwrap();
634        let sc = result.unwrap().unwrap().unwrap();
635        assert_eq!(sc.assigns.len(), 1);
636        assert_eq!(sc.words, []);
637        assert_eq!(*sc.redirs, []);
638        assert_eq!(sc.assigns[0].name, "a");
639        assert_matches!(&sc.assigns[0].value, Array(words) => {
640            assert_eq!(words, &[]);
641        });
642
643        let next = parser.peek_token().now_or_never().unwrap().unwrap();
644        assert_eq!(next.id, EndOfInput);
645    }
646
647    #[test]
648    fn parser_simple_command_empty_assignment_followed_by_blank_and_parenthesis() {
649        let mut lexer = Lexer::with_code("a= ()");
650        let mut parser = Parser::new(&mut lexer);
651
652        let result = parser.simple_command().now_or_never().unwrap();
653        let sc = result.unwrap().unwrap().unwrap();
654        assert_eq!(sc.assigns.len(), 1);
655        assert_eq!(sc.words, []);
656        assert_eq!(*sc.redirs, []);
657        assert_eq!(sc.assigns[0].name, "a");
658        assert_eq!(sc.assigns[0].value.to_string(), "");
659        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "a= ()");
660        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
661        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
662        assert_eq!(sc.assigns[0].location.range, 0..2);
663
664        let next = parser.peek_token().now_or_never().unwrap().unwrap();
665        assert_eq!(next.id, Operator(OpenParen));
666    }
667
668    #[test]
669    fn parser_simple_command_non_empty_assignment_followed_by_parenthesis() {
670        let mut lexer = Lexer::with_code("a=b()");
671        let mut parser = Parser::new(&mut lexer);
672
673        let result = parser.simple_command().now_or_never().unwrap();
674        let sc = result.unwrap().unwrap().unwrap();
675        assert_eq!(sc.assigns.len(), 1);
676        assert_eq!(sc.words, []);
677        assert_eq!(*sc.redirs, []);
678        assert_eq!(sc.assigns[0].name, "a");
679        assert_eq!(sc.assigns[0].value.to_string(), "b");
680        assert_eq!(*sc.assigns[0].location.code.value.borrow(), "a=b()");
681        assert_eq!(sc.assigns[0].location.code.start_line_number.get(), 1);
682        assert_eq!(*sc.assigns[0].location.code.source, Source::Unknown);
683        assert_eq!(sc.assigns[0].location.range, 0..3);
684
685        let next = parser.peek_token().now_or_never().unwrap().unwrap();
686        assert_eq!(next.id, Operator(OpenParen));
687    }
688
689    #[test]
690    fn word_with_single_expansion_mode_in_declaration_utility() {
691        // "export" is a declaration utility, so the expansion mode of the word
692        // "a=b" should be single.
693        let mut lexer = Lexer::with_code("export a=b");
694        let mut parser = Parser::new(&mut lexer);
695
696        let result = parser.simple_command().now_or_never().unwrap();
697        let sc = result.unwrap().unwrap().unwrap();
698        assert_eq!(sc.assigns, []);
699        assert_eq!(sc.words.len(), 2);
700        assert_eq!(*sc.redirs, []);
701        assert_eq!(sc.words[0].0.to_string(), "export");
702        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
703        assert_eq!(sc.words[1].0.to_string(), "a=b");
704        assert_eq!(sc.words[1].1, ExpansionMode::Single);
705    }
706
707    #[test]
708    fn word_with_multiple_expansion_mode_in_declaration_utility() {
709        // The expansion mode of the word "foo" should be multiple because it
710        // cannot be parsed as an assignment.
711        let mut lexer = Lexer::with_code("export foo");
712        let mut parser = Parser::new(&mut lexer);
713
714        let result = parser.simple_command().now_or_never().unwrap();
715        let sc = result.unwrap().unwrap().unwrap();
716        assert_eq!(sc.assigns, []);
717        assert_eq!(sc.words.len(), 2);
718        assert_eq!(*sc.redirs, []);
719        assert_eq!(sc.words[0].0.to_string(), "export");
720        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
721        assert_eq!(sc.words[1].0.to_string(), "foo");
722        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
723    }
724
725    #[test]
726    fn word_with_multiple_expansion_mode_in_non_declaration_utility() {
727        // "foo" is not a declaration utility, so the expansion mode of the word
728        // "a=b" should be multiple.
729        let mut lexer = Lexer::with_code("foo a=b");
730        let mut parser = Parser::new(&mut lexer);
731
732        let result = parser.simple_command().now_or_never().unwrap();
733        let sc = result.unwrap().unwrap().unwrap();
734        assert_eq!(sc.assigns, []);
735        assert_eq!(sc.words.len(), 2);
736        assert_eq!(*sc.redirs, []);
737        assert_eq!(sc.words[0].0.to_string(), "foo");
738        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
739        assert_eq!(sc.words[1].0.to_string(), "a=b");
740        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
741    }
742
743    #[test]
744    fn declaration_utility_determined_by_non_first_word() {
745        // "command" delegates to the next word to determine whether it is a
746        // declaration utility.
747        let mut lexer = Lexer::with_code("command command export foo a=b");
748        let mut parser = Parser::new(&mut lexer);
749        let result = parser.simple_command().now_or_never().unwrap();
750        let sc = result.unwrap().unwrap().unwrap();
751        assert_eq!(sc.words[4].0.to_string(), "a=b");
752        assert_eq!(sc.words[4].1, ExpansionMode::Single);
753
754        let mut lexer = Lexer::with_code("command command foo export 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::Multiple);
760    }
761
762    #[test]
763    fn no_declaration_utilities_with_empty_glossary() {
764        // "export" is not a declaration utility in the empty glossary.
765        let mut lexer = Lexer::with_code("export a=b");
766        let mut parser = Parser::config()
767            .declaration_utilities(&EmptyGlossary)
768            .input(&mut lexer);
769
770        let result = parser.simple_command().now_or_never().unwrap();
771        let sc = result.unwrap().unwrap().unwrap();
772        assert_eq!(sc.assigns, []);
773        assert_eq!(sc.words.len(), 2);
774        assert_eq!(*sc.redirs, []);
775        assert_eq!(sc.words[0].0.to_string(), "export");
776        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
777        assert_eq!(sc.words[1].0.to_string(), "a=b");
778        assert_eq!(sc.words[1].1, ExpansionMode::Multiple);
779    }
780
781    #[test]
782    fn custom_declaration_utility_glossary() {
783        // "foo" is a declaration utility in the custom glossary.
784        #[derive(Debug)]
785        struct CustomGlossary;
786        impl crate::decl_util::Glossary for CustomGlossary {
787            fn is_declaration_utility(&self, name: &str) -> Option<bool> {
788                Some(name == "foo")
789            }
790        }
791
792        let mut lexer = Lexer::with_code("foo a=b");
793        let mut parser = Parser::config()
794            .declaration_utilities(&CustomGlossary)
795            .input(&mut lexer);
796
797        let result = parser.simple_command().now_or_never().unwrap();
798        let sc = result.unwrap().unwrap().unwrap();
799        assert_eq!(sc.assigns, []);
800        assert_eq!(sc.words.len(), 2);
801        assert_eq!(*sc.redirs, []);
802        assert_eq!(sc.words[0].0.to_string(), "foo");
803        assert_eq!(sc.words[0].1, ExpansionMode::Multiple);
804        assert_eq!(sc.words[1].0.to_string(), "a=b");
805        assert_eq!(sc.words[1].1, ExpansionMode::Single);
806    }
807
808    fn portable_mode() -> yash_env::parser::Mode {
809        let mut mode = yash_env::parser::Mode::default();
810        mode.portable = true;
811        mode
812    }
813
814    #[test]
815    fn command_name_ending_with_colon_rejected_in_portable_mode() {
816        let mut lexer = Lexer::with_code("$foo:");
817        lexer.set_mode(portable_mode());
818        let mut parser = Parser::new(&mut lexer);
819
820        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
821        assert_eq!(
822            e.cause,
823            ErrorCause::Syntax(SyntaxError::ColonSuffixedCommandName)
824        );
825        assert_eq!(*e.location.code.value.borrow(), "$foo:");
826        assert_eq!(e.location.range, 0..5);
827    }
828
829    #[test]
830    fn lone_colon_command_name_accepted_in_portable_mode() {
831        // The `:` built-in is not a word reserved by POSIX.
832        let mut lexer = Lexer::with_code(":");
833        lexer.set_mode(portable_mode());
834        let mut parser = Parser::new(&mut lexer);
835
836        let result = parser.simple_command().now_or_never().unwrap();
837        let sc = result.unwrap().unwrap().unwrap();
838        assert_eq!(sc.words.len(), 1);
839        assert_eq!(sc.words[0].0.to_string(), ":");
840    }
841
842    #[test]
843    fn command_name_ending_with_colon_accepted_without_portable_mode() {
844        let mut lexer = Lexer::with_code("$foo:");
845        let mut parser = Parser::new(&mut lexer);
846
847        let result = parser.simple_command().now_or_never().unwrap();
848        let sc = result.unwrap().unwrap().unwrap();
849        assert_eq!(sc.words.len(), 1);
850        assert_eq!(sc.words[0].0.to_string(), "$foo:");
851    }
852
853    #[test]
854    fn argument_ending_with_colon_accepted_in_portable_mode() {
855        // Only the command name is a reserved-word recognition position.
856        let mut lexer = Lexer::with_code("echo foo:");
857        lexer.set_mode(portable_mode());
858        let mut parser = Parser::new(&mut lexer);
859
860        let result = parser.simple_command().now_or_never().unwrap();
861        let sc = result.unwrap().unwrap().unwrap();
862        assert_eq!(sc.words.len(), 2);
863        assert_eq!(sc.words[0].0.to_string(), "echo");
864        assert_eq!(sc.words[1].0.to_string(), "foo:");
865    }
866
867    #[test]
868    fn command_name_ending_with_colon_after_assignment_accepted_in_portable_mode() {
869        // A word after an assignment is not a reserved-word recognition position.
870        let mut lexer = Lexer::with_code("a=b foo:");
871        lexer.set_mode(portable_mode());
872        let mut parser = Parser::new(&mut lexer);
873
874        let result = parser.simple_command().now_or_never().unwrap();
875        let sc = result.unwrap().unwrap().unwrap();
876        assert_eq!(sc.assigns.len(), 1);
877        assert_eq!(sc.words.len(), 1);
878        assert_eq!(sc.words[0].0.to_string(), "foo:");
879    }
880
881    #[test]
882    fn quoted_colon_suffix_command_name_accepted_in_portable_mode() {
883        // A quoted trailing colon is not recognized as a reserved word.
884        let mut lexer = Lexer::with_code(r"foo\:");
885        lexer.set_mode(portable_mode());
886        let mut parser = Parser::new(&mut lexer);
887
888        let result = parser.simple_command().now_or_never().unwrap();
889        let sc = result.unwrap().unwrap().unwrap();
890        assert_eq!(sc.words.len(), 1);
891        assert_eq!(sc.words[0].0.to_string(), r"foo\:");
892    }
893
894    #[test]
895    fn assignment_value_ending_with_colon_accepted_in_portable_mode() {
896        // An assignment whose value ends with `:` is not a command name.
897        let mut lexer = Lexer::with_code("a=b:");
898        lexer.set_mode(portable_mode());
899        let mut parser = Parser::new(&mut lexer);
900
901        let result = parser.simple_command().now_or_never().unwrap();
902        let sc = result.unwrap().unwrap().unwrap();
903        assert_eq!(sc.assigns.len(), 1);
904        assert_eq!(sc.assigns[0].name, "a");
905        assert_eq!(sc.assigns[0].value.to_string(), "b:");
906    }
907
908    #[test]
909    fn assignment_name_starting_with_digit_rejected_in_portable_mode() {
910        let mut lexer = Lexer::with_code("1a=b");
911        lexer.set_mode(portable_mode());
912        let mut parser = Parser::new(&mut lexer);
913
914        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
915        assert_eq!(
916            e.cause,
917            ErrorCause::Syntax(SyntaxError::NonPortableAssignmentName)
918        );
919        assert_eq!(*e.location.code.value.borrow(), "1a=b");
920        assert_eq!(e.location.range, 0..4);
921    }
922
923    #[test]
924    fn assignment_name_with_non_portable_character_rejected_in_portable_mode() {
925        let mut lexer = Lexer::with_code("a.b=c");
926        lexer.set_mode(portable_mode());
927        let mut parser = Parser::new(&mut lexer);
928
929        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
930        assert_eq!(
931            e.cause,
932            ErrorCause::Syntax(SyntaxError::NonPortableAssignmentName)
933        );
934    }
935
936    #[test]
937    fn array_assignment_name_starting_with_digit_rejected_in_portable_mode() {
938        let mut lexer = Lexer::with_code("1a=(b c)");
939        lexer.set_mode(portable_mode());
940        let mut parser = Parser::new(&mut lexer);
941
942        let e = parser.simple_command().now_or_never().unwrap().unwrap_err();
943        assert_eq!(
944            e.cause,
945            ErrorCause::Syntax(SyntaxError::NonPortableAssignmentName)
946        );
947    }
948
949    #[test]
950    fn assignment_portable_name_allowed_in_portable_mode() {
951        let mut lexer = Lexer::with_code("_Az9=b");
952        lexer.set_mode(portable_mode());
953        let mut parser = Parser::new(&mut lexer);
954
955        let result = parser.simple_command().now_or_never().unwrap();
956        let sc = result.unwrap().unwrap().unwrap();
957        assert_eq!(sc.assigns.len(), 1);
958        assert_eq!(sc.assigns[0].name, "_Az9");
959        assert_eq!(sc.assigns[0].value.to_string(), "b");
960    }
961
962    #[test]
963    fn assignment_non_portable_name_allowed_without_portable_mode() {
964        let mut lexer = Lexer::with_code("1a=b");
965        let mut parser = Parser::new(&mut lexer);
966
967        let result = parser.simple_command().now_or_never().unwrap();
968        let sc = result.unwrap().unwrap().unwrap();
969        assert_eq!(sc.assigns.len(), 1);
970        assert_eq!(sc.assigns[0].name, "1a");
971        assert_eq!(sc.assigns[0].value.to_string(), "b");
972    }
973
974    #[test]
975    fn assignment_is_not_considered_for_declaration_utility() {
976        #[derive(Debug)]
977        struct CustomGlossary;
978        impl crate::decl_util::Glossary for CustomGlossary {
979            fn is_declaration_utility(&self, _name: &str) -> Option<bool> {
980                unreachable!("is_declaration_utility should not be called for assignments");
981            }
982        }
983
984        let mut lexer = Lexer::with_code("a=b");
985        let mut parser = Parser::config()
986            .declaration_utilities(&CustomGlossary)
987            .input(&mut lexer);
988
989        let result = parser.simple_command().now_or_never().unwrap();
990        let sc = result.unwrap().unwrap().unwrap();
991        assert_eq!(sc.assigns.len(), 1);
992        assert_eq!(sc.words, []);
993        assert_eq!(*sc.redirs, [])
994    }
995}