use thiserror::Error;
use crate::dialogue::Value;
#[derive(Error, Debug)]
pub enum HostError {
#[error("unknown function `{0}`")]
UnknownFunction(String),
#[error("`{name}`: `{message}`")]
Failed { name: String, message: String },
#[error("builtin `{name}`: `{message}`")]
BuiltinFailed { name: String, message: String },
#[error("subject {0} is unknown")]
UnknownSubject(String),
#[error("feature {0} is unknown")]
UnknownFeature(String),
#[error("feature {feature} is not on the subject {subject}")]
FeatureNotOnSubject { feature: String, subject: String },
}
pub trait RepliqueHost {
fn call(&mut self, name: &str, args: Vec<Value>) -> Result<Value, HostError>;
fn has_feature(&mut self, subject: &str, name: &str) -> Result<bool, HostError>;
fn feature(&mut self, subject: &str, name: &str) -> Result<Value, HostError>;
}
pub struct NoHost;
impl RepliqueHost for NoHost {
fn call(&mut self, name: &str, _: Vec<Value>) -> Result<Value, HostError> {
Err(HostError::UnknownFunction(name.into()))
}
fn has_feature(&mut self, _: &str, name: &str) -> Result<bool, HostError> {
Err(HostError::UnknownFeature(name.into()))
}
fn feature(&mut self, _: &str, name: &str) -> Result<Value, HostError> {
Err(HostError::UnknownFeature(name.into()))
}
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
#[derive(Default)]
pub(crate) struct TestHost {
pub calls: Vec<(String, Vec<Value>)>,
}
impl TestHost {
pub fn called(&self) -> Vec<&str> {
self.calls.iter().map(|(name, _)| name.as_str()).collect()
}
}
impl RepliqueHost for TestHost {
fn call(&mut self, name: &str, args: Vec<Value>) -> Result<Value, HostError> {
self.calls.push((name.to_owned(), args.clone()));
let failed = |message: &str| HostError::Failed {
name: name.to_owned(),
message: message.to_owned(),
};
match name {
"upper_new" => match args.as_slice() {
[Value::String(text)] => Ok(Value::String(text.to_uppercase())),
_ => Err(failed("expected one string")),
},
"double" => match args.as_slice() {
[Value::Int(n)] => Ok(Value::Int(n * 2)),
_ => Err(failed("expected one int")),
},
"is_even" => match args.as_slice() {
[Value::Int(n)] => Ok(Value::Bool(n % 2 == 0)),
_ => Err(failed("expected one int")),
},
"boom" => Err(failed("this function always fails")),
_ => Err(HostError::UnknownFunction(name.to_owned())),
}
}
fn has_feature(&mut self, subject: &str, name: &str) -> Result<bool, HostError> {
let supported_features = [
"happy", "sad", "angry", "hungry", "thirsty", "mana", "stats",
];
let alice_features = ["happy", "hungry", "mana", "stats"];
let bob_features = ["sad", "hungry", "thirsty"];
if !supported_features.contains(&name) {
return Err(HostError::UnknownFeature(name.to_string()));
}
match subject {
"Alice" => Ok(alice_features.contains(&name)),
"Bob" => Ok(bob_features.contains(&name)),
_ => Err(HostError::UnknownSubject(subject.to_string())),
}
}
fn feature(&mut self, subject: &str, name: &str) -> Result<Value, HostError> {
if !self.has_feature(subject, name)? {
return Err(HostError::FeatureNotOnSubject {
feature: name.to_owned(),
subject: subject.to_owned(),
});
}
Ok(match name {
"mana" => Value::Int(12),
"stats" => Value::Dict([("hp".to_owned(), Value::Int(7))].into_iter().collect()),
_ => Value::Bool(true),
})
}
}
}