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}
253
254#[derive(Debug, Clone, PartialEq)]
256pub enum TestExpr {
257 FileTest { op: FileTestOp, path: Box<Expr> },
259 StringTest { op: StringTestOp, value: Box<Expr> },
261 Comparison { left: Box<Expr>, op: TestCmpOp, right: Box<Expr> },
263 And { left: Box<TestExpr>, right: Box<TestExpr> },
265 Or { left: Box<TestExpr>, right: Box<TestExpr> },
267 Not { expr: Box<TestExpr> },
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273pub enum FileTestOp {
274 Exists,
276 IsFile,
278 IsDir,
280 Readable,
282 Writable,
284 Executable,
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum StringTestOp {
291 IsEmpty,
293 IsNonEmpty,
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub enum TestCmpOp {
300 Eq,
302 NotEq,
304 Match,
306 NotMatch,
308 Gt,
310 Lt,
312 GtEq,
314 LtEq,
316}
317
318pub use kaish_types::{BlobRef, Value};
320
321#[derive(Debug, Clone, PartialEq)]
326pub struct VarPath {
327 pub segments: Vec<VarSegment>,
328}
329
330impl VarPath {
331 pub fn simple(name: impl Into<String>) -> Self {
333 Self {
334 segments: vec![VarSegment::Field(name.into())],
335 }
336 }
337}
338
339#[derive(Debug, Clone, PartialEq)]
341pub enum VarSegment {
342 Field(String),
345}
346
347#[derive(Debug, Clone, PartialEq)]
349pub enum StringPart {
350 Literal(String),
352 Var(VarPath),
354 VarWithDefault { name: String, default: Vec<StringPart> },
356 VarLength(String),
358 Positional(usize),
360 AllArgs,
362 ArgCount,
364 Arithmetic(String),
366 CommandSubst(Pipeline),
368 LastExitCode,
370 CurrentPid,
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub enum BinaryOp {
377 And,
379 Or,
381 Eq,
383 NotEq,
385 Match,
387 NotMatch,
389 Lt,
391 Gt,
393 LtEq,
395 GtEq,
397}
398
399impl fmt::Display for BinaryOp {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 match self {
402 BinaryOp::And => write!(f, "&&"),
403 BinaryOp::Or => write!(f, "||"),
404 BinaryOp::Eq => write!(f, "=="),
405 BinaryOp::NotEq => write!(f, "!="),
406 BinaryOp::Match => write!(f, "=~"),
407 BinaryOp::NotMatch => write!(f, "!~"),
408 BinaryOp::Lt => write!(f, "<"),
409 BinaryOp::Gt => write!(f, ">"),
410 BinaryOp::LtEq => write!(f, "<="),
411 BinaryOp::GtEq => write!(f, ">="),
412 }
413 }
414}
415
416impl fmt::Display for RedirectKind {
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 match self {
419 RedirectKind::StdoutOverwrite => write!(f, ">"),
420 RedirectKind::StdoutAppend => write!(f, ">>"),
421 RedirectKind::Stdin => write!(f, "<"),
422 RedirectKind::HereDoc => write!(f, "<<"),
423 RedirectKind::Stderr => write!(f, "2>"),
424 RedirectKind::Both => write!(f, "&>"),
425 RedirectKind::MergeStderr => write!(f, "2>&1"),
426 RedirectKind::MergeStdout => write!(f, "1>&2"),
427 }
428 }
429}
430
431impl fmt::Display for FileTestOp {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 match self {
434 FileTestOp::Exists => write!(f, "-e"),
435 FileTestOp::IsFile => write!(f, "-f"),
436 FileTestOp::IsDir => write!(f, "-d"),
437 FileTestOp::Readable => write!(f, "-r"),
438 FileTestOp::Writable => write!(f, "-w"),
439 FileTestOp::Executable => write!(f, "-x"),
440 }
441 }
442}
443
444impl fmt::Display for StringTestOp {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 match self {
447 StringTestOp::IsEmpty => write!(f, "-z"),
448 StringTestOp::IsNonEmpty => write!(f, "-n"),
449 }
450 }
451}
452
453impl fmt::Display for TestCmpOp {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 match self {
456 TestCmpOp::Eq => write!(f, "=="),
457 TestCmpOp::NotEq => write!(f, "!="),
458 TestCmpOp::Match => write!(f, "=~"),
459 TestCmpOp::NotMatch => write!(f, "!~"),
460 TestCmpOp::Gt => write!(f, "-gt"),
461 TestCmpOp::Lt => write!(f, "-lt"),
462 TestCmpOp::GtEq => write!(f, "-ge"),
463 TestCmpOp::LtEq => write!(f, "-le"),
464 }
465 }
466}