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
318#[derive(Debug, Clone, PartialEq)]
323pub enum Value {
324 Null,
325 Bool(bool),
326 Int(i64),
327 Float(f64),
328 String(String),
329 Json(serde_json::Value),
332 Blob(BlobRef),
334}
335
336#[derive(Debug, Clone, PartialEq)]
341pub struct BlobRef {
342 pub id: String,
344 pub size: u64,
346 pub content_type: String,
348 pub hash: Option<Vec<u8>>,
350}
351
352impl BlobRef {
353 pub fn new(id: impl Into<String>, size: u64, content_type: impl Into<String>) -> Self {
355 Self {
356 id: id.into(),
357 size,
358 content_type: content_type.into(),
359 hash: None,
360 }
361 }
362
363 pub fn with_hash(mut self, hash: Vec<u8>) -> Self {
365 self.hash = Some(hash);
366 self
367 }
368
369 pub fn path(&self) -> String {
371 format!("/v/blobs/{}", self.id)
372 }
373
374 pub fn formatted_size(&self) -> String {
376 const KB: u64 = 1024;
377 const MB: u64 = 1024 * KB;
378 const GB: u64 = 1024 * MB;
379
380 if self.size >= GB {
381 format!("{:.1}GB", self.size as f64 / GB as f64)
382 } else if self.size >= MB {
383 format!("{:.1}MB", self.size as f64 / MB as f64)
384 } else if self.size >= KB {
385 format!("{:.1}KB", self.size as f64 / KB as f64)
386 } else {
387 format!("{}B", self.size)
388 }
389 }
390}
391
392#[derive(Debug, Clone, PartialEq)]
397pub struct VarPath {
398 pub segments: Vec<VarSegment>,
399}
400
401impl VarPath {
402 pub fn simple(name: impl Into<String>) -> Self {
404 Self {
405 segments: vec![VarSegment::Field(name.into())],
406 }
407 }
408}
409
410#[derive(Debug, Clone, PartialEq)]
412pub enum VarSegment {
413 Field(String),
416}
417
418#[derive(Debug, Clone, PartialEq)]
420pub enum StringPart {
421 Literal(String),
423 Var(VarPath),
425 VarWithDefault { name: String, default: Vec<StringPart> },
427 VarLength(String),
429 Positional(usize),
431 AllArgs,
433 ArgCount,
435 Arithmetic(String),
437 CommandSubst(Pipeline),
439 LastExitCode,
441 CurrentPid,
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
447pub enum BinaryOp {
448 And,
450 Or,
452 Eq,
454 NotEq,
456 Match,
458 NotMatch,
460 Lt,
462 Gt,
464 LtEq,
466 GtEq,
468}
469
470impl fmt::Display for BinaryOp {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 match self {
473 BinaryOp::And => write!(f, "&&"),
474 BinaryOp::Or => write!(f, "||"),
475 BinaryOp::Eq => write!(f, "=="),
476 BinaryOp::NotEq => write!(f, "!="),
477 BinaryOp::Match => write!(f, "=~"),
478 BinaryOp::NotMatch => write!(f, "!~"),
479 BinaryOp::Lt => write!(f, "<"),
480 BinaryOp::Gt => write!(f, ">"),
481 BinaryOp::LtEq => write!(f, "<="),
482 BinaryOp::GtEq => write!(f, ">="),
483 }
484 }
485}
486
487impl fmt::Display for RedirectKind {
488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 match self {
490 RedirectKind::StdoutOverwrite => write!(f, ">"),
491 RedirectKind::StdoutAppend => write!(f, ">>"),
492 RedirectKind::Stdin => write!(f, "<"),
493 RedirectKind::HereDoc => write!(f, "<<"),
494 RedirectKind::Stderr => write!(f, "2>"),
495 RedirectKind::Both => write!(f, "&>"),
496 RedirectKind::MergeStderr => write!(f, "2>&1"),
497 RedirectKind::MergeStdout => write!(f, "1>&2"),
498 }
499 }
500}
501
502impl fmt::Display for FileTestOp {
503 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504 match self {
505 FileTestOp::Exists => write!(f, "-e"),
506 FileTestOp::IsFile => write!(f, "-f"),
507 FileTestOp::IsDir => write!(f, "-d"),
508 FileTestOp::Readable => write!(f, "-r"),
509 FileTestOp::Writable => write!(f, "-w"),
510 FileTestOp::Executable => write!(f, "-x"),
511 }
512 }
513}
514
515impl fmt::Display for StringTestOp {
516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517 match self {
518 StringTestOp::IsEmpty => write!(f, "-z"),
519 StringTestOp::IsNonEmpty => write!(f, "-n"),
520 }
521 }
522}
523
524impl fmt::Display for TestCmpOp {
525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526 match self {
527 TestCmpOp::Eq => write!(f, "=="),
528 TestCmpOp::NotEq => write!(f, "!="),
529 TestCmpOp::Match => write!(f, "=~"),
530 TestCmpOp::NotMatch => write!(f, "!~"),
531 TestCmpOp::Gt => write!(f, "-gt"),
532 TestCmpOp::Lt => write!(f, "-lt"),
533 TestCmpOp::GtEq => write!(f, "-ge"),
534 TestCmpOp::LtEq => write!(f, "-le"),
535 }
536 }
537}