1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct Program {
8 pub statements: Vec<Stmt>,
9}
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum Stmt {
14 Assignment(Assignment),
16 Command(Command),
18 Pipeline(Pipeline),
20 If(IfStmt),
22 For(ForLoop),
24 While(WhileLoop),
26 Case(CaseStmt),
28 Break(Option<usize>),
30 Continue(Option<usize>),
32 Return(Option<Box<Expr>>),
34 Exit(Option<Box<Expr>>),
36 ToolDef(ToolDef),
38 Test(TestExpr),
40 AndChain { left: Box<Stmt>, right: Box<Stmt> },
42 OrChain { left: Box<Stmt>, right: Box<Stmt> },
44 Empty,
46}
47
48impl Stmt {
49 pub fn kind_name(&self) -> &'static str {
51 match self {
52 Stmt::Assignment(_) => "assignment",
53 Stmt::Command(_) => "command",
54 Stmt::Pipeline(_) => "pipeline",
55 Stmt::If(_) => "if",
56 Stmt::For(_) => "for",
57 Stmt::While(_) => "while",
58 Stmt::Case(_) => "case",
59 Stmt::Break(_) => "break",
60 Stmt::Continue(_) => "continue",
61 Stmt::Return(_) => "return",
62 Stmt::Exit(_) => "exit",
63 Stmt::ToolDef(_) => "tooldef",
64 Stmt::Test(_) => "test",
65 Stmt::AndChain { .. } => "and_chain",
66 Stmt::OrChain { .. } => "or_chain",
67 Stmt::Empty => "empty",
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq)]
74pub struct Assignment {
75 pub name: String,
76 pub value: Expr,
77 pub local: bool,
79}
80
81#[derive(Debug, Clone, PartialEq)]
83pub struct Command {
84 pub name: String,
85 pub args: Vec<Arg>,
86 pub redirects: Vec<Redirect>,
87}
88
89#[derive(Debug, Clone, PartialEq)]
91pub struct Pipeline {
92 pub commands: Vec<Command>,
93 pub background: bool,
94}
95
96#[derive(Debug, Clone, PartialEq)]
98pub struct IfStmt {
99 pub condition: Box<Expr>,
100 pub then_branch: Vec<Stmt>,
101 pub else_branch: Option<Vec<Stmt>>,
102}
103
104#[derive(Debug, Clone, PartialEq)]
106pub struct ForLoop {
107 pub variable: String,
108 pub items: Vec<Expr>,
110 pub body: Vec<Stmt>,
111}
112
113#[derive(Debug, Clone, PartialEq)]
115pub struct WhileLoop {
116 pub condition: Box<Expr>,
117 pub body: Vec<Stmt>,
118}
119
120#[derive(Debug, Clone, PartialEq)]
130pub struct CaseStmt {
131 pub expr: Expr,
133 pub branches: Vec<CaseBranch>,
135}
136
137#[derive(Debug, Clone, PartialEq)]
139pub struct CaseBranch {
140 pub patterns: Vec<String>,
142 pub body: Vec<Stmt>,
144}
145
146#[derive(Debug, Clone, PartialEq)]
148pub struct ToolDef {
149 pub name: String,
150 pub params: Vec<ParamDef>,
151 pub body: Vec<Stmt>,
152}
153
154#[derive(Debug, Clone, PartialEq)]
156pub struct ParamDef {
157 pub name: String,
158 pub param_type: Option<ParamType>,
159 pub default: Option<Expr>,
160}
161
162#[derive(Debug, Clone, PartialEq)]
164pub enum ParamType {
165 String,
166 Int,
167 Float,
168 Bool,
169}
170
171#[derive(Debug, Clone, PartialEq)]
173pub enum Arg {
174 Positional(Expr),
176 Named { key: String, value: Expr },
178 ShortFlag(String),
180 LongFlag(String),
182 DoubleDash,
184}
185
186#[derive(Debug, Clone, PartialEq)]
188pub struct Redirect {
189 pub kind: RedirectKind,
190 pub target: Expr,
191}
192
193#[derive(Debug, Clone, PartialEq)]
195pub enum RedirectKind {
196 StdoutOverwrite,
198 StdoutAppend,
200 Stdin,
202 HereDoc,
204 Stderr,
206 Both,
208 MergeStderr,
210 MergeStdout,
212}
213
214#[derive(Debug, Clone, PartialEq)]
216pub enum Expr {
217 Literal(Value),
219 VarRef(VarPath),
221 Interpolated(Vec<StringPart>),
223 BinaryOp {
225 left: Box<Expr>,
226 op: BinaryOp,
227 right: Box<Expr>,
228 },
229 CommandSubst(Box<Pipeline>),
231 Test(Box<TestExpr>),
233 Positional(usize),
235 AllArgs,
237 ArgCount,
239 VarLength(String),
241 VarWithDefault { name: String, default: Vec<StringPart> },
244 Arithmetic(String),
246 Command(Command),
248 LastExitCode,
250 CurrentPid,
252 GlobPattern(String),
254}
255
256#[derive(Debug, Clone, PartialEq)]
258pub enum TestExpr {
259 FileTest { op: FileTestOp, path: Box<Expr> },
261 StringTest { op: StringTestOp, value: Box<Expr> },
263 Comparison { left: Box<Expr>, op: TestCmpOp, right: Box<Expr> },
265 And { left: Box<TestExpr>, right: Box<TestExpr> },
267 Or { left: Box<TestExpr>, right: Box<TestExpr> },
269 Not { expr: Box<TestExpr> },
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum FileTestOp {
276 Exists,
278 IsFile,
280 IsDir,
282 Readable,
284 Writable,
286 Executable,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum StringTestOp {
293 IsEmpty,
295 IsNonEmpty,
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub enum TestCmpOp {
302 Eq,
304 NotEq,
306 Match,
308 NotMatch,
310 Gt,
312 Lt,
314 GtEq,
316 LtEq,
318}
319
320pub use kaish_types::{BlobRef, Value};
322
323#[derive(Debug, Clone, PartialEq)]
328pub struct VarPath {
329 pub segments: Vec<VarSegment>,
330}
331
332impl VarPath {
333 pub fn simple(name: impl Into<String>) -> Self {
335 Self {
336 segments: vec![VarSegment::Field(name.into())],
337 }
338 }
339}
340
341#[derive(Debug, Clone, PartialEq)]
343pub enum VarSegment {
344 Field(String),
347}
348
349#[derive(Debug, Clone, PartialEq)]
351pub enum StringPart {
352 Literal(String),
354 Var(VarPath),
356 VarWithDefault { name: String, default: Vec<StringPart> },
358 VarLength(String),
360 Positional(usize),
362 AllArgs,
364 ArgCount,
366 Arithmetic(String),
368 CommandSubst(Pipeline),
370 LastExitCode,
372 CurrentPid,
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378pub enum BinaryOp {
379 And,
381 Or,
383 Eq,
385 NotEq,
387 Match,
389 NotMatch,
391 Lt,
393 Gt,
395 LtEq,
397 GtEq,
399}
400
401impl fmt::Display for BinaryOp {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 match self {
404 BinaryOp::And => write!(f, "&&"),
405 BinaryOp::Or => write!(f, "||"),
406 BinaryOp::Eq => write!(f, "=="),
407 BinaryOp::NotEq => write!(f, "!="),
408 BinaryOp::Match => write!(f, "=~"),
409 BinaryOp::NotMatch => write!(f, "!~"),
410 BinaryOp::Lt => write!(f, "<"),
411 BinaryOp::Gt => write!(f, ">"),
412 BinaryOp::LtEq => write!(f, "<="),
413 BinaryOp::GtEq => write!(f, ">="),
414 }
415 }
416}
417
418impl fmt::Display for RedirectKind {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 match self {
421 RedirectKind::StdoutOverwrite => write!(f, ">"),
422 RedirectKind::StdoutAppend => write!(f, ">>"),
423 RedirectKind::Stdin => write!(f, "<"),
424 RedirectKind::HereDoc => write!(f, "<<"),
425 RedirectKind::Stderr => write!(f, "2>"),
426 RedirectKind::Both => write!(f, "&>"),
427 RedirectKind::MergeStderr => write!(f, "2>&1"),
428 RedirectKind::MergeStdout => write!(f, "1>&2"),
429 }
430 }
431}
432
433impl fmt::Display for FileTestOp {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 match self {
436 FileTestOp::Exists => write!(f, "-e"),
437 FileTestOp::IsFile => write!(f, "-f"),
438 FileTestOp::IsDir => write!(f, "-d"),
439 FileTestOp::Readable => write!(f, "-r"),
440 FileTestOp::Writable => write!(f, "-w"),
441 FileTestOp::Executable => write!(f, "-x"),
442 }
443 }
444}
445
446impl fmt::Display for StringTestOp {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 match self {
449 StringTestOp::IsEmpty => write!(f, "-z"),
450 StringTestOp::IsNonEmpty => write!(f, "-n"),
451 }
452 }
453}
454
455impl fmt::Display for TestCmpOp {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 match self {
458 TestCmpOp::Eq => write!(f, "=="),
459 TestCmpOp::NotEq => write!(f, "!="),
460 TestCmpOp::Match => write!(f, "=~"),
461 TestCmpOp::NotMatch => write!(f, "!~"),
462 TestCmpOp::Gt => write!(f, "-gt"),
463 TestCmpOp::Lt => write!(f, "-lt"),
464 TestCmpOp::GtEq => write!(f, "-ge"),
465 TestCmpOp::LtEq => write!(f, "-le"),
466 }
467 }
468}