yash_syntax/syntax/
impl_display.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
17use super::*;
18use itertools::Itertools as _;
19use std::fmt;
20use std::fmt::Write as _;
21
22impl fmt::Display for SpecialParam {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        self.as_char().fmt(f)
25    }
26}
27
28impl fmt::Display for Param {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        self.id.fmt(f)
31    }
32}
33
34impl fmt::Display for SwitchAction {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        use SwitchAction::*;
37        let c = match self {
38            Alter => '+',
39            Default => '-',
40            Assign => '=',
41            Error => '?',
42        };
43        f.write_char(c)
44    }
45}
46
47impl fmt::Display for SwitchCondition {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        use SwitchCondition::*;
50        match self {
51            Unset => Ok(()),
52            UnsetOrEmpty => f.write_char(':'),
53        }
54    }
55}
56
57impl fmt::Display for Switch {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{}{}{}", self.condition, self.action, self.word)
60    }
61}
62
63impl fmt::Display for TrimSide {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        use TrimSide::*;
66        let c = match self {
67            Prefix => '#',
68            Suffix => '%',
69        };
70        f.write_char(c)
71    }
72}
73
74impl fmt::Display for Trim {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        self.side.fmt(f)?;
77        match self.length {
78            TrimLength::Shortest => (),
79            TrimLength::Longest => self.side.fmt(f)?,
80        }
81        self.pattern.fmt(f)
82    }
83}
84
85impl fmt::Display for BracedParam {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        use Modifier::*;
88        match self.modifier {
89            None => write!(f, "${{{}}}", self.param),
90            Length => write!(f, "${{#{}}}", self.param),
91            Switch(ref switch) => write!(f, "${{{}{}}}", self.param, switch),
92            Trim(ref trim) => write!(f, "${{{}{}}}", self.param, trim),
93        }
94    }
95}
96
97impl fmt::Display for BackquoteUnit {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            BackquoteUnit::Literal(c) => write!(f, "{c}"),
101            BackquoteUnit::Backslashed(c) => write!(f, "\\{c}"),
102        }
103    }
104}
105
106impl fmt::Display for TextUnit {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Literal(c) => write!(f, "{c}"),
110            Backslashed(c) => write!(f, "\\{c}"),
111            RawParam { param, .. } => write!(f, "${param}"),
112            BracedParam(param) => param.fmt(f),
113            CommandSubst { content, .. } => write!(f, "$({content})"),
114            Backquote { content, .. } => {
115                f.write_char('`')?;
116                content.iter().try_for_each(|unit| unit.fmt(f))?;
117                f.write_char('`')
118            }
119            Arith { content, .. } => write!(f, "$(({content}))"),
120        }
121    }
122}
123
124impl fmt::Display for Text {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        self.0.iter().try_for_each(|unit| unit.fmt(f))
127    }
128}
129
130impl fmt::Display for EscapeUnit {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        match self {
133            Self::Literal(c) => c.fmt(f),
134            Self::DoubleQuote => f.write_str("\\\""),
135            Self::SingleQuote => f.write_str("\\'"),
136            Self::Backslash => f.write_str("\\\\"),
137            Self::Question => f.write_str("\\?"),
138            Self::Alert => f.write_str("\\a"),
139            Self::Backspace => f.write_str("\\b"),
140            Self::Escape => f.write_str("\\e"),
141            Self::FormFeed => f.write_str("\\f"),
142            Self::Newline => f.write_str("\\n"),
143            Self::CarriageReturn => f.write_str("\\r"),
144            Self::Tab => f.write_str("\\t"),
145            Self::VerticalTab => f.write_str("\\v"),
146            Self::Control(b) => write!(f, "\\c{}", (*b ^ 0x40) as char),
147            Self::Octal(b) => write!(f, "\\{b:03o}"),
148            Self::Hex(b) => write!(f, "\\x{b:02X}"),
149            Self::Unicode(c) if *c <= '\u{FFFF}' => write!(f, "\\u{:04x}", *c as u32),
150            Self::Unicode(c) => write!(f, "\\U{:08X}", *c as u32),
151        }
152    }
153}
154
155impl fmt::Display for EscapedString {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        self.0.iter().try_for_each(|unit| unit.fmt(f))
158    }
159}
160
161impl fmt::Display for WordUnit {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            Unquoted(dq) => dq.fmt(f),
165            SingleQuote(s) => write!(f, "'{s}'"),
166            DoubleQuote(content) => write!(f, "\"{content}\""),
167            DollarSingleQuote(content) => write!(f, "$'{content}'"),
168            Tilde { name, .. } => write!(f, "~{name}"),
169        }
170    }
171}
172
173impl fmt::Display for Word {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        self.units.iter().try_for_each(|unit| write!(f, "{unit}"))
176    }
177}
178
179impl fmt::Display for Value {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        match self {
182            Scalar(word) => word.fmt(f),
183            Array(words) => write!(f, "({})", words.iter().format(" ")),
184        }
185    }
186}
187
188impl fmt::Display for Assign {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        write!(f, "{}={}", &self.name, &self.value)
191    }
192}
193
194impl fmt::Display for RedirOp {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        Operator::from(*self).fmt(f)
197    }
198}
199
200impl fmt::Display for HereDoc {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        f.write_str(if self.remove_tabs { "<<-" } else { "<<" })?;
203
204        // This space is to disambiguate `<< --` and `<<- -`
205        if let Some(Unquoted(Literal('-'))) = self.delimiter.units.first() {
206            f.write_char(' ')?;
207        }
208
209        write!(f, "{}", self.delimiter)
210    }
211}
212
213impl fmt::Display for RedirBody {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        match self {
216            RedirBody::Normal { operator, operand } => write!(f, "{operator}{operand}"),
217            RedirBody::HereDoc(h) => write!(f, "{h}"),
218        }
219    }
220}
221
222impl fmt::Display for Redir {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        if let Some(fd) = self.fd {
225            write!(f, "{fd}")?;
226        }
227        write!(f, "{}", self.body)
228    }
229}
230
231impl fmt::Display for SimpleCommand {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        let i1 = self.assigns.iter().map(|x| x as &dyn fmt::Display);
234        let i2 = self.words.iter().map(|x| &x.0 as &dyn fmt::Display);
235        let i3 = self.redirs.iter().map(|x| x as &dyn fmt::Display);
236
237        if !self.assigns.is_empty() || !self.first_word_is_keyword() {
238            write!(f, "{}", i1.chain(i2).chain(i3).format(" "))
239        } else {
240            // We usually display the words before the redirections, but when
241            // the first word is a keyword and there are no assignments, we
242            // display the redirections first to make sure the simple command is
243            // not mistaken for a compound command.
244            write!(f, "{}", i3.chain(i2).format(" "))
245        }
246    }
247}
248
249impl fmt::Display for ElifThen {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        write!(f, "elif {:#} then ", self.condition)?;
252        if f.alternate() {
253            write!(f, "{:#}", self.body)
254        } else {
255            write!(f, "{}", self.body)
256        }
257    }
258}
259
260impl fmt::Display for CaseContinuation {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        Operator::from(*self).fmt(f)
263    }
264}
265
266impl fmt::Display for CaseItem {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        write!(
269            f,
270            "({}) {}{}",
271            self.patterns.iter().format(" | "),
272            self.body,
273            self.continuation,
274        )
275    }
276}
277
278impl fmt::Display for CompoundCommand {
279    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280        use CompoundCommand::*;
281        match self {
282            Grouping(list) => write!(f, "{{ {list:#} }}"),
283            Subshell { body, .. } => write!(f, "({body})"),
284            For { name, values, body } => {
285                write!(f, "for {name}")?;
286                if let Some(values) = values {
287                    f.write_str(" in")?;
288                    for value in values {
289                        write!(f, " {value}")?;
290                    }
291                    f.write_char(';')?;
292                }
293                write!(f, " do {body:#} done")
294            }
295            While { condition, body } => write!(f, "while {condition:#} do {body:#} done"),
296            Until { condition, body } => write!(f, "until {condition:#} do {body:#} done"),
297            If {
298                condition,
299                body,
300                elifs,
301                r#else,
302            } => {
303                write!(f, "if {condition:#} then {body:#} ")?;
304                for elif in elifs {
305                    write!(f, "{elif:#} ")?;
306                }
307                if let Some(r#else) = r#else {
308                    write!(f, "else {else:#} ")?;
309                }
310                f.write_str("fi")
311            }
312            Case { subject, items } => {
313                write!(f, "case {subject} in ")?;
314                for item in items {
315                    write!(f, "{item} ")?;
316                }
317                f.write_str("esac")
318            }
319        }
320    }
321}
322
323impl fmt::Display for FullCompoundCommand {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        let FullCompoundCommand { command, redirs } = self;
326        write!(f, "{command}")?;
327        redirs.iter().try_for_each(|redir| write!(f, " {redir}"))
328    }
329}
330
331impl fmt::Display for FunctionDefinition {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        if self.has_keyword {
334            f.write_str("function ")?;
335        }
336        write!(f, "{}() {}", self.name, self.body)
337    }
338}
339
340impl fmt::Display for Command {
341    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
342        match self {
343            Command::Simple(c) => c.fmt(f),
344            Command::Compound(c) => c.fmt(f),
345            Command::Function(c) => c.fmt(f),
346        }
347    }
348}
349
350impl fmt::Display for Pipeline {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
352        if self.negation {
353            write!(f, "! ")?;
354        }
355        write!(f, "{}", self.commands.iter().format(" | "))
356    }
357}
358
359impl fmt::Display for AndOr {
360    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361        match self {
362            AndOr::AndThen => write!(f, "&&"),
363            AndOr::OrElse => write!(f, "||"),
364        }
365    }
366}
367
368impl fmt::Display for AndOrList {
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        write!(f, "{}", self.first)?;
371        self.rest
372            .iter()
373            .try_for_each(|(c, p)| write!(f, " {c} {p}"))
374    }
375}
376
377/// Allows conversion from Item to String.
378///
379/// By default, the `;` terminator is omitted from the formatted string.
380/// When the alternate flag is specified as in `{:#}`, the result is always
381/// terminated by either `;` or `&`.
382impl fmt::Display for Item {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        write!(f, "{}", self.and_or)?;
385        if self.async_flag.is_some() {
386            write!(f, "&")
387        } else if f.alternate() {
388            write!(f, ";")
389        } else {
390            Ok(())
391        }
392    }
393}
394
395/// Allows conversion from List to String.
396///
397/// By default, the last `;` terminator is omitted from the formatted string.
398/// When the alternate flag is specified as in `{:#}`, the result is always
399/// terminated by either `;` or `&`.
400impl fmt::Display for List {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        if let Some((last, others)) = self.0.split_last() {
403            for item in others {
404                write!(f, "{item:#} ")?;
405            }
406            if f.alternate() {
407                write!(f, "{last:#}")
408            } else {
409                write!(f, "{last}")
410            }
411        } else {
412            Ok(())
413        }
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    #[test]
422    fn switch_display() {
423        let switch = Switch {
424            action: SwitchAction::Alter,
425            condition: SwitchCondition::Unset,
426            word: "".parse().unwrap(),
427        };
428        assert_eq!(switch.to_string(), "+");
429
430        let switch = Switch {
431            action: SwitchAction::Default,
432            condition: SwitchCondition::UnsetOrEmpty,
433            word: "foo".parse().unwrap(),
434        };
435        assert_eq!(switch.to_string(), ":-foo");
436
437        let switch = Switch {
438            action: SwitchAction::Assign,
439            condition: SwitchCondition::UnsetOrEmpty,
440            word: "bar baz".parse().unwrap(),
441        };
442        assert_eq!(switch.to_string(), ":=bar baz");
443
444        let switch = Switch {
445            action: SwitchAction::Error,
446            condition: SwitchCondition::Unset,
447            word: "?error".parse().unwrap(),
448        };
449        assert_eq!(switch.to_string(), "??error");
450    }
451
452    #[test]
453    fn trim_display() {
454        let trim = Trim {
455            side: TrimSide::Prefix,
456            length: TrimLength::Shortest,
457            pattern: "foo".parse().unwrap(),
458        };
459        assert_eq!(trim.to_string(), "#foo");
460
461        let trim = Trim {
462            side: TrimSide::Prefix,
463            length: TrimLength::Longest,
464            pattern: "".parse().unwrap(),
465        };
466        assert_eq!(trim.to_string(), "##");
467
468        let trim = Trim {
469            side: TrimSide::Suffix,
470            length: TrimLength::Shortest,
471            pattern: "bar".parse().unwrap(),
472        };
473        assert_eq!(trim.to_string(), "%bar");
474
475        let trim = Trim {
476            side: TrimSide::Suffix,
477            length: TrimLength::Longest,
478            pattern: "*".parse().unwrap(),
479        };
480        assert_eq!(trim.to_string(), "%%*");
481    }
482
483    #[test]
484    fn braced_param_display() {
485        let param = BracedParam {
486            param: Param::variable("foo"),
487            modifier: Modifier::None,
488            location: Location::dummy(""),
489        };
490        assert_eq!(param.to_string(), "${foo}");
491
492        let param = BracedParam {
493            modifier: Modifier::Length,
494            ..param
495        };
496        assert_eq!(param.to_string(), "${#foo}");
497
498        let switch = Switch {
499            action: SwitchAction::Assign,
500            condition: SwitchCondition::UnsetOrEmpty,
501            word: "bar baz".parse().unwrap(),
502        };
503        let param = BracedParam {
504            modifier: Modifier::Switch(switch),
505            ..param
506        };
507        assert_eq!(param.to_string(), "${foo:=bar baz}");
508
509        let trim = Trim {
510            side: TrimSide::Suffix,
511            length: TrimLength::Shortest,
512            pattern: "baz' 'bar".parse().unwrap(),
513        };
514        let param = BracedParam {
515            modifier: Modifier::Trim(trim),
516            ..param
517        };
518        assert_eq!(param.to_string(), "${foo%baz' 'bar}");
519    }
520
521    #[test]
522    fn backquote_unit_display() {
523        let literal = BackquoteUnit::Literal('A');
524        assert_eq!(literal.to_string(), "A");
525        let backslashed = BackquoteUnit::Backslashed('X');
526        assert_eq!(backslashed.to_string(), r"\X");
527    }
528
529    #[test]
530    fn text_unit_display() {
531        let literal = Literal('A');
532        assert_eq!(literal.to_string(), "A");
533        let backslashed = Backslashed('X');
534        assert_eq!(backslashed.to_string(), r"\X");
535
536        let raw_param = RawParam {
537            param: Param::variable("PARAM"),
538            location: Location::dummy(""),
539        };
540        assert_eq!(raw_param.to_string(), "$PARAM");
541
542        let command_subst = CommandSubst {
543            content: r"foo\bar".into(),
544            location: Location::dummy(""),
545        };
546        assert_eq!(command_subst.to_string(), r"$(foo\bar)");
547
548        let backquote = Backquote {
549            content: vec![
550                BackquoteUnit::Literal('a'),
551                BackquoteUnit::Backslashed('b'),
552                BackquoteUnit::Backslashed('c'),
553                BackquoteUnit::Literal('d'),
554            ],
555            location: Location::dummy(""),
556        };
557        assert_eq!(backquote.to_string(), r"`a\b\cd`");
558
559        let arith = Arith {
560            content: Text(vec![literal, backslashed, command_subst, backquote]),
561            location: Location::dummy(""),
562        };
563        assert_eq!(arith.to_string(), r"$((A\X$(foo\bar)`a\b\cd`))");
564    }
565
566    #[test]
567    fn escape_unit_display() {
568        use EscapeUnit::*;
569
570        assert_eq!(Literal('A').to_string(), "A");
571        assert_eq!(DoubleQuote.to_string(), r#"\""#);
572        assert_eq!(SingleQuote.to_string(), r"\'");
573        assert_eq!(Backslash.to_string(), r"\\");
574        assert_eq!(Question.to_string(), r"\?");
575        assert_eq!(Alert.to_string(), r"\a");
576        assert_eq!(Backspace.to_string(), r"\b");
577        assert_eq!(Escape.to_string(), r"\e");
578        assert_eq!(FormFeed.to_string(), r"\f");
579        assert_eq!(Newline.to_string(), r"\n");
580        assert_eq!(CarriageReturn.to_string(), r"\r");
581        assert_eq!(Tab.to_string(), r"\t");
582        assert_eq!(VerticalTab.to_string(), r"\v");
583        assert_eq!(Control(b'\x01').to_string(), r"\cA");
584        assert_eq!(Control(b'\x7F').to_string(), r"\c?");
585        assert_eq!(Octal(0o003).to_string(), r"\003");
586        assert_eq!(Octal(0o123).to_string(), r"\123");
587        assert_eq!(Hex(0x05).to_string(), r"\x05");
588        assert_eq!(Hex(0xAB).to_string(), r"\xAB");
589        assert_eq!(Unicode('A').to_string(), r"\u0041");
590        assert_eq!(Unicode('😊').to_string(), r"\U0001F60A");
591    }
592
593    #[test]
594    fn word_unit_display() {
595        let unquoted = Unquoted(Literal('A'));
596        assert_eq!(unquoted.to_string(), "A");
597        let unquoted = Unquoted(Backslashed('B'));
598        assert_eq!(unquoted.to_string(), "\\B");
599
600        let single_quote = SingleQuote("".to_string());
601        assert_eq!(single_quote.to_string(), "''");
602        let single_quote = SingleQuote(r#"a"b"c\"#.to_string());
603        assert_eq!(single_quote.to_string(), r#"'a"b"c\'"#);
604
605        let double_quote = DoubleQuote(Text(vec![]));
606        assert_eq!(double_quote.to_string(), "\"\"");
607        let double_quote = DoubleQuote(Text(vec![Literal('A'), Backslashed('B')]));
608        assert_eq!(double_quote.to_string(), "\"A\\B\"");
609
610        let dollar_single_quote = DollarSingleQuote(EscapedString(vec![]));
611        assert_eq!(dollar_single_quote.to_string(), "$''");
612        let dollar_single_quote = DollarSingleQuote(EscapedString(vec![
613            EscapeUnit::Literal('A'),
614            EscapeUnit::Backslash,
615        ]));
616        assert_eq!(dollar_single_quote.to_string(), r"$'A\\'");
617
618        let tilde = Tilde {
619            name: "".to_string(),
620            followed_by_slash: false,
621        };
622        assert_eq!(tilde.to_string(), "~");
623        let tilde = Tilde {
624            name: "foo".to_string(),
625            followed_by_slash: true,
626        };
627        assert_eq!(tilde.to_string(), "~foo");
628    }
629
630    #[test]
631    fn scalar_display() {
632        let s = Scalar(Word::from_str("my scalar value").unwrap());
633        assert_eq!(s.to_string(), "my scalar value");
634    }
635
636    #[test]
637    fn array_display_empty() {
638        let a = Array(vec![]);
639        assert_eq!(a.to_string(), "()");
640    }
641
642    #[test]
643    fn array_display_one() {
644        let a = Array(vec![Word::from_str("one").unwrap()]);
645        assert_eq!(a.to_string(), "(one)");
646    }
647
648    #[test]
649    fn array_display_many() {
650        let a = Array(vec![
651            Word::from_str("let").unwrap(),
652            Word::from_str("me").unwrap(),
653            Word::from_str("see").unwrap(),
654        ]);
655        assert_eq!(a.to_string(), "(let me see)");
656    }
657
658    #[test]
659    fn assign_display() {
660        let mut a = Assign::from_str("foo=bar").unwrap();
661        assert_eq!(a.to_string(), "foo=bar");
662
663        a.value = Array(vec![]);
664        assert_eq!(a.to_string(), "foo=()");
665    }
666
667    #[test]
668    fn here_doc_display() {
669        let heredoc = HereDoc {
670            delimiter: Word::from_str("END").unwrap(),
671            remove_tabs: true,
672            content: Text::from_str("here").unwrap().into(),
673        };
674        assert_eq!(heredoc.to_string(), "<<-END");
675
676        let heredoc = HereDoc {
677            delimiter: Word::from_str("XXX").unwrap(),
678            remove_tabs: false,
679            content: Text::from_str("there").unwrap().into(),
680        };
681        assert_eq!(heredoc.to_string(), "<<XXX");
682    }
683
684    #[test]
685    fn here_doc_display_disambiguation() {
686        let heredoc = HereDoc {
687            delimiter: Word::from_str("--").unwrap(),
688            remove_tabs: false,
689            content: Text::from_str("here").unwrap().into(),
690        };
691        assert_eq!(heredoc.to_string(), "<< --");
692
693        let heredoc = HereDoc {
694            delimiter: Word::from_str("-").unwrap(),
695            remove_tabs: true,
696            content: Text::from_str("here").unwrap().into(),
697        };
698        assert_eq!(heredoc.to_string(), "<<- -");
699    }
700
701    #[test]
702    fn redir_display() {
703        let heredoc = HereDoc {
704            delimiter: Word::from_str("END").unwrap(),
705            remove_tabs: false,
706            content: Text::from_str("here").unwrap().into(),
707        };
708
709        let redir = Redir {
710            fd: None,
711            body: heredoc.into(),
712        };
713        assert_eq!(redir.to_string(), "<<END");
714        let redir = Redir {
715            fd: Some(Fd(0)),
716            ..redir
717        };
718        assert_eq!(redir.to_string(), "0<<END");
719        let redir = Redir {
720            fd: Some(Fd(9)),
721            ..redir
722        };
723        assert_eq!(redir.to_string(), "9<<END");
724    }
725
726    #[test]
727    fn simple_command_display() {
728        let mut command = SimpleCommand {
729            assigns: vec![],
730            words: vec![],
731            redirs: vec![].into(),
732        };
733        assert_eq!(command.to_string(), "");
734
735        command
736            .assigns
737            .push(Assign::from_str("name=value").unwrap());
738        assert_eq!(command.to_string(), "name=value");
739
740        command
741            .assigns
742            .push(Assign::from_str("hello=world").unwrap());
743        assert_eq!(command.to_string(), "name=value hello=world");
744
745        command
746            .words
747            .push((Word::from_str("echo").unwrap(), ExpansionMode::Multiple));
748        assert_eq!(command.to_string(), "name=value hello=world echo");
749
750        command
751            .words
752            .push((Word::from_str("foo").unwrap(), ExpansionMode::Single));
753        assert_eq!(command.to_string(), "name=value hello=world echo foo");
754
755        Rc::make_mut(&mut command.redirs).push(Redir {
756            fd: None,
757            body: RedirBody::from(HereDoc {
758                delimiter: Word::from_str("END").unwrap(),
759                remove_tabs: false,
760                content: Text::from_str("").unwrap().into(),
761            }),
762        });
763        assert_eq!(command.to_string(), "name=value hello=world echo foo <<END");
764
765        command.assigns.clear();
766        assert_eq!(command.to_string(), "echo foo <<END");
767
768        command.words.clear();
769        assert_eq!(command.to_string(), "<<END");
770
771        Rc::make_mut(&mut command.redirs).push(Redir {
772            fd: Some(Fd(1)),
773            body: RedirBody::from(HereDoc {
774                delimiter: Word::from_str("here").unwrap(),
775                remove_tabs: true,
776                content: Text::from_str("ignored").unwrap().into(),
777            }),
778        });
779        assert_eq!(command.to_string(), "<<END 1<<-here");
780
781        command.assigns.push(Assign::from_str("foo=bar").unwrap());
782        assert_eq!(command.to_string(), "foo=bar <<END 1<<-here");
783    }
784
785    #[test]
786    fn simple_command_display_with_keyword() {
787        let command = SimpleCommand {
788            assigns: vec![],
789            words: vec![("if".parse().unwrap(), ExpansionMode::Multiple)],
790            redirs: vec!["<foo".parse().unwrap()].into(),
791        };
792        assert_eq!(command.to_string(), "<foo if");
793    }
794
795    #[test]
796    fn elif_then_display() {
797        let condition: List = "c 1& c 2".parse().unwrap();
798        let body = "b 1& b 2".parse().unwrap();
799        let elif = ElifThen { condition, body };
800        assert_eq!(format!("{elif}"), "elif c 1& c 2; then b 1& b 2");
801        assert_eq!(format!("{elif:#}"), "elif c 1& c 2; then b 1& b 2;");
802
803        let condition: List = "c&".parse().unwrap();
804        let body = "b&".parse().unwrap();
805        let elif = ElifThen { condition, body };
806        assert_eq!(format!("{elif}"), "elif c& then b&");
807        assert_eq!(format!("{elif:#}"), "elif c& then b&");
808    }
809
810    #[test]
811    fn case_item_display() {
812        let item = CaseItem {
813            patterns: vec!["foo".parse().unwrap()],
814            body: "".parse::<List>().unwrap(),
815            continuation: CaseContinuation::Break,
816        };
817        assert_eq!(item.to_string(), "(foo) ;;");
818
819        let item = CaseItem {
820            patterns: vec!["bar".parse().unwrap()],
821            body: "echo ok".parse::<List>().unwrap(),
822            continuation: CaseContinuation::Break,
823        };
824        assert_eq!(item.to_string(), "(bar) echo ok;;");
825
826        let item = CaseItem {
827            patterns: ["a", "b", "c"].iter().map(|s| s.parse().unwrap()).collect(),
828            body: "foo; bar&".parse::<List>().unwrap(),
829            continuation: CaseContinuation::Break,
830        };
831        assert_eq!(item.to_string(), "(a | b | c) foo; bar&;;");
832
833        let item = CaseItem {
834            patterns: vec!["foo".parse().unwrap()],
835            body: "bar".parse::<List>().unwrap(),
836            continuation: CaseContinuation::FallThrough,
837        };
838        assert_eq!(item.to_string(), "(foo) bar;&");
839    }
840
841    #[test]
842    fn grouping_display() {
843        let list = "foo".parse::<List>().unwrap();
844        let grouping = CompoundCommand::Grouping(list);
845        assert_eq!(grouping.to_string(), "{ foo; }");
846    }
847
848    #[test]
849    fn for_display_without_values() {
850        let name = Word::from_str("foo").unwrap();
851        let values = None;
852        let body = "echo ok".parse::<List>().unwrap();
853        let r#for = CompoundCommand::For { name, values, body };
854        assert_eq!(r#for.to_string(), "for foo do echo ok; done");
855    }
856
857    #[test]
858    fn for_display_with_empty_values() {
859        let name = Word::from_str("foo").unwrap();
860        let values = Some(vec![]);
861        let body = "echo ok".parse::<List>().unwrap();
862        let r#for = CompoundCommand::For { name, values, body };
863        assert_eq!(r#for.to_string(), "for foo in; do echo ok; done");
864    }
865
866    #[test]
867    fn for_display_with_some_values() {
868        let name = Word::from_str("V").unwrap();
869        let values = Some(vec![
870            Word::from_str("a").unwrap(),
871            Word::from_str("b").unwrap(),
872        ]);
873        let body = "one; two&".parse::<List>().unwrap();
874        let r#for = CompoundCommand::For { name, values, body };
875        assert_eq!(r#for.to_string(), "for V in a b; do one; two& done");
876    }
877
878    #[test]
879    fn while_display() {
880        let condition = "true& false".parse::<List>().unwrap();
881        let body = "echo ok".parse::<List>().unwrap();
882        let r#while = CompoundCommand::While { condition, body };
883        assert_eq!(r#while.to_string(), "while true& false; do echo ok; done");
884    }
885
886    #[test]
887    fn until_display() {
888        let condition = "true& false".parse::<List>().unwrap();
889        let body = "echo ok".parse::<List>().unwrap();
890        let until = CompoundCommand::Until { condition, body };
891        assert_eq!(until.to_string(), "until true& false; do echo ok; done");
892    }
893
894    #[test]
895    fn if_display() {
896        let r#if: CompoundCommand = CompoundCommand::If {
897            condition: "c 1; c 2&".parse().unwrap(),
898            body: "b 1; b 2&".parse().unwrap(),
899            elifs: vec![],
900            r#else: None,
901        };
902        assert_eq!(r#if.to_string(), "if c 1; c 2& then b 1; b 2& fi");
903
904        let r#if: CompoundCommand = CompoundCommand::If {
905            condition: "c 1& c 2;".parse().unwrap(),
906            body: "b 1& b 2;".parse().unwrap(),
907            elifs: vec![ElifThen {
908                condition: "c 3&".parse().unwrap(),
909                body: "b 3&".parse().unwrap(),
910            }],
911            r#else: Some("b 4".parse().unwrap()),
912        };
913        assert_eq!(
914            r#if.to_string(),
915            "if c 1& c 2; then b 1& b 2; elif c 3& then b 3& else b 4; fi"
916        );
917
918        let r#if: CompoundCommand = CompoundCommand::If {
919            condition: "true".parse().unwrap(),
920            body: ":".parse().unwrap(),
921            elifs: vec![
922                ElifThen {
923                    condition: "false".parse().unwrap(),
924                    body: "a".parse().unwrap(),
925                },
926                ElifThen {
927                    condition: "echo&".parse().unwrap(),
928                    body: "b&".parse().unwrap(),
929                },
930            ],
931            r#else: None,
932        };
933        assert_eq!(
934            r#if.to_string(),
935            "if true; then :; elif false; then a; elif echo& then b& fi"
936        );
937    }
938
939    #[test]
940    fn case_display() {
941        let subject = "foo".parse().unwrap();
942        let items = Vec::<CaseItem>::new();
943        let case = CompoundCommand::Case { subject, items };
944        assert_eq!(case.to_string(), "case foo in esac");
945
946        let subject = "bar".parse().unwrap();
947        let items = vec!["foo)".parse::<CaseItem>().unwrap()];
948        let case = CompoundCommand::Case { subject, items };
949        assert_eq!(case.to_string(), "case bar in (foo) ;; esac");
950
951        let subject = "baz".parse().unwrap();
952        let items = vec![
953            "1)".parse::<CaseItem>().unwrap(),
954            "(a|b|c) :&".parse().unwrap(),
955        ];
956        let case = CompoundCommand::Case { subject, items };
957        assert_eq!(case.to_string(), "case baz in (1) ;; (a | b | c) :&;; esac");
958    }
959
960    #[test]
961    fn function_definition_display() {
962        let body = FullCompoundCommand {
963            command: "( bar )".parse::<CompoundCommand>().unwrap(),
964            redirs: vec![],
965        };
966        let fd = FunctionDefinition {
967            has_keyword: false,
968            name: Word::from_str("foo").unwrap(),
969            body: Rc::new(body),
970        };
971        assert_eq!(fd.to_string(), "foo() (bar)");
972    }
973
974    #[test]
975    fn pipeline_display() {
976        let mut p = Pipeline {
977            commands: vec![Rc::new("first".parse::<Command>().unwrap())],
978            negation: false,
979        };
980        assert_eq!(p.to_string(), "first");
981
982        p.negation = true;
983        assert_eq!(p.to_string(), "! first");
984
985        p.commands.push(Rc::new("second".parse().unwrap()));
986        assert_eq!(p.to_string(), "! first | second");
987
988        p.commands.push(Rc::new("third".parse().unwrap()));
989        p.negation = false;
990        assert_eq!(p.to_string(), "first | second | third");
991    }
992
993    #[test]
994    fn and_or_list_display() {
995        let p = "first".parse::<Pipeline>().unwrap();
996        let mut aol = AndOrList {
997            first: p,
998            rest: vec![],
999        };
1000        assert_eq!(aol.to_string(), "first");
1001
1002        let p = "second".parse().unwrap();
1003        aol.rest.push((AndOr::AndThen, p));
1004        assert_eq!(aol.to_string(), "first && second");
1005
1006        let p = "third".parse().unwrap();
1007        aol.rest.push((AndOr::OrElse, p));
1008        assert_eq!(aol.to_string(), "first && second || third");
1009    }
1010
1011    #[test]
1012    fn list_display() {
1013        let and_or = "first".parse::<AndOrList>().unwrap();
1014        let item = Item {
1015            and_or: Rc::new(and_or),
1016            async_flag: None,
1017        };
1018        let mut list = List(vec![item]);
1019        assert_eq!(list.to_string(), "first");
1020
1021        let and_or = "second".parse().unwrap();
1022        let item = Item {
1023            and_or: Rc::new(and_or),
1024            async_flag: Some(Location::dummy("")),
1025        };
1026        list.0.push(item);
1027        assert_eq!(list.to_string(), "first; second&");
1028
1029        let and_or = "third".parse().unwrap();
1030        let item = Item {
1031            and_or: Rc::new(and_or),
1032            async_flag: None,
1033        };
1034        list.0.push(item);
1035        assert_eq!(list.to_string(), "first; second& third");
1036    }
1037
1038    #[test]
1039    fn list_display_alternate() {
1040        let and_or = "first".parse::<AndOrList>().unwrap();
1041        let item = Item {
1042            and_or: Rc::new(and_or),
1043            async_flag: None,
1044        };
1045        let mut list = List(vec![item]);
1046        assert_eq!(format!("{list:#}"), "first;");
1047
1048        let and_or = "second".parse().unwrap();
1049        let item = Item {
1050            and_or: Rc::new(and_or),
1051            async_flag: Some(Location::dummy("")),
1052        };
1053        list.0.push(item);
1054        assert_eq!(format!("{list:#}"), "first; second&");
1055
1056        let and_or = "third".parse().unwrap();
1057        let item = Item {
1058            and_or: Rc::new(and_or),
1059            async_flag: None,
1060        };
1061        list.0.push(item);
1062        assert_eq!(format!("{list:#}"), "first; second& third;");
1063    }
1064}