1use std::{cmp::Ordering, sync::Arc};
2
3use sim_kernel::{
4 CapabilityName, Cx, Error, HintMetadata, MatchScore, Result, Shape, Symbol, Value,
5};
6
7pub type MethodBody = Arc<dyn Fn(&mut Cx, &[Value]) -> Result<Value> + Send + Sync>;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
18pub enum MethodRole {
19 Around,
21 Before,
23 Primary,
25 After,
27}
28
29impl MethodRole {
30 pub fn as_symbol(self) -> Symbol {
32 match self {
33 Self::Around => Symbol::qualified("method-role", "around"),
34 Self::Before => Symbol::qualified("method-role", "before"),
35 Self::Primary => Symbol::qualified("method-role", "primary"),
36 Self::After => Symbol::qualified("method-role", "after"),
37 }
38 }
39
40 pub fn combination_rank(self) -> u8 {
44 match self {
45 Self::Around => 0,
46 Self::Before => 1,
47 Self::Primary => 2,
48 Self::After => 3,
49 }
50 }
51}
52
53#[derive(Clone)]
59pub struct DispatchMethod {
60 id: Symbol,
61 role: MethodRole,
62 parameter_shapes: Vec<Arc<dyn Shape>>,
63 body: MethodBody,
64 hints: Vec<HintMetadata>,
65}
66
67impl DispatchMethod {
68 pub fn new(
70 id: Symbol,
71 role: MethodRole,
72 parameter_shapes: Vec<Arc<dyn Shape>>,
73 body: MethodBody,
74 ) -> Self {
75 Self {
76 id,
77 role,
78 parameter_shapes,
79 body,
80 hints: Vec::new(),
81 }
82 }
83
84 pub fn id(&self) -> &Symbol {
86 &self.id
87 }
88
89 pub fn role(&self) -> MethodRole {
91 self.role
92 }
93
94 pub fn arity(&self) -> usize {
96 self.parameter_shapes.len()
97 }
98
99 pub fn parameter_shapes(&self) -> &[Arc<dyn Shape>] {
101 &self.parameter_shapes
102 }
103
104 pub fn hints(&self) -> &[HintMetadata] {
106 &self.hints
107 }
108
109 pub fn with_hint(mut self, hint: HintMetadata) -> Self {
111 self.hints.push(hint);
112 self
113 }
114
115 pub fn with_argument_hint(self, argument: Symbol, detail: impl Into<String>) -> Self {
117 let title = format!("argument {argument}");
118 self.with_hint(
119 HintMetadata::new(Symbol::qualified("runtime-hint", "argument"), title)
120 .with_detail(detail)
121 .with_tag(Symbol::qualified("runtime", "argument"))
122 .with_argument(argument),
123 )
124 }
125
126 pub fn with_capability_requirement(self, capability: CapabilityName) -> Self {
128 self.with_hint(
129 HintMetadata::new(
130 Symbol::qualified("runtime-hint", "capability"),
131 format!("requires {capability}"),
132 )
133 .with_tag(Symbol::qualified("runtime", "capability"))
134 .with_capability(capability),
135 )
136 }
137
138 pub fn with_codec_safe_form(self, form: Symbol) -> Self {
140 self.with_hint(
141 HintMetadata::new(
142 Symbol::qualified("runtime-hint", "codec-form"),
143 format!("codec form {form}"),
144 )
145 .with_tag(Symbol::qualified("runtime", "codec"))
146 .with_codec_form(form),
147 )
148 }
149
150 pub fn with_example(self, example: impl Into<String>) -> Self {
152 self.with_hint(
153 HintMetadata::new(
154 Symbol::qualified("runtime-hint", "example"),
155 "operation example",
156 )
157 .with_tag(Symbol::qualified("runtime", "example"))
158 .with_example(example),
159 )
160 }
161
162 pub fn match_args(&self, cx: &mut Cx, args: &[Value]) -> Result<Option<MethodSpecificity>> {
167 if args.len() != self.parameter_shapes.len() {
168 return Ok(None);
169 }
170
171 let mut total = MatchScore::exact(0);
172 let mut argument_scores = Vec::with_capacity(args.len());
173 for (shape, arg) in self.parameter_shapes.iter().zip(args.iter()) {
174 let matched = shape.check_value(cx, arg.clone())?;
175 if !matched.accepted {
176 return Ok(None);
177 }
178 total += matched.score;
179 argument_scores.push(matched.score);
180 }
181
182 Ok(Some(MethodSpecificity::new(
183 self.id.clone(),
184 self.role,
185 total,
186 argument_scores,
187 )))
188 }
189
190 pub fn invoke(&self, cx: &mut Cx, args: &[Value]) -> Result<Value> {
192 (self.body)(cx, args)
193 }
194}
195
196#[derive(Clone, Debug, PartialEq, Eq)]
202pub struct MethodSpecificity {
203 method: Symbol,
204 role: MethodRole,
205 score: MatchScore,
206 argument_scores: Vec<MatchScore>,
207}
208
209impl MethodSpecificity {
210 pub fn new(
212 method: Symbol,
213 role: MethodRole,
214 score: MatchScore,
215 argument_scores: Vec<MatchScore>,
216 ) -> Self {
217 Self {
218 method,
219 role,
220 score,
221 argument_scores,
222 }
223 }
224
225 pub fn method(&self) -> &Symbol {
227 &self.method
228 }
229
230 pub fn role(&self) -> MethodRole {
232 self.role
233 }
234
235 pub fn score(&self) -> MatchScore {
237 self.score
238 }
239
240 pub fn argument_scores(&self) -> &[MatchScore] {
242 &self.argument_scores
243 }
244}
245
246pub fn compare_specificity(left: &MethodSpecificity, right: &MethodSpecificity) -> Ordering {
251 left.argument_scores
252 .cmp(&right.argument_scores)
253 .then_with(|| left.score.cmp(&right.score))
254}
255
256pub(crate) fn ambiguous_primary_error(name: &Symbol, left: &Symbol, right: &Symbol) -> Error {
257 Error::Eval(format!(
258 "generic function {name} has ambiguous primary methods {left} and {right}"
259 ))
260}