strat9_kernel/shell/
scripting.rs1use crate::sync::SpinLock;
11use alloc::{
12 collections::BTreeMap,
13 string::{String, ToString},
14 vec::Vec,
15};
16
17static SHELL_VARS: SpinLock<BTreeMap<String, String>> = SpinLock::new(BTreeMap::new());
21static LAST_EXIT: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(0);
22
23pub fn set_last_exit(code: i32) {
25 LAST_EXIT.store(code, core::sync::atomic::Ordering::Relaxed);
26}
27
28pub fn last_exit() -> i32 {
30 LAST_EXIT.load(core::sync::atomic::Ordering::Relaxed)
31}
32
33pub fn set_var(key: &str, val: &str) {
35 SHELL_VARS
36 .lock()
37 .insert(String::from(key), String::from(val));
38}
39
40pub fn get_var(key: &str) -> Option<String> {
42 SHELL_VARS.lock().get(key).cloned()
43}
44
45pub fn unset_var(key: &str) {
47 SHELL_VARS.lock().remove(key);
48}
49
50pub fn expand_vars(input: &str) -> String {
52 let mut result = String::new();
53 let bytes = input.as_bytes();
54 let mut i = 0;
55
56 while i < bytes.len() {
57 if bytes[i] == b'$' {
58 i += 1;
59 if i < bytes.len() && bytes[i] == b'?' {
60 result.push_str(&alloc::format!("{}", last_exit()));
61 i += 1;
62 } else {
63 let start = i;
64 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
65 i += 1;
66 }
67 if i == start {
68 result.push('$');
70 continue;
71 }
72 let var_name = core::str::from_utf8(&bytes[start..i]).unwrap_or("");
73 if let Some(val) = get_var(var_name) {
74 result.push_str(&val);
75 } else if let Some(env_val) = super::commands::util::shell_getenv(var_name) {
76 result.push_str(&env_val);
77 }
78 }
79 } else {
80 result.push(bytes[i] as char);
81 i += 1;
82 }
83 }
84
85 result
86}
87
88pub enum ScriptConstruct {
90 Simple(String),
92 SetVar { key: String, val: String },
94 UnsetVar(String),
96 ForLoop {
98 var: String,
99 items: Vec<String>,
100 body: Vec<String>,
101 },
102 WhileLoop { cond: String, body: Vec<String> },
104 IfElse {
106 cond: String,
107 then_body: Vec<String>,
108 else_body: Vec<String>,
109 },
110}
111
112pub fn parse_script(line: &str) -> ScriptConstruct {
117 let trimmed = line.trim();
118
119 if trimmed.starts_with("set ") {
120 let rest = &trimmed[4..];
121 if let Some(eq) = rest.find('=') {
122 return ScriptConstruct::SetVar {
123 key: String::from(rest[..eq].trim()),
124 val: String::from(rest[eq + 1..].trim()),
125 };
126 }
127 return ScriptConstruct::Simple(String::from(trimmed));
128 }
129
130 if trimmed.starts_with("unset ") {
131 return ScriptConstruct::UnsetVar(String::from(trimmed[6..].trim()));
132 }
133
134 let parts: Vec<&str> = trimmed.split(';').map(|s| s.trim()).collect();
135
136 if parts.first().map(|p| p.starts_with("for ")) == Some(true) {
137 return parse_for_loop(&parts);
138 }
139 if parts.first().map(|p| p.starts_with("while ")) == Some(true) {
140 return parse_while_loop(&parts);
141 }
142 if parts.first().map(|p| p.starts_with("if ")) == Some(true) {
143 return parse_if_else(&parts);
144 }
145
146 ScriptConstruct::Simple(String::from(trimmed))
147}
148
149fn parse_for_loop(parts: &[&str]) -> ScriptConstruct {
151 if parts.is_empty() {
152 return ScriptConstruct::Simple(String::new());
153 }
154 let header = parts[0];
155 let tokens: Vec<&str> = header.split_whitespace().collect();
156 if tokens.len() < 4 || tokens[0] != "for" || tokens[2] != "in" {
157 return ScriptConstruct::Simple(String::from(header));
158 }
159
160 let var = tokens.get(1).unwrap_or(&"_").to_string();
161 let items: Vec<String> = tokens
162 .iter()
163 .skip(3) .map(|s| String::from(*s))
165 .collect();
166
167 let Some(do_idx) = parts.iter().position(|p| *p == "do") else {
168 return ScriptConstruct::Simple(String::from(header));
169 };
170 let Some(done_idx) = parts.iter().position(|p| *p == "done") else {
171 return ScriptConstruct::Simple(String::from(header));
172 };
173 if done_idx <= do_idx {
174 return ScriptConstruct::Simple(String::from(header));
175 }
176
177 let body: Vec<String> = parts[do_idx + 1..done_idx]
178 .iter()
179 .filter(|s| !s.is_empty())
180 .map(|s| String::from(*s))
181 .collect();
182
183 ScriptConstruct::ForLoop {
184 var: String::from(var),
185 items,
186 body,
187 }
188}
189
190fn parse_while_loop(parts: &[&str]) -> ScriptConstruct {
192 if parts.is_empty() {
193 return ScriptConstruct::Simple(String::new());
194 }
195 let header = parts[0];
196 let Some(cond) = header.strip_prefix("while ") else {
197 return ScriptConstruct::Simple(String::from(header));
198 };
199 let Some(do_idx) = parts.iter().position(|p| *p == "do") else {
200 return ScriptConstruct::Simple(String::from(header));
201 };
202 let Some(done_idx) = parts.iter().position(|p| *p == "done") else {
203 return ScriptConstruct::Simple(String::from(header));
204 };
205 if done_idx <= do_idx {
206 return ScriptConstruct::Simple(String::from(header));
207 }
208
209 let body: Vec<String> = parts[do_idx + 1..done_idx]
210 .iter()
211 .filter(|s| !s.is_empty())
212 .map(|s| String::from(*s))
213 .collect();
214
215 ScriptConstruct::WhileLoop {
216 cond: String::from(cond),
217 body,
218 }
219}
220
221fn parse_if_else(parts: &[&str]) -> ScriptConstruct {
223 if parts.is_empty() {
224 return ScriptConstruct::Simple(String::new());
225 }
226 let header = parts[0];
227 let Some(cond) = header.strip_prefix("if ") else {
228 return ScriptConstruct::Simple(String::from(header));
229 };
230 let Some(then_idx) = parts.iter().position(|p| *p == "then") else {
231 return ScriptConstruct::Simple(String::from(header));
232 };
233 let else_idx = parts.iter().position(|p| *p == "else");
234 let Some(fi_idx) = parts.iter().position(|p| *p == "fi") else {
235 return ScriptConstruct::Simple(String::from(header));
236 };
237 if fi_idx <= then_idx {
238 return ScriptConstruct::Simple(String::from(header));
239 }
240
241 let then_end = else_idx.unwrap_or(fi_idx);
242 let then_body: Vec<String> = parts[then_idx + 1..then_end]
243 .iter()
244 .filter(|s| !s.is_empty())
245 .map(|s| String::from(*s))
246 .collect();
247
248 let else_body: Vec<String> = if let Some(ei) = else_idx {
249 parts[ei + 1..fi_idx]
250 .iter()
251 .filter(|s| !s.is_empty())
252 .map(|s| String::from(*s))
253 .collect()
254 } else {
255 Vec::new()
256 };
257
258 ScriptConstruct::IfElse {
259 cond: String::from(cond),
260 then_body,
261 else_body,
262 }
263}