use std::{cmp::Ordering, sync::Arc};
use sim_kernel::{
CapabilityName, Cx, Error, HintMetadata, MatchScore, Result, Shape, Symbol, Value,
};
pub type MethodBody = Arc<dyn Fn(&mut Cx, &[Value]) -> Result<Value> + Send + Sync>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum MethodRole {
Around,
Before,
Primary,
After,
}
impl MethodRole {
pub fn as_symbol(self) -> Symbol {
match self {
Self::Around => Symbol::qualified("method-role", "around"),
Self::Before => Symbol::qualified("method-role", "before"),
Self::Primary => Symbol::qualified("method-role", "primary"),
Self::After => Symbol::qualified("method-role", "after"),
}
}
pub fn combination_rank(self) -> u8 {
match self {
Self::Around => 0,
Self::Before => 1,
Self::Primary => 2,
Self::After => 3,
}
}
}
#[derive(Clone)]
pub struct DispatchMethod {
id: Symbol,
role: MethodRole,
parameter_shapes: Vec<Arc<dyn Shape>>,
body: MethodBody,
hints: Vec<HintMetadata>,
}
impl DispatchMethod {
pub fn new(
id: Symbol,
role: MethodRole,
parameter_shapes: Vec<Arc<dyn Shape>>,
body: MethodBody,
) -> Self {
Self {
id,
role,
parameter_shapes,
body,
hints: Vec::new(),
}
}
pub fn id(&self) -> &Symbol {
&self.id
}
pub fn role(&self) -> MethodRole {
self.role
}
pub fn arity(&self) -> usize {
self.parameter_shapes.len()
}
pub fn parameter_shapes(&self) -> &[Arc<dyn Shape>] {
&self.parameter_shapes
}
pub fn hints(&self) -> &[HintMetadata] {
&self.hints
}
pub fn with_hint(mut self, hint: HintMetadata) -> Self {
self.hints.push(hint);
self
}
pub fn with_argument_hint(self, argument: Symbol, detail: impl Into<String>) -> Self {
let title = format!("argument {argument}");
self.with_hint(
HintMetadata::new(Symbol::qualified("runtime-hint", "argument"), title)
.with_detail(detail)
.with_tag(Symbol::qualified("runtime", "argument"))
.with_argument(argument),
)
}
pub fn with_capability_requirement(self, capability: CapabilityName) -> Self {
self.with_hint(
HintMetadata::new(
Symbol::qualified("runtime-hint", "capability"),
format!("requires {capability}"),
)
.with_tag(Symbol::qualified("runtime", "capability"))
.with_capability(capability),
)
}
pub fn with_codec_safe_form(self, form: Symbol) -> Self {
self.with_hint(
HintMetadata::new(
Symbol::qualified("runtime-hint", "codec-form"),
format!("codec form {form}"),
)
.with_tag(Symbol::qualified("runtime", "codec"))
.with_codec_form(form),
)
}
pub fn with_example(self, example: impl Into<String>) -> Self {
self.with_hint(
HintMetadata::new(
Symbol::qualified("runtime-hint", "example"),
"operation example",
)
.with_tag(Symbol::qualified("runtime", "example"))
.with_example(example),
)
}
pub fn match_args(&self, cx: &mut Cx, args: &[Value]) -> Result<Option<MethodSpecificity>> {
if args.len() != self.parameter_shapes.len() {
return Ok(None);
}
let mut total = MatchScore::exact(0);
let mut argument_scores = Vec::with_capacity(args.len());
for (shape, arg) in self.parameter_shapes.iter().zip(args.iter()) {
let matched = shape.check_value(cx, arg.clone())?;
if !matched.accepted {
return Ok(None);
}
total += matched.score;
argument_scores.push(matched.score);
}
Ok(Some(MethodSpecificity::new(
self.id.clone(),
self.role,
total,
argument_scores,
)))
}
pub fn invoke(&self, cx: &mut Cx, args: &[Value]) -> Result<Value> {
(self.body)(cx, args)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MethodSpecificity {
method: Symbol,
role: MethodRole,
score: MatchScore,
argument_scores: Vec<MatchScore>,
}
impl MethodSpecificity {
pub fn new(
method: Symbol,
role: MethodRole,
score: MatchScore,
argument_scores: Vec<MatchScore>,
) -> Self {
Self {
method,
role,
score,
argument_scores,
}
}
pub fn method(&self) -> &Symbol {
&self.method
}
pub fn role(&self) -> MethodRole {
self.role
}
pub fn score(&self) -> MatchScore {
self.score
}
pub fn argument_scores(&self) -> &[MatchScore] {
&self.argument_scores
}
}
pub fn compare_specificity(left: &MethodSpecificity, right: &MethodSpecificity) -> Ordering {
left.argument_scores
.cmp(&right.argument_scores)
.then_with(|| left.score.cmp(&right.score))
}
pub(crate) fn ambiguous_primary_error(name: &Symbol, left: &Symbol, right: &Symbol) -> Error {
Error::Eval(format!(
"generic function {name} has ambiguous primary methods {left} and {right}"
))
}