slash_lang/parser/ast.rs
1/// The root of a parsed slash-command program.
2///
3/// A program is a sequence of [`Pipeline`]s connected by `&&` / `||` operators.
4/// The operator stored on each pipeline indicates how it connects to the *next* one.
5#[derive(Debug, Clone, PartialEq)]
6pub struct Program {
7 pub pipelines: Vec<Pipeline>,
8}
9
10/// A sequence of [`Command`]s connected by `|` / `|&` operators within one logical unit.
11///
12/// `operator` indicates how this pipeline connects to the next one in the [`Program`]:
13/// - `Some(Op::And)` — next pipeline runs only if this one succeeds (`&&`)
14/// - `Some(Op::Or)` — next pipeline runs only if this one fails (`||`)
15/// - `None` — this is the last pipeline
16#[derive(Debug, Clone, PartialEq)]
17pub struct Pipeline {
18 pub commands: Vec<Command>,
19 pub operator: Option<Op>,
20}
21
22/// An operator connecting commands or pipelines.
23#[derive(Debug, Clone, PartialEq)]
24pub enum Op {
25 /// `&&` — run next pipeline only on success
26 And,
27 /// `||` — run next pipeline only on failure
28 Or,
29 /// `|` — pipe stdout to the next command
30 Pipe,
31 /// `|&` — pipe both stdout and stderr to the next command
32 PipeErr,
33}
34
35/// A single builder-chain argument on a command.
36///
37/// Parsed from dotted segments after the command name:
38/// `/cmd.flag` → `Arg { name: "flag", value: None }`
39/// `/cmd.key(val)` → `Arg { name: "key", value: Some("val") }`
40#[derive(Debug, Clone, PartialEq)]
41pub struct Arg {
42 pub name: String,
43 pub value: Option<String>,
44}
45
46/// A single parsed slash command.
47///
48/// # Priority
49///
50/// Inferred from the **shape** of the raw token before normalization:
51///
52/// | Token shape | Priority |
53/// |------------------|----------|
54/// | `ALL_CAPS` | Max |
55/// | `TitleCase` | High |
56/// | `camelCase` | Medium |
57/// | `kebab-case` | Low |
58/// | `snake_case` | Lowest |
59///
60/// # Suffixes (applied outer → inner)
61///
62/// ```text
63/// /TitleCase+++???! → invalid (double ? is an error)
64/// /Deploy?! → name="deploy", optional, urgency=Low
65/// /ALERT!!! → urgency=High
66/// /verbose+++ → verbosity=+3
67/// /quiet-- → verbosity=-2
68/// ```
69#[derive(Debug, Clone, PartialEq)]
70pub struct Command {
71 /// The original token as it appeared in the input (e.g. `/Build.flag(v)!`).
72 pub raw: String,
73 /// Normalized lowercase name (e.g. `build`).
74 pub name: String,
75 /// Primary argument from `/cmd(value)` syntax.
76 pub primary: Option<String>,
77 /// Builder-chain arguments (e.g. `.flag(val).other`).
78 pub args: Vec<Arg>,
79 /// Priority inferred from the raw token's casing/separators.
80 pub priority: Priority,
81 /// Urgency from trailing `!` / `!!` / `!!!`.
82 pub urgency: Urgency,
83 /// Verbosity level from trailing `+` (positive) or `-` (negative) markers.
84 pub verbosity: i8,
85 /// Whether the command is optional (`?` suffix) — its return value may be absent.
86 pub optional: bool,
87 /// Numeric test identifier for `/test`-family commands (e.g. `/test3` → `Some(3)`).
88 pub test_id: Option<u32>,
89 /// Output redirection, if present. Closes the pipeline — no further `|` is allowed.
90 pub redirect: Option<Redirection>,
91 /// Pipe operator connecting this command to the next one within the same pipeline.
92 pub pipe: Option<Op>,
93}
94
95/// Priority level inferred from the command token's shape.
96#[derive(Debug, Clone, PartialEq)]
97pub enum Priority {
98 /// `ALL_CAPS` token
99 Max,
100 /// `TitleCase` token
101 High,
102 /// `camelCase` token
103 Medium,
104 /// `kebab-case` or plain lowercase token
105 Low,
106 /// `snake_case` token
107 Lowest,
108}
109
110/// Urgency level from trailing `!` markers.
111#[derive(Debug, Clone, PartialEq)]
112pub enum Urgency {
113 None,
114 /// `!`
115 Low,
116 /// `!!`
117 Medium,
118 /// `!!!`
119 High,
120}
121
122/// Output redirection target.
123#[derive(Debug, Clone, PartialEq)]
124pub enum Redirection {
125 /// `> file` — overwrite
126 Truncate(String),
127 /// `>> file` — append
128 Append(String),
129}
130
131impl Command {
132 pub fn new(name: String, priority: Priority) -> Self {
133 Self {
134 raw: String::new(),
135 name,
136 primary: None,
137 args: vec![],
138 priority,
139 urgency: Urgency::None,
140 verbosity: 0,
141 optional: false,
142 test_id: None,
143 redirect: None,
144 pipe: None,
145 }
146 }
147}