1use std::fmt::{Display, Write};
5
6use crate::tokenizer;
7
8const DISPLAY_INDENT: &str = " ";
9
10#[derive(Clone, Debug)]
12#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
13#[cfg_attr(test, derive(PartialEq, Eq))]
14pub struct Program {
15 pub complete_commands: Vec<CompleteCommand>,
17}
18
19impl Display for Program {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 for complete_command in &self.complete_commands {
22 write!(f, "{}", complete_command)?;
23 }
24 Ok(())
25 }
26}
27
28pub type CompleteCommand = CompoundList;
30
31pub type CompleteCommandItem = CompoundListItem;
33
34#[derive(Clone, Debug)]
36#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
37#[cfg_attr(test, derive(PartialEq, Eq))]
38pub enum SeparatorOperator {
39 Async,
41 Sequence,
43}
44
45impl Display for SeparatorOperator {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 SeparatorOperator::Async => write!(f, "&"),
49 SeparatorOperator::Sequence => write!(f, ";"),
50 }
51 }
52}
53
54#[derive(Clone, Debug)]
56#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
57#[cfg_attr(test, derive(PartialEq, Eq))]
58pub struct AndOrList {
59 pub first: Pipeline,
61 pub additional: Vec<AndOr>,
63}
64
65impl Display for AndOrList {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 write!(f, "{}", self.first)?;
68 for item in &self.additional {
69 write!(f, "{}", item)?;
70 }
71
72 Ok(())
73 }
74}
75
76#[derive(Clone, Debug)]
79#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
80#[cfg_attr(test, derive(PartialEq, Eq))]
81pub enum AndOr {
82 And(Pipeline),
85 Or(Pipeline),
88}
89
90impl Display for AndOr {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 AndOr::And(pipeline) => write!(f, " && {}", pipeline),
94 AndOr::Or(pipeline) => write!(f, " || {}", pipeline),
95 }
96 }
97}
98
99#[derive(Clone, Debug)]
101#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
102#[cfg_attr(test, derive(PartialEq, Eq))]
103pub enum PipelineTimed {
104 Timed,
106 TimedWithPosixOutput,
108}
109
110#[derive(Clone, Debug)]
113#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
114#[cfg_attr(test, derive(PartialEq, Eq))]
115pub struct Pipeline {
116 pub timed: Option<PipelineTimed>,
119 pub bang: bool,
122 pub seq: Vec<Command>,
124}
125
126impl Display for Pipeline {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 if self.bang {
129 write!(f, "!")?;
130 }
131 for (i, command) in self.seq.iter().enumerate() {
132 if i > 0 {
133 write!(f, " |")?;
134 }
135 write!(f, "{}", command)?;
136 }
137
138 Ok(())
139 }
140}
141
142#[derive(Clone, Debug)]
144#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
145#[cfg_attr(test, derive(PartialEq, Eq))]
146pub enum Command {
147 Simple(SimpleCommand),
150 Compound(CompoundCommand, Option<RedirectList>),
152 Function(FunctionDefinition),
154 ExtendedTest(ExtendedTestExpr),
156}
157
158impl Display for Command {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 match self {
161 Command::Simple(simple_command) => write!(f, "{}", simple_command),
162 Command::Compound(compound_command, redirect_list) => {
163 write!(f, "{}", compound_command)?;
164 if let Some(redirect_list) = redirect_list {
165 write!(f, "{}", redirect_list)?;
166 }
167 Ok(())
168 }
169 Command::Function(function_definition) => write!(f, "{}", function_definition),
170 Command::ExtendedTest(extended_test_expr) => {
171 write!(f, "[[ {} ]]", extended_test_expr)
172 }
173 }
174 }
175}
176
177#[derive(Clone, Debug)]
179#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
180#[cfg_attr(test, derive(PartialEq, Eq))]
181pub enum CompoundCommand {
182 Arithmetic(ArithmeticCommand),
184 ArithmeticForClause(ArithmeticForClauseCommand),
186 BraceGroup(BraceGroupCommand),
188 Subshell(SubshellCommand),
190 ForClause(ForClauseCommand),
192 CaseClause(CaseClauseCommand),
195 IfClause(IfClauseCommand),
197 WhileClause(WhileOrUntilClauseCommand),
199 UntilClause(WhileOrUntilClauseCommand),
201}
202
203impl Display for CompoundCommand {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 match self {
206 CompoundCommand::Arithmetic(arithmetic_command) => write!(f, "{}", arithmetic_command),
207 CompoundCommand::ArithmeticForClause(arithmetic_for_clause_command) => {
208 write!(f, "{}", arithmetic_for_clause_command)
209 }
210 CompoundCommand::BraceGroup(brace_group_command) => {
211 write!(f, "{}", brace_group_command)
212 }
213 CompoundCommand::Subshell(subshell_command) => write!(f, "{}", subshell_command),
214 CompoundCommand::ForClause(for_clause_command) => write!(f, "{}", for_clause_command),
215 CompoundCommand::CaseClause(case_clause_command) => {
216 write!(f, "{}", case_clause_command)
217 }
218 CompoundCommand::IfClause(if_clause_command) => write!(f, "{}", if_clause_command),
219 CompoundCommand::WhileClause(while_or_until_clause_command) => {
220 write!(f, "while {}", while_or_until_clause_command)
221 }
222 CompoundCommand::UntilClause(while_or_until_clause_command) => {
223 write!(f, "until {}", while_or_until_clause_command)
224 }
225 }
226 }
227}
228
229#[derive(Clone, Debug)]
231#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
232#[cfg_attr(test, derive(PartialEq, Eq))]
233pub struct ArithmeticCommand {
234 pub expr: UnexpandedArithmeticExpr,
236}
237
238impl Display for ArithmeticCommand {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(f, "(({}))", self.expr)
241 }
242}
243
244#[derive(Clone, Debug)]
246#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
247#[cfg_attr(test, derive(PartialEq, Eq))]
248pub struct SubshellCommand(pub CompoundList);
249
250impl Display for SubshellCommand {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 write!(f, "( ")?;
253 write!(f, "{}", self.0)?;
254 write!(f, " )")
255 }
256}
257
258#[derive(Clone, Debug)]
260#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
261#[cfg_attr(test, derive(PartialEq, Eq))]
262pub struct ForClauseCommand {
263 pub variable_name: String,
265 pub values: Option<Vec<Word>>,
267 pub body: DoGroupCommand,
269}
270
271impl Display for ForClauseCommand {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 write!(f, "for {} in ", self.variable_name)?;
274
275 if let Some(values) = &self.values {
276 for (i, value) in values.iter().enumerate() {
277 if i > 0 {
278 write!(f, " ")?;
279 }
280
281 write!(f, "{}", value)?;
282 }
283 }
284
285 writeln!(f, ";")?;
286
287 write!(f, "{}", self.body)
288 }
289}
290
291#[derive(Clone, Debug)]
293#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
294#[cfg_attr(test, derive(PartialEq, Eq))]
295pub struct ArithmeticForClauseCommand {
296 pub initializer: Option<UnexpandedArithmeticExpr>,
298 pub condition: Option<UnexpandedArithmeticExpr>,
300 pub updater: Option<UnexpandedArithmeticExpr>,
302 pub body: DoGroupCommand,
304}
305
306impl Display for ArithmeticForClauseCommand {
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 write!(f, "for ((")?;
309
310 if let Some(initializer) = &self.initializer {
311 write!(f, "{}", initializer)?;
312 }
313
314 write!(f, "; ")?;
315
316 if let Some(condition) = &self.condition {
317 write!(f, "{}", condition)?;
318 }
319
320 write!(f, "; ")?;
321
322 if let Some(updater) = &self.updater {
323 write!(f, "{}", updater)?;
324 }
325
326 writeln!(f, "))")?;
327
328 write!(f, "{}", self.body)
329 }
330}
331
332#[derive(Clone, Debug)]
335#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
336#[cfg_attr(test, derive(PartialEq, Eq))]
337pub struct CaseClauseCommand {
338 pub value: Word,
340 pub cases: Vec<CaseItem>,
342}
343
344impl Display for CaseClauseCommand {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346 write!(f, "case {} in", self.value)?;
347 for case in &self.cases {
348 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{}", case)?;
349 }
350 writeln!(f)?;
351 write!(f, "esac")
352 }
353}
354
355#[derive(Clone, Debug)]
357#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
358#[cfg_attr(test, derive(PartialEq, Eq))]
359pub struct CompoundList(pub Vec<CompoundListItem>);
360
361impl Display for CompoundList {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 for (i, item) in self.0.iter().enumerate() {
364 if i > 0 {
365 writeln!(f)?;
366 }
367
368 write!(f, "{}", item.0)?;
370
371 if i == self.0.len() - 1 && matches!(item.1, SeparatorOperator::Sequence) {
373 } else {
375 write!(f, "{}", item.1)?;
376 }
377 }
378
379 Ok(())
380 }
381}
382
383#[derive(Clone, Debug)]
385#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
386#[cfg_attr(test, derive(PartialEq, Eq))]
387pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
388
389impl Display for CompoundListItem {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 write!(f, "{}", self.0)?;
392 write!(f, "{}", self.1)?;
393 Ok(())
394 }
395}
396
397#[derive(Clone, Debug)]
399#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
400#[cfg_attr(test, derive(PartialEq, Eq))]
401pub struct IfClauseCommand {
402 pub condition: CompoundList,
404 pub then: CompoundList,
406 pub elses: Option<Vec<ElseClause>>,
408}
409
410impl Display for IfClauseCommand {
411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412 writeln!(f, "if {}; then", self.condition)?;
413 write!(
414 indenter::indented(f).with_str(DISPLAY_INDENT),
415 "{}",
416 self.then
417 )?;
418 if let Some(elses) = &self.elses {
419 for else_clause in elses {
420 write!(f, "{}", else_clause)?;
421 }
422 }
423
424 writeln!(f)?;
425 write!(f, "fi")?;
426
427 Ok(())
428 }
429}
430
431#[derive(Clone, Debug)]
433#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
434#[cfg_attr(test, derive(PartialEq, Eq))]
435pub struct ElseClause {
436 pub condition: Option<CompoundList>,
438 pub body: CompoundList,
440}
441
442impl Display for ElseClause {
443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444 writeln!(f)?;
445 if let Some(condition) = &self.condition {
446 writeln!(f, "elif {}; then", condition)?;
447 } else {
448 writeln!(f, "else")?;
449 }
450
451 write!(
452 indenter::indented(f).with_str(DISPLAY_INDENT),
453 "{}",
454 self.body
455 )
456 }
457}
458
459#[derive(Clone, Debug)]
461#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
462#[cfg_attr(test, derive(PartialEq, Eq))]
463pub struct CaseItem {
464 pub patterns: Vec<Word>,
466 pub cmd: Option<CompoundList>,
468 pub post_action: CaseItemPostAction,
470}
471
472impl Display for CaseItem {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 writeln!(f)?;
475 for (i, pattern) in self.patterns.iter().enumerate() {
476 if i > 0 {
477 write!(f, "|")?;
478 }
479 write!(f, "{}", pattern)?;
480 }
481 writeln!(f, ")")?;
482
483 if let Some(cmd) = &self.cmd {
484 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{}", cmd)?;
485 }
486 writeln!(f)?;
487 write!(f, "{}", self.post_action)
488 }
489}
490
491#[derive(Clone, Debug)]
493#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
494#[cfg_attr(test, derive(PartialEq, Eq))]
495pub enum CaseItemPostAction {
496 ExitCase,
498 UnconditionallyExecuteNextCaseItem,
501 ContinueEvaluatingCases,
504}
505
506impl Display for CaseItemPostAction {
507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508 match self {
509 CaseItemPostAction::ExitCase => write!(f, ";;"),
510 CaseItemPostAction::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
511 CaseItemPostAction::ContinueEvaluatingCases => write!(f, ";;&"),
512 }
513 }
514}
515
516#[derive(Clone, Debug)]
518#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
519#[cfg_attr(test, derive(PartialEq, Eq))]
520pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand);
521
522impl Display for WhileOrUntilClauseCommand {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 write!(f, "{}; {}", self.0, self.1)
525 }
526}
527
528#[derive(Clone, Debug)]
530#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
531#[cfg_attr(test, derive(PartialEq, Eq))]
532pub struct FunctionDefinition {
533 pub fname: String,
535 pub body: FunctionBody,
537 pub source: String,
539}
540
541impl Display for FunctionDefinition {
542 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
543 writeln!(f, "{} () ", self.fname)?;
544 write!(f, "{}", self.body)?;
545 Ok(())
546 }
547}
548
549#[derive(Clone, Debug)]
551#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
552#[cfg_attr(test, derive(PartialEq, Eq))]
553pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
554
555impl Display for FunctionBody {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 write!(f, "{}", self.0)?;
558 if let Some(redirect_list) = &self.1 {
559 write!(f, "{}", redirect_list)?;
560 }
561
562 Ok(())
563 }
564}
565
566#[derive(Clone, Debug)]
568#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
569#[cfg_attr(test, derive(PartialEq, Eq))]
570pub struct BraceGroupCommand(pub CompoundList);
571
572impl Display for BraceGroupCommand {
573 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574 writeln!(f, "{{ ")?;
575 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{}", self.0)?;
576 writeln!(f)?;
577 write!(f, "}}")?;
578
579 Ok(())
580 }
581}
582
583#[derive(Clone, Debug)]
585#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
586#[cfg_attr(test, derive(PartialEq, Eq))]
587pub struct DoGroupCommand(pub CompoundList);
588
589impl Display for DoGroupCommand {
590 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
591 writeln!(f, "do")?;
592 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{}", self.0)?;
593 writeln!(f)?;
594 write!(f, "done")
595 }
596}
597
598#[derive(Clone, Debug)]
600#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
601#[cfg_attr(test, derive(PartialEq, Eq))]
602pub struct SimpleCommand {
603 pub prefix: Option<CommandPrefix>,
605 pub word_or_name: Option<Word>,
607 pub suffix: Option<CommandSuffix>,
609}
610
611impl Display for SimpleCommand {
612 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
613 let mut wrote_something = false;
614
615 if let Some(prefix) = &self.prefix {
616 if wrote_something {
617 write!(f, " ")?;
618 }
619
620 write!(f, "{}", prefix)?;
621 wrote_something = true;
622 }
623
624 if let Some(word_or_name) = &self.word_or_name {
625 if wrote_something {
626 write!(f, " ")?;
627 }
628
629 write!(f, "{}", word_or_name)?;
630 wrote_something = true;
631 }
632
633 if let Some(suffix) = &self.suffix {
634 if wrote_something {
635 write!(f, " ")?;
636 }
637
638 write!(f, "{}", suffix)?;
639 }
640
641 Ok(())
642 }
643}
644
645#[derive(Clone, Debug, Default)]
647#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
648#[cfg_attr(test, derive(PartialEq, Eq))]
649pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
650
651impl Display for CommandPrefix {
652 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
653 for (i, item) in self.0.iter().enumerate() {
654 if i > 0 {
655 write!(f, " ")?;
656 }
657
658 write!(f, "{}", item)?;
659 }
660 Ok(())
661 }
662}
663
664#[derive(Clone, Default, Debug)]
666#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
667#[cfg_attr(test, derive(PartialEq, Eq))]
668pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
669
670impl Display for CommandSuffix {
671 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
672 for (i, item) in self.0.iter().enumerate() {
673 if i > 0 {
674 write!(f, " ")?;
675 }
676
677 write!(f, "{}", item)?;
678 }
679 Ok(())
680 }
681}
682
683#[derive(Clone, Debug)]
685#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
686#[cfg_attr(test, derive(PartialEq, Eq))]
687pub enum ProcessSubstitutionKind {
688 Read,
690 Write,
692}
693
694impl Display for ProcessSubstitutionKind {
695 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
696 match self {
697 ProcessSubstitutionKind::Read => write!(f, "<"),
698 ProcessSubstitutionKind::Write => write!(f, ">"),
699 }
700 }
701}
702
703#[derive(Clone, Debug)]
705#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
706#[cfg_attr(test, derive(PartialEq, Eq))]
707pub enum CommandPrefixOrSuffixItem {
708 IoRedirect(IoRedirect),
710 Word(Word),
712 AssignmentWord(Assignment, Word),
714 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
716}
717
718impl Display for CommandPrefixOrSuffixItem {
719 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
720 match self {
721 CommandPrefixOrSuffixItem::IoRedirect(io_redirect) => write!(f, "{}", io_redirect),
722 CommandPrefixOrSuffixItem::Word(word) => write!(f, "{}", word),
723 CommandPrefixOrSuffixItem::AssignmentWord(_assignment, word) => write!(f, "{}", word),
724 CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => {
725 write!(f, "{}({})", kind, subshell_command)
726 }
727 }
728 }
729}
730
731#[derive(Clone, Debug)]
733#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
734#[cfg_attr(test, derive(PartialEq, Eq))]
735pub struct Assignment {
736 pub name: AssignmentName,
738 pub value: AssignmentValue,
740 pub append: bool,
742}
743
744impl Display for Assignment {
745 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
746 write!(f, "{}", self.name)?;
747 if self.append {
748 write!(f, "+")?;
749 }
750 write!(f, "={}", self.value)
751 }
752}
753
754#[derive(Clone, Debug)]
756#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
757#[cfg_attr(test, derive(PartialEq, Eq))]
758pub enum AssignmentName {
759 VariableName(String),
761 ArrayElementName(String, String),
763}
764
765impl Display for AssignmentName {
766 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
767 match self {
768 AssignmentName::VariableName(name) => write!(f, "{}", name),
769 AssignmentName::ArrayElementName(name, index) => {
770 write!(f, "{}[{}]", name, index)
771 }
772 }
773 }
774}
775
776#[derive(Clone, Debug)]
778#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
779#[cfg_attr(test, derive(PartialEq, Eq))]
780pub enum AssignmentValue {
781 Scalar(Word),
783 Array(Vec<(Option<Word>, Word)>),
785}
786
787impl Display for AssignmentValue {
788 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
789 match self {
790 AssignmentValue::Scalar(word) => write!(f, "{}", word),
791 AssignmentValue::Array(words) => {
792 write!(f, "(")?;
793 for (i, value) in words.iter().enumerate() {
794 if i > 0 {
795 write!(f, " ")?;
796 }
797 match value {
798 (Some(key), value) => write!(f, "[{}]={}", key, value)?,
799 (None, value) => write!(f, "{}", value)?,
800 }
801 }
802 write!(f, ")")
803 }
804 }
805 }
806}
807
808#[derive(Clone, Debug)]
810#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
811#[cfg_attr(test, derive(PartialEq, Eq))]
812pub struct RedirectList(pub Vec<IoRedirect>);
813
814impl Display for RedirectList {
815 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
816 for item in &self.0 {
817 write!(f, "{}", item)?;
818 }
819 Ok(())
820 }
821}
822
823#[derive(Clone, Debug)]
825#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
826#[cfg_attr(test, derive(PartialEq, Eq))]
827pub enum IoRedirect {
828 File(Option<u32>, IoFileRedirectKind, IoFileRedirectTarget),
830 HereDocument(Option<u32>, IoHereDocument),
832 HereString(Option<u32>, Word),
834 OutputAndError(Word, bool),
836}
837
838impl Display for IoRedirect {
839 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
840 match self {
841 IoRedirect::File(fd_num, kind, target) => {
842 if let Some(fd_num) = fd_num {
843 write!(f, "{}", fd_num)?;
844 }
845
846 write!(f, "{} {}", kind, target)?;
847 }
848 IoRedirect::OutputAndError(target, append) => {
849 write!(f, "&>")?;
850 if *append {
851 write!(f, ">")?;
852 }
853 write!(f, " {}", target)?;
854 }
855 IoRedirect::HereDocument(
856 fd_num,
857 IoHereDocument {
858 remove_tabs,
859 here_end,
860 doc,
861 ..
862 },
863 ) => {
864 if let Some(fd_num) = fd_num {
865 write!(f, "{}", fd_num)?;
866 }
867
868 write!(f, "<<")?;
869 if *remove_tabs {
870 write!(f, "-")?;
871 }
872
873 writeln!(f, "{}", here_end)?;
874
875 write!(f, "{}", doc)?;
876 writeln!(f, "{}", here_end)?;
877 }
878 IoRedirect::HereString(fd_num, s) => {
879 if let Some(fd_num) = fd_num {
880 write!(f, "{}", fd_num)?;
881 }
882
883 write!(f, "<<< {}", s)?;
884 }
885 }
886
887 Ok(())
888 }
889}
890
891#[derive(Clone, Debug)]
893#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
894#[cfg_attr(test, derive(PartialEq, Eq))]
895pub enum IoFileRedirectKind {
896 Read,
898 Write,
900 Append,
902 ReadAndWrite,
904 Clobber,
906 DuplicateInput,
908 DuplicateOutput,
910}
911
912impl Display for IoFileRedirectKind {
913 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
914 match self {
915 IoFileRedirectKind::Read => write!(f, "<"),
916 IoFileRedirectKind::Write => write!(f, ">"),
917 IoFileRedirectKind::Append => write!(f, ">>"),
918 IoFileRedirectKind::ReadAndWrite => write!(f, "<>"),
919 IoFileRedirectKind::Clobber => write!(f, ">|"),
920 IoFileRedirectKind::DuplicateInput => write!(f, "<&"),
921 IoFileRedirectKind::DuplicateOutput => write!(f, ">&"),
922 }
923 }
924}
925
926#[derive(Clone, Debug)]
928#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
929#[cfg_attr(test, derive(PartialEq, Eq))]
930pub enum IoFileRedirectTarget {
931 Filename(Word),
933 Fd(u32),
935 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
938}
939
940impl Display for IoFileRedirectTarget {
941 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
942 match self {
943 IoFileRedirectTarget::Filename(word) => write!(f, "{}", word),
944 IoFileRedirectTarget::Fd(fd) => write!(f, "{}", fd),
945 IoFileRedirectTarget::ProcessSubstitution(kind, subshell_command) => {
946 write!(f, "{kind}{subshell_command}")
947 }
948 }
949 }
950}
951
952#[derive(Clone, Debug)]
954#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
955#[cfg_attr(test, derive(PartialEq, Eq))]
956pub struct IoHereDocument {
957 pub remove_tabs: bool,
959 pub requires_expansion: bool,
961 pub here_end: Word,
963 pub doc: Word,
965}
966
967#[derive(Clone, Debug)]
969pub enum TestExpr {
970 False,
972 Literal(String),
974 And(Box<TestExpr>, Box<TestExpr>),
976 Or(Box<TestExpr>, Box<TestExpr>),
978 Not(Box<TestExpr>),
980 Parenthesized(Box<TestExpr>),
982 UnaryTest(UnaryPredicate, String),
984 BinaryTest(BinaryPredicate, String, String),
986}
987
988impl Display for TestExpr {
989 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
990 match self {
991 TestExpr::False => Ok(()),
992 TestExpr::Literal(s) => write!(f, "{s}"),
993 TestExpr::And(left, right) => write!(f, "{left} -a {right}"),
994 TestExpr::Or(left, right) => write!(f, "{left} -o {right}"),
995 TestExpr::Not(expr) => write!(f, "! {}", expr),
996 TestExpr::Parenthesized(expr) => write!(f, "( {expr} )"),
997 TestExpr::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
998 TestExpr::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
999 }
1000 }
1001}
1002
1003#[derive(Clone, Debug)]
1005#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1006#[cfg_attr(test, derive(PartialEq, Eq))]
1007pub enum ExtendedTestExpr {
1008 And(Box<ExtendedTestExpr>, Box<ExtendedTestExpr>),
1010 Or(Box<ExtendedTestExpr>, Box<ExtendedTestExpr>),
1012 Not(Box<ExtendedTestExpr>),
1014 Parenthesized(Box<ExtendedTestExpr>),
1016 UnaryTest(UnaryPredicate, Word),
1018 BinaryTest(BinaryPredicate, Word, Word),
1020}
1021
1022impl Display for ExtendedTestExpr {
1023 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1024 match self {
1025 ExtendedTestExpr::And(left, right) => {
1026 write!(f, "{} && {}", left, right)
1027 }
1028 ExtendedTestExpr::Or(left, right) => {
1029 write!(f, "{} || {}", left, right)
1030 }
1031 ExtendedTestExpr::Not(expr) => {
1032 write!(f, "! {}", expr)
1033 }
1034 ExtendedTestExpr::Parenthesized(expr) => {
1035 write!(f, "( {} )", expr)
1036 }
1037 ExtendedTestExpr::UnaryTest(pred, word) => {
1038 write!(f, "{} {}", pred, word)
1039 }
1040 ExtendedTestExpr::BinaryTest(pred, left, right) => {
1041 write!(f, "{} {} {}", left, pred, right)
1042 }
1043 }
1044 }
1045}
1046
1047#[derive(Clone, Debug)]
1049#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1050#[cfg_attr(test, derive(PartialEq, Eq))]
1051pub enum UnaryPredicate {
1052 FileExists,
1054 FileExistsAndIsBlockSpecialFile,
1056 FileExistsAndIsCharSpecialFile,
1058 FileExistsAndIsDir,
1060 FileExistsAndIsRegularFile,
1062 FileExistsAndIsSetgid,
1064 FileExistsAndIsSymlink,
1066 FileExistsAndHasStickyBit,
1068 FileExistsAndIsFifo,
1070 FileExistsAndIsReadable,
1072 FileExistsAndIsNotZeroLength,
1074 FdIsOpenTerminal,
1076 FileExistsAndIsSetuid,
1078 FileExistsAndIsWritable,
1080 FileExistsAndIsExecutable,
1082 FileExistsAndOwnedByEffectiveGroupId,
1085 FileExistsAndModifiedSinceLastRead,
1088 FileExistsAndOwnedByEffectiveUserId,
1091 FileExistsAndIsSocket,
1093 ShellOptionEnabled,
1095 ShellVariableIsSetAndAssigned,
1097 ShellVariableIsSetAndNameRef,
1099 StringHasZeroLength,
1101 StringHasNonZeroLength,
1103}
1104
1105impl Display for UnaryPredicate {
1106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1107 match self {
1108 UnaryPredicate::FileExists => write!(f, "-e"),
1109 UnaryPredicate::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1110 UnaryPredicate::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1111 UnaryPredicate::FileExistsAndIsDir => write!(f, "-d"),
1112 UnaryPredicate::FileExistsAndIsRegularFile => write!(f, "-f"),
1113 UnaryPredicate::FileExistsAndIsSetgid => write!(f, "-g"),
1114 UnaryPredicate::FileExistsAndIsSymlink => write!(f, "-h"),
1115 UnaryPredicate::FileExistsAndHasStickyBit => write!(f, "-k"),
1116 UnaryPredicate::FileExistsAndIsFifo => write!(f, "-p"),
1117 UnaryPredicate::FileExistsAndIsReadable => write!(f, "-r"),
1118 UnaryPredicate::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1119 UnaryPredicate::FdIsOpenTerminal => write!(f, "-t"),
1120 UnaryPredicate::FileExistsAndIsSetuid => write!(f, "-u"),
1121 UnaryPredicate::FileExistsAndIsWritable => write!(f, "-w"),
1122 UnaryPredicate::FileExistsAndIsExecutable => write!(f, "-x"),
1123 UnaryPredicate::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1124 UnaryPredicate::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1125 UnaryPredicate::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1126 UnaryPredicate::FileExistsAndIsSocket => write!(f, "-S"),
1127 UnaryPredicate::ShellOptionEnabled => write!(f, "-o"),
1128 UnaryPredicate::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1129 UnaryPredicate::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1130 UnaryPredicate::StringHasZeroLength => write!(f, "-z"),
1131 UnaryPredicate::StringHasNonZeroLength => write!(f, "-n"),
1132 }
1133 }
1134}
1135
1136#[derive(Clone, Debug)]
1138#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1139#[cfg_attr(test, derive(PartialEq, Eq))]
1140pub enum BinaryPredicate {
1141 FilesReferToSameDeviceAndInodeNumbers,
1143 LeftFileIsNewerOrExistsWhenRightDoesNot,
1145 LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1147 StringExactlyMatchesPattern,
1149 StringDoesNotExactlyMatchPattern,
1151 StringMatchesRegex,
1153 StringExactlyMatchesString,
1155 StringDoesNotExactlyMatchString,
1157 StringContainsSubstring,
1159 LeftSortsBeforeRight,
1161 LeftSortsAfterRight,
1163 ArithmeticEqualTo,
1165 ArithmeticNotEqualTo,
1167 ArithmeticLessThan,
1169 ArithmeticLessThanOrEqualTo,
1171 ArithmeticGreaterThan,
1173 ArithmeticGreaterThanOrEqualTo,
1175}
1176
1177impl Display for BinaryPredicate {
1178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1179 match self {
1180 BinaryPredicate::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
1181 BinaryPredicate::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
1182 BinaryPredicate::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
1183 BinaryPredicate::StringExactlyMatchesPattern => write!(f, "=="),
1184 BinaryPredicate::StringDoesNotExactlyMatchPattern => write!(f, "!="),
1185 BinaryPredicate::StringMatchesRegex => write!(f, "=~"),
1186 BinaryPredicate::StringContainsSubstring => write!(f, "=~"),
1187 BinaryPredicate::StringExactlyMatchesString => write!(f, "=="),
1188 BinaryPredicate::StringDoesNotExactlyMatchString => write!(f, "!="),
1189 BinaryPredicate::LeftSortsBeforeRight => write!(f, "<"),
1190 BinaryPredicate::LeftSortsAfterRight => write!(f, ">"),
1191 BinaryPredicate::ArithmeticEqualTo => write!(f, "-eq"),
1192 BinaryPredicate::ArithmeticNotEqualTo => write!(f, "-ne"),
1193 BinaryPredicate::ArithmeticLessThan => write!(f, "-lt"),
1194 BinaryPredicate::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
1195 BinaryPredicate::ArithmeticGreaterThan => write!(f, "-gt"),
1196 BinaryPredicate::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
1197 }
1198 }
1199}
1200
1201#[derive(Clone, Debug)]
1203#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1204#[cfg_attr(test, derive(PartialEq, Eq))]
1205pub struct Word {
1206 pub value: String,
1208}
1209
1210impl Display for Word {
1211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1212 write!(f, "{}", self.value)
1213 }
1214}
1215
1216impl From<&tokenizer::Token> for Word {
1217 fn from(t: &tokenizer::Token) -> Word {
1218 match t {
1219 tokenizer::Token::Word(value, _) => Word {
1220 value: value.clone(),
1221 },
1222 tokenizer::Token::Operator(value, _) => Word {
1223 value: value.clone(),
1224 },
1225 }
1226 }
1227}
1228
1229impl From<String> for Word {
1230 fn from(s: String) -> Word {
1231 Word { value: s }
1232 }
1233}
1234
1235impl Word {
1236 pub fn new(s: &str) -> Self {
1238 Self {
1239 value: s.to_owned(),
1240 }
1241 }
1242
1243 pub fn flatten(&self) -> String {
1245 self.value.clone()
1246 }
1247}
1248
1249#[derive(Clone, Debug)]
1251#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1252#[cfg_attr(test, derive(PartialEq, Eq))]
1253pub struct UnexpandedArithmeticExpr {
1254 pub value: String,
1256}
1257
1258impl Display for UnexpandedArithmeticExpr {
1259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1260 write!(f, "{}", self.value)
1261 }
1262}
1263
1264#[derive(Clone, Debug)]
1266pub enum ArithmeticExpr {
1267 Literal(i64),
1269 Reference(ArithmeticTarget),
1271 UnaryOp(UnaryOperator, Box<ArithmeticExpr>),
1273 BinaryOp(BinaryOperator, Box<ArithmeticExpr>, Box<ArithmeticExpr>),
1275 Conditional(
1277 Box<ArithmeticExpr>,
1278 Box<ArithmeticExpr>,
1279 Box<ArithmeticExpr>,
1280 ),
1281 Assignment(ArithmeticTarget, Box<ArithmeticExpr>),
1283 BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<ArithmeticExpr>),
1285 UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
1287}
1288
1289#[cfg(feature = "fuzz-testing")]
1290impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
1291 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1292 let variant = u.choose(&[
1293 "Literal",
1294 "Reference",
1295 "UnaryOp",
1296 "BinaryOp",
1297 "Conditional",
1298 "Assignment",
1299 "BinaryAssignment",
1300 "UnaryAssignment",
1301 ])?;
1302
1303 match *variant {
1304 "Literal" => Ok(ArithmeticExpr::Literal(i64::arbitrary(u)?)),
1305 "Reference" => Ok(ArithmeticExpr::Reference(ArithmeticTarget::arbitrary(u)?)),
1306 "UnaryOp" => Ok(ArithmeticExpr::UnaryOp(
1307 UnaryOperator::arbitrary(u)?,
1308 Box::new(ArithmeticExpr::arbitrary(u)?),
1309 )),
1310 "BinaryOp" => Ok(ArithmeticExpr::BinaryOp(
1311 BinaryOperator::arbitrary(u)?,
1312 Box::new(ArithmeticExpr::arbitrary(u)?),
1313 Box::new(ArithmeticExpr::arbitrary(u)?),
1314 )),
1315 "Conditional" => Ok(ArithmeticExpr::Conditional(
1316 Box::new(ArithmeticExpr::arbitrary(u)?),
1317 Box::new(ArithmeticExpr::arbitrary(u)?),
1318 Box::new(ArithmeticExpr::arbitrary(u)?),
1319 )),
1320 "Assignment" => Ok(ArithmeticExpr::Assignment(
1321 ArithmeticTarget::arbitrary(u)?,
1322 Box::new(ArithmeticExpr::arbitrary(u)?),
1323 )),
1324 "BinaryAssignment" => Ok(ArithmeticExpr::BinaryAssignment(
1325 BinaryOperator::arbitrary(u)?,
1326 ArithmeticTarget::arbitrary(u)?,
1327 Box::new(ArithmeticExpr::arbitrary(u)?),
1328 )),
1329 "UnaryAssignment" => Ok(ArithmeticExpr::UnaryAssignment(
1330 UnaryAssignmentOperator::arbitrary(u)?,
1331 ArithmeticTarget::arbitrary(u)?,
1332 )),
1333 _ => unreachable!(),
1334 }
1335 }
1336}
1337
1338impl Display for ArithmeticExpr {
1339 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1340 match self {
1341 ArithmeticExpr::Literal(literal) => write!(f, "{literal}"),
1342 ArithmeticExpr::Reference(target) => write!(f, "{target}"),
1343 ArithmeticExpr::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
1344 ArithmeticExpr::BinaryOp(op, left, right) => {
1345 if matches!(op, BinaryOperator::Comma) {
1346 write!(f, "{left}{op} {right}")
1347 } else {
1348 write!(f, "{left} {op} {right}")
1349 }
1350 }
1351 ArithmeticExpr::Conditional(condition, if_branch, else_branch) => {
1352 write!(f, "{condition} ? {if_branch} : {else_branch}")
1353 }
1354 ArithmeticExpr::Assignment(target, value) => write!(f, "{target} = {value}"),
1355 ArithmeticExpr::BinaryAssignment(op, target, operand) => {
1356 write!(f, "{target} {op}= {operand}")
1357 }
1358 ArithmeticExpr::UnaryAssignment(op, target) => match op {
1359 UnaryAssignmentOperator::PrefixIncrement
1360 | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
1361 UnaryAssignmentOperator::PostfixIncrement
1362 | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
1363 },
1364 }
1365 }
1366}
1367
1368#[derive(Clone, Copy, Debug)]
1370#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1371pub enum BinaryOperator {
1372 Power,
1374 Multiply,
1376 Divide,
1378 Modulo,
1380 Comma,
1382 Add,
1384 Subtract,
1386 ShiftLeft,
1388 ShiftRight,
1390 LessThan,
1392 LessThanOrEqualTo,
1394 GreaterThan,
1396 GreaterThanOrEqualTo,
1398 Equals,
1400 NotEquals,
1402 BitwiseAnd,
1404 BitwiseXor,
1406 BitwiseOr,
1408 LogicalAnd,
1410 LogicalOr,
1412}
1413
1414impl Display for BinaryOperator {
1415 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1416 match self {
1417 BinaryOperator::Power => write!(f, "**"),
1418 BinaryOperator::Multiply => write!(f, "*"),
1419 BinaryOperator::Divide => write!(f, "/"),
1420 BinaryOperator::Modulo => write!(f, "%"),
1421 BinaryOperator::Comma => write!(f, ","),
1422 BinaryOperator::Add => write!(f, "+"),
1423 BinaryOperator::Subtract => write!(f, "-"),
1424 BinaryOperator::ShiftLeft => write!(f, "<<"),
1425 BinaryOperator::ShiftRight => write!(f, ">>"),
1426 BinaryOperator::LessThan => write!(f, "<"),
1427 BinaryOperator::LessThanOrEqualTo => write!(f, "<="),
1428 BinaryOperator::GreaterThan => write!(f, ">"),
1429 BinaryOperator::GreaterThanOrEqualTo => write!(f, ">="),
1430 BinaryOperator::Equals => write!(f, "=="),
1431 BinaryOperator::NotEquals => write!(f, "!="),
1432 BinaryOperator::BitwiseAnd => write!(f, "&"),
1433 BinaryOperator::BitwiseXor => write!(f, "^"),
1434 BinaryOperator::BitwiseOr => write!(f, "|"),
1435 BinaryOperator::LogicalAnd => write!(f, "&&"),
1436 BinaryOperator::LogicalOr => write!(f, "||"),
1437 }
1438 }
1439}
1440
1441#[derive(Clone, Copy, Debug)]
1443#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1444pub enum UnaryOperator {
1445 UnaryPlus,
1447 UnaryMinus,
1449 BitwiseNot,
1451 LogicalNot,
1453}
1454
1455impl Display for UnaryOperator {
1456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1457 match self {
1458 UnaryOperator::UnaryPlus => write!(f, "+"),
1459 UnaryOperator::UnaryMinus => write!(f, "-"),
1460 UnaryOperator::BitwiseNot => write!(f, "~"),
1461 UnaryOperator::LogicalNot => write!(f, "!"),
1462 }
1463 }
1464}
1465
1466#[derive(Clone, Copy, Debug)]
1468#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1469pub enum UnaryAssignmentOperator {
1470 PrefixIncrement,
1472 PrefixDecrement,
1474 PostfixIncrement,
1476 PostfixDecrement,
1478}
1479
1480impl Display for UnaryAssignmentOperator {
1481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1482 match self {
1483 UnaryAssignmentOperator::PrefixIncrement => write!(f, "++"),
1484 UnaryAssignmentOperator::PrefixDecrement => write!(f, "--"),
1485 UnaryAssignmentOperator::PostfixIncrement => write!(f, "++"),
1486 UnaryAssignmentOperator::PostfixDecrement => write!(f, "--"),
1487 }
1488 }
1489}
1490
1491#[derive(Clone, Debug)]
1493#[cfg_attr(feature = "fuzz-testing", derive(arbitrary::Arbitrary))]
1494pub enum ArithmeticTarget {
1495 Variable(String),
1497 ArrayElement(String, Box<ArithmeticExpr>),
1499}
1500
1501impl Display for ArithmeticTarget {
1502 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1503 match self {
1504 ArithmeticTarget::Variable(name) => write!(f, "{name}"),
1505 ArithmeticTarget::ArrayElement(name, index) => write!(f, "{}[{}]", name, index),
1506 }
1507 }
1508}