yash_syntax/parser/
from_str.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 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
17use super::Error;
18use super::ErrorCause;
19use super::Parser;
20use super::SyntaxError;
21use super::lex::Lexer;
22use super::lex::Operator;
23use super::lex::ParseOperatorError;
24use super::lex::Token;
25use super::lex::TokenId;
26use super::lex::WordContext;
27use super::lex::WordLexer;
28use crate::syntax::*;
29use std::str::FromStr;
30
31/// Polls the given future, assuming it returns `Ready`.
32fn unwrap_ready<F: Future>(f: F) -> <F as Future>::Output {
33    use futures_util::future::FutureExt;
34    f.now_or_never()
35        .expect("Expected Ready but received Pending")
36}
37
38/// Returns an error if the parser has a remaining token.
39async fn reject_redundant_token(parser: &mut Parser<'_, '_>) -> Result<(), Error> {
40    let token = parser.take_token_raw().await?;
41    if token.id == TokenId::EndOfInput {
42        Ok(())
43    } else {
44        Err(Error {
45            cause: ErrorCause::Syntax(SyntaxError::RedundantToken),
46            location: token.word.location,
47        })
48    }
49}
50
51/// Helper for implementing FromStr
52trait Shift {
53    type Output;
54    fn shift(self) -> Self::Output;
55}
56
57impl<T, E> Shift for Result<Option<T>, E> {
58    type Output = Result<T, Option<E>>;
59    fn shift(self) -> Result<T, Option<E>> {
60        match self {
61            Ok(Some(t)) => Ok(t),
62            Ok(None) => Err(None),
63            Err(e) => Err(Some(e)),
64        }
65    }
66}
67
68impl FromStr for BracedParam {
69    /// Optional error value
70    ///
71    /// The error is `None` if the input is empty or does not start with `${`.
72    /// A proper error is returned in `Some(_)` in case of a syntax error in the
73    /// parameter.
74    type Err = Option<Error>;
75
76    fn from_str(s: &str) -> Result<BracedParam, Option<Error>> {
77        match TextUnit::from_str(s)? {
78            TextUnit::BracedParam(param) => Ok(param),
79            _ => Err(None),
80        }
81    }
82}
83
84/// Parses a [`TextUnit`] by `lexer.text_unit(|_| false, |_| true)`.
85impl FromStr for TextUnit {
86    /// Optional error value
87    ///
88    /// The error is `None` if the input is empty.
89    /// A proper error is returned in `Some(_)` in case of a syntax error.
90    type Err = Option<Error>;
91
92    fn from_str(s: &str) -> Result<TextUnit, Option<Error>> {
93        let mut lexer = Lexer::with_code(s);
94        let mut lexer = WordLexer {
95            lexer: &mut lexer,
96            context: WordContext::Word,
97        };
98        unwrap_ready(lexer.text_unit(|_| false, |_| true)).shift()
99    }
100}
101
102/// Parses a text by `lexer.text(|_| false, |_| true)`.
103impl FromStr for Text {
104    type Err = Error;
105    fn from_str(s: &str) -> Result<Text, Error> {
106        let mut lexer = Lexer::with_code(s);
107        unwrap_ready(lexer.text(|_| false, |_| true))
108    }
109}
110
111impl FromStr for EscapeUnit {
112    /// Optional error value
113    ///
114    /// The error is `None` if the input is empty.
115    /// A proper error is returned in `Some(_)` in case of a syntax error.
116    type Err = Option<Error>;
117
118    fn from_str(s: &str) -> Result<Self, Self::Err> {
119        let mut lexer = Lexer::with_code(s);
120        unwrap_ready(lexer.escape_unit()).shift()
121    }
122}
123
124/// Parses an escaped string by `lexer.escaped_string(|_| false)`.
125impl FromStr for EscapedString {
126    type Err = Error;
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        let mut lexer = Lexer::with_code(s);
129        unwrap_ready(lexer.escaped_string(|_| false))
130    }
131}
132
133impl FromStr for WordUnit {
134    /// Optional error value
135    ///
136    /// The error is `None` if the input is empty.
137    /// A proper error is returned in `Some(_)` in case of a syntax error.
138    type Err = Option<Error>;
139
140    fn from_str(s: &str) -> Result<WordUnit, Option<Error>> {
141        let mut lexer = Lexer::with_code(s);
142        let mut lexer = WordLexer {
143            lexer: &mut lexer,
144            context: WordContext::Word,
145        };
146        unwrap_ready(lexer.word_unit(|_| false)).shift()
147    }
148}
149
150/// Converts a string to a word.
151///
152/// This implementation does not parse any tilde expansions in the word.
153/// To parse them, you need to call [`Word::parse_tilde_front`] or
154/// [`Word::parse_tilde_everywhere`] on the resultant word.
155impl FromStr for Word {
156    type Err = Error;
157
158    fn from_str(s: &str) -> Result<Word, Error> {
159        let mut lexer = Lexer::with_code(s);
160        let mut lexer = WordLexer {
161            lexer: &mut lexer,
162            context: WordContext::Word,
163        };
164        unwrap_ready(lexer.word(|_| false))
165    }
166}
167
168impl FromStr for Value {
169    type Err = Error;
170    fn from_str(s: &str) -> Result<Value, Error> {
171        let s = format!("x={s}");
172        let a = s.parse::<Assign>().map_err(Option::unwrap)?;
173        Ok(a.value)
174    }
175}
176
177/// Converts a string to an assignment.
178impl FromStr for Assign {
179    /// Optional error value
180    ///
181    /// The error is `None` if the input is a valid word but not an assignment.
182    /// A proper error is returned in `Some(_)` in case of a syntax error.
183    type Err = Option<Error>;
184
185    fn from_str(s: &str) -> Result<Assign, Option<Error>> {
186        let mut c = s.parse::<SimpleCommand>()?;
187
188        match c.assigns.pop() {
189            Some(last) if c.assigns.is_empty() => {
190                if let Some(word) = c.words.pop() {
191                    Err(Some(Error {
192                        cause: ErrorCause::Syntax(SyntaxError::RedundantToken),
193                        location: word.0.location,
194                    }))
195                } else if let Some(redir) = c.redirs.first() {
196                    Err(Some(Error {
197                        cause: ErrorCause::Syntax(SyntaxError::RedundantToken),
198                        location: redir.body.operand().location.clone(),
199                    }))
200                } else {
201                    Ok(last)
202                }
203            }
204            Some(last) => Err(Some(Error {
205                cause: ErrorCause::Syntax(SyntaxError::RedundantToken),
206                location: last.location,
207            })),
208            None => Err(None),
209        }
210    }
211}
212
213impl FromStr for Operator {
214    type Err = ParseOperatorError;
215    fn from_str(s: &str) -> Result<Operator, ParseOperatorError> {
216        let mut lexer = Lexer::with_code(s);
217        match unwrap_ready(lexer.operator()) {
218            Ok(Some(Token {
219                id: TokenId::Operator(op),
220                ..
221            })) => Ok(op),
222
223            _ => Err(ParseOperatorError),
224        }
225    }
226}
227
228impl FromStr for RedirOp {
229    type Err = ParseOperatorError;
230    fn from_str(s: &str) -> Result<RedirOp, ParseOperatorError> {
231        Operator::from_str(s)?
232            .try_into()
233            .map_err(|_| ParseOperatorError)
234    }
235}
236
237/// Converts a string to a redirection.
238///
239/// This implementation does not support parsing a here-document.
240impl FromStr for Redir {
241    /// Optional error value
242    ///
243    /// The error is `None` if the first token is not a redirection operator.
244    /// A proper error is returned in `Some(_)` in case of a syntax error.
245    type Err = Option<Error>;
246
247    fn from_str(s: &str) -> Result<Redir, Option<Error>> {
248        let mut lexer = Lexer::with_code(s);
249        let mut parser = Parser::new(&mut lexer);
250        unwrap_ready(async {
251            let redir = parser.redirection().await?;
252            if redir.is_some() {
253                reject_redundant_token(&mut parser).await?;
254                // If this redirection is a here-document, its content cannot be
255                // filled because there is no newline token that would make the
256                // content to be read.
257                parser.ensure_no_unread_here_doc()?;
258            }
259            Ok(redir)
260        })
261        .shift()
262    }
263}
264
265/// Converts a string to a simple command.
266///
267/// This implementation does not support parsing a command that contains a
268/// here-document.
269impl FromStr for SimpleCommand {
270    /// Optional error value
271    ///
272    /// The error is `None` if the first token does not start a simple command.
273    /// A proper error is returned in `Some(_)` in case of a syntax error.
274    type Err = Option<Error>;
275
276    fn from_str(s: &str) -> Result<SimpleCommand, Option<Error>> {
277        let mut lexer = Lexer::with_code(s);
278        let mut parser = Parser::new(&mut lexer);
279        unwrap_ready(async {
280            let command = parser.simple_command().await?.unwrap();
281            if command.is_some() {
282                reject_redundant_token(&mut parser).await?;
283                // If the simple command contains a here-document, its content
284                // cannot be filled because there is no newline token that would
285                // make the content to be read.
286                parser.ensure_no_unread_here_doc()?;
287            }
288            Ok(command)
289        })
290        .shift()
291    }
292}
293
294/// Converts a string to a case item.
295impl FromStr for CaseItem {
296    /// Optional error value
297    ///
298    /// The error is `None` if the first token is `esac`.
299    /// A proper error is returned in `Some(_)` in case of a syntax error.
300    type Err = Option<Error>;
301
302    fn from_str(s: &str) -> Result<CaseItem, Option<Error>> {
303        let mut lexer = Lexer::with_code(s);
304        let mut parser = Parser::new(&mut lexer);
305        unwrap_ready(async {
306            let item = parser.case_item().await?.map(|(item, _)| item);
307            if item.is_some() {
308                if parser.peek_token().await?.id == TokenId::Operator(Operator::SemicolonSemicolon)
309                {
310                    parser.take_token_raw().await?;
311                }
312                reject_redundant_token(&mut parser).await?;
313                parser.ensure_no_unread_here_doc()?;
314            }
315            Ok(item)
316        })
317        .shift()
318    }
319}
320
321/// Converts a string to a compound command.
322impl FromStr for CompoundCommand {
323    /// Optional error value
324    ///
325    /// The error is `None` if the first token does not start a compound command.
326    /// A proper error is returned in `Some(_)` in case of a syntax error.
327    type Err = Option<Error>;
328
329    fn from_str(s: &str) -> Result<CompoundCommand, Option<Error>> {
330        let mut lexer = Lexer::with_code(s);
331        let mut parser = Parser::new(&mut lexer);
332        unwrap_ready(async {
333            let command = parser.compound_command().await?;
334            if command.is_some() {
335                reject_redundant_token(&mut parser).await?;
336                parser.ensure_no_unread_here_doc()?;
337            }
338            Ok(command)
339        })
340        .shift()
341    }
342}
343
344/// Converts a string to a compound command.
345impl FromStr for FullCompoundCommand {
346    /// Optional error value
347    ///
348    /// The error is `None` if the first token does not start a compound command.
349    /// A proper error is returned in `Some(_)` in case of a syntax error.
350    type Err = Option<Error>;
351
352    fn from_str(s: &str) -> Result<FullCompoundCommand, Option<Error>> {
353        let mut lexer = Lexer::with_code(s);
354        let mut parser = Parser::new(&mut lexer);
355        unwrap_ready(async {
356            let command = parser.full_compound_command().await?;
357            if command.is_some() {
358                reject_redundant_token(&mut parser).await?;
359                parser.ensure_no_unread_here_doc()?;
360            }
361            Ok(command)
362        })
363        .shift()
364    }
365}
366
367/// Converts a string to a command.
368impl FromStr for Command {
369    /// Optional error value
370    ///
371    /// The error is `None` if the first token does not start a command.
372    /// A proper error is returned in `Some(_)` in case of a syntax error.
373    type Err = Option<Error>;
374
375    fn from_str(s: &str) -> Result<Command, Option<Error>> {
376        let mut lexer = Lexer::with_code(s);
377        let mut parser = Parser::new(&mut lexer);
378        unwrap_ready(async {
379            let command = parser.command().await?.unwrap();
380            if command.is_some() {
381                reject_redundant_token(&mut parser).await?;
382                parser.ensure_no_unread_here_doc()?;
383            }
384            Ok(command)
385        })
386        .shift()
387    }
388}
389
390/// Converts a string to a pipeline.
391impl FromStr for Pipeline {
392    /// Optional error value
393    ///
394    /// The error is `None` if the first token does not start a pipeline.
395    /// A proper error is returned in `Some(_)` in case of a syntax error.
396    type Err = Option<Error>;
397
398    fn from_str(s: &str) -> Result<Pipeline, Option<Error>> {
399        let mut lexer = Lexer::with_code(s);
400        let mut parser = Parser::new(&mut lexer);
401        unwrap_ready(async {
402            let pipeline = parser.pipeline().await?.unwrap();
403            if pipeline.is_some() {
404                reject_redundant_token(&mut parser).await?;
405                parser.ensure_no_unread_here_doc()?;
406            }
407            Ok(pipeline)
408        })
409        .shift()
410    }
411}
412
413impl FromStr for AndOr {
414    type Err = ParseOperatorError;
415    fn from_str(s: &str) -> Result<AndOr, ParseOperatorError> {
416        Operator::from_str(s)?
417            .try_into()
418            .map_err(|_| ParseOperatorError)
419    }
420}
421
422/// Converts a string to an and-or list.
423impl FromStr for AndOrList {
424    /// Optional error value
425    ///
426    /// The error is `None` if the first token does not start an and-or list.
427    /// A proper error is returned in `Some(_)` in case of a syntax error.
428    type Err = Option<Error>;
429
430    fn from_str(s: &str) -> Result<AndOrList, Option<Error>> {
431        let mut lexer = Lexer::with_code(s);
432        let mut parser = Parser::new(&mut lexer);
433        unwrap_ready(async {
434            let list = parser.and_or_list().await?.unwrap();
435            if list.is_some() {
436                reject_redundant_token(&mut parser).await?;
437                parser.ensure_no_unread_here_doc()?;
438            }
439            Ok(list)
440        })
441        .shift()
442    }
443}
444
445/// Converts a string to a list.
446impl FromStr for List {
447    type Err = Error;
448    fn from_str(s: &str) -> Result<List, Error> {
449        let mut lexer = Lexer::with_code(s);
450        let mut parser = Parser::new(&mut lexer);
451        let list = unwrap_ready(parser.maybe_compound_list())?;
452        parser.ensure_no_unread_here_doc()?;
453        Ok(list)
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460    use assert_matches::assert_matches;
461    use futures_executor::block_on;
462
463    // Most of the tests below are surrounded with `block_on(async {...})` in
464    // order to make sure `str::parse` can be called in an executor context.
465
466    #[test]
467    fn param_from_str() {
468        block_on(async {
469            let parse: BracedParam = "${foo}".parse().unwrap();
470            assert_eq!(parse.to_string(), "${foo}");
471
472            let error = "".parse::<BracedParam>().unwrap_err();
473            assert!(error.is_none(), "{error:?}");
474
475            let error = "foo".parse::<BracedParam>().unwrap_err();
476            assert!(error.is_none(), "{error:?}");
477        })
478    }
479
480    #[test]
481    fn text_unit_from_str() {
482        block_on(async {
483            let parse: TextUnit = "a".parse().unwrap();
484            assert_eq!(parse.to_string(), "a");
485
486            let error = "".parse::<TextUnit>().unwrap_err();
487            assert!(error.is_none(), "{error:?}");
488        })
489    }
490
491    #[test]
492    fn text_from_str() {
493        block_on(async {
494            let parse: Text = r"a\b$(c)".parse().unwrap();
495            assert_eq!(parse.0.len(), 3);
496            assert_eq!(parse.0[0], Literal('a'));
497            assert_eq!(parse.0[1], Backslashed('b'));
498            assert_matches!(&parse.0[2], CommandSubst { content, .. } => {
499                assert_eq!(&**content, "c");
500            });
501        })
502    }
503
504    #[test]
505    fn escape_unit_from_str() {
506        block_on(async {
507            let parse: EscapeUnit = r"\n".parse().unwrap();
508            assert_eq!(parse, EscapeUnit::Newline);
509        })
510    }
511
512    #[test]
513    fn escaped_string_from_str() {
514        block_on(async {
515            let parse: EscapedString = r"a\nb".parse().unwrap();
516            use EscapeUnit::*;
517            assert_eq!(parse.0, [Literal('a'), Newline, Literal('b')]);
518        })
519    }
520
521    #[test]
522    fn word_unit_from_str() {
523        block_on(async {
524            let parse: WordUnit = "a".parse().unwrap();
525            assert_eq!(parse.to_string(), "a");
526
527            let error = "".parse::<WordUnit>().unwrap_err();
528            assert!(error.is_none(), "{error:?}");
529        })
530    }
531
532    #[test]
533    fn word_from_str() {
534        block_on(async {
535            let parse: Word = "a".parse().unwrap();
536            assert_eq!(parse.to_string(), "a");
537        })
538    }
539
540    #[test]
541    fn value_from_str() {
542        block_on(async {
543            let parse: Value = "v".parse().unwrap();
544            assert_eq!(parse.to_string(), "v");
545
546            let parse: Value = "(1 2 3)".parse().unwrap();
547            assert_eq!(parse.to_string(), "(1 2 3)");
548        })
549    }
550
551    #[test]
552    fn assign_from_str() {
553        block_on(async {
554            let parse: Assign = "a=b".parse().unwrap();
555            assert_eq!(parse.to_string(), "a=b");
556
557            let parse: Assign = "x=(1 2 3)".parse().unwrap();
558            assert_eq!(parse.to_string(), "x=(1 2 3)");
559        })
560    }
561
562    #[test]
563    fn assign_from_str_empty() {
564        block_on(async {
565            let e = "".parse::<Assign>().unwrap_err();
566            assert!(e.is_none(), "{e:?}");
567        })
568    }
569
570    #[test]
571    fn assign_from_str_redundant_word() {
572        block_on(async {
573            let e = "a=b c".parse::<Assign>().unwrap_err().unwrap();
574            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
575            assert_eq!(*e.location.code.value.borrow(), "a=b c");
576            assert_eq!(e.location.range, 4..5);
577        })
578    }
579
580    #[test]
581    fn assign_from_str_redundant_redir() {
582        block_on(async {
583            let e = "a=b <c".parse::<Assign>().unwrap_err().unwrap();
584            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
585            assert_eq!(*e.location.code.value.borrow(), "a=b <c");
586            assert_eq!(e.location.range, 5..6);
587        })
588    }
589
590    #[test]
591    fn assign_from_str_redundant_assign() {
592        block_on(async {
593            let e = "a=b c=".parse::<Assign>().unwrap_err().unwrap();
594            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
595            assert_eq!(*e.location.code.value.borrow(), "a=b c=");
596            assert_eq!(e.location.range, 4..6);
597        })
598    }
599
600    #[test]
601    fn operator_from_str() {
602        block_on(async {
603            let parse: Operator = "<<".parse().unwrap();
604            assert_eq!(parse, Operator::LessLess);
605        })
606    }
607
608    #[test]
609    fn redir_op_from_str() {
610        block_on(async {
611            let parse: RedirOp = ">|".parse().unwrap();
612            assert_eq!(parse, RedirOp::FileClobber);
613        })
614    }
615
616    #[test]
617    fn redir_from_str() {
618        block_on(async {
619            let parse: Redir = "2> /dev/null".parse().unwrap();
620            assert_eq!(parse.fd, Some(Fd(2)));
621            assert_matches!(parse.body, RedirBody::Normal { operator, operand } => {
622                assert_eq!(operator, RedirOp::FileOut);
623                assert_eq!(operand.to_string(), "/dev/null");
624            });
625        })
626    }
627
628    #[test]
629    fn redir_from_str_redundant_token() {
630        block_on(async {
631            let e = "2> /dev/null x".parse::<Redir>().unwrap_err().unwrap();
632            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
633            assert_eq!(*e.location.code.value.borrow(), "2> /dev/null x");
634            assert_eq!(e.location.range, 13..14);
635        })
636    }
637
638    #[test]
639    fn redir_from_str_unfillable_here_doc_content() {
640        block_on(async {
641            let result: Result<Redir, _> = "<<FOO".parse();
642            let e = result.unwrap_err().unwrap();
643            assert_eq!(
644                e.cause,
645                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
646            );
647        })
648    }
649
650    #[test]
651    fn simple_command_from_str() {
652        block_on(async {
653            let parse: SimpleCommand = " a=b</dev/null foo ".parse().unwrap();
654            assert_eq!(parse.to_string(), "a=b foo </dev/null");
655        })
656    }
657
658    #[test]
659    fn simple_command_from_str_empty() {
660        block_on(async {
661            let e = "".parse::<SimpleCommand>().unwrap_err();
662            assert_eq!(e, None);
663
664            let e = "if".parse::<SimpleCommand>().unwrap_err();
665            assert_eq!(e, None);
666        })
667    }
668
669    #[test]
670    fn simple_command_from_str_unfillable_here_doc_content() {
671        block_on(async {
672            let result: Result<SimpleCommand, _> = "<<FOO".parse();
673            let e = result.unwrap_err().unwrap();
674            assert_eq!(
675                e.cause,
676                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
677            );
678        })
679    }
680
681    #[test]
682    fn simple_command_from_str_redundant_token() {
683        block_on(async {
684            let e = "x\n".parse::<SimpleCommand>().unwrap_err().unwrap();
685            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
686            assert_eq!(*e.location.code.value.borrow(), "x\n");
687            assert_eq!(e.location.range, 1..2);
688        })
689    }
690
691    #[test]
692    fn case_item_from_str() {
693        block_on(async {
694            let parse: CaseItem = " foo) ".parse().unwrap();
695            assert_eq!(parse.to_string(), "(foo) ;;");
696        })
697    }
698
699    #[test]
700    fn case_item_from_str_with_double_semicolon() {
701        block_on(async {
702            let parse: CaseItem = " foo) ;; ".parse().unwrap();
703            assert_eq!(parse.to_string(), "(foo) ;;");
704
705            let parse: CaseItem = " foo) echo;; ".parse().unwrap();
706            assert_eq!(parse.to_string(), "(foo) echo;;");
707        })
708    }
709
710    #[test]
711    fn case_item_from_str_empty() {
712        block_on(async {
713            let e = "esac".parse::<CaseItem>().unwrap_err();
714            assert_eq!(e, None);
715        })
716    }
717
718    #[test]
719    fn case_item_from_str_unfillable_here_doc_content() {
720        block_on(async {
721            let result: Result<CaseItem, _> = "(foo) <<FOO".parse();
722            let e = result.unwrap_err().unwrap();
723            assert_eq!(
724                e.cause,
725                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
726            );
727        })
728    }
729
730    #[test]
731    fn case_item_from_str_redundant_token() {
732        block_on(async {
733            let e = "foo)fi".parse::<CaseItem>().unwrap_err().unwrap();
734            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
735            assert_eq!(*e.location.code.value.borrow(), "foo)fi");
736            assert_eq!(e.location.range, 4..6);
737        })
738    }
739
740    #[test]
741    fn compound_command_from_str() {
742        block_on(async {
743            let parse: CompoundCommand = " { :; } ".parse().unwrap();
744            assert_eq!(parse.to_string(), "{ :; }");
745        })
746    }
747
748    #[test]
749    fn compound_command_from_str_redundant_token() {
750        block_on(async {
751            let e = " { :; } x".parse::<CompoundCommand>().unwrap_err().unwrap();
752            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
753            assert_eq!(*e.location.code.value.borrow(), " { :; } x");
754            assert_eq!(e.location.range, 8..9);
755        })
756    }
757
758    #[test]
759    fn compound_command_from_str_unfillable_here_doc_content() {
760        block_on(async {
761            let result: Result<CompoundCommand, _> = "{ <<FOO; }".parse();
762            let e = result.unwrap_err().unwrap();
763            assert_eq!(
764                e.cause,
765                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
766            );
767        })
768    }
769
770    #[test]
771    fn full_compound_command_from_str() {
772        block_on(async {
773            let parse: FullCompoundCommand = " { :; } <&- ".parse().unwrap();
774            assert_eq!(parse.to_string(), "{ :; } <&-");
775        })
776    }
777
778    #[test]
779    fn full_compound_command_from_str_redundant_token() {
780        block_on(async {
781            let e = " { :; } <&- ;"
782                .parse::<FullCompoundCommand>()
783                .unwrap_err()
784                .unwrap();
785            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
786            assert_eq!(*e.location.code.value.borrow(), " { :; } <&- ;");
787            assert_eq!(e.location.range, 12..13);
788        })
789    }
790
791    #[test]
792    fn full_compound_command_from_str_unfillable_here_doc_content() {
793        block_on(async {
794            let result: Result<FullCompoundCommand, _> = "{ :; } <<FOO".parse();
795            let e = result.unwrap_err().unwrap();
796            assert_eq!(
797                e.cause,
798                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
799            );
800        })
801    }
802
803    #[test]
804    fn command_from_str() {
805        block_on(async {
806            let parse: Command = "f(){ :; }>&2".parse().unwrap();
807            assert_eq!(parse.to_string(), "f() { :; } >&2");
808        })
809    }
810
811    #[test]
812    fn command_from_str_redundant_token() {
813        block_on(async {
814            let e = "f(){ :; }>&2 ;".parse::<Command>().unwrap_err().unwrap();
815            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
816            assert_eq!(*e.location.code.value.borrow(), "f(){ :; }>&2 ;");
817            assert_eq!(e.location.range, 13..14);
818        })
819    }
820
821    #[test]
822    fn command_from_str_unfillable_here_doc_content() {
823        block_on(async {
824            let result: Result<Command, _> = "<<FOO".parse();
825            let e = result.unwrap_err().unwrap();
826            assert_eq!(
827                e.cause,
828                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
829            );
830        })
831    }
832
833    #[test]
834    fn pipeline_from_str() {
835        block_on(async {
836            let parse: Pipeline = " ! a|b|c".parse().unwrap();
837            assert_eq!(parse.to_string(), "! a | b | c");
838        })
839    }
840
841    #[test]
842    fn pipeline_from_str_redundant_token() {
843        block_on(async {
844            let e = "a|b|c ;".parse::<Pipeline>().unwrap_err().unwrap();
845            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
846            assert_eq!(*e.location.code.value.borrow(), "a|b|c ;");
847            assert_eq!(e.location.range, 6..7);
848        })
849    }
850
851    #[test]
852    fn pipeline_from_str_unfillable_here_doc_content() {
853        block_on(async {
854            let result: Result<Pipeline, _> = "<<FOO".parse();
855            let e = result.unwrap_err().unwrap();
856            assert_eq!(
857                e.cause,
858                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
859            );
860        })
861    }
862
863    #[test]
864    fn and_or_from_str() {
865        assert_eq!(AndOr::from_str("&&"), Ok(AndOr::AndThen));
866        assert_eq!(AndOr::from_str("||"), Ok(AndOr::OrElse));
867    }
868
869    #[test]
870    fn and_or_list_from_str() {
871        block_on(async {
872            let parse: AndOrList = " a|b&&! c||d|e ".parse().unwrap();
873            assert_eq!(parse.to_string(), "a | b && ! c || d | e");
874        })
875    }
876
877    #[test]
878    fn and_or_list_from_str_redundant_token() {
879        block_on(async {
880            let e = "a||b;".parse::<AndOrList>().unwrap_err().unwrap();
881            assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::RedundantToken));
882            assert_eq!(*e.location.code.value.borrow(), "a||b;");
883            assert_eq!(e.location.range, 4..5);
884        })
885    }
886
887    #[test]
888    fn and_or_list_from_str_unfillable_here_doc_content() {
889        block_on(async {
890            let result: Result<AndOrList, _> = "<<FOO".parse();
891            let e = result.unwrap_err().unwrap();
892            assert_eq!(
893                e.cause,
894                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
895            );
896        })
897    }
898
899    #[test]
900    fn list_from_str() {
901        block_on(async {
902            let parse: List = " a;b&&c&d ".parse().unwrap();
903            assert_eq!(parse.to_string(), "a; b && c& d");
904        })
905    }
906
907    #[test]
908    fn list_from_str_unfillable_here_doc_content() {
909        block_on(async {
910            let result: Result<List, _> = "<<FOO".parse();
911            let e = result.unwrap_err();
912            assert_eq!(
913                e.cause,
914                ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
915            );
916        })
917    }
918}