use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
pub statements: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
Assignment(Assignment),
Command(Command),
Pipeline(Pipeline),
If(IfStmt),
For(ForLoop),
While(WhileLoop),
Case(CaseStmt),
Break(Option<usize>),
Continue(Option<usize>),
Return(Option<Box<Expr>>),
Exit(Option<Box<Expr>>),
ToolDef(ToolDef),
Test(TestExpr),
AndChain { left: Box<Stmt>, right: Box<Stmt> },
OrChain { left: Box<Stmt>, right: Box<Stmt> },
EnvScoped { assignments: Vec<Assignment>, body: Box<Stmt> },
Empty,
}
impl Stmt {
pub fn kind_name(&self) -> &'static str {
match self {
Stmt::Assignment(_) => "assignment",
Stmt::Command(_) => "command",
Stmt::Pipeline(_) => "pipeline",
Stmt::If(_) => "if",
Stmt::For(_) => "for",
Stmt::While(_) => "while",
Stmt::Case(_) => "case",
Stmt::Break(_) => "break",
Stmt::Continue(_) => "continue",
Stmt::Return(_) => "return",
Stmt::Exit(_) => "exit",
Stmt::ToolDef(_) => "tooldef",
Stmt::Test(_) => "test",
Stmt::AndChain { .. } => "and_chain",
Stmt::OrChain { .. } => "or_chain",
Stmt::EnvScoped { .. } => "env_scoped",
Stmt::Empty => "empty",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Assignment {
pub name: String,
pub value: Expr,
pub local: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Command {
pub name: String,
pub args: Vec<Arg>,
pub redirects: Vec<Redirect>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Pipeline {
pub commands: Vec<Command>,
pub background: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct IfStmt {
pub condition: Box<Expr>,
pub then_branch: Vec<Stmt>,
pub else_branch: Option<Vec<Stmt>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ForLoop {
pub variable: String,
pub items: Vec<Expr>,
pub body: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WhileLoop {
pub condition: Box<Expr>,
pub body: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CaseStmt {
pub expr: Expr,
pub branches: Vec<CaseBranch>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CaseBranch {
pub patterns: Vec<String>,
pub body: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ToolDef {
pub name: String,
pub params: Vec<ParamDef>,
pub body: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParamDef {
pub name: String,
pub param_type: Option<ParamType>,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParamType {
String,
Int,
Float,
Bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Arg {
Positional(Expr),
Named { key: String, value: Expr },
WordAssign { key: String, value: Expr },
ShortFlag(String),
LongFlag(String),
DoubleDash,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Redirect {
pub kind: RedirectKind,
pub target: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RedirectKind {
StdoutOverwrite,
StdoutAppend,
Stdin,
HereDoc,
HereString,
Stderr,
Both,
MergeStderr,
MergeStdout,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpannedPart {
pub part: StringPart,
pub offset: usize,
pub len: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Literal(Value),
VarRef(VarPath),
Interpolated(Vec<StringPart>),
HereDocBody {
parts: Vec<SpannedPart>,
strip_tabs: bool,
},
BinaryOp {
left: Box<Expr>,
op: BinaryOp,
right: Box<Expr>,
},
CommandSubst(Vec<Stmt>),
Test(Box<TestExpr>),
Positional(usize),
AllArgs,
ArgCount,
VarLength(String),
VarWithDefault { name: String, default: Vec<StringPart> },
Arithmetic(String),
Command(Command),
LastExitCode,
CurrentPid,
GlobPattern(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum TestExpr {
FileTest { op: FileTestOp, path: Box<Expr> },
StringTest { op: StringTestOp, value: Box<Expr> },
Comparison { left: Box<Expr>, op: TestCmpOp, right: Box<Expr> },
And { left: Box<TestExpr>, right: Box<TestExpr> },
Or { left: Box<TestExpr>, right: Box<TestExpr> },
Not { expr: Box<TestExpr> },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileTestOp {
Exists,
IsFile,
IsDir,
Readable,
Writable,
Executable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringTestOp {
IsEmpty,
IsNonEmpty,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestCmpOp {
Eq,
NotEq,
Match,
NotMatch,
Gt,
Lt,
GtEq,
LtEq,
NumEq,
NumNotEq,
NumGt,
NumLt,
NumGtEq,
NumLtEq,
}
pub use kaish_types::{BlobRef, Value};
#[derive(Debug, Clone, PartialEq)]
pub struct VarPath {
pub segments: Vec<VarSegment>,
}
impl VarPath {
pub fn simple(name: impl Into<String>) -> Self {
Self {
segments: vec![VarSegment::Field(name.into())],
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VarSegment {
Field(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum StringPart {
Literal(String),
Var(VarPath),
VarWithDefault { name: String, default: Vec<StringPart> },
VarLength(String),
Positional(usize),
AllArgs,
ArgCount,
Arithmetic(String),
CommandSubst(Vec<Stmt>),
LastExitCode,
CurrentPid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
And,
Or,
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BinaryOp::And => write!(f, "&&"),
BinaryOp::Or => write!(f, "||"),
}
}
}
impl fmt::Display for RedirectKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RedirectKind::StdoutOverwrite => write!(f, ">"),
RedirectKind::StdoutAppend => write!(f, ">>"),
RedirectKind::Stdin => write!(f, "<"),
RedirectKind::HereDoc => write!(f, "<<"),
RedirectKind::HereString => write!(f, "<<<"),
RedirectKind::Stderr => write!(f, "2>"),
RedirectKind::Both => write!(f, "&>"),
RedirectKind::MergeStderr => write!(f, "2>&1"),
RedirectKind::MergeStdout => write!(f, "1>&2"),
}
}
}
impl fmt::Display for FileTestOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FileTestOp::Exists => write!(f, "-e"),
FileTestOp::IsFile => write!(f, "-f"),
FileTestOp::IsDir => write!(f, "-d"),
FileTestOp::Readable => write!(f, "-r"),
FileTestOp::Writable => write!(f, "-w"),
FileTestOp::Executable => write!(f, "-x"),
}
}
}
impl fmt::Display for StringTestOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StringTestOp::IsEmpty => write!(f, "-z"),
StringTestOp::IsNonEmpty => write!(f, "-n"),
}
}
}
impl fmt::Display for TestCmpOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TestCmpOp::Eq => write!(f, "=="),
TestCmpOp::NotEq => write!(f, "!="),
TestCmpOp::Match => write!(f, "=~"),
TestCmpOp::NotMatch => write!(f, "!~"),
TestCmpOp::Gt => write!(f, ">"),
TestCmpOp::Lt => write!(f, "<"),
TestCmpOp::GtEq => write!(f, ">="),
TestCmpOp::LtEq => write!(f, "<="),
TestCmpOp::NumEq => write!(f, "-eq"),
TestCmpOp::NumNotEq => write!(f, "-ne"),
TestCmpOp::NumGt => write!(f, "-gt"),
TestCmpOp::NumLt => write!(f, "-lt"),
TestCmpOp::NumGtEq => write!(f, "-ge"),
TestCmpOp::NumLtEq => write!(f, "-le"),
}
}
}