strat9_kernel/shell/
parser.rs1use alloc::{
12 string::{String, ToString},
13 vec::Vec,
14};
15
16#[derive(Debug)]
18pub struct Command {
19 pub name: String,
21 pub args: Vec<String>,
23}
24
25#[derive(Debug, Clone)]
27pub enum Redirect {
28 Truncate(String),
30 Append(String),
32}
33
34#[derive(Debug)]
36pub struct PipelineStage {
37 pub command: Command,
39 pub stdout_redirect: Option<Redirect>,
41 pub stdin_redirect: Option<String>,
43}
44
45#[derive(Debug)]
47pub struct Pipeline {
48 pub stages: Vec<PipelineStage>,
50}
51
52pub fn parse_pipeline(line: &str) -> Option<Pipeline> {
56 let trimmed = line.trim();
57 if trimmed.is_empty() {
58 return None;
59 }
60
61 let segments: Vec<&str> = trimmed.split('|').collect();
62 let mut stages = Vec::new();
63
64 for seg in &segments {
65 let stage = parse_stage(seg.trim())?;
66 stages.push(stage);
67 }
68
69 if stages.is_empty() {
70 return None;
71 }
72
73 Some(Pipeline { stages })
74}
75
76fn parse_stage(segment: &str) -> Option<PipelineStage> {
78 let tokens = tokenize(segment);
79 if tokens.is_empty() {
80 return None;
81 }
82
83 let mut cmd_tokens: Vec<String> = Vec::new();
84 let mut stdout_redirect: Option<Redirect> = None;
85 let mut stdin_redirect: Option<String> = None;
86
87 let mut i = 0;
88 while i < tokens.len() {
89 if tokens[i] == ">>" {
90 i += 1;
91 if i < tokens.len() {
92 stdout_redirect = Some(Redirect::Append(tokens[i].clone()));
93 }
94 } else if tokens[i] == ">" {
95 i += 1;
96 if i < tokens.len() {
97 stdout_redirect = Some(Redirect::Truncate(tokens[i].clone()));
98 }
99 } else if tokens[i] == "<" {
100 i += 1;
101 if i < tokens.len() {
102 stdin_redirect = Some(tokens[i].clone());
103 }
104 } else {
105 cmd_tokens.push(tokens[i].clone());
106 }
107 i += 1;
108 }
109
110 if cmd_tokens.is_empty() {
111 return None;
112 }
113
114 let name = cmd_tokens.remove(0);
115 Some(PipelineStage {
116 command: Command {
117 name,
118 args: cmd_tokens,
119 },
120 stdout_redirect,
121 stdin_redirect,
122 })
123}
124
125fn tokenize(input: &str) -> Vec<String> {
127 let mut tokens = Vec::new();
128 let mut current = String::new();
129 let chars: Vec<char> = input.chars().collect();
130 let mut i = 0;
131
132 while i < chars.len() {
133 match chars[i] {
134 '>' => {
135 if !current.is_empty() {
136 tokens.push(core::mem::take(&mut current));
137 }
138 if i + 1 < chars.len() && chars[i + 1] == '>' {
139 tokens.push(String::from(">>"));
140 i += 2;
141 } else {
142 tokens.push(String::from(">"));
143 i += 1;
144 }
145 }
146 '<' => {
147 if !current.is_empty() {
148 tokens.push(core::mem::take(&mut current));
149 }
150 tokens.push(String::from("<"));
151 i += 1;
152 }
153 ' ' | '\t' => {
154 if !current.is_empty() {
155 tokens.push(core::mem::take(&mut current));
156 }
157 i += 1;
158 }
159 ch => {
160 current.push(ch);
161 i += 1;
162 }
163 }
164 }
165
166 if !current.is_empty() {
167 tokens.push(current);
168 }
169
170 tokens
171}
172
173pub fn parse(line: &str) -> Option<Command> {
177 let trimmed = line.trim();
178 if trimmed.is_empty() {
179 return None;
180 }
181
182 let mut parts = trimmed.split_whitespace();
183 let name = parts.next()?.to_string();
184 let args: Vec<String> = parts.map(|s| s.to_string()).collect();
185
186 Some(Command { name, args })
187}