1use async_trait::async_trait;
2
3use crate::report::result::ExecError;
4use crate::report::result::Failure;
5use crate::vm::Vm;
6use relux_core::diagnostics::IrSpan;
7
8#[async_trait]
13pub trait Bif: Send + Sync {
14 fn name(&self) -> &str;
15 fn arity(&self) -> usize;
16 async fn call(
17 &self,
18 vm: &mut Vm,
19 args: Vec<String>,
20 span: &IrSpan,
21 ) -> Result<String, ExecError>;
22}
23
24pub fn lookup_impure(name: &str, arity: usize) -> Option<Box<dyn Bif>> {
27 match (name, arity) {
28 ("sleep", 1) => Some(Box::new(Sleep)),
29 ("annotate", 1) => Some(Box::new(Annotate)),
30 ("log", 1) => Some(Box::new(Log)),
31 ("match_prompt", 0) => Some(Box::new(MatchPrompt)),
32 ("match_exit_code", 1) => Some(Box::new(MatchExitCode)),
33 ("match_ok", 0) => Some(Box::new(MatchOk)),
34 ("match_not_ok", 0) => Some(Box::new(MatchNotOk)),
35 ("match_not_ok", 1) => Some(Box::new(MatchNotOkWithCode)),
36 ("ctrl_c", 0) => Some(Box::new(CtrlChar {
37 name: "ctrl_c",
38 byte: 0x03,
39 })),
40 ("ctrl_d", 0) => Some(Box::new(CtrlChar {
41 name: "ctrl_d",
42 byte: 0x04,
43 })),
44 ("ctrl_z", 0) => Some(Box::new(CtrlChar {
45 name: "ctrl_z",
46 byte: 0x1A,
47 })),
48 ("ctrl_l", 0) => Some(Box::new(CtrlChar {
49 name: "ctrl_l",
50 byte: 0x0C,
51 })),
52 ("ctrl_backslash", 0) => Some(Box::new(CtrlChar {
53 name: "ctrl_backslash",
54 byte: 0x1C,
55 })),
56 _ => None,
57 }
58}
59
60pub fn is_known(name: &str, arity: usize) -> bool {
62 relux_core::pure::bifs::is_pure_bif(name, arity) || lookup_impure(name, arity).is_some()
63}
64
65pub fn is_pure_bif(name: &str, arity: usize) -> bool {
67 relux_core::pure::bifs::is_pure_bif(name, arity)
68}
69
70pub fn is_impure_bif(name: &str, arity: usize) -> bool {
72 lookup_impure(name, arity).is_some()
73}
74
75async fn runtime_error(vm: &Vm, message: String, span: &IrSpan) -> Failure {
76 let context = vm.capture_failure_context().await;
77 Failure::Runtime {
78 message,
79 span: span.clone(),
80 shell: Some(vm.current_name()),
81 context,
82 }
83}
84
85pub struct Sleep;
88
89#[async_trait]
90impl Bif for Sleep {
91 fn name(&self) -> &str {
92 "sleep"
93 }
94 fn arity(&self) -> usize {
95 1
96 }
97
98 async fn call(
99 &self,
100 vm: &mut Vm,
101 args: Vec<String>,
102 span: &IrSpan,
103 ) -> Result<String, ExecError> {
104 let duration = match humantime::parse_duration(args[0].trim()) {
105 Ok(d) => d,
106 Err(_) => {
107 return Err(
108 runtime_error(vm, format!("invalid duration: `{}`", args[0]), span)
109 .await
110 .into(),
111 );
112 }
113 };
114 let span_id = vm.current_span();
115 let shell = vm.current_name();
116 let marker = vm.shell_marker().to_string();
117 vm.log
118 .emit_sleep_start(span_id, &shell, &marker, duration, Some(span));
119 tokio::select! {
120 _ = tokio::time::sleep(duration) => {}
121 _ = vm.cancel.cancelled() => {
122 let shell = vm.current_name();
123 vm.log.emit_sleep_done(span_id, &shell, &marker, Some(span));
124 return Err(vm.observed_cancel(Some(span.clone())).await);
125 }
126 }
127 let shell = vm.current_name();
128 vm.log.emit_sleep_done(span_id, &shell, &marker, Some(span));
129 Ok(String::new())
130 }
131}
132
133pub struct Annotate;
134
135#[async_trait]
136impl Bif for Annotate {
137 fn name(&self) -> &str {
138 "annotate"
139 }
140 fn arity(&self) -> usize {
141 1
142 }
143
144 async fn call(
145 &self,
146 vm: &mut Vm,
147 args: Vec<String>,
148 span: &IrSpan,
149 ) -> Result<String, ExecError> {
150 let text = args[0].clone();
151 let shell = vm.current_name();
152 let marker = vm.shell_marker().to_string();
153 vm.log
154 .emit_annotate(vm.current_span(), &shell, &marker, &text, Some(span));
155 Ok(text)
156 }
157}
158
159pub struct Log;
160
161#[async_trait]
162impl Bif for Log {
163 fn name(&self) -> &str {
164 "log"
165 }
166 fn arity(&self) -> usize {
167 1
168 }
169
170 async fn call(
171 &self,
172 vm: &mut Vm,
173 args: Vec<String>,
174 span: &IrSpan,
175 ) -> Result<String, ExecError> {
176 let message = args[0].clone();
177 let shell = vm.current_name();
178 let marker = vm.shell_marker().to_string();
179 vm.log
180 .emit_log(vm.current_span(), &shell, &marker, &message, Some(span));
181 Ok(message)
182 }
183}
184
185pub struct MatchPrompt;
186
187#[async_trait]
188impl Bif for MatchPrompt {
189 fn name(&self) -> &str {
190 "match_prompt"
191 }
192 fn arity(&self) -> usize {
193 0
194 }
195
196 async fn call(
197 &self,
198 vm: &mut Vm,
199 _args: Vec<String>,
200 span: &IrSpan,
201 ) -> Result<String, ExecError> {
202 let prompt = vm.shell_prompt().to_string();
203 vm.match_literal(&prompt, span).await
204 }
205}
206
207pub struct MatchExitCode;
208
209#[async_trait]
210impl Bif for MatchExitCode {
211 fn name(&self) -> &str {
212 "match_exit_code"
213 }
214 fn arity(&self) -> usize {
215 1
216 }
217
218 async fn call(
219 &self,
220 vm: &mut Vm,
221 args: Vec<String>,
222 span: &IrSpan,
223 ) -> Result<String, ExecError> {
224 let prompt = vm.shell_prompt().to_string();
225 vm.send_line("echo ::$?::", span).await?;
226 vm.match_literal(&format!("::{}::", args[0]), span).await?;
227 vm.match_literal(&prompt, span).await
228 }
229}
230
231pub struct MatchOk;
232
233#[async_trait]
234impl Bif for MatchOk {
235 fn name(&self) -> &str {
236 "match_ok"
237 }
238 fn arity(&self) -> usize {
239 0
240 }
241
242 async fn call(
243 &self,
244 vm: &mut Vm,
245 _args: Vec<String>,
246 span: &IrSpan,
247 ) -> Result<String, ExecError> {
248 let prompt = vm.shell_prompt().to_string();
249 vm.match_literal(&prompt, span).await?;
250 vm.send_line("echo ::$?::", span).await?;
251 vm.match_literal("::0::", span).await?;
252 vm.match_literal(&prompt, span).await
253 }
254}
255
256pub struct MatchNotOk;
257
258#[async_trait]
259impl Bif for MatchNotOk {
260 fn name(&self) -> &str {
261 "match_not_ok"
262 }
263 fn arity(&self) -> usize {
264 0
265 }
266
267 async fn call(
268 &self,
269 vm: &mut Vm,
270 _args: Vec<String>,
271 span: &IrSpan,
272 ) -> Result<String, ExecError> {
273 let prompt = vm.shell_prompt().to_string();
274 vm.match_literal(&prompt, span).await?;
275 vm.send_line(
276 "__RE=$(echo ::$?::) && test \"${__RE}\" != '::0::' && echo ${__RE}",
277 span,
278 )
279 .await?;
280 vm.match_literal("::", span).await?;
281 vm.match_literal(&prompt, span).await
282 }
283}
284
285pub struct MatchNotOkWithCode;
286
287#[async_trait]
288impl Bif for MatchNotOkWithCode {
289 fn name(&self) -> &str {
290 "match_not_ok"
291 }
292 fn arity(&self) -> usize {
293 1
294 }
295
296 async fn call(
297 &self,
298 vm: &mut Vm,
299 args: Vec<String>,
300 span: &IrSpan,
301 ) -> Result<String, ExecError> {
302 let prompt = vm.shell_prompt().to_string();
303 vm.match_literal(&prompt, span).await?;
304 vm.send_line(
305 "__RE=$(echo ::$?::) && test \"${__RE}\" != '::0::' && echo ${__RE}",
306 span,
307 )
308 .await?;
309 vm.match_literal(&format!("::{}::", args[0]), span).await?;
310 vm.match_literal(&prompt, span).await
311 }
312}
313
314pub struct CtrlChar {
315 name: &'static str,
316 byte: u8,
317}
318
319#[async_trait]
320impl Bif for CtrlChar {
321 fn name(&self) -> &str {
322 self.name
323 }
324 fn arity(&self) -> usize {
325 0
326 }
327
328 async fn call(
329 &self,
330 vm: &mut Vm,
331 _args: Vec<String>,
332 span: &IrSpan,
333 ) -> Result<String, ExecError> {
334 vm.send_raw(&[self.byte], span).await?;
335 Ok(String::new())
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[tokio::test]
348 async fn test_lookup() {
349 assert!(is_pure_bif("trim", 1));
351 assert!(is_pure_bif("upper", 1));
352 assert!(is_pure_bif("rand", 1));
353 assert!(is_pure_bif("rand", 2));
354 assert!(is_pure_bif("uuid", 0));
355 assert!(is_pure_bif("available_port", 0));
356 assert!(is_pure_bif("which", 1));
357 assert!(is_pure_bif("default", 2));
358 assert!(lookup_impure("sleep", 1).is_some());
360 assert!(lookup_impure("annotate", 1).is_some());
361 assert!(lookup_impure("log", 1).is_some());
362 assert!(lookup_impure("match_prompt", 0).is_some());
363 assert!(lookup_impure("match_exit_code", 1).is_some());
364 assert!(lookup_impure("match_ok", 0).is_some());
365 assert!(lookup_impure("match_not_ok", 0).is_some());
366 assert!(lookup_impure("ctrl_c", 0).is_some());
367 assert!(lookup_impure("ctrl_d", 0).is_some());
368 assert!(lookup_impure("ctrl_z", 0).is_some());
369 assert!(lookup_impure("ctrl_l", 0).is_some());
370 assert!(lookup_impure("ctrl_backslash", 0).is_some());
371 assert!(!is_known("nonexistent", 0));
372 assert!(!is_known("trim", 2));
373 }
374}