use async_trait::async_trait;
use crate::report::result::ExecError;
use crate::report::result::Failure;
use crate::vm::Vm;
use relux_core::diagnostics::IrSpan;
#[async_trait]
pub trait Bif: Send + Sync {
fn name(&self) -> &str;
fn arity(&self) -> usize;
async fn call(
&self,
vm: &mut Vm,
args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError>;
}
pub fn lookup_impure(name: &str, arity: usize) -> Option<Box<dyn Bif>> {
match (name, arity) {
("sleep", 1) => Some(Box::new(Sleep)),
("annotate", 1) => Some(Box::new(Annotate)),
("log", 1) => Some(Box::new(Log)),
("match_prompt", 0) => Some(Box::new(MatchPrompt)),
("match_exit_code", 1) => Some(Box::new(MatchExitCode)),
("match_ok", 0) => Some(Box::new(MatchOk)),
("match_not_ok", 0) => Some(Box::new(MatchNotOk)),
("match_not_ok", 1) => Some(Box::new(MatchNotOkWithCode)),
("ctrl_c", 0) => Some(Box::new(CtrlChar {
name: "ctrl_c",
byte: 0x03,
})),
("ctrl_d", 0) => Some(Box::new(CtrlChar {
name: "ctrl_d",
byte: 0x04,
})),
("ctrl_z", 0) => Some(Box::new(CtrlChar {
name: "ctrl_z",
byte: 0x1A,
})),
("ctrl_l", 0) => Some(Box::new(CtrlChar {
name: "ctrl_l",
byte: 0x0C,
})),
("ctrl_backslash", 0) => Some(Box::new(CtrlChar {
name: "ctrl_backslash",
byte: 0x1C,
})),
_ => None,
}
}
pub fn is_known(name: &str, arity: usize) -> bool {
relux_core::pure::bifs::is_pure_bif(name, arity) || lookup_impure(name, arity).is_some()
}
pub fn is_pure_bif(name: &str, arity: usize) -> bool {
relux_core::pure::bifs::is_pure_bif(name, arity)
}
pub fn is_impure_bif(name: &str, arity: usize) -> bool {
lookup_impure(name, arity).is_some()
}
async fn runtime_error(vm: &Vm, message: String, span: &IrSpan) -> Failure {
let context = vm.capture_failure_context().await;
Failure::Runtime {
message,
span: Some(span.clone()),
shell: Some(vm.current_name()),
context,
}
}
pub struct Sleep;
#[async_trait]
impl Bif for Sleep {
fn name(&self) -> &str {
"sleep"
}
fn arity(&self) -> usize {
1
}
async fn call(
&self,
vm: &mut Vm,
args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let duration = match humantime::parse_duration(args[0].trim()) {
Ok(d) => d,
Err(_) => {
return Err(
runtime_error(vm, format!("invalid duration: `{}`", args[0]), span)
.await
.into(),
);
}
};
let span_id = vm.current_span();
let shell = vm.current_name();
let marker = vm.shell_marker().to_string();
vm.log
.emit_sleep_start(span_id, &shell, &marker, duration, Some(span));
tokio::select! {
_ = tokio::time::sleep(duration) => {}
_ = vm.cancel.cancelled() => {
let shell = vm.current_name();
vm.log.emit_sleep_done(span_id, &shell, &marker, Some(span));
return Err(vm.observed_cancel(Some(span.clone())).await);
}
}
let shell = vm.current_name();
vm.log.emit_sleep_done(span_id, &shell, &marker, Some(span));
Ok(String::new())
}
}
pub struct Annotate;
#[async_trait]
impl Bif for Annotate {
fn name(&self) -> &str {
"annotate"
}
fn arity(&self) -> usize {
1
}
async fn call(
&self,
vm: &mut Vm,
args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let text = args[0].clone();
let shell = vm.current_name();
let marker = vm.shell_marker().to_string();
vm.log
.emit_annotate(vm.current_span(), &shell, &marker, &text, Some(span));
Ok(text)
}
}
pub struct Log;
#[async_trait]
impl Bif for Log {
fn name(&self) -> &str {
"log"
}
fn arity(&self) -> usize {
1
}
async fn call(
&self,
vm: &mut Vm,
args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let message = args[0].clone();
let shell = vm.current_name();
let marker = vm.shell_marker().to_string();
vm.log
.emit_log(vm.current_span(), &shell, &marker, &message, Some(span));
Ok(message)
}
}
pub struct MatchPrompt;
#[async_trait]
impl Bif for MatchPrompt {
fn name(&self) -> &str {
"match_prompt"
}
fn arity(&self) -> usize {
0
}
async fn call(
&self,
vm: &mut Vm,
_args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let prompt = vm.shell_prompt().to_string();
vm.match_literal(&prompt, span).await
}
}
pub struct MatchExitCode;
#[async_trait]
impl Bif for MatchExitCode {
fn name(&self) -> &str {
"match_exit_code"
}
fn arity(&self) -> usize {
1
}
async fn call(
&self,
vm: &mut Vm,
args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let prompt = vm.shell_prompt().to_string();
vm.send_line("echo ::$?::", span).await?;
vm.match_literal(&format!("::{}::", args[0]), span).await?;
vm.match_literal(&prompt, span).await
}
}
pub struct MatchOk;
#[async_trait]
impl Bif for MatchOk {
fn name(&self) -> &str {
"match_ok"
}
fn arity(&self) -> usize {
0
}
async fn call(
&self,
vm: &mut Vm,
_args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let prompt = vm.shell_prompt().to_string();
vm.match_literal(&prompt, span).await?;
vm.send_line("echo ::$?::", span).await?;
vm.match_literal("::0::", span).await?;
vm.match_literal(&prompt, span).await
}
}
pub struct MatchNotOk;
#[async_trait]
impl Bif for MatchNotOk {
fn name(&self) -> &str {
"match_not_ok"
}
fn arity(&self) -> usize {
0
}
async fn call(
&self,
vm: &mut Vm,
_args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let prompt = vm.shell_prompt().to_string();
vm.match_literal(&prompt, span).await?;
vm.send_line(
"__RE=$(echo ::$?::) && test \"${__RE}\" != '::0::' && echo ${__RE}",
span,
)
.await?;
vm.match_literal("::", span).await?;
vm.match_literal(&prompt, span).await
}
}
pub struct MatchNotOkWithCode;
#[async_trait]
impl Bif for MatchNotOkWithCode {
fn name(&self) -> &str {
"match_not_ok"
}
fn arity(&self) -> usize {
1
}
async fn call(
&self,
vm: &mut Vm,
args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
let prompt = vm.shell_prompt().to_string();
vm.match_literal(&prompt, span).await?;
vm.send_line(
"__RE=$(echo ::$?::) && test \"${__RE}\" != '::0::' && echo ${__RE}",
span,
)
.await?;
vm.match_literal(&format!("::{}::", args[0]), span).await?;
vm.match_literal(&prompt, span).await
}
}
pub struct CtrlChar {
name: &'static str,
byte: u8,
}
#[async_trait]
impl Bif for CtrlChar {
fn name(&self) -> &str {
self.name
}
fn arity(&self) -> usize {
0
}
async fn call(
&self,
vm: &mut Vm,
_args: Vec<String>,
span: &IrSpan,
) -> Result<String, ExecError> {
vm.send_raw(&[self.byte], span).await?;
Ok(String::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_lookup() {
assert!(is_pure_bif("trim", 1));
assert!(is_pure_bif("upper", 1));
assert!(is_pure_bif("rand", 1));
assert!(is_pure_bif("rand", 2));
assert!(is_pure_bif("uuid", 0));
assert!(is_pure_bif("available_port", 0));
assert!(is_pure_bif("which", 1));
assert!(is_pure_bif("default", 2));
assert!(lookup_impure("sleep", 1).is_some());
assert!(lookup_impure("annotate", 1).is_some());
assert!(lookup_impure("log", 1).is_some());
assert!(lookup_impure("match_prompt", 0).is_some());
assert!(lookup_impure("match_exit_code", 1).is_some());
assert!(lookup_impure("match_ok", 0).is_some());
assert!(lookup_impure("match_not_ok", 0).is_some());
assert!(lookup_impure("ctrl_c", 0).is_some());
assert!(lookup_impure("ctrl_d", 0).is_some());
assert!(lookup_impure("ctrl_z", 0).is_some());
assert!(lookup_impure("ctrl_l", 0).is_some());
assert!(lookup_impure("ctrl_backslash", 0).is_some());
assert!(!is_known("nonexistent", 0));
assert!(!is_known("trim", 2));
}
}