1use tracing::debug;
4
5use crate::interpreter::call::{retarget_call_expr, S3DispatchContext};
6use crate::interpreter::environment::Environment;
7use crate::interpreter::value::{RError, RFlow, RFunction, RSignal, RValue, Vector};
8use crate::interpreter::Interpreter;
9use crate::parser::ast::{Arg, Expr};
10
11struct S3MethodCall<'a> {
12 method: &'a RValue,
13 method_name: &'a str,
14 generic: &'a str,
15 classes: &'a [String],
16 class_index: usize,
17 dispatch_object: RValue,
18 positional: &'a [RValue],
19 named: &'a [(String, RValue)],
20 env: &'a Environment,
21 call_expr: Option<Expr>,
22}
23
24impl Interpreter {
25 pub(crate) fn dispatch_next_method(
26 &self,
27 positional: &[RValue],
28 named: &[(String, RValue)],
29 env: &Environment,
30 ) -> Result<RValue, RFlow> {
31 let ctx = self
32 .s3_dispatch_stack
33 .borrow()
34 .last()
35 .cloned()
36 .ok_or_else(|| {
37 RError::other("NextMethod called outside of a method dispatch".to_string())
38 })?;
39
40 let args: Vec<RValue> = if positional.is_empty() {
41 vec![ctx.object.clone()]
42 } else {
43 positional.to_vec()
44 };
45
46 for i in (ctx.class_index + 1)..ctx.classes.len() {
48 let method_name = format!("{}.{}", ctx.generic, ctx.classes[i]);
49 let method = env
50 .get(&method_name)
51 .or_else(|| self.lookup_s3_method(&ctx.generic, &ctx.classes[i]));
52 if let Some(method) = method {
53 return self.call_s3_method(S3MethodCall {
54 method: &method,
55 method_name: &method_name,
56 generic: &ctx.generic,
57 classes: &ctx.classes,
58 class_index: i,
59 dispatch_object: args.first().cloned().unwrap_or(RValue::Null),
60 positional: &args,
61 named,
62 env,
63 call_expr: self.current_call_expr(),
64 });
65 }
66 }
67
68 let default_name = format!("{}.default", ctx.generic);
69 let default_method = env
70 .get(&default_name)
71 .or_else(|| self.lookup_s3_method(&ctx.generic, "default"));
72 if let Some(method) = default_method {
73 return self.call_s3_method(S3MethodCall {
74 method: &method,
75 method_name: &default_name,
76 generic: &ctx.generic,
77 classes: &ctx.classes,
78 class_index: ctx.classes.len(),
79 dispatch_object: args.first().cloned().unwrap_or(RValue::Null),
80 positional: &args,
81 named,
82 env,
83 call_expr: self.current_call_expr(),
84 });
85 }
86
87 Err(RError::other(format!("no more methods to dispatch for '{}'", ctx.generic)).into())
88 }
89
90 pub(crate) fn eval_use_method(&self, args: &[Arg], env: &Environment) -> Result<RValue, RFlow> {
91 let frame = self.current_call_frame().ok_or_else(|| {
92 RError::other("'UseMethod' used in an inappropriate fashion".to_string())
93 })?;
94
95 let generic_expr = match args
96 .iter()
97 .find(|arg| arg.name.as_deref() == Some("generic"))
98 .or_else(|| args.first())
99 .and_then(|arg| arg.value.as_ref())
100 {
101 Some(expr) => expr,
102 None => {
103 return Err(RError::other("there must be a 'generic' argument".to_string()).into());
104 }
105 };
106
107 let generic = self.eval_generic_name(generic_expr, env)?;
108
109 let object_expr = args
110 .iter()
111 .find(|arg| arg.name.as_deref() == Some("object"))
112 .or_else(|| args.get(1))
113 .and_then(|arg| arg.value.as_ref());
114
115 let dispatch_object = match object_expr {
116 Some(expr) => Some(self.eval_in(expr, env)?),
117 None => {
118 let obj = self.default_use_method_object(&frame)?;
120 match obj {
121 Some(val) => Some(self.force_value(val)?),
122 None => None,
123 }
124 }
125 };
126
127 let value = self.dispatch_s3(
128 &generic,
129 &frame.supplied_positional,
130 &frame.supplied_named,
131 dispatch_object,
132 env,
133 frame.call.clone(),
134 )?;
135
136 Err(RSignal::Return(value).into())
137 }
138
139 fn eval_generic_name(&self, generic_expr: &Expr, env: &Environment) -> Result<String, RFlow> {
140 let generic_value = self.eval_in(generic_expr, env)?;
141 match generic_value {
142 RValue::Vector(rv) => match &rv.inner {
143 Vector::Character(values) if values.len() == 1 => {
144 values.first().cloned().flatten().ok_or_else(|| {
145 RError::other("'generic' argument must be a character string".to_string())
146 .into()
147 })
148 }
149 _ => Err(RError::other(
150 "'generic' argument must be a character string".to_string(),
151 )
152 .into()),
153 },
154 _ => Err(
155 RError::other("'generic' argument must be a character string".to_string()).into(),
156 ),
157 }
158 }
159
160 fn default_use_method_object(
161 &self,
162 frame: &crate::interpreter::call::CallFrame,
163 ) -> Result<Option<RValue>, RFlow> {
164 match &frame.function {
165 RValue::Function(RFunction::Closure { params, .. }) => match params.first() {
166 Some(param) if param.is_dots => {
167 Ok(frame.env.get("...").and_then(|value| match value {
168 RValue::List(list) => list.values.first().map(|(_, value)| value.clone()),
169 _ => None,
170 }))
171 }
172 Some(param) => Ok(frame.env.get(¶m.name)),
173 None => Ok(None),
174 },
175 _ => Err(
176 RError::other("'UseMethod' used in an inappropriate fashion".to_string()).into(),
177 ),
178 }
179 }
180
181 #[tracing::instrument(
185 level = "debug",
186 skip(self, positional, named, dispatch_object, env, call_expr)
187 )]
188 fn dispatch_s3(
189 &self,
190 generic: &str,
191 positional: &[RValue],
192 named: &[(String, RValue)],
193 dispatch_object: Option<RValue>,
194 env: &Environment,
195 call_expr: Option<Expr>,
196 ) -> Result<RValue, RFlow> {
197 let raw_dispatch =
198 dispatch_object.unwrap_or_else(|| positional.first().cloned().unwrap_or(RValue::Null));
199 let dispatch_object = self.force_value(raw_dispatch)?;
201 let classes = self.s3_classes_for(&dispatch_object);
202
203 for (i, class) in classes.iter().enumerate() {
205 let method_name = format!("{}.{}", generic, class);
206 if let Some(method) = env.get(&method_name) {
207 debug!(
208 generic,
209 method = method_name.as_str(),
210 "S3 dispatch resolved"
211 );
212 return self.call_s3_method(S3MethodCall {
213 method: &method,
214 method_name: &method_name,
215 generic,
216 classes: &classes,
217 class_index: i,
218 dispatch_object: dispatch_object.clone(),
219 positional,
220 named,
221 env,
222 call_expr: call_expr.clone(),
223 });
224 }
225 }
226
227 for (i, class) in classes.iter().enumerate() {
229 if let Some(method) = self.lookup_s3_method(generic, class) {
230 let method_name = format!("{}.{}", generic, class);
231 debug!(
232 generic,
233 method = method_name.as_str(),
234 "S3 dispatch resolved (registry)"
235 );
236 return self.call_s3_method(S3MethodCall {
237 method: &method,
238 method_name: &method_name,
239 generic,
240 classes: &classes,
241 class_index: i,
242 dispatch_object: dispatch_object.clone(),
243 positional,
244 named,
245 env,
246 call_expr: call_expr.clone(),
247 });
248 }
249 }
250
251 let default_name = format!("{}.default", generic);
253 if let Some(method) = env.get(&default_name) {
254 debug!(
255 generic,
256 method = default_name.as_str(),
257 "S3 dispatch resolved (default)"
258 );
259 return self.call_s3_method(S3MethodCall {
260 method: &method,
261 method_name: &default_name,
262 generic,
263 classes: &classes,
264 class_index: classes.len(),
265 dispatch_object: dispatch_object.clone(),
266 positional,
267 named,
268 env,
269 call_expr: call_expr.clone(),
270 });
271 }
272
273 if let Some(method) = self.lookup_s3_method(generic, "default") {
275 debug!(
276 generic,
277 method = default_name.as_str(),
278 "S3 dispatch resolved (registry default)"
279 );
280 return self.call_s3_method(S3MethodCall {
281 method: &method,
282 method_name: &default_name,
283 generic,
284 classes: &classes,
285 class_index: classes.len(),
286 dispatch_object: dispatch_object.clone(),
287 positional,
288 named,
289 env,
290 call_expr,
291 });
292 }
293
294 debug!(generic, ?classes, "S3 dispatch failed: no method found");
295 Err(RError::other(format!(
296 "no applicable method for '{}' applied to an object of class \"{}\"",
297 generic,
298 classes.first().unwrap_or(&"unknown".to_string())
299 ))
300 .into())
301 }
302
303 pub(crate) fn s3_classes_for(&self, dispatch_object: &RValue) -> Vec<String> {
304 match dispatch_object {
305 RValue::List(list) => {
306 if let Some(RValue::Vector(rv)) = list.get_attr("class") {
307 if let Vector::Character(classes) = &rv.inner {
308 classes
309 .iter()
310 .filter_map(|class| class.clone())
311 .collect::<Vec<_>>()
312 } else {
313 vec!["list".to_string()]
314 }
315 } else {
316 vec!["list".to_string()]
317 }
318 }
319 RValue::Vector(rv) => rv.class().unwrap_or_else(|| match &rv.inner {
320 Vector::Raw(_) => vec!["raw".to_string()],
321 Vector::Logical(_) => vec!["logical".to_string()],
322 Vector::Integer(_) => vec!["integer".to_string()],
323 Vector::Double(_) => vec!["numeric".to_string()],
324 Vector::Complex(_) => vec!["complex".to_string()],
325 Vector::Character(_) => vec!["character".to_string()],
326 }),
327 RValue::Function(_) => vec!["function".to_string()],
328 RValue::Null => vec!["NULL".to_string()],
329 RValue::Language(lang) => lang.class().unwrap_or_default(),
330 _ => vec![],
331 }
332 }
333
334 fn call_s3_method(&self, dispatch: S3MethodCall<'_>) -> Result<RValue, RFlow> {
335 let ctx = S3DispatchContext {
336 generic: dispatch.generic.to_string(),
337 classes: dispatch.classes.to_vec(),
338 class_index: dispatch.class_index,
339 object: dispatch.dispatch_object,
340 };
341 self.s3_dispatch_stack.borrow_mut().push(ctx);
342 let method_call = retarget_call_expr(dispatch.call_expr, dispatch.method_name);
343 let result = self.call_function_with_call(
344 dispatch.method,
345 dispatch.positional,
346 dispatch.named,
347 dispatch.env,
348 method_call,
349 );
350 self.s3_dispatch_stack.borrow_mut().pop();
351 result
352 }
353}