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