1use std::fmt::{Display, Write};
5
6use crate::{SourceSpan, tokenizer};
7
8const DISPLAY_INDENT: &str = " ";
9
10pub trait Node: Display + SourceLocation {}
13
14pub trait SourceLocation {
16 fn location(&self) -> Option<SourceSpan>;
18}
19
20pub(crate) fn maybe_location(
21 start: Option<&SourceSpan>,
22 end: Option<&SourceSpan>,
23) -> Option<SourceSpan> {
24 if let (Some(s), Some(e)) = (start, end) {
25 Some(SourceSpan::within(s, e))
26 } else {
27 None
28 }
29}
30
31#[derive(Clone, Debug)]
33#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
34#[cfg_attr(
35 any(test, feature = "serde"),
36 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
37)]
38pub struct Program {
39 pub complete_commands: Vec<CompleteCommand>,
41}
42
43impl Node for Program {}
44
45impl SourceLocation for Program {
46 fn location(&self) -> Option<SourceSpan> {
47 let start = self
48 .complete_commands
49 .first()
50 .and_then(SourceLocation::location);
51 let end = self
52 .complete_commands
53 .last()
54 .and_then(SourceLocation::location);
55 maybe_location(start.as_ref(), end.as_ref())
56 }
57}
58
59impl Display for Program {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 for complete_command in &self.complete_commands {
62 write!(f, "{complete_command}")?;
63 }
64 Ok(())
65 }
66}
67
68pub type CompleteCommand = CompoundList;
70
71pub type CompleteCommandItem = CompoundListItem;
73
74#[derive(Clone, Debug)]
77#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
78#[cfg_attr(
79 any(test, feature = "serde"),
80 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
81)]
82pub enum SeparatorOperator {
83 Async,
85 Sequence,
87}
88
89impl Display for SeparatorOperator {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 Self::Async => write!(f, "&"),
93 Self::Sequence => write!(f, ";"),
94 }
95 }
96}
97
98#[derive(Clone, Debug)]
100#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
101#[cfg_attr(
102 any(test, feature = "serde"),
103 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
104)]
105pub struct AndOrList {
106 pub first: Pipeline,
108 #[cfg_attr(
110 any(test, feature = "serde"),
111 serde(skip_serializing_if = "Vec::is_empty", default)
112 )]
113 pub additional: Vec<AndOr>,
114}
115
116impl Node for AndOrList {}
117
118impl SourceLocation for AndOrList {
119 fn location(&self) -> Option<SourceSpan> {
120 let start = self.first.location();
121 let last = self.additional.last();
122 let end = last.and_then(SourceLocation::location);
123
124 match (start, end) {
125 (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
126 (start, _) => start,
127 }
128 }
129}
130
131impl Display for AndOrList {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 write!(f, "{}", self.first)?;
134 for item in &self.additional {
135 write!(f, "{item}")?;
136 }
137
138 Ok(())
139 }
140}
141
142#[derive(PartialEq, Eq)]
144pub enum PipelineOperator {
145 And,
147 Or,
149}
150
151impl PartialEq<AndOr> for PipelineOperator {
152 fn eq(&self, other: &AndOr) -> bool {
153 matches!(
154 (self, other),
155 (Self::And, AndOr::And(_)) | (Self::Or, AndOr::Or(_))
156 )
157 }
158}
159
160#[expect(clippy::from_over_into)]
162impl Into<PipelineOperator> for AndOr {
163 fn into(self) -> PipelineOperator {
164 match self {
165 Self::And(_) => PipelineOperator::And,
166 Self::Or(_) => PipelineOperator::Or,
167 }
168 }
169}
170
171pub struct AndOrListIter<'a> {
173 first: Option<&'a Pipeline>,
174 additional_iter: std::slice::Iter<'a, AndOr>,
175}
176
177impl<'a> Iterator for AndOrListIter<'a> {
178 type Item = (PipelineOperator, &'a Pipeline);
179
180 fn next(&mut self) -> Option<Self::Item> {
181 if let Some(first) = self.first.take() {
182 Some((PipelineOperator::And, first))
183 } else {
184 self.additional_iter.next().map(|and_or| match and_or {
185 AndOr::And(pipeline) => (PipelineOperator::And, pipeline),
186 AndOr::Or(pipeline) => (PipelineOperator::Or, pipeline),
187 })
188 }
189 }
190}
191
192impl<'a> IntoIterator for &'a AndOrList {
193 type Item = (PipelineOperator, &'a Pipeline);
194 type IntoIter = AndOrListIter<'a>;
195
196 fn into_iter(self) -> Self::IntoIter {
197 AndOrListIter {
198 first: Some(&self.first),
199 additional_iter: self.additional.iter(),
200 }
201 }
202}
203
204impl<'a> From<(PipelineOperator, &'a Pipeline)> for AndOr {
205 fn from(value: (PipelineOperator, &'a Pipeline)) -> Self {
206 match value.0 {
207 PipelineOperator::Or => Self::Or(value.1.to_owned()),
208 PipelineOperator::And => Self::And(value.1.to_owned()),
209 }
210 }
211}
212
213impl AndOrList {
214 pub fn iter(&self) -> AndOrListIter<'_> {
216 self.into_iter()
217 }
218}
219
220#[derive(Clone, Debug)]
223#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
224#[cfg_attr(
225 any(test, feature = "serde"),
226 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
227)]
228pub enum AndOr {
229 And(Pipeline),
232 Or(Pipeline),
235}
236
237impl Node for AndOr {}
238
239impl SourceLocation for AndOr {
241 fn location(&self) -> Option<SourceSpan> {
242 match self {
243 Self::And(p) => p.location(),
244 Self::Or(p) => p.location(),
245 }
246 }
247}
248
249impl Display for AndOr {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 match self {
252 Self::And(pipeline) => write!(f, " && {pipeline}"),
253 Self::Or(pipeline) => write!(f, " || {pipeline}"),
254 }
255 }
256}
257
258#[derive(Clone, Debug)]
260#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
261#[cfg_attr(
262 any(test, feature = "serde"),
263 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
264)]
265pub enum PipelineTimed {
266 Timed(SourceSpan),
268 TimedWithPosixOutput(SourceSpan),
270}
271
272impl Node for PipelineTimed {}
273
274impl SourceLocation for PipelineTimed {
275 fn location(&self) -> Option<SourceSpan> {
276 match self {
277 Self::Timed(t) => Some(t.to_owned()),
278 Self::TimedWithPosixOutput(t) => Some(t.to_owned()),
279 }
280 }
281}
282
283impl Display for PipelineTimed {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 match self {
286 Self::Timed(_) => write!(f, "time"),
287 Self::TimedWithPosixOutput(_) => write!(f, "time -p"),
288 }
289 }
290}
291
292impl PipelineTimed {
293 pub const fn is_posix_output(&self) -> bool {
295 matches!(self, Self::TimedWithPosixOutput(_))
296 }
297}
298
299#[derive(Clone, Debug)]
302#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
303#[cfg_attr(
304 any(test, feature = "serde"),
305 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
306)]
307pub struct Pipeline {
308 #[cfg_attr(
311 any(test, feature = "serde"),
312 serde(skip_serializing_if = "Option::is_none", default)
313 )]
314 pub timed: Option<PipelineTimed>,
315 #[cfg_attr(
318 any(test, feature = "serde"),
319 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
320 )]
321 pub bang: bool,
322 pub seq: Vec<Command>,
324}
325
326impl Node for Pipeline {}
327
328impl SourceLocation for Pipeline {
330 fn location(&self) -> Option<SourceSpan> {
331 let start = self
332 .timed
333 .as_ref()
334 .and_then(SourceLocation::location)
335 .or_else(|| self.seq.first().and_then(SourceLocation::location));
336 let end = self.seq.last().and_then(SourceLocation::location);
337
338 maybe_location(start.as_ref(), end.as_ref())
339 }
340}
341
342impl Display for Pipeline {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 if let Some(timed) = &self.timed {
345 write!(f, "{timed} ")?;
346 }
347
348 if self.bang {
349 write!(f, "!")?;
350 }
351 for (i, command) in self.seq.iter().enumerate() {
352 if i > 0 {
353 write!(f, " |")?;
354 }
355 write!(f, "{command}")?;
356 }
357
358 Ok(())
359 }
360}
361
362#[derive(Clone, Debug)]
364#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
365#[cfg_attr(
366 any(test, feature = "serde"),
367 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
368)]
369pub enum Command {
370 Simple(SimpleCommand),
373 Compound(CompoundCommand, Option<RedirectList>),
375 Function(FunctionDefinition),
377 ExtendedTest(ExtendedTestExprCommand, Option<RedirectList>),
379}
380
381impl Node for Command {}
382
383impl SourceLocation for Command {
384 fn location(&self) -> Option<SourceSpan> {
385 match self {
386 Self::Simple(s) => s.location(),
387 Self::Compound(c, r) => {
388 match (c.location(), r.as_ref().and_then(SourceLocation::location)) {
389 (Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
390 (s, _) => s,
391 }
392 }
393 Self::Function(f) => f.location(),
394 Self::ExtendedTest(e, _) => e.location(),
395 }
396 }
397}
398
399impl Display for Command {
400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 match self {
402 Self::Simple(simple_command) => write!(f, "{simple_command}"),
403 Self::Compound(compound_command, redirect_list) => {
404 write!(f, "{compound_command}")?;
405 if let Some(redirect_list) = redirect_list {
406 write!(f, "{redirect_list}")?;
407 }
408 Ok(())
409 }
410 Self::Function(function_definition) => write!(f, "{function_definition}"),
411 Self::ExtendedTest(extended_test_expr, redirect_list) => {
412 write!(f, "[[ {extended_test_expr} ]]")?;
413 if let Some(redirect_list) = redirect_list {
414 write!(f, "{redirect_list}")?;
415 }
416 Ok(())
417 }
418 }
419 }
420}
421
422#[derive(Clone, Debug)]
424#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
425#[cfg_attr(
426 any(test, feature = "serde"),
427 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
428)]
429pub enum CompoundCommand {
430 Arithmetic(ArithmeticCommand),
432 ArithmeticForClause(ArithmeticForClauseCommand),
434 BraceGroup(BraceGroupCommand),
436 Subshell(SubshellCommand),
438 ForClause(ForClauseCommand),
440 CaseClause(CaseClauseCommand),
443 IfClause(IfClauseCommand),
445 WhileClause(WhileOrUntilClauseCommand),
447 UntilClause(WhileOrUntilClauseCommand),
449 Coprocess(CoprocessCommand),
451}
452
453impl Node for CompoundCommand {}
454
455impl SourceLocation for CompoundCommand {
456 fn location(&self) -> Option<SourceSpan> {
457 match self {
458 Self::Arithmetic(a) => a.location(),
459 Self::ArithmeticForClause(a) => a.location(),
460 Self::BraceGroup(b) => b.location(),
461 Self::Subshell(s) => s.location(),
462 Self::ForClause(f) => f.location(),
463 Self::CaseClause(c) => c.location(),
464 Self::IfClause(i) => i.location(),
465 Self::WhileClause(w) => w.location(),
466 Self::UntilClause(u) => u.location(),
467 Self::Coprocess(c) => c.location(),
468 }
469 }
470}
471
472impl Display for CompoundCommand {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 match self {
475 Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
476 Self::ArithmeticForClause(arithmetic_for_clause_command) => {
477 write!(f, "{arithmetic_for_clause_command}")
478 }
479 Self::BraceGroup(brace_group_command) => {
480 write!(f, "{brace_group_command}")
481 }
482 Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
483 Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
484 Self::CaseClause(case_clause_command) => {
485 write!(f, "{case_clause_command}")
486 }
487 Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
488 Self::WhileClause(while_or_until_clause_command) => {
489 write!(f, "while {while_or_until_clause_command}")
490 }
491 Self::UntilClause(while_or_until_clause_command) => {
492 write!(f, "until {while_or_until_clause_command}")
493 }
494 Self::Coprocess(coproc_clause_command) => {
495 write!(f, "{coproc_clause_command}")
496 }
497 }
498 }
499}
500
501#[derive(Clone, Debug)]
503#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
504#[cfg_attr(
505 any(test, feature = "serde"),
506 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
507)]
508pub struct ArithmeticCommand {
509 pub expr: UnexpandedArithmeticExpr,
511 pub loc: SourceSpan,
513}
514
515impl Node for ArithmeticCommand {}
516
517impl SourceLocation for ArithmeticCommand {
518 fn location(&self) -> Option<SourceSpan> {
519 Some(self.loc.clone())
520 }
521}
522
523impl Display for ArithmeticCommand {
524 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
525 write!(f, "(({}))", self.expr)
526 }
527}
528
529#[derive(Clone, Debug)]
531#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
532#[cfg_attr(
533 any(test, feature = "serde"),
534 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
535)]
536pub struct SubshellCommand {
537 pub list: CompoundList,
539 pub loc: SourceSpan,
541}
542
543impl Node for SubshellCommand {}
544
545impl SourceLocation for SubshellCommand {
546 fn location(&self) -> Option<SourceSpan> {
547 Some(self.loc.clone())
548 }
549}
550
551impl Display for SubshellCommand {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 write!(f, "( ")?;
554 write!(f, "{}", self.list)?;
555 write!(f, " )")
556 }
557}
558
559#[derive(Clone, Debug)]
561#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
562#[cfg_attr(
563 any(test, feature = "serde"),
564 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
565)]
566pub struct ForClauseCommand {
567 pub variable_name: String,
569 pub values: Option<Vec<Word>>,
571 pub body: DoGroupCommand,
573 pub loc: SourceSpan,
575}
576
577impl Node for ForClauseCommand {}
578
579impl SourceLocation for ForClauseCommand {
580 fn location(&self) -> Option<SourceSpan> {
581 Some(self.loc.clone())
582 }
583}
584
585impl Display for ForClauseCommand {
586 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
587 write!(f, "for {} in ", self.variable_name)?;
588
589 if let Some(values) = &self.values {
590 for (i, value) in values.iter().enumerate() {
591 if i > 0 {
592 write!(f, " ")?;
593 }
594
595 write!(f, "{value}")?;
596 }
597 }
598
599 writeln!(f, ";")?;
600
601 write!(f, "{}", self.body)
602 }
603}
604
605#[derive(Clone, Debug)]
607#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
608#[cfg_attr(
609 any(test, feature = "serde"),
610 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
611)]
612pub struct ArithmeticForClauseCommand {
613 pub initializer: Option<UnexpandedArithmeticExpr>,
615 pub condition: Option<UnexpandedArithmeticExpr>,
617 pub updater: Option<UnexpandedArithmeticExpr>,
619 pub body: DoGroupCommand,
621 pub loc: SourceSpan,
623}
624
625impl Node for ArithmeticForClauseCommand {}
626
627impl SourceLocation for ArithmeticForClauseCommand {
628 fn location(&self) -> Option<SourceSpan> {
629 Some(self.loc.clone())
630 }
631}
632
633impl Display for ArithmeticForClauseCommand {
634 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
635 write!(f, "for ((")?;
636
637 if let Some(initializer) = &self.initializer {
638 write!(f, "{initializer}")?;
639 }
640
641 write!(f, "; ")?;
642
643 if let Some(condition) = &self.condition {
644 write!(f, "{condition}")?;
645 }
646
647 write!(f, "; ")?;
648
649 if let Some(updater) = &self.updater {
650 write!(f, "{updater}")?;
651 }
652
653 writeln!(f, "))")?;
654
655 write!(f, "{}", self.body)
656 }
657}
658
659#[derive(Clone, Debug)]
662#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
663#[cfg_attr(
664 any(test, feature = "serde"),
665 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
666)]
667pub struct CaseClauseCommand {
668 pub value: Word,
670 pub cases: Vec<CaseItem>,
672 pub loc: SourceSpan,
674}
675
676impl Node for CaseClauseCommand {}
677
678impl SourceLocation for CaseClauseCommand {
679 fn location(&self) -> Option<SourceSpan> {
680 Some(self.loc.clone())
681 }
682}
683
684impl Display for CaseClauseCommand {
685 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686 write!(f, "case {} in", self.value)?;
687 for case in &self.cases {
688 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
689 }
690 writeln!(f)?;
691 write!(f, "esac")
692 }
693}
694
695#[derive(Clone, Debug)]
697#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
698#[cfg_attr(
699 any(test, feature = "serde"),
700 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
701)]
702pub struct CompoundList(pub Vec<CompoundListItem>);
703
704impl Node for CompoundList {}
705
706impl SourceLocation for CompoundList {
708 fn location(&self) -> Option<SourceSpan> {
709 let start = self.0.first().and_then(SourceLocation::location);
710 let end = self.0.last().and_then(SourceLocation::location);
711
712 if let (Some(s), Some(e)) = (start, end) {
713 Some(SourceSpan::within(&s, &e))
714 } else {
715 None
716 }
717 }
718}
719
720impl Display for CompoundList {
721 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722 for (i, item) in self.0.iter().enumerate() {
723 if i > 0 {
724 writeln!(f)?;
725 }
726
727 write!(f, "{}", item.0)?;
729
730 if i == self.0.len() - 1 && matches!(item.1, SeparatorOperator::Sequence) {
732 } else {
734 write!(f, "{}", item.1)?;
735 }
736 }
737
738 Ok(())
739 }
740}
741
742#[derive(Clone, Debug)]
744#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
745#[cfg_attr(
746 any(test, feature = "serde"),
747 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
748)]
749pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
750
751impl Node for CompoundListItem {}
752
753impl SourceLocation for CompoundListItem {
755 fn location(&self) -> Option<SourceSpan> {
756 self.0.location()
757 }
758}
759
760impl Display for CompoundListItem {
761 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762 write!(f, "{}", self.0)?;
763 write!(f, "{}", self.1)?;
764 Ok(())
765 }
766}
767
768#[derive(Clone, Debug)]
770#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
771#[cfg_attr(
772 any(test, feature = "serde"),
773 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
774)]
775pub struct IfClauseCommand {
776 pub condition: CompoundList,
778 pub then: CompoundList,
780 #[cfg_attr(
782 any(test, feature = "serde"),
783 serde(skip_serializing_if = "Option::is_none", default)
784 )]
785 pub elses: Option<Vec<ElseClause>>,
786 pub loc: SourceSpan,
788}
789
790impl Node for IfClauseCommand {}
791
792impl SourceLocation for IfClauseCommand {
793 fn location(&self) -> Option<SourceSpan> {
794 Some(self.loc.clone())
795 }
796}
797
798impl Display for IfClauseCommand {
799 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
800 writeln!(f, "if {}; then", self.condition)?;
801 write!(
802 indenter::indented(f).with_str(DISPLAY_INDENT),
803 "{}",
804 self.then
805 )?;
806 if let Some(elses) = &self.elses {
807 for else_clause in elses {
808 write!(f, "{else_clause}")?;
809 }
810 }
811
812 writeln!(f)?;
813 write!(f, "fi")?;
814
815 Ok(())
816 }
817}
818
819#[derive(Clone, Debug)]
821#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
822#[cfg_attr(
823 any(test, feature = "serde"),
824 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
825)]
826pub struct ElseClause {
827 #[cfg_attr(
829 any(test, feature = "serde"),
830 serde(skip_serializing_if = "Option::is_none", default)
831 )]
832 pub condition: Option<CompoundList>,
833 pub body: CompoundList,
835}
836
837impl Display for ElseClause {
838 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
839 writeln!(f)?;
840 if let Some(condition) = &self.condition {
841 writeln!(f, "elif {condition}; then")?;
842 } else {
843 writeln!(f, "else")?;
844 }
845
846 write!(
847 indenter::indented(f).with_str(DISPLAY_INDENT),
848 "{}",
849 self.body
850 )
851 }
852}
853
854#[derive(Clone, Debug)]
856#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
857#[cfg_attr(
858 any(test, feature = "serde"),
859 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
860)]
861pub struct CoprocessCommand {
862 #[cfg_attr(
864 any(test, feature = "serde"),
865 serde(skip_serializing_if = "Option::is_none", default)
866 )]
867 pub name: Option<Word>,
868 pub body: Box<Command>,
870 #[cfg_attr(any(test, feature = "serde"), serde(skip_serializing, default))]
872 pub loc: SourceSpan,
873}
874
875impl Node for CoprocessCommand {}
876
877impl SourceLocation for CoprocessCommand {
878 fn location(&self) -> Option<SourceSpan> {
879 Some(self.loc.clone())
880 }
881}
882
883impl Display for CoprocessCommand {
884 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
885 write!(f, "coproc")?;
886 if let Some(name) = &self.name {
887 write!(f, " {name}")?;
888 }
889 write!(f, " {}", self.body)?;
890 Ok(())
891 }
892}
893
894#[derive(Clone, Debug)]
896#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
897#[cfg_attr(
898 any(test, feature = "serde"),
899 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
900)]
901pub struct CaseItem {
902 pub patterns: Vec<Word>,
904 pub cmd: Option<CompoundList>,
906 pub post_action: CaseItemPostAction,
908 pub loc: Option<SourceSpan>,
910}
911
912impl Node for CaseItem {}
913
914impl SourceLocation for CaseItem {
915 fn location(&self) -> Option<SourceSpan> {
916 self.loc.clone()
917 }
918}
919
920impl Display for CaseItem {
921 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
922 writeln!(f)?;
923 for (i, pattern) in self.patterns.iter().enumerate() {
924 if i > 0 {
925 write!(f, "|")?;
926 }
927 write!(f, "{pattern}")?;
928 }
929 writeln!(f, ")")?;
930
931 if let Some(cmd) = &self.cmd {
932 write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
933 }
934 writeln!(f)?;
935 write!(f, "{}", self.post_action)
936 }
937}
938
939#[derive(Clone, Debug)]
941#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
942#[cfg_attr(
943 any(test, feature = "serde"),
944 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
945)]
946pub enum CaseItemPostAction {
947 ExitCase,
949 UnconditionallyExecuteNextCaseItem,
952 ContinueEvaluatingCases,
955}
956
957impl Display for CaseItemPostAction {
958 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
959 match self {
960 Self::ExitCase => write!(f, ";;"),
961 Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
962 Self::ContinueEvaluatingCases => write!(f, ";;&"),
963 }
964 }
965}
966
967#[derive(Clone, Debug)]
969#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
970#[cfg_attr(
971 any(test, feature = "serde"),
972 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
973)]
974pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand, pub SourceSpan);
975
976impl Node for WhileOrUntilClauseCommand {}
977
978impl SourceLocation for WhileOrUntilClauseCommand {
979 fn location(&self) -> Option<SourceSpan> {
980 Some(self.2.clone())
981 }
982}
983
984impl Display for WhileOrUntilClauseCommand {
985 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
986 write!(f, "{}; {}", self.0, self.1)
987 }
988}
989
990#[derive(Clone, Debug)]
992#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
993#[cfg_attr(
994 any(test, feature = "serde"),
995 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
996)]
997pub struct FunctionDefinition {
998 pub fname: Word,
1000 pub body: FunctionBody,
1002}
1003
1004impl Node for FunctionDefinition {}
1005
1006impl SourceLocation for FunctionDefinition {
1009 fn location(&self) -> Option<SourceSpan> {
1010 let start = self.fname.location();
1011 let end = self.body.location();
1012
1013 if let (Some(s), Some(e)) = (start, end) {
1014 Some(SourceSpan::within(&s, &e))
1015 } else {
1016 None
1017 }
1018 }
1019}
1020
1021impl Display for FunctionDefinition {
1022 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1023 writeln!(f, "{} () ", self.fname.value)?;
1024 write!(f, "{}", self.body)?;
1025 Ok(())
1026 }
1027}
1028
1029#[derive(Clone, Debug)]
1031#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1032#[cfg_attr(
1033 any(test, feature = "serde"),
1034 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1035)]
1036pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
1037
1038impl Node for FunctionBody {}
1039
1040impl SourceLocation for FunctionBody {
1041 fn location(&self) -> Option<SourceSpan> {
1042 let cmd_span = self.0.location();
1043 let redirect_span = self.1.as_ref().and_then(SourceLocation::location);
1044
1045 match (cmd_span, redirect_span) {
1046 (Some(cmd_span), Some(redirect_span)) => {
1048 Some(SourceSpan::within(&cmd_span, &redirect_span))
1049 }
1050 (Some(cmd_span), None) => Some(cmd_span),
1052 _ => None,
1053 }
1054 }
1055}
1056
1057impl Display for FunctionBody {
1058 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1059 write!(f, "{}", self.0)?;
1060 if let Some(redirect_list) = &self.1 {
1061 write!(f, "{redirect_list}")?;
1062 }
1063
1064 Ok(())
1065 }
1066}
1067
1068#[derive(Clone, Debug)]
1070#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1071#[cfg_attr(
1072 any(test, feature = "serde"),
1073 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1074)]
1075pub struct BraceGroupCommand {
1076 pub list: CompoundList,
1078 pub loc: SourceSpan,
1080}
1081
1082impl Node for BraceGroupCommand {}
1083
1084impl SourceLocation for BraceGroupCommand {
1085 fn location(&self) -> Option<SourceSpan> {
1086 Some(self.loc.clone())
1087 }
1088}
1089
1090impl Display for BraceGroupCommand {
1091 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1092 writeln!(f, "{{ ")?;
1093 write!(
1094 indenter::indented(f).with_str(DISPLAY_INDENT),
1095 "{}",
1096 self.list
1097 )?;
1098 writeln!(f)?;
1099 write!(f, "}}")?;
1100
1101 Ok(())
1102 }
1103}
1104
1105#[derive(Clone, Debug)]
1107#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1108#[cfg_attr(
1109 any(test, feature = "serde"),
1110 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1111)]
1112pub struct DoGroupCommand {
1113 pub list: CompoundList,
1115 pub loc: SourceSpan,
1117}
1118
1119impl Display for DoGroupCommand {
1120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1121 writeln!(f, "do")?;
1122 write!(
1123 indenter::indented(f).with_str(DISPLAY_INDENT),
1124 "{}",
1125 self.list
1126 )?;
1127 writeln!(f)?;
1128 write!(f, "done")
1129 }
1130}
1131
1132#[derive(Clone, Debug)]
1134#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1135#[cfg_attr(
1136 any(test, feature = "serde"),
1137 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1138)]
1139pub struct SimpleCommand {
1140 #[cfg_attr(
1142 any(test, feature = "serde"),
1143 serde(skip_serializing_if = "Option::is_none", default)
1144 )]
1145 pub prefix: Option<CommandPrefix>,
1146 #[cfg_attr(
1148 any(test, feature = "serde"),
1149 serde(skip_serializing_if = "Option::is_none", default)
1150 )]
1151 pub word_or_name: Option<Word>,
1152 #[cfg_attr(
1154 any(test, feature = "serde"),
1155 serde(skip_serializing_if = "Option::is_none", default)
1156 )]
1157 pub suffix: Option<CommandSuffix>,
1158}
1159
1160impl Node for SimpleCommand {}
1161
1162impl SourceLocation for SimpleCommand {
1163 fn location(&self) -> Option<SourceSpan> {
1164 let mid = &self
1165 .word_or_name
1166 .as_ref()
1167 .and_then(SourceLocation::location);
1168 let start = self.prefix.as_ref().and_then(SourceLocation::location);
1169 let end = self.suffix.as_ref().and_then(SourceLocation::location);
1170
1171 match (start, mid, end) {
1172 (Some(start), _, Some(end)) => Some(SourceSpan::within(&start, &end)),
1173 (Some(start), Some(mid), None) => Some(SourceSpan::within(&start, mid)),
1174 (Some(start), None, None) => Some(start),
1175 (None, Some(mid), Some(end)) => Some(SourceSpan::within(mid, &end)),
1176 (None, Some(mid), None) => Some(mid.clone()),
1177 _ => None,
1178 }
1179 }
1180}
1181
1182impl Display for SimpleCommand {
1183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1184 let mut wrote_something = false;
1185
1186 if let Some(prefix) = &self.prefix {
1187 if wrote_something {
1188 write!(f, " ")?;
1189 }
1190
1191 write!(f, "{prefix}")?;
1192 wrote_something = true;
1193 }
1194
1195 if let Some(word_or_name) = &self.word_or_name {
1196 if wrote_something {
1197 write!(f, " ")?;
1198 }
1199
1200 write!(f, "{word_or_name}")?;
1201 wrote_something = true;
1202 }
1203
1204 if let Some(suffix) = &self.suffix {
1205 if wrote_something {
1206 write!(f, " ")?;
1207 }
1208
1209 write!(f, "{suffix}")?;
1210 }
1211
1212 Ok(())
1213 }
1214}
1215
1216#[derive(Clone, Debug, Default)]
1218#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1219#[cfg_attr(
1220 any(test, feature = "serde"),
1221 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1222)]
1223pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
1224
1225impl Node for CommandPrefix {}
1226
1227impl SourceLocation for CommandPrefix {
1228 fn location(&self) -> Option<SourceSpan> {
1229 let start = self.0.first().and_then(SourceLocation::location);
1230 let end = self.0.last().and_then(SourceLocation::location);
1231
1232 maybe_location(start.as_ref(), end.as_ref())
1233 }
1234}
1235
1236impl Display for CommandPrefix {
1237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1238 for (i, item) in self.0.iter().enumerate() {
1239 if i > 0 {
1240 write!(f, " ")?;
1241 }
1242
1243 write!(f, "{item}")?;
1244 }
1245 Ok(())
1246 }
1247}
1248
1249#[derive(Clone, Default, Debug)]
1251#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1252#[cfg_attr(
1253 any(test, feature = "serde"),
1254 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1255)]
1256pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
1257
1258impl Node for CommandSuffix {}
1259
1260impl SourceLocation for CommandSuffix {
1261 fn location(&self) -> Option<SourceSpan> {
1262 let start = self.0.first().and_then(SourceLocation::location);
1263 let end = self.0.last().and_then(SourceLocation::location);
1264
1265 maybe_location(start.as_ref(), end.as_ref())
1266 }
1267}
1268
1269impl Display for CommandSuffix {
1270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1271 for (i, item) in self.0.iter().enumerate() {
1272 if i > 0 {
1273 write!(f, " ")?;
1274 }
1275
1276 write!(f, "{item}")?;
1277 }
1278 Ok(())
1279 }
1280}
1281
1282#[derive(Clone, Debug)]
1284#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1285#[cfg_attr(
1286 any(test, feature = "serde"),
1287 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1288)]
1289pub enum ProcessSubstitutionKind {
1290 Read,
1292 Write,
1294}
1295
1296impl Display for ProcessSubstitutionKind {
1297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1298 match self {
1299 Self::Read => write!(f, "<"),
1300 Self::Write => write!(f, ">"),
1301 }
1302 }
1303}
1304
1305#[derive(Clone, Debug)]
1307#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1308#[cfg_attr(
1309 any(test, feature = "serde"),
1310 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1311)]
1312pub enum CommandPrefixOrSuffixItem {
1313 IoRedirect(IoRedirect),
1315 Word(Word),
1317 AssignmentWord(Assignment, Word),
1319 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1321}
1322
1323impl Node for CommandPrefixOrSuffixItem {}
1324
1325impl SourceLocation for CommandPrefixOrSuffixItem {
1326 fn location(&self) -> Option<SourceSpan> {
1327 match self {
1328 Self::Word(w) => w.location(),
1329 Self::IoRedirect(io_redirect) => io_redirect.location(),
1330 Self::AssignmentWord(assignment, _word) => assignment.location(),
1331 Self::ProcessSubstitution(_kind, cmd) => cmd.location(),
1333 }
1334 }
1335}
1336
1337impl Display for CommandPrefixOrSuffixItem {
1338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1339 match self {
1340 Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
1341 Self::Word(word) => write!(f, "{word}"),
1342 Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
1343 Self::ProcessSubstitution(kind, subshell_command) => {
1344 write!(f, "{kind}({subshell_command})")
1345 }
1346 }
1347 }
1348}
1349
1350#[derive(Clone, Debug)]
1352#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1353#[cfg_attr(
1354 any(test, feature = "serde"),
1355 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1356)]
1357pub struct Assignment {
1358 pub name: AssignmentName,
1360 pub value: AssignmentValue,
1362 #[cfg_attr(
1364 any(test, feature = "serde"),
1365 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1366 )]
1367 pub append: bool,
1368 pub loc: SourceSpan,
1370}
1371
1372impl Node for Assignment {}
1373
1374impl SourceLocation for Assignment {
1375 fn location(&self) -> Option<SourceSpan> {
1376 Some(self.loc.clone())
1377 }
1378}
1379
1380impl Display for Assignment {
1381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1382 write!(f, "{}", self.name)?;
1383 if self.append {
1384 write!(f, "+")?;
1385 }
1386 write!(f, "={}", self.value)
1387 }
1388}
1389
1390#[derive(Clone, Debug)]
1392#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1393#[cfg_attr(
1394 any(test, feature = "serde"),
1395 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1396)]
1397pub enum AssignmentName {
1398 VariableName(String),
1400 ArrayElementName(String, String),
1402}
1403
1404impl Display for AssignmentName {
1405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1406 match self {
1407 Self::VariableName(name) => write!(f, "{name}"),
1408 Self::ArrayElementName(name, index) => {
1409 write!(f, "{name}[{index}]")
1410 }
1411 }
1412 }
1413}
1414
1415#[derive(Clone, Debug)]
1417#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1418#[cfg_attr(
1419 any(test, feature = "serde"),
1420 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1421)]
1422pub enum AssignmentValue {
1423 Scalar(Word),
1425 Array(Vec<(Option<Word>, Word)>),
1427}
1428
1429impl Node for AssignmentValue {}
1430
1431impl SourceLocation for AssignmentValue {
1432 fn location(&self) -> Option<SourceSpan> {
1433 match self {
1434 Self::Scalar(word) => word.location(),
1435 Self::Array(words) => {
1436 let first = words.first().and_then(|(_key, value)| value.location());
1438 let last = words.last().and_then(|(_key, value)| value.location());
1439 maybe_location(first.as_ref(), last.as_ref())
1440 }
1441 }
1442 }
1443}
1444
1445impl Display for AssignmentValue {
1446 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1447 match self {
1448 Self::Scalar(word) => write!(f, "{word}"),
1449 Self::Array(words) => {
1450 write!(f, "(")?;
1451 for (i, value) in words.iter().enumerate() {
1452 if i > 0 {
1453 write!(f, " ")?;
1454 }
1455 match value {
1456 (Some(key), value) => write!(f, "[{key}]={value}")?,
1457 (None, value) => write!(f, "{value}")?,
1458 }
1459 }
1460 write!(f, ")")
1461 }
1462 }
1463 }
1464}
1465
1466#[derive(Clone, Debug)]
1468#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1469#[cfg_attr(
1470 any(test, feature = "serde"),
1471 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1472)]
1473pub struct RedirectList(pub Vec<IoRedirect>);
1474
1475impl Node for RedirectList {}
1476
1477impl SourceLocation for RedirectList {
1478 fn location(&self) -> Option<SourceSpan> {
1479 let first = self.0.first().and_then(SourceLocation::location);
1480 let last = self.0.last().and_then(SourceLocation::location);
1481 maybe_location(first.as_ref(), last.as_ref())
1482 }
1483}
1484
1485impl Display for RedirectList {
1486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1487 for item in &self.0 {
1488 write!(f, "{item}")?;
1489 }
1490 Ok(())
1491 }
1492}
1493
1494pub type IoFd = i32;
1496
1497#[derive(Clone, Debug)]
1499#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1500#[cfg_attr(
1501 any(test, feature = "serde"),
1502 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1503)]
1504pub enum IoRedirect {
1505 File(Option<IoFd>, IoFileRedirectKind, IoFileRedirectTarget),
1507 HereDocument(Option<IoFd>, IoHereDocument),
1509 HereString(Option<IoFd>, Word),
1511 OutputAndError(Word, bool),
1513}
1514
1515impl Node for IoRedirect {}
1516
1517impl SourceLocation for IoRedirect {
1518 fn location(&self) -> Option<SourceSpan> {
1519 None
1521 }
1522}
1523
1524impl Display for IoRedirect {
1525 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1526 match self {
1527 Self::File(fd_num, kind, target) => {
1528 if let Some(fd_num) = fd_num {
1529 write!(f, "{fd_num}")?;
1530 }
1531
1532 write!(f, "{kind} {target}")?;
1533 }
1534 Self::OutputAndError(target, append) => {
1535 write!(f, "&>")?;
1536 if *append {
1537 write!(f, ">")?;
1538 }
1539 write!(f, " {target}")?;
1540 }
1541 Self::HereDocument(fd_num, here_doc) => {
1542 if let Some(fd_num) = fd_num {
1543 write!(f, "{fd_num}")?;
1544 }
1545
1546 write!(f, "<<{here_doc}")?;
1547 }
1548 Self::HereString(fd_num, s) => {
1549 if let Some(fd_num) = fd_num {
1550 write!(f, "{fd_num}")?;
1551 }
1552
1553 write!(f, "<<< {s}")?;
1554 }
1555 }
1556
1557 Ok(())
1558 }
1559}
1560
1561#[derive(Clone, Debug)]
1563#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1564#[cfg_attr(
1565 any(test, feature = "serde"),
1566 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1567)]
1568pub enum IoFileRedirectKind {
1569 Read,
1571 Write,
1573 Append,
1575 ReadAndWrite,
1577 Clobber,
1579 DuplicateInput,
1581 DuplicateOutput,
1583}
1584
1585impl Display for IoFileRedirectKind {
1586 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1587 match self {
1588 Self::Read => write!(f, "<"),
1589 Self::Write => write!(f, ">"),
1590 Self::Append => write!(f, ">>"),
1591 Self::ReadAndWrite => write!(f, "<>"),
1592 Self::Clobber => write!(f, ">|"),
1593 Self::DuplicateInput => write!(f, "<&"),
1594 Self::DuplicateOutput => write!(f, ">&"),
1595 }
1596 }
1597}
1598
1599#[derive(Clone, Debug)]
1601#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1602#[cfg_attr(
1603 any(test, feature = "serde"),
1604 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1605)]
1606pub enum IoFileRedirectTarget {
1607 Filename(Word),
1609 Fd(IoFd),
1611 ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
1614 Duplicate(Word),
1618}
1619
1620impl Display for IoFileRedirectTarget {
1621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1622 match self {
1623 Self::Filename(word) => write!(f, "{word}"),
1624 Self::Fd(fd) => write!(f, "{fd}"),
1625 Self::ProcessSubstitution(kind, subshell_command) => {
1626 write!(f, "{kind}{subshell_command}")
1627 }
1628 Self::Duplicate(word) => write!(f, "{word}"),
1629 }
1630 }
1631}
1632
1633#[derive(Clone, Debug)]
1635#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1636#[cfg_attr(
1637 any(test, feature = "serde"),
1638 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1639)]
1640pub struct IoHereDocument {
1641 #[cfg_attr(
1643 any(test, feature = "serde"),
1644 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1645 )]
1646 pub remove_tabs: bool,
1647 #[cfg_attr(
1649 any(test, feature = "serde"),
1650 serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
1651 )]
1652 pub requires_expansion: bool,
1653 pub here_end: Word,
1655 pub doc: Word,
1657}
1658
1659impl Node for IoHereDocument {}
1660
1661impl SourceLocation for IoHereDocument {
1662 fn location(&self) -> Option<SourceSpan> {
1663 None
1665 }
1666}
1667
1668impl Display for IoHereDocument {
1669 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1670 if self.remove_tabs {
1671 write!(f, "-")?;
1672 }
1673
1674 writeln!(f, "{}", self.here_end)?;
1675 write!(f, "{}", self.doc)?;
1676 writeln!(f, "{}", self.here_end)?;
1677
1678 Ok(())
1679 }
1680}
1681
1682#[derive(Clone, Debug)]
1684#[cfg_attr(
1685 any(test, feature = "serde"),
1686 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1687)]
1688pub enum TestExpr {
1689 False,
1691 Literal(String),
1693 And(Box<Self>, Box<Self>),
1695 Or(Box<Self>, Box<Self>),
1697 Not(Box<Self>),
1699 Parenthesized(Box<Self>),
1701 UnaryTest(UnaryPredicate, String),
1703 BinaryTest(BinaryPredicate, String, String),
1705}
1706
1707impl Node for TestExpr {}
1708
1709impl SourceLocation for TestExpr {
1710 fn location(&self) -> Option<SourceSpan> {
1711 None
1713 }
1714}
1715
1716impl Display for TestExpr {
1717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1718 match self {
1719 Self::False => Ok(()),
1720 Self::Literal(s) => write!(f, "{s}"),
1721 Self::And(left, right) => write!(f, "{left} -a {right}"),
1722 Self::Or(left, right) => write!(f, "{left} -o {right}"),
1723 Self::Not(expr) => write!(f, "! {expr}"),
1724 Self::Parenthesized(expr) => write!(f, "( {expr} )"),
1725 Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
1726 Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
1727 }
1728 }
1729}
1730
1731#[derive(Clone, Debug)]
1733#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1734#[cfg_attr(
1735 any(test, feature = "serde"),
1736 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1737)]
1738pub enum ExtendedTestExpr {
1739 And(Box<Self>, Box<Self>),
1741 Or(Box<Self>, Box<Self>),
1743 Not(Box<Self>),
1745 Parenthesized(Box<Self>),
1747 UnaryTest(UnaryPredicate, Word),
1749 BinaryTest(BinaryPredicate, Word, Word),
1751}
1752
1753impl Display for ExtendedTestExpr {
1754 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1755 match self {
1756 Self::And(left, right) => {
1757 write!(f, "{left} && {right}")
1758 }
1759 Self::Or(left, right) => {
1760 write!(f, "{left} || {right}")
1761 }
1762 Self::Not(expr) => {
1763 write!(f, "! {expr}")
1764 }
1765 Self::Parenthesized(expr) => {
1766 write!(f, "( {expr} )")
1767 }
1768 Self::UnaryTest(pred, word) => {
1769 write!(f, "{pred} {word}")
1770 }
1771 Self::BinaryTest(pred, left, right) => {
1772 write!(f, "{left} {pred} {right}")
1773 }
1774 }
1775 }
1776}
1777
1778#[derive(Clone, Debug)]
1780#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1781#[cfg_attr(
1782 any(test, feature = "serde"),
1783 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1784)]
1785pub struct ExtendedTestExprCommand {
1786 pub expr: ExtendedTestExpr,
1788 pub loc: SourceSpan,
1790}
1791
1792impl Node for ExtendedTestExprCommand {}
1793
1794impl SourceLocation for ExtendedTestExprCommand {
1795 fn location(&self) -> Option<SourceSpan> {
1796 Some(self.loc.clone())
1797 }
1798}
1799
1800impl Display for ExtendedTestExprCommand {
1801 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1802 self.expr.fmt(f)
1803 }
1804}
1805
1806#[derive(Clone, Debug)]
1808#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1809#[cfg_attr(
1810 any(test, feature = "serde"),
1811 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1812)]
1813pub enum UnaryPredicate {
1814 FileExists,
1816 FileExistsAndIsBlockSpecialFile,
1818 FileExistsAndIsCharSpecialFile,
1820 FileExistsAndIsDir,
1822 FileExistsAndIsRegularFile,
1824 FileExistsAndIsSetgid,
1826 FileExistsAndIsSymlink,
1828 FileExistsAndHasStickyBit,
1830 FileExistsAndIsFifo,
1832 FileExistsAndIsReadable,
1834 FileExistsAndIsNotZeroLength,
1836 FdIsOpenTerminal,
1838 FileExistsAndIsSetuid,
1840 FileExistsAndIsWritable,
1842 FileExistsAndIsExecutable,
1844 FileExistsAndOwnedByEffectiveGroupId,
1847 FileExistsAndModifiedSinceLastRead,
1850 FileExistsAndOwnedByEffectiveUserId,
1853 FileExistsAndIsSocket,
1855 ShellOptionEnabled,
1857 ShellVariableIsSetAndAssigned,
1859 ShellVariableIsSetAndNameRef,
1861 StringHasZeroLength,
1863 StringHasNonZeroLength,
1865}
1866
1867impl Display for UnaryPredicate {
1868 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1869 match self {
1870 Self::FileExists => write!(f, "-e"),
1871 Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
1872 Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
1873 Self::FileExistsAndIsDir => write!(f, "-d"),
1874 Self::FileExistsAndIsRegularFile => write!(f, "-f"),
1875 Self::FileExistsAndIsSetgid => write!(f, "-g"),
1876 Self::FileExistsAndIsSymlink => write!(f, "-h"),
1877 Self::FileExistsAndHasStickyBit => write!(f, "-k"),
1878 Self::FileExistsAndIsFifo => write!(f, "-p"),
1879 Self::FileExistsAndIsReadable => write!(f, "-r"),
1880 Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
1881 Self::FdIsOpenTerminal => write!(f, "-t"),
1882 Self::FileExistsAndIsSetuid => write!(f, "-u"),
1883 Self::FileExistsAndIsWritable => write!(f, "-w"),
1884 Self::FileExistsAndIsExecutable => write!(f, "-x"),
1885 Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
1886 Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
1887 Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
1888 Self::FileExistsAndIsSocket => write!(f, "-S"),
1889 Self::ShellOptionEnabled => write!(f, "-o"),
1890 Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
1891 Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
1892 Self::StringHasZeroLength => write!(f, "-z"),
1893 Self::StringHasNonZeroLength => write!(f, "-n"),
1894 }
1895 }
1896}
1897
1898#[derive(Clone, Debug)]
1900#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1901#[cfg_attr(
1902 any(test, feature = "serde"),
1903 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1904)]
1905pub enum BinaryPredicate {
1906 FilesReferToSameDeviceAndInodeNumbers,
1908 LeftFileIsNewerOrExistsWhenRightDoesNot,
1910 LeftFileIsOlderOrDoesNotExistWhenRightDoes,
1912 StringExactlyMatchesPattern,
1914 StringDoesNotExactlyMatchPattern,
1916 StringMatchesRegex,
1918 StringExactlyMatchesString,
1920 StringDoesNotExactlyMatchString,
1922 StringContainsSubstring,
1924 LeftSortsBeforeRight,
1926 LeftSortsAfterRight,
1928 ArithmeticEqualTo,
1930 ArithmeticNotEqualTo,
1932 ArithmeticLessThan,
1934 ArithmeticLessThanOrEqualTo,
1936 ArithmeticGreaterThan,
1938 ArithmeticGreaterThanOrEqualTo,
1940}
1941
1942impl Display for BinaryPredicate {
1943 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1944 match self {
1945 Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
1946 Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
1947 Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
1948 Self::StringExactlyMatchesPattern => write!(f, "=="),
1949 Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
1950 Self::StringMatchesRegex => write!(f, "=~"),
1951 Self::StringContainsSubstring => write!(f, "=~"),
1952 Self::StringExactlyMatchesString => write!(f, "=="),
1953 Self::StringDoesNotExactlyMatchString => write!(f, "!="),
1954 Self::LeftSortsBeforeRight => write!(f, "<"),
1955 Self::LeftSortsAfterRight => write!(f, ">"),
1956 Self::ArithmeticEqualTo => write!(f, "-eq"),
1957 Self::ArithmeticNotEqualTo => write!(f, "-ne"),
1958 Self::ArithmeticLessThan => write!(f, "-lt"),
1959 Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
1960 Self::ArithmeticGreaterThan => write!(f, "-gt"),
1961 Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
1962 }
1963 }
1964}
1965
1966#[derive(Clone, Debug)]
1968#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1969#[cfg_attr(
1970 any(test, feature = "serde"),
1971 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
1972)]
1973pub struct Word {
1974 pub value: String,
1976 pub loc: Option<SourceSpan>,
1978}
1979
1980impl Node for Word {}
1981
1982impl SourceLocation for Word {
1983 fn location(&self) -> Option<SourceSpan> {
1984 self.loc.clone()
1985 }
1986}
1987
1988impl Display for Word {
1989 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1990 write!(f, "{}", self.value)
1991 }
1992}
1993
1994impl From<&tokenizer::Token> for Word {
1995 fn from(t: &tokenizer::Token) -> Self {
1996 match t {
1997 tokenizer::Token::Word(value, loc) => Self {
1998 value: value.clone(),
1999 loc: Some(loc.clone()),
2000 },
2001 tokenizer::Token::Operator(value, loc) => Self {
2002 value: value.clone(),
2003 loc: Some(loc.clone()),
2004 },
2005 }
2006 }
2007}
2008
2009impl From<String> for Word {
2010 fn from(s: String) -> Self {
2011 Self {
2012 value: s,
2013 loc: None,
2014 }
2015 }
2016}
2017
2018impl AsRef<str> for Word {
2019 fn as_ref(&self) -> &str {
2020 &self.value
2021 }
2022}
2023
2024impl Word {
2025 pub fn new(s: &str) -> Self {
2027 Self {
2028 value: s.to_owned(),
2029 loc: None,
2030 }
2031 }
2032
2033 pub fn with_location(s: &str, loc: &SourceSpan) -> Self {
2035 Self {
2036 value: s.to_owned(),
2037 loc: Some(loc.to_owned()),
2038 }
2039 }
2040
2041 pub fn flatten(&self) -> String {
2043 self.value.clone()
2044 }
2045}
2046
2047#[derive(Clone, Debug)]
2049#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2050#[cfg_attr(
2051 any(test, feature = "serde"),
2052 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2053)]
2054pub struct UnexpandedArithmeticExpr {
2055 pub value: String,
2057}
2058
2059impl Display for UnexpandedArithmeticExpr {
2060 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2061 write!(f, "{}", self.value)
2062 }
2063}
2064
2065#[derive(Clone, Debug)]
2067#[cfg_attr(
2068 any(test, feature = "serde"),
2069 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2070)]
2071pub enum ArithmeticExpr {
2072 Literal(i64),
2074 Reference(ArithmeticTarget),
2076 UnaryOp(UnaryOperator, Box<Self>),
2078 BinaryOp(BinaryOperator, Box<Self>, Box<Self>),
2080 Conditional(Box<Self>, Box<Self>, Box<Self>),
2082 Assignment(ArithmeticTarget, Box<Self>),
2084 BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<Self>),
2086 UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
2088}
2089
2090impl Node for ArithmeticExpr {}
2091
2092impl SourceLocation for ArithmeticExpr {
2093 fn location(&self) -> Option<SourceSpan> {
2094 None
2096 }
2097}
2098
2099#[cfg(feature = "arbitrary")]
2100impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
2101 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2102 let variant = u.choose(&[
2103 "Literal",
2104 "Reference",
2105 "UnaryOp",
2106 "BinaryOp",
2107 "Conditional",
2108 "Assignment",
2109 "BinaryAssignment",
2110 "UnaryAssignment",
2111 ])?;
2112
2113 match *variant {
2114 "Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
2115 "Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
2116 "UnaryOp" => Ok(Self::UnaryOp(
2117 UnaryOperator::arbitrary(u)?,
2118 Box::new(Self::arbitrary(u)?),
2119 )),
2120 "BinaryOp" => Ok(Self::BinaryOp(
2121 BinaryOperator::arbitrary(u)?,
2122 Box::new(Self::arbitrary(u)?),
2123 Box::new(Self::arbitrary(u)?),
2124 )),
2125 "Conditional" => Ok(Self::Conditional(
2126 Box::new(Self::arbitrary(u)?),
2127 Box::new(Self::arbitrary(u)?),
2128 Box::new(Self::arbitrary(u)?),
2129 )),
2130 "Assignment" => Ok(Self::Assignment(
2131 ArithmeticTarget::arbitrary(u)?,
2132 Box::new(Self::arbitrary(u)?),
2133 )),
2134 "BinaryAssignment" => Ok(Self::BinaryAssignment(
2135 BinaryOperator::arbitrary(u)?,
2136 ArithmeticTarget::arbitrary(u)?,
2137 Box::new(Self::arbitrary(u)?),
2138 )),
2139 "UnaryAssignment" => Ok(Self::UnaryAssignment(
2140 UnaryAssignmentOperator::arbitrary(u)?,
2141 ArithmeticTarget::arbitrary(u)?,
2142 )),
2143 _ => unreachable!(),
2144 }
2145 }
2146}
2147
2148impl Display for ArithmeticExpr {
2149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2150 match self {
2151 Self::Literal(literal) => write!(f, "{literal}"),
2152 Self::Reference(target) => write!(f, "{target}"),
2153 Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
2154 Self::BinaryOp(op, left, right) => {
2155 if matches!(op, BinaryOperator::Comma) {
2156 write!(f, "{left}{op} {right}")
2157 } else {
2158 write!(f, "{left} {op} {right}")
2159 }
2160 }
2161 Self::Conditional(condition, if_branch, else_branch) => {
2162 write!(f, "{condition} ? {if_branch} : {else_branch}")
2163 }
2164 Self::Assignment(target, value) => write!(f, "{target} = {value}"),
2165 Self::BinaryAssignment(op, target, operand) => {
2166 write!(f, "{target} {op}= {operand}")
2167 }
2168 Self::UnaryAssignment(op, target) => match op {
2169 UnaryAssignmentOperator::PrefixIncrement
2170 | UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
2171 UnaryAssignmentOperator::PostfixIncrement
2172 | UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
2173 },
2174 }
2175 }
2176}
2177
2178#[derive(Clone, Copy, Debug)]
2180#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2181#[cfg_attr(
2182 any(test, feature = "serde"),
2183 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2184)]
2185pub enum BinaryOperator {
2186 Power,
2188 Multiply,
2190 Divide,
2192 Modulo,
2194 Comma,
2196 Add,
2198 Subtract,
2200 ShiftLeft,
2202 ShiftRight,
2204 LessThan,
2206 LessThanOrEqualTo,
2208 GreaterThan,
2210 GreaterThanOrEqualTo,
2212 Equals,
2214 NotEquals,
2216 BitwiseAnd,
2218 BitwiseXor,
2220 BitwiseOr,
2222 LogicalAnd,
2224 LogicalOr,
2226}
2227
2228impl Display for BinaryOperator {
2229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2230 match self {
2231 Self::Power => write!(f, "**"),
2232 Self::Multiply => write!(f, "*"),
2233 Self::Divide => write!(f, "/"),
2234 Self::Modulo => write!(f, "%"),
2235 Self::Comma => write!(f, ","),
2236 Self::Add => write!(f, "+"),
2237 Self::Subtract => write!(f, "-"),
2238 Self::ShiftLeft => write!(f, "<<"),
2239 Self::ShiftRight => write!(f, ">>"),
2240 Self::LessThan => write!(f, "<"),
2241 Self::LessThanOrEqualTo => write!(f, "<="),
2242 Self::GreaterThan => write!(f, ">"),
2243 Self::GreaterThanOrEqualTo => write!(f, ">="),
2244 Self::Equals => write!(f, "=="),
2245 Self::NotEquals => write!(f, "!="),
2246 Self::BitwiseAnd => write!(f, "&"),
2247 Self::BitwiseXor => write!(f, "^"),
2248 Self::BitwiseOr => write!(f, "|"),
2249 Self::LogicalAnd => write!(f, "&&"),
2250 Self::LogicalOr => write!(f, "||"),
2251 }
2252 }
2253}
2254
2255#[derive(Clone, Copy, Debug)]
2257#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2258#[cfg_attr(
2259 any(test, feature = "serde"),
2260 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2261)]
2262pub enum UnaryOperator {
2263 UnaryPlus,
2265 UnaryMinus,
2267 BitwiseNot,
2269 LogicalNot,
2271}
2272
2273impl Display for UnaryOperator {
2274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2275 match self {
2276 Self::UnaryPlus => write!(f, "+"),
2277 Self::UnaryMinus => write!(f, "-"),
2278 Self::BitwiseNot => write!(f, "~"),
2279 Self::LogicalNot => write!(f, "!"),
2280 }
2281 }
2282}
2283
2284#[derive(Clone, Copy, Debug)]
2286#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2287#[cfg_attr(
2288 any(test, feature = "serde"),
2289 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2290)]
2291pub enum UnaryAssignmentOperator {
2292 PrefixIncrement,
2294 PrefixDecrement,
2296 PostfixIncrement,
2298 PostfixDecrement,
2300}
2301
2302impl Display for UnaryAssignmentOperator {
2303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2304 match self {
2305 Self::PrefixIncrement => write!(f, "++"),
2306 Self::PrefixDecrement => write!(f, "--"),
2307 Self::PostfixIncrement => write!(f, "++"),
2308 Self::PostfixDecrement => write!(f, "--"),
2309 }
2310 }
2311}
2312
2313#[derive(Clone, Debug)]
2315#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2316#[cfg_attr(
2317 any(test, feature = "serde"),
2318 derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
2319)]
2320pub enum ArithmeticTarget {
2321 Variable(String),
2323 ArrayElement(String, Box<ArithmeticExpr>),
2325}
2326
2327impl Node for ArithmeticTarget {}
2328
2329impl SourceLocation for ArithmeticTarget {
2330 fn location(&self) -> Option<SourceSpan> {
2331 None
2333 }
2334}
2335
2336impl Display for ArithmeticTarget {
2337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2338 match self {
2339 Self::Variable(name) => write!(f, "{name}"),
2340 Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
2341 }
2342 }
2343}
2344
2345#[cfg(test)]
2346#[allow(clippy::panic)]
2347mod tests {
2348 use super::*;
2349 use crate::{ParserOptions, SourcePosition};
2350 use std::io::BufReader;
2351
2352 fn parse(input: &str) -> Program {
2353 let reader = BufReader::new(input.as_bytes());
2354 let mut parser = crate::Parser::new(reader, &ParserOptions::default());
2355 parser.parse_program().unwrap()
2356 }
2357
2358 #[test]
2359 fn program_source_loc() {
2360 const INPUT: &str = r"echo hi
2361echo there
2362";
2363
2364 let loc = parse(INPUT).location().unwrap();
2365
2366 assert_eq!(
2367 *(loc.start),
2368 SourcePosition {
2369 line: 1,
2370 column: 1,
2371 index: 0
2372 }
2373 );
2374 assert_eq!(
2375 *(loc.end),
2376 SourcePosition {
2377 line: 2,
2378 column: 11,
2379 index: 18
2380 }
2381 );
2382 }
2383
2384 #[test]
2385 fn function_def_loc() {
2386 const INPUT: &str = r"my_func() {
2387 echo hi
2388 echo there
2389}
2390
2391my_func
2392";
2393
2394 let program = parse(INPUT);
2395
2396 let Command::Function(func_def) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2397 panic!("expected function definition");
2398 };
2399
2400 let loc = func_def.location().unwrap();
2401
2402 assert_eq!(
2403 *(loc.start),
2404 SourcePosition {
2405 line: 1,
2406 column: 1,
2407 index: 0
2408 }
2409 );
2410 assert_eq!(
2411 *(loc.end),
2412 SourcePosition {
2413 line: 4,
2414 column: 2,
2415 index: 36
2416 }
2417 );
2418 }
2419
2420 #[test]
2421 fn simple_cmd_loc() {
2422 const INPUT: &str = r"var=value somecmd arg1 arg2
2423";
2424
2425 let program = parse(INPUT);
2426
2427 let Command::Simple(cmd) = &program.complete_commands[0].0[0].0.first.seq[0] else {
2428 panic!("expected function definition");
2429 };
2430
2431 let loc = cmd.location().unwrap();
2432
2433 assert_eq!(
2434 *(loc.start),
2435 SourcePosition {
2436 line: 1,
2437 column: 1,
2438 index: 0
2439 }
2440 );
2441 assert_eq!(
2442 *(loc.end),
2443 SourcePosition {
2444 line: 1,
2445 column: 28,
2446 index: 27
2447 }
2448 );
2449 }
2450}