use std::fmt::{Display, Write};
use crate::{SourceSpan, tokenizer};
const DISPLAY_INDENT: &str = " ";
pub trait Node: Display + SourceLocation {}
pub trait SourceLocation {
fn location(&self) -> Option<SourceSpan>;
}
pub(crate) fn maybe_location(
start: Option<&SourceSpan>,
end: Option<&SourceSpan>,
) -> Option<SourceSpan> {
if let (Some(s), Some(e)) = (start, end) {
Some(SourceSpan::within(s, e))
} else {
None
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct Program {
pub complete_commands: Vec<CompleteCommand>,
}
impl Node for Program {}
impl SourceLocation for Program {
fn location(&self) -> Option<SourceSpan> {
let start = self
.complete_commands
.first()
.and_then(SourceLocation::location);
let end = self
.complete_commands
.last()
.and_then(SourceLocation::location);
maybe_location(start.as_ref(), end.as_ref())
}
}
impl Display for Program {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for complete_command in &self.complete_commands {
write!(f, "{complete_command}")?;
}
Ok(())
}
}
pub type CompleteCommand = CompoundList;
pub type CompleteCommandItem = CompoundListItem;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum SeparatorOperator {
Async,
Sequence,
}
impl Display for SeparatorOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Async => write!(f, "&"),
Self::Sequence => write!(f, ";"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct AndOrList {
pub first: Pipeline,
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "Vec::is_empty", default)
)]
pub additional: Vec<AndOr>,
}
impl Node for AndOrList {}
impl SourceLocation for AndOrList {
fn location(&self) -> Option<SourceSpan> {
let start = self.first.location();
let last = self.additional.last();
let end = last.and_then(SourceLocation::location);
match (start, end) {
(Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
(start, _) => start,
}
}
}
impl Display for AndOrList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.first)?;
for item in &self.additional {
write!(f, "{item}")?;
}
Ok(())
}
}
#[derive(PartialEq, Eq)]
pub enum PipelineOperator {
And,
Or,
}
impl PartialEq<AndOr> for PipelineOperator {
fn eq(&self, other: &AndOr) -> bool {
matches!(
(self, other),
(Self::And, AndOr::And(_)) | (Self::Or, AndOr::Or(_))
)
}
}
#[expect(clippy::from_over_into)]
impl Into<PipelineOperator> for AndOr {
fn into(self) -> PipelineOperator {
match self {
Self::And(_) => PipelineOperator::And,
Self::Or(_) => PipelineOperator::Or,
}
}
}
pub struct AndOrListIter<'a> {
first: Option<&'a Pipeline>,
additional_iter: std::slice::Iter<'a, AndOr>,
}
impl<'a> Iterator for AndOrListIter<'a> {
type Item = (PipelineOperator, &'a Pipeline);
fn next(&mut self) -> Option<Self::Item> {
if let Some(first) = self.first.take() {
Some((PipelineOperator::And, first))
} else {
self.additional_iter.next().map(|and_or| match and_or {
AndOr::And(pipeline) => (PipelineOperator::And, pipeline),
AndOr::Or(pipeline) => (PipelineOperator::Or, pipeline),
})
}
}
}
impl<'a> IntoIterator for &'a AndOrList {
type Item = (PipelineOperator, &'a Pipeline);
type IntoIter = AndOrListIter<'a>;
fn into_iter(self) -> Self::IntoIter {
AndOrListIter {
first: Some(&self.first),
additional_iter: self.additional.iter(),
}
}
}
impl<'a> From<(PipelineOperator, &'a Pipeline)> for AndOr {
fn from(value: (PipelineOperator, &'a Pipeline)) -> Self {
match value.0 {
PipelineOperator::Or => Self::Or(value.1.to_owned()),
PipelineOperator::And => Self::And(value.1.to_owned()),
}
}
}
impl AndOrList {
pub fn iter(&self) -> AndOrListIter<'_> {
self.into_iter()
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum AndOr {
And(Pipeline),
Or(Pipeline),
}
impl Node for AndOr {}
impl SourceLocation for AndOr {
fn location(&self) -> Option<SourceSpan> {
match self {
Self::And(p) => p.location(),
Self::Or(p) => p.location(),
}
}
}
impl Display for AndOr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::And(pipeline) => write!(f, " && {pipeline}"),
Self::Or(pipeline) => write!(f, " || {pipeline}"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum PipelineTimed {
Timed(SourceSpan),
TimedWithPosixOutput(SourceSpan),
}
impl Node for PipelineTimed {}
impl SourceLocation for PipelineTimed {
fn location(&self) -> Option<SourceSpan> {
match self {
Self::Timed(t) => Some(t.to_owned()),
Self::TimedWithPosixOutput(t) => Some(t.to_owned()),
}
}
}
impl Display for PipelineTimed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Timed(_) => write!(f, "time"),
Self::TimedWithPosixOutput(_) => write!(f, "time -p"),
}
}
}
impl PipelineTimed {
pub const fn is_posix_output(&self) -> bool {
matches!(self, Self::TimedWithPosixOutput(_))
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct Pipeline {
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "Option::is_none", default)
)]
pub timed: Option<PipelineTimed>,
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
)]
pub bang: bool,
pub seq: Vec<Command>,
}
impl Node for Pipeline {}
impl SourceLocation for Pipeline {
fn location(&self) -> Option<SourceSpan> {
let start = self
.timed
.as_ref()
.and_then(SourceLocation::location)
.or_else(|| self.seq.first().and_then(SourceLocation::location));
let end = self.seq.last().and_then(SourceLocation::location);
maybe_location(start.as_ref(), end.as_ref())
}
}
impl Display for Pipeline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(timed) = &self.timed {
write!(f, "{timed} ")?;
}
if self.bang {
write!(f, "!")?;
}
for (i, command) in self.seq.iter().enumerate() {
if i > 0 {
write!(f, " |")?;
}
write!(f, "{command}")?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum Command {
Simple(SimpleCommand),
Compound(CompoundCommand, Option<RedirectList>),
Function(FunctionDefinition),
ExtendedTest(ExtendedTestExprCommand, Option<RedirectList>),
}
impl Node for Command {}
impl SourceLocation for Command {
fn location(&self) -> Option<SourceSpan> {
match self {
Self::Simple(s) => s.location(),
Self::Compound(c, r) => {
match (c.location(), r.as_ref().and_then(SourceLocation::location)) {
(Some(s), Some(e)) => Some(SourceSpan::within(&s, &e)),
(s, _) => s,
}
}
Self::Function(f) => f.location(),
Self::ExtendedTest(e, _) => e.location(),
}
}
}
impl Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Simple(simple_command) => write!(f, "{simple_command}"),
Self::Compound(compound_command, redirect_list) => {
write!(f, "{compound_command}")?;
if let Some(redirect_list) = redirect_list {
write!(f, "{redirect_list}")?;
}
Ok(())
}
Self::Function(function_definition) => write!(f, "{function_definition}"),
Self::ExtendedTest(extended_test_expr, redirect_list) => {
write!(f, "[[ {extended_test_expr} ]]")?;
if let Some(redirect_list) = redirect_list {
write!(f, "{redirect_list}")?;
}
Ok(())
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum CompoundCommand {
Arithmetic(ArithmeticCommand),
ArithmeticForClause(ArithmeticForClauseCommand),
BraceGroup(BraceGroupCommand),
Subshell(SubshellCommand),
ForClause(ForClauseCommand),
CaseClause(CaseClauseCommand),
IfClause(IfClauseCommand),
WhileClause(WhileOrUntilClauseCommand),
UntilClause(WhileOrUntilClauseCommand),
}
impl Node for CompoundCommand {}
impl SourceLocation for CompoundCommand {
fn location(&self) -> Option<SourceSpan> {
match self {
Self::Arithmetic(a) => a.location(),
Self::ArithmeticForClause(a) => a.location(),
Self::BraceGroup(b) => b.location(),
Self::Subshell(s) => s.location(),
Self::ForClause(f) => f.location(),
Self::CaseClause(c) => c.location(),
Self::IfClause(i) => i.location(),
Self::WhileClause(w) => w.location(),
Self::UntilClause(u) => u.location(),
}
}
}
impl Display for CompoundCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Arithmetic(arithmetic_command) => write!(f, "{arithmetic_command}"),
Self::ArithmeticForClause(arithmetic_for_clause_command) => {
write!(f, "{arithmetic_for_clause_command}")
}
Self::BraceGroup(brace_group_command) => {
write!(f, "{brace_group_command}")
}
Self::Subshell(subshell_command) => write!(f, "{subshell_command}"),
Self::ForClause(for_clause_command) => write!(f, "{for_clause_command}"),
Self::CaseClause(case_clause_command) => {
write!(f, "{case_clause_command}")
}
Self::IfClause(if_clause_command) => write!(f, "{if_clause_command}"),
Self::WhileClause(while_or_until_clause_command) => {
write!(f, "while {while_or_until_clause_command}")
}
Self::UntilClause(while_or_until_clause_command) => {
write!(f, "until {while_or_until_clause_command}")
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct ArithmeticCommand {
pub expr: UnexpandedArithmeticExpr,
pub loc: SourceSpan,
}
impl Node for ArithmeticCommand {}
impl SourceLocation for ArithmeticCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for ArithmeticCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(({}))", self.expr)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct SubshellCommand {
pub list: CompoundList,
pub loc: SourceSpan,
}
impl Node for SubshellCommand {}
impl SourceLocation for SubshellCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for SubshellCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "( ")?;
write!(f, "{}", self.list)?;
write!(f, " )")
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct ForClauseCommand {
pub variable_name: String,
pub values: Option<Vec<Word>>,
pub body: DoGroupCommand,
pub loc: SourceSpan,
}
impl Node for ForClauseCommand {}
impl SourceLocation for ForClauseCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for ForClauseCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "for {} in ", self.variable_name)?;
if let Some(values) = &self.values {
for (i, value) in values.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{value}")?;
}
}
writeln!(f, ";")?;
write!(f, "{}", self.body)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct ArithmeticForClauseCommand {
pub initializer: Option<UnexpandedArithmeticExpr>,
pub condition: Option<UnexpandedArithmeticExpr>,
pub updater: Option<UnexpandedArithmeticExpr>,
pub body: DoGroupCommand,
pub loc: SourceSpan,
}
impl Node for ArithmeticForClauseCommand {}
impl SourceLocation for ArithmeticForClauseCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for ArithmeticForClauseCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "for ((")?;
if let Some(initializer) = &self.initializer {
write!(f, "{initializer}")?;
}
write!(f, "; ")?;
if let Some(condition) = &self.condition {
write!(f, "{condition}")?;
}
write!(f, "; ")?;
if let Some(updater) = &self.updater {
write!(f, "{updater}")?;
}
writeln!(f, "))")?;
write!(f, "{}", self.body)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct CaseClauseCommand {
pub value: Word,
pub cases: Vec<CaseItem>,
pub loc: SourceSpan,
}
impl Node for CaseClauseCommand {}
impl SourceLocation for CaseClauseCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for CaseClauseCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "case {} in", self.value)?;
for case in &self.cases {
write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{case}")?;
}
writeln!(f)?;
write!(f, "esac")
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct CompoundList(pub Vec<CompoundListItem>);
impl Node for CompoundList {}
impl SourceLocation for CompoundList {
fn location(&self) -> Option<SourceSpan> {
let start = self.0.first().and_then(SourceLocation::location);
let end = self.0.last().and_then(SourceLocation::location);
if let (Some(s), Some(e)) = (start, end) {
Some(SourceSpan::within(&s, &e))
} else {
None
}
}
}
impl Display for CompoundList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, item) in self.0.iter().enumerate() {
if i > 0 {
writeln!(f)?;
}
write!(f, "{}", item.0)?;
if i == self.0.len() - 1 && matches!(item.1, SeparatorOperator::Sequence) {
} else {
write!(f, "{}", item.1)?;
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct CompoundListItem(pub AndOrList, pub SeparatorOperator);
impl Node for CompoundListItem {}
impl SourceLocation for CompoundListItem {
fn location(&self) -> Option<SourceSpan> {
self.0.location()
}
}
impl Display for CompoundListItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)?;
write!(f, "{}", self.1)?;
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct IfClauseCommand {
pub condition: CompoundList,
pub then: CompoundList,
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "Option::is_none", default)
)]
pub elses: Option<Vec<ElseClause>>,
pub loc: SourceSpan,
}
impl Node for IfClauseCommand {}
impl SourceLocation for IfClauseCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for IfClauseCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "if {}; then", self.condition)?;
write!(
indenter::indented(f).with_str(DISPLAY_INDENT),
"{}",
self.then
)?;
if let Some(elses) = &self.elses {
for else_clause in elses {
write!(f, "{else_clause}")?;
}
}
writeln!(f)?;
write!(f, "fi")?;
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct ElseClause {
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "Option::is_none", default)
)]
pub condition: Option<CompoundList>,
pub body: CompoundList,
}
impl Display for ElseClause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f)?;
if let Some(condition) = &self.condition {
writeln!(f, "elif {condition}; then")?;
} else {
writeln!(f, "else")?;
}
write!(
indenter::indented(f).with_str(DISPLAY_INDENT),
"{}",
self.body
)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct CaseItem {
pub patterns: Vec<Word>,
pub cmd: Option<CompoundList>,
pub post_action: CaseItemPostAction,
pub loc: Option<SourceSpan>,
}
impl Node for CaseItem {}
impl SourceLocation for CaseItem {
fn location(&self) -> Option<SourceSpan> {
self.loc.clone()
}
}
impl Display for CaseItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f)?;
for (i, pattern) in self.patterns.iter().enumerate() {
if i > 0 {
write!(f, "|")?;
}
write!(f, "{pattern}")?;
}
writeln!(f, ")")?;
if let Some(cmd) = &self.cmd {
write!(indenter::indented(f).with_str(DISPLAY_INDENT), "{cmd}")?;
}
writeln!(f)?;
write!(f, "{}", self.post_action)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum CaseItemPostAction {
ExitCase,
UnconditionallyExecuteNextCaseItem,
ContinueEvaluatingCases,
}
impl Display for CaseItemPostAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ExitCase => write!(f, ";;"),
Self::UnconditionallyExecuteNextCaseItem => write!(f, ";&"),
Self::ContinueEvaluatingCases => write!(f, ";;&"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct WhileOrUntilClauseCommand(pub CompoundList, pub DoGroupCommand, pub SourceSpan);
impl Node for WhileOrUntilClauseCommand {}
impl SourceLocation for WhileOrUntilClauseCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.2.clone())
}
}
impl Display for WhileOrUntilClauseCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}; {}", self.0, self.1)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct FunctionDefinition {
pub fname: Word,
pub body: FunctionBody,
}
impl Node for FunctionDefinition {}
impl SourceLocation for FunctionDefinition {
fn location(&self) -> Option<SourceSpan> {
let start = self.fname.location();
let end = self.body.location();
if let (Some(s), Some(e)) = (start, end) {
Some(SourceSpan::within(&s, &e))
} else {
None
}
}
}
impl Display for FunctionDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{} () ", self.fname.value)?;
write!(f, "{}", self.body)?;
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct FunctionBody(pub CompoundCommand, pub Option<RedirectList>);
impl Node for FunctionBody {}
impl SourceLocation for FunctionBody {
fn location(&self) -> Option<SourceSpan> {
let cmd_span = self.0.location();
let redirect_span = self.1.as_ref().and_then(SourceLocation::location);
match (cmd_span, redirect_span) {
(Some(cmd_span), Some(redirect_span)) => {
Some(SourceSpan::within(&cmd_span, &redirect_span))
}
(Some(cmd_span), None) => Some(cmd_span),
_ => None,
}
}
}
impl Display for FunctionBody {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)?;
if let Some(redirect_list) = &self.1 {
write!(f, "{redirect_list}")?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct BraceGroupCommand {
pub list: CompoundList,
pub loc: SourceSpan,
}
impl Node for BraceGroupCommand {}
impl SourceLocation for BraceGroupCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for BraceGroupCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{{ ")?;
write!(
indenter::indented(f).with_str(DISPLAY_INDENT),
"{}",
self.list
)?;
writeln!(f)?;
write!(f, "}}")?;
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct DoGroupCommand {
pub list: CompoundList,
pub loc: SourceSpan,
}
impl Display for DoGroupCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "do")?;
write!(
indenter::indented(f).with_str(DISPLAY_INDENT),
"{}",
self.list
)?;
writeln!(f)?;
write!(f, "done")
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct SimpleCommand {
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "Option::is_none", default)
)]
pub prefix: Option<CommandPrefix>,
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "Option::is_none", default)
)]
pub word_or_name: Option<Word>,
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "Option::is_none", default)
)]
pub suffix: Option<CommandSuffix>,
}
impl Node for SimpleCommand {}
impl SourceLocation for SimpleCommand {
fn location(&self) -> Option<SourceSpan> {
let mid = &self
.word_or_name
.as_ref()
.and_then(SourceLocation::location);
let start = self.prefix.as_ref().and_then(SourceLocation::location);
let end = self.suffix.as_ref().and_then(SourceLocation::location);
match (start, mid, end) {
(Some(start), _, Some(end)) => Some(SourceSpan::within(&start, &end)),
(Some(start), Some(mid), None) => Some(SourceSpan::within(&start, mid)),
(Some(start), None, None) => Some(start),
(None, Some(mid), Some(end)) => Some(SourceSpan::within(mid, &end)),
(None, Some(mid), None) => Some(mid.clone()),
_ => None,
}
}
}
impl Display for SimpleCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut wrote_something = false;
if let Some(prefix) = &self.prefix {
if wrote_something {
write!(f, " ")?;
}
write!(f, "{prefix}")?;
wrote_something = true;
}
if let Some(word_or_name) = &self.word_or_name {
if wrote_something {
write!(f, " ")?;
}
write!(f, "{word_or_name}")?;
wrote_something = true;
}
if let Some(suffix) = &self.suffix {
if wrote_something {
write!(f, " ")?;
}
write!(f, "{suffix}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct CommandPrefix(pub Vec<CommandPrefixOrSuffixItem>);
impl Node for CommandPrefix {}
impl SourceLocation for CommandPrefix {
fn location(&self) -> Option<SourceSpan> {
let start = self.0.first().and_then(SourceLocation::location);
let end = self.0.last().and_then(SourceLocation::location);
maybe_location(start.as_ref(), end.as_ref())
}
}
impl Display for CommandPrefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, item) in self.0.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{item}")?;
}
Ok(())
}
}
#[derive(Clone, Default, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct CommandSuffix(pub Vec<CommandPrefixOrSuffixItem>);
impl Node for CommandSuffix {}
impl SourceLocation for CommandSuffix {
fn location(&self) -> Option<SourceSpan> {
let start = self.0.first().and_then(SourceLocation::location);
let end = self.0.last().and_then(SourceLocation::location);
maybe_location(start.as_ref(), end.as_ref())
}
}
impl Display for CommandSuffix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, item) in self.0.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{item}")?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum ProcessSubstitutionKind {
Read,
Write,
}
impl Display for ProcessSubstitutionKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Read => write!(f, "<"),
Self::Write => write!(f, ">"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum CommandPrefixOrSuffixItem {
IoRedirect(IoRedirect),
Word(Word),
AssignmentWord(Assignment, Word),
ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
}
impl Node for CommandPrefixOrSuffixItem {}
impl SourceLocation for CommandPrefixOrSuffixItem {
fn location(&self) -> Option<SourceSpan> {
match self {
Self::Word(w) => w.location(),
Self::IoRedirect(io_redirect) => io_redirect.location(),
Self::AssignmentWord(assignment, _word) => assignment.location(),
Self::ProcessSubstitution(_kind, cmd) => cmd.location(),
}
}
}
impl Display for CommandPrefixOrSuffixItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IoRedirect(io_redirect) => write!(f, "{io_redirect}"),
Self::Word(word) => write!(f, "{word}"),
Self::AssignmentWord(_assignment, word) => write!(f, "{word}"),
Self::ProcessSubstitution(kind, subshell_command) => {
write!(f, "{kind}({subshell_command})")
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct Assignment {
pub name: AssignmentName,
pub value: AssignmentValue,
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
)]
pub append: bool,
pub loc: SourceSpan,
}
impl Node for Assignment {}
impl SourceLocation for Assignment {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for Assignment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)?;
if self.append {
write!(f, "+")?;
}
write!(f, "={}", self.value)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum AssignmentName {
VariableName(String),
ArrayElementName(String, String),
}
impl Display for AssignmentName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::VariableName(name) => write!(f, "{name}"),
Self::ArrayElementName(name, index) => {
write!(f, "{name}[{index}]")
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum AssignmentValue {
Scalar(Word),
Array(Vec<(Option<Word>, Word)>),
}
impl Node for AssignmentValue {}
impl SourceLocation for AssignmentValue {
fn location(&self) -> Option<SourceSpan> {
match self {
Self::Scalar(word) => word.location(),
Self::Array(words) => {
let first = words.first().and_then(|(_key, value)| value.location());
let last = words.last().and_then(|(_key, value)| value.location());
maybe_location(first.as_ref(), last.as_ref())
}
}
}
}
impl Display for AssignmentValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Scalar(word) => write!(f, "{word}"),
Self::Array(words) => {
write!(f, "(")?;
for (i, value) in words.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
match value {
(Some(key), value) => write!(f, "[{key}]={value}")?,
(None, value) => write!(f, "{value}")?,
}
}
write!(f, ")")
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct RedirectList(pub Vec<IoRedirect>);
impl Node for RedirectList {}
impl SourceLocation for RedirectList {
fn location(&self) -> Option<SourceSpan> {
let first = self.0.first().and_then(SourceLocation::location);
let last = self.0.last().and_then(SourceLocation::location);
maybe_location(first.as_ref(), last.as_ref())
}
}
impl Display for RedirectList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for item in &self.0 {
write!(f, "{item}")?;
}
Ok(())
}
}
pub type IoFd = i32;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum IoRedirect {
File(Option<IoFd>, IoFileRedirectKind, IoFileRedirectTarget),
HereDocument(Option<IoFd>, IoHereDocument),
HereString(Option<IoFd>, Word),
OutputAndError(Word, bool),
}
impl Node for IoRedirect {}
impl SourceLocation for IoRedirect {
fn location(&self) -> Option<SourceSpan> {
None
}
}
impl Display for IoRedirect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::File(fd_num, kind, target) => {
if let Some(fd_num) = fd_num {
write!(f, "{fd_num}")?;
}
write!(f, "{kind} {target}")?;
}
Self::OutputAndError(target, append) => {
write!(f, "&>")?;
if *append {
write!(f, ">")?;
}
write!(f, " {target}")?;
}
Self::HereDocument(fd_num, here_doc) => {
if let Some(fd_num) = fd_num {
write!(f, "{fd_num}")?;
}
write!(f, "<<{here_doc}")?;
}
Self::HereString(fd_num, s) => {
if let Some(fd_num) = fd_num {
write!(f, "{fd_num}")?;
}
write!(f, "<<< {s}")?;
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum IoFileRedirectKind {
Read,
Write,
Append,
ReadAndWrite,
Clobber,
DuplicateInput,
DuplicateOutput,
}
impl Display for IoFileRedirectKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Read => write!(f, "<"),
Self::Write => write!(f, ">"),
Self::Append => write!(f, ">>"),
Self::ReadAndWrite => write!(f, "<>"),
Self::Clobber => write!(f, ">|"),
Self::DuplicateInput => write!(f, "<&"),
Self::DuplicateOutput => write!(f, ">&"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum IoFileRedirectTarget {
Filename(Word),
Fd(IoFd),
ProcessSubstitution(ProcessSubstitutionKind, SubshellCommand),
Duplicate(Word),
}
impl Display for IoFileRedirectTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Filename(word) => write!(f, "{word}"),
Self::Fd(fd) => write!(f, "{fd}"),
Self::ProcessSubstitution(kind, subshell_command) => {
write!(f, "{kind}{subshell_command}")
}
Self::Duplicate(word) => write!(f, "{word}"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct IoHereDocument {
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
)]
pub remove_tabs: bool,
#[cfg_attr(
any(test, feature = "serde"),
serde(skip_serializing_if = "<&bool as std::ops::Not>::not", default)
)]
pub requires_expansion: bool,
pub here_end: Word,
pub doc: Word,
}
impl Node for IoHereDocument {}
impl SourceLocation for IoHereDocument {
fn location(&self) -> Option<SourceSpan> {
None
}
}
impl Display for IoHereDocument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.remove_tabs {
write!(f, "-")?;
}
writeln!(f, "{}", self.here_end)?;
write!(f, "{}", self.doc)?;
writeln!(f, "{}", self.here_end)?;
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum TestExpr {
False,
Literal(String),
And(Box<Self>, Box<Self>),
Or(Box<Self>, Box<Self>),
Not(Box<Self>),
Parenthesized(Box<Self>),
UnaryTest(UnaryPredicate, String),
BinaryTest(BinaryPredicate, String, String),
}
impl Node for TestExpr {}
impl SourceLocation for TestExpr {
fn location(&self) -> Option<SourceSpan> {
None
}
}
impl Display for TestExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::False => Ok(()),
Self::Literal(s) => write!(f, "{s}"),
Self::And(left, right) => write!(f, "{left} -a {right}"),
Self::Or(left, right) => write!(f, "{left} -o {right}"),
Self::Not(expr) => write!(f, "! {expr}"),
Self::Parenthesized(expr) => write!(f, "( {expr} )"),
Self::UnaryTest(pred, word) => write!(f, "{pred} {word}"),
Self::BinaryTest(left, op, right) => write!(f, "{left} {op} {right}"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum ExtendedTestExpr {
And(Box<Self>, Box<Self>),
Or(Box<Self>, Box<Self>),
Not(Box<Self>),
Parenthesized(Box<Self>),
UnaryTest(UnaryPredicate, Word),
BinaryTest(BinaryPredicate, Word, Word),
}
impl Display for ExtendedTestExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::And(left, right) => {
write!(f, "{left} && {right}")
}
Self::Or(left, right) => {
write!(f, "{left} || {right}")
}
Self::Not(expr) => {
write!(f, "! {expr}")
}
Self::Parenthesized(expr) => {
write!(f, "( {expr} )")
}
Self::UnaryTest(pred, word) => {
write!(f, "{pred} {word}")
}
Self::BinaryTest(pred, left, right) => {
write!(f, "{left} {pred} {right}")
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct ExtendedTestExprCommand {
pub expr: ExtendedTestExpr,
pub loc: SourceSpan,
}
impl Node for ExtendedTestExprCommand {}
impl SourceLocation for ExtendedTestExprCommand {
fn location(&self) -> Option<SourceSpan> {
Some(self.loc.clone())
}
}
impl Display for ExtendedTestExprCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.expr.fmt(f)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum UnaryPredicate {
FileExists,
FileExistsAndIsBlockSpecialFile,
FileExistsAndIsCharSpecialFile,
FileExistsAndIsDir,
FileExistsAndIsRegularFile,
FileExistsAndIsSetgid,
FileExistsAndIsSymlink,
FileExistsAndHasStickyBit,
FileExistsAndIsFifo,
FileExistsAndIsReadable,
FileExistsAndIsNotZeroLength,
FdIsOpenTerminal,
FileExistsAndIsSetuid,
FileExistsAndIsWritable,
FileExistsAndIsExecutable,
FileExistsAndOwnedByEffectiveGroupId,
FileExistsAndModifiedSinceLastRead,
FileExistsAndOwnedByEffectiveUserId,
FileExistsAndIsSocket,
ShellOptionEnabled,
ShellVariableIsSetAndAssigned,
ShellVariableIsSetAndNameRef,
StringHasZeroLength,
StringHasNonZeroLength,
}
impl Display for UnaryPredicate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FileExists => write!(f, "-e"),
Self::FileExistsAndIsBlockSpecialFile => write!(f, "-b"),
Self::FileExistsAndIsCharSpecialFile => write!(f, "-c"),
Self::FileExistsAndIsDir => write!(f, "-d"),
Self::FileExistsAndIsRegularFile => write!(f, "-f"),
Self::FileExistsAndIsSetgid => write!(f, "-g"),
Self::FileExistsAndIsSymlink => write!(f, "-h"),
Self::FileExistsAndHasStickyBit => write!(f, "-k"),
Self::FileExistsAndIsFifo => write!(f, "-p"),
Self::FileExistsAndIsReadable => write!(f, "-r"),
Self::FileExistsAndIsNotZeroLength => write!(f, "-s"),
Self::FdIsOpenTerminal => write!(f, "-t"),
Self::FileExistsAndIsSetuid => write!(f, "-u"),
Self::FileExistsAndIsWritable => write!(f, "-w"),
Self::FileExistsAndIsExecutable => write!(f, "-x"),
Self::FileExistsAndOwnedByEffectiveGroupId => write!(f, "-G"),
Self::FileExistsAndModifiedSinceLastRead => write!(f, "-N"),
Self::FileExistsAndOwnedByEffectiveUserId => write!(f, "-O"),
Self::FileExistsAndIsSocket => write!(f, "-S"),
Self::ShellOptionEnabled => write!(f, "-o"),
Self::ShellVariableIsSetAndAssigned => write!(f, "-v"),
Self::ShellVariableIsSetAndNameRef => write!(f, "-R"),
Self::StringHasZeroLength => write!(f, "-z"),
Self::StringHasNonZeroLength => write!(f, "-n"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum BinaryPredicate {
FilesReferToSameDeviceAndInodeNumbers,
LeftFileIsNewerOrExistsWhenRightDoesNot,
LeftFileIsOlderOrDoesNotExistWhenRightDoes,
StringExactlyMatchesPattern,
StringDoesNotExactlyMatchPattern,
StringMatchesRegex,
StringExactlyMatchesString,
StringDoesNotExactlyMatchString,
StringContainsSubstring,
LeftSortsBeforeRight,
LeftSortsAfterRight,
ArithmeticEqualTo,
ArithmeticNotEqualTo,
ArithmeticLessThan,
ArithmeticLessThanOrEqualTo,
ArithmeticGreaterThan,
ArithmeticGreaterThanOrEqualTo,
}
impl Display for BinaryPredicate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FilesReferToSameDeviceAndInodeNumbers => write!(f, "-ef"),
Self::LeftFileIsNewerOrExistsWhenRightDoesNot => write!(f, "-nt"),
Self::LeftFileIsOlderOrDoesNotExistWhenRightDoes => write!(f, "-ot"),
Self::StringExactlyMatchesPattern => write!(f, "=="),
Self::StringDoesNotExactlyMatchPattern => write!(f, "!="),
Self::StringMatchesRegex => write!(f, "=~"),
Self::StringContainsSubstring => write!(f, "=~"),
Self::StringExactlyMatchesString => write!(f, "=="),
Self::StringDoesNotExactlyMatchString => write!(f, "!="),
Self::LeftSortsBeforeRight => write!(f, "<"),
Self::LeftSortsAfterRight => write!(f, ">"),
Self::ArithmeticEqualTo => write!(f, "-eq"),
Self::ArithmeticNotEqualTo => write!(f, "-ne"),
Self::ArithmeticLessThan => write!(f, "-lt"),
Self::ArithmeticLessThanOrEqualTo => write!(f, "-le"),
Self::ArithmeticGreaterThan => write!(f, "-gt"),
Self::ArithmeticGreaterThanOrEqualTo => write!(f, "-ge"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct Word {
pub value: String,
pub loc: Option<SourceSpan>,
}
impl Node for Word {}
impl SourceLocation for Word {
fn location(&self) -> Option<SourceSpan> {
self.loc.clone()
}
}
impl Display for Word {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)
}
}
impl From<&tokenizer::Token> for Word {
fn from(t: &tokenizer::Token) -> Self {
match t {
tokenizer::Token::Word(value, loc) => Self {
value: value.clone(),
loc: Some(loc.clone()),
},
tokenizer::Token::Operator(value, loc) => Self {
value: value.clone(),
loc: Some(loc.clone()),
},
}
}
}
impl From<String> for Word {
fn from(s: String) -> Self {
Self {
value: s,
loc: None,
}
}
}
impl AsRef<str> for Word {
fn as_ref(&self) -> &str {
&self.value
}
}
impl Word {
pub fn new(s: &str) -> Self {
Self {
value: s.to_owned(),
loc: None,
}
}
pub fn with_location(s: &str, loc: &SourceSpan) -> Self {
Self {
value: s.to_owned(),
loc: Some(loc.to_owned()),
}
}
pub fn flatten(&self) -> String {
self.value.clone()
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub struct UnexpandedArithmeticExpr {
pub value: String,
}
impl Display for UnexpandedArithmeticExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum ArithmeticExpr {
Literal(i64),
Reference(ArithmeticTarget),
UnaryOp(UnaryOperator, Box<Self>),
BinaryOp(BinaryOperator, Box<Self>, Box<Self>),
Conditional(Box<Self>, Box<Self>, Box<Self>),
Assignment(ArithmeticTarget, Box<Self>),
BinaryAssignment(BinaryOperator, ArithmeticTarget, Box<Self>),
UnaryAssignment(UnaryAssignmentOperator, ArithmeticTarget),
}
impl Node for ArithmeticExpr {}
impl SourceLocation for ArithmeticExpr {
fn location(&self) -> Option<SourceSpan> {
None
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for ArithmeticExpr {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let variant = u.choose(&[
"Literal",
"Reference",
"UnaryOp",
"BinaryOp",
"Conditional",
"Assignment",
"BinaryAssignment",
"UnaryAssignment",
])?;
match *variant {
"Literal" => Ok(Self::Literal(i64::arbitrary(u)?)),
"Reference" => Ok(Self::Reference(ArithmeticTarget::arbitrary(u)?)),
"UnaryOp" => Ok(Self::UnaryOp(
UnaryOperator::arbitrary(u)?,
Box::new(Self::arbitrary(u)?),
)),
"BinaryOp" => Ok(Self::BinaryOp(
BinaryOperator::arbitrary(u)?,
Box::new(Self::arbitrary(u)?),
Box::new(Self::arbitrary(u)?),
)),
"Conditional" => Ok(Self::Conditional(
Box::new(Self::arbitrary(u)?),
Box::new(Self::arbitrary(u)?),
Box::new(Self::arbitrary(u)?),
)),
"Assignment" => Ok(Self::Assignment(
ArithmeticTarget::arbitrary(u)?,
Box::new(Self::arbitrary(u)?),
)),
"BinaryAssignment" => Ok(Self::BinaryAssignment(
BinaryOperator::arbitrary(u)?,
ArithmeticTarget::arbitrary(u)?,
Box::new(Self::arbitrary(u)?),
)),
"UnaryAssignment" => Ok(Self::UnaryAssignment(
UnaryAssignmentOperator::arbitrary(u)?,
ArithmeticTarget::arbitrary(u)?,
)),
_ => unreachable!(),
}
}
}
impl Display for ArithmeticExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Literal(literal) => write!(f, "{literal}"),
Self::Reference(target) => write!(f, "{target}"),
Self::UnaryOp(op, operand) => write!(f, "{op}{operand}"),
Self::BinaryOp(op, left, right) => {
if matches!(op, BinaryOperator::Comma) {
write!(f, "{left}{op} {right}")
} else {
write!(f, "{left} {op} {right}")
}
}
Self::Conditional(condition, if_branch, else_branch) => {
write!(f, "{condition} ? {if_branch} : {else_branch}")
}
Self::Assignment(target, value) => write!(f, "{target} = {value}"),
Self::BinaryAssignment(op, target, operand) => {
write!(f, "{target} {op}= {operand}")
}
Self::UnaryAssignment(op, target) => match op {
UnaryAssignmentOperator::PrefixIncrement
| UnaryAssignmentOperator::PrefixDecrement => write!(f, "{op}{target}"),
UnaryAssignmentOperator::PostfixIncrement
| UnaryAssignmentOperator::PostfixDecrement => write!(f, "{target}{op}"),
},
}
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum BinaryOperator {
Power,
Multiply,
Divide,
Modulo,
Comma,
Add,
Subtract,
ShiftLeft,
ShiftRight,
LessThan,
LessThanOrEqualTo,
GreaterThan,
GreaterThanOrEqualTo,
Equals,
NotEquals,
BitwiseAnd,
BitwiseXor,
BitwiseOr,
LogicalAnd,
LogicalOr,
}
impl Display for BinaryOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Power => write!(f, "**"),
Self::Multiply => write!(f, "*"),
Self::Divide => write!(f, "/"),
Self::Modulo => write!(f, "%"),
Self::Comma => write!(f, ","),
Self::Add => write!(f, "+"),
Self::Subtract => write!(f, "-"),
Self::ShiftLeft => write!(f, "<<"),
Self::ShiftRight => write!(f, ">>"),
Self::LessThan => write!(f, "<"),
Self::LessThanOrEqualTo => write!(f, "<="),
Self::GreaterThan => write!(f, ">"),
Self::GreaterThanOrEqualTo => write!(f, ">="),
Self::Equals => write!(f, "=="),
Self::NotEquals => write!(f, "!="),
Self::BitwiseAnd => write!(f, "&"),
Self::BitwiseXor => write!(f, "^"),
Self::BitwiseOr => write!(f, "|"),
Self::LogicalAnd => write!(f, "&&"),
Self::LogicalOr => write!(f, "||"),
}
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum UnaryOperator {
UnaryPlus,
UnaryMinus,
BitwiseNot,
LogicalNot,
}
impl Display for UnaryOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnaryPlus => write!(f, "+"),
Self::UnaryMinus => write!(f, "-"),
Self::BitwiseNot => write!(f, "~"),
Self::LogicalNot => write!(f, "!"),
}
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum UnaryAssignmentOperator {
PrefixIncrement,
PrefixDecrement,
PostfixIncrement,
PostfixDecrement,
}
impl Display for UnaryAssignmentOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::PrefixIncrement => write!(f, "++"),
Self::PrefixDecrement => write!(f, "--"),
Self::PostfixIncrement => write!(f, "++"),
Self::PostfixDecrement => write!(f, "--"),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(test, feature = "serde"),
derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)
)]
pub enum ArithmeticTarget {
Variable(String),
ArrayElement(String, Box<ArithmeticExpr>),
}
impl Node for ArithmeticTarget {}
impl SourceLocation for ArithmeticTarget {
fn location(&self) -> Option<SourceSpan> {
None
}
}
impl Display for ArithmeticTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Variable(name) => write!(f, "{name}"),
Self::ArrayElement(name, index) => write!(f, "{name}[{index}]"),
}
}
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
use crate::{ParserOptions, SourcePosition};
use std::io::BufReader;
fn parse(input: &str) -> Program {
let reader = BufReader::new(input.as_bytes());
let mut parser = crate::Parser::new(reader, &ParserOptions::default());
parser.parse_program().unwrap()
}
#[test]
fn program_source_loc() {
const INPUT: &str = r"echo hi
echo there
";
let loc = parse(INPUT).location().unwrap();
assert_eq!(
*(loc.start),
SourcePosition {
line: 1,
column: 1,
index: 0
}
);
assert_eq!(
*(loc.end),
SourcePosition {
line: 2,
column: 11,
index: 18
}
);
}
#[test]
fn function_def_loc() {
const INPUT: &str = r"my_func() {
echo hi
echo there
}
my_func
";
let program = parse(INPUT);
let Command::Function(func_def) = &program.complete_commands[0].0[0].0.first.seq[0] else {
panic!("expected function definition");
};
let loc = func_def.location().unwrap();
assert_eq!(
*(loc.start),
SourcePosition {
line: 1,
column: 1,
index: 0
}
);
assert_eq!(
*(loc.end),
SourcePosition {
line: 4,
column: 2,
index: 36
}
);
}
#[test]
fn simple_cmd_loc() {
const INPUT: &str = r"var=value somecmd arg1 arg2
";
let program = parse(INPUT);
let Command::Simple(cmd) = &program.complete_commands[0].0[0].0.first.seq[0] else {
panic!("expected function definition");
};
let loc = cmd.location().unwrap();
assert_eq!(
*(loc.start),
SourcePosition {
line: 1,
column: 1,
index: 0
}
);
assert_eq!(
*(loc.end),
SourcePosition {
line: 1,
column: 28,
index: 27
}
);
}
}