use std::fmt;
use std::ops::BitOr;
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
pub(crate) struct RedirectFlags(u8);
impl RedirectFlags {
pub(crate) const NONE: Self = Self(0);
pub(crate) const STDIN: Self = Self(1);
pub(crate) const STDOUT: Self = Self(2);
pub(crate) const STDERR: Self = Self(4);
pub(crate) const APPEND: Self = Self(8);
pub(crate) const DUPLICATE_OUT: Self = Self(16);
pub(crate) fn bits(self) -> u8 {
self.0
}
pub(crate) fn is_empty(self) -> bool {
self.0 == 0
}
pub(crate) fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub(crate) fn without(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
}
impl BitOr for RedirectFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl fmt::Debug for RedirectFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RedirectFlags({:#07b})", self.0)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct Script {
pub stmts: Vec<Stmt>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct Stmt {
pub exprs: Vec<Expr>,
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum Expr {
Assign(Vec<Assign>),
Binary(Box<Binary>),
Pipeline(Vec<Expr>),
Cmd(Box<Cmd>),
Subshell(Box<Subshell>),
If(Box<If>),
CondExpr(Box<CondExpr>),
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Assign {
pub label: String,
pub value: Atom,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BinaryOp {
And,
Or,
}
impl BinaryOp {
#[cfg(test)]
pub(crate) fn as_str(self) -> &'static str {
match self {
BinaryOp::And => "and",
BinaryOp::Or => "or",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Binary {
pub op: BinaryOp,
pub left: Expr,
pub right: Expr,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Cmd {
pub assigns: Vec<Assign>,
pub name_and_args: Vec<Atom>,
pub redirect: RedirectFlags,
pub redirect_file: Option<Redirect>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Subshell {
pub script: Script,
pub redirect: Option<Redirect>,
pub redirect_flags: RedirectFlags,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct If {
pub cond: Vec<Stmt>,
pub then: Vec<Stmt>,
pub else_parts: Vec<Vec<Stmt>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CondExprOp {
IsFile,
IsEmpty,
IsNonEmpty,
IsDirectory,
IsCharDevice,
Eq,
NotEq,
}
impl CondExprOp {
pub(super) fn from_str(s: &str) -> Option<Self> {
Some(match s {
"-f" => Self::IsFile,
"-z" => Self::IsEmpty,
"-n" => Self::IsNonEmpty,
"-d" => Self::IsDirectory,
"-c" => Self::IsCharDevice,
"==" => Self::Eq,
"!=" => Self::NotEq,
_ => return None,
})
}
#[cfg(test)]
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::IsFile => "-f",
Self::IsEmpty => "-z",
Self::IsNonEmpty => "-n",
Self::IsDirectory => "-d",
Self::IsCharDevice => "-c",
Self::Eq => "==",
Self::NotEq => "!=",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct CondExpr {
pub op: CondExprOp,
pub args: Vec<Atom>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Atom {
Simple(SimpleAtom),
Compound(CompoundAtom),
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct CompoundAtom {
pub atoms: Vec<SimpleAtom>,
pub brace_expansion_hint: bool,
pub glob_hint: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum SimpleAtom {
Var(String),
VarArgv(u8),
Text(String),
QuotedEmpty,
Asterisk,
DoubleAsterisk,
BraceBegin,
BraceEnd,
Comma,
Tilde,
CmdSubst(Box<CmdSubst>),
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct CmdSubst {
pub script: Script,
pub quoted: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Redirect {
Atom(Atom),
JsBuf(usize),
}
fn is_brace_atom(a: &SimpleAtom) -> bool {
matches!(a, SimpleAtom::BraceBegin | SimpleAtom::BraceEnd)
}
fn is_glob_atom(a: &SimpleAtom) -> bool {
matches!(a, SimpleAtom::Asterisk | SimpleAtom::DoubleAsterisk)
}
impl Atom {
pub(super) fn merge(self, right: Atom) -> Atom {
let brace = self.merge_brace_hint() || right.merge_brace_hint();
let glob = self.has_glob_expansion() || right.has_glob_expansion();
let mut atoms = self.into_atoms();
atoms.extend(right.into_atoms());
Atom::Compound(CompoundAtom {
atoms,
brace_expansion_hint: brace,
glob_hint: glob,
})
}
fn merge_brace_hint(&self) -> bool {
match self {
Atom::Simple(a) => is_brace_atom(a),
Atom::Compound(c) => c.brace_expansion_hint,
}
}
fn into_atoms(self) -> Vec<SimpleAtom> {
match self {
Atom::Simple(a) => vec![a],
Atom::Compound(c) => c.atoms,
}
}
pub(crate) fn has_glob_expansion(&self) -> bool {
match self {
Atom::Simple(a) => is_glob_atom(a),
Atom::Compound(c) => c.glob_hint,
}
}
pub(crate) fn has_brace_expansion(&self) -> bool {
matches!(self, Atom::Compound(c) if c.brace_expansion_hint)
}
pub(crate) fn atoms(&self) -> &[SimpleAtom] {
match self {
Atom::Simple(a) => std::slice::from_ref(a),
Atom::Compound(c) => &c.atoms,
}
}
}