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