1use doge_compiler as dc;
7use doge_runtime::{
8 builtin_method, callee_function, enter_call, exit_call, function_arity_error, object_class_id,
9 DogeError, DogeResult, ErrorKind, Value,
10};
11
12use crate::natives::call_native;
13use crate::{cell, scope, Callable, Flow, Interp, Scope, Template};
14
15type EvaluatedArgs = (Vec<Value>, Vec<(String, Value)>);
18
19impl Interp {
20 pub(crate) fn eval_call(
24 &mut self,
25 callee: &dc::Expr,
26 args: &[dc::Expr],
27 kwargs: &[(String, dc::Expr)],
28 frame: &Scope,
29 fid: u32,
30 ) -> DogeResult<Value> {
31 match callee {
32 dc::Expr::Ident { name, .. } => {
33 if let Some(cell) = self.lookup(frame, fid, name) {
36 let value = cell.borrow().clone();
37 let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
38 return self.call_value(value, args, kwargs);
39 }
40 if let Some(id) = self.builtin_ids.get(name).copied() {
41 let args = self.eval_positional(args, frame, fid)?;
42 return self.call_id(id, Vec::new(), args, Vec::new(), name);
43 }
44 if let Some(class_id) = self.class_id_in(fid, name) {
45 let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
46 return self.construct(class_id, args, kwargs, name);
47 }
48 Err(DogeError::type_error(format!(
49 "cannot call {name} — it is not a function"
50 )))
51 }
52 dc::Expr::Attr { obj, name, .. }
54 if matches!(obj.as_ref(), dc::Expr::Ident { name: base, .. }
55 if self.lookup(frame, fid, base).is_none()
56 && self.import_ref(fid, base).is_some()) =>
57 {
58 let dc::Expr::Ident { name: base, .. } = obj.as_ref() else {
59 unreachable!("guarded to an Ident base")
60 };
61 let module = self
62 .import_ref(fid, base)
63 .expect("guarded: base is an import");
64 self.call_module_member(module, base, name, args, kwargs, frame, fid)
65 }
66 dc::Expr::Attr { obj, name, .. } => {
68 let recv = self.eval(obj, frame, fid)?;
69 let args = self.eval_positional(args, frame, fid)?;
70 self.call_method(recv, name, args)
71 }
72 other => {
74 let value = self.eval(other, frame, fid)?;
75 let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
76 self.call_value(value, args, kwargs)
77 }
78 }
79 }
80
81 pub fn call_entry_function(&mut self, name: &str) -> DogeResult<Value> {
86 let value = self
87 .lookup(&self.globals(0), 0, name)
88 .map(|cell| cell.borrow().clone())
89 .ok_or_else(|| {
90 DogeError::new(ErrorKind::ValueError, format!("no function named {name}"))
91 })?;
92 self.call_value(value, Vec::new(), Vec::new())
93 }
94
95 fn eval_positional(
97 &mut self,
98 args: &[dc::Expr],
99 frame: &Scope,
100 fid: u32,
101 ) -> DogeResult<Vec<Value>> {
102 let mut out = Vec::with_capacity(args.len());
103 for arg in args {
104 out.push(self.eval(arg, frame, fid)?);
105 }
106 Ok(out)
107 }
108
109 fn eval_args(
111 &mut self,
112 args: &[dc::Expr],
113 kwargs: &[(String, dc::Expr)],
114 frame: &Scope,
115 fid: u32,
116 ) -> DogeResult<EvaluatedArgs> {
117 let positional = self.eval_positional(args, frame, fid)?;
118 let mut keyword = Vec::with_capacity(kwargs.len());
119 for (name, value) in kwargs {
120 keyword.push((name.clone(), self.eval(value, frame, fid)?));
121 }
122 Ok((positional, keyword))
123 }
124
125 pub(crate) fn call_value(
128 &mut self,
129 value: Value,
130 args: Vec<Value>,
131 kwargs: Vec<(String, Value)>,
132 ) -> DogeResult<Value> {
133 if let Value::BoundMethod(m) = &value {
138 if !kwargs.is_empty() {
139 return Err(DogeError::type_error(
140 "cannot call a bound method with keyword arguments",
141 ));
142 }
143 let recv = m.receiver.clone();
144 let method = m.method.clone();
145 return self.call_method(recv, &method, args);
146 }
147 let func = callee_function(&value)?;
148 self.call_id(
149 func.fn_id as usize,
150 func.captures.clone(),
151 args,
152 kwargs,
153 &func.name,
154 )
155 }
156
157 fn call_id(
160 &mut self,
161 id: usize,
162 captures: Vec<crate::Cell>,
163 args: Vec<Value>,
164 kwargs: Vec<(String, Value)>,
165 label: &str,
166 ) -> DogeResult<Value> {
167 let callable = self.callables[id].clone();
168 match callable.as_ref() {
169 Callable::Native(native) if native.runtime_fn == dc::PACK_ZOOM_RUNTIME_FN => {
173 self.interp_zoom(args)
174 }
175 Callable::Native(native) => call_native(native, args),
176 Callable::User(template) => {
177 self.call_user(template, &captures, args, kwargs, None, label)
178 }
179 Callable::Ctor(class_id) => self.construct(*class_id, args, kwargs, label),
181 }
182 }
183
184 fn call_user(
188 &mut self,
189 template: &Template,
190 captures: &[crate::Cell],
191 args: Vec<Value>,
192 kwargs: Vec<(String, Value)>,
193 self_value: Option<Value>,
194 label: &str,
195 ) -> DogeResult<Value> {
196 enter_call(&mut self.depth)?;
197 let saved_class = self.current_method_class;
198 self.current_method_class = template.method_class;
199 let result = self.user_body(template, captures, args, kwargs, self_value, label);
200 self.current_method_class = saved_class;
201 exit_call(&mut self.depth);
202 result
203 }
204
205 fn user_body(
206 &mut self,
207 template: &Template,
208 captures: &[crate::Cell],
209 args: Vec<Value>,
210 kwargs: Vec<(String, Value)>,
211 self_value: Option<Value>,
212 label: &str,
213 ) -> DogeResult<Value> {
214 let bound = self.bind_args(&template.params, args, kwargs, label, template.file_id)?;
215
216 let frame = scope();
217 {
218 let mut f = frame.borrow_mut();
219 for (name, captured) in template.capture_names.iter().zip(captures) {
220 f.insert(name.clone(), captured.clone());
221 }
222 if let Some(self_value) = self_value {
223 f.insert("self".to_string(), cell(self_value));
224 }
225 for (name, value) in template.params.binding_names().iter().zip(bound) {
226 f.insert(name.clone(), cell(value));
227 }
228 }
229 for name in dc::hoisted_names(&template.body) {
232 frame
233 .borrow_mut()
234 .entry(name)
235 .or_insert_with(|| cell(Value::None));
236 }
237
238 match self.exec_stmts(&template.body, &frame, template.file_id)? {
239 Flow::Return(value) => Ok(value),
240 _ => Ok(Value::None),
242 }
243 }
244
245 fn bind_args(
250 &mut self,
251 params: &dc::Params,
252 args: Vec<Value>,
253 kwargs: Vec<(String, Value)>,
254 label: &str,
255 fid: u32,
256 ) -> DogeResult<Vec<Value>> {
257 let n = params.params.len();
258 let has_vararg = params.has_vararg();
259 let required = params.required();
260 let max = params.max_positional();
261 let total = args.len() + kwargs.len();
262
263 if !has_vararg && args.len() > n {
264 return Err(function_arity_error(label, required, max, total));
265 }
266
267 let mut slot: Vec<Option<Value>> = vec![None; n];
268 let mut extras: Vec<Value> = Vec::new();
269 for (i, arg) in args.into_iter().enumerate() {
270 if i < n {
271 slot[i] = Some(arg);
272 } else {
273 extras.push(arg);
274 }
275 }
276 for (name, value) in kwargs {
277 match params.params.iter().position(|p| p.name == name) {
278 Some(idx) if slot[idx].is_none() => slot[idx] = Some(value),
279 Some(_) => {
280 return Err(DogeError::type_error(format!(
281 "{label} got parameter {name} twice"
282 )))
283 }
284 None => {
285 return Err(DogeError::type_error(format!(
286 "{label} has no parameter {name}"
287 )))
288 }
289 }
290 }
291
292 let mut out = Vec::with_capacity(n + has_vararg as usize);
293 for (i, filled) in slot.into_iter().enumerate() {
294 match filled {
295 Some(value) => out.push(value),
296 None => match ¶ms.params[i].default {
297 Some(default) => out.push(self.eval(default, &scope(), fid)?),
298 None => return Err(function_arity_error(label, required, max, total)),
299 },
300 }
301 }
302 if has_vararg {
303 out.push(Value::list(extras));
304 }
305 Ok(out)
306 }
307
308 fn call_method(&mut self, recv: Value, name: &str, args: Vec<Value>) -> DogeResult<Value> {
312 if !matches!(recv, Value::Object(_)) {
313 return builtin_method(&recv, name, args);
314 }
315 let class_id = object_class_id(&recv)?;
316 match self.resolve_method(class_id, name) {
317 Some((fn_id, def_class)) => {
318 let label = format!("{}.{name}", self.classes[def_class as usize].name);
319 self.invoke_method(fn_id, recv, args, &label)
320 }
321 None => Err(doge_runtime::no_such_method(&recv, name)),
322 }
323 }
324
325 fn invoke_method(
327 &mut self,
328 fn_id: usize,
329 recv: Value,
330 args: Vec<Value>,
331 label: &str,
332 ) -> DogeResult<Value> {
333 let callable = self.callables[fn_id].clone();
334 let Callable::User(template) = callable.as_ref() else {
335 unreachable!("interp bug: a method id points at a native");
336 };
337 self.call_user(template, &[], args, Vec::new(), Some(recv), label)
338 }
339
340 pub(crate) fn eval_super_call(
343 &mut self,
344 method: &str,
345 args: &[dc::Expr],
346 frame: &Scope,
347 fid: u32,
348 ) -> DogeResult<Value> {
349 let class_id = self
350 .current_method_class
351 .expect("checker guarantees super is inside a method");
352 let parent = self.classes[class_id as usize]
353 .parent
354 .expect("checker guarantees the class has a parent");
355 let (fn_id, def_class) = self
356 .resolve_method(parent, method)
357 .expect("checker guarantees a parent defines the method");
358 let self_value = self
359 .lookup(frame, fid, "self")
360 .expect("a method frame always binds self")
361 .borrow()
362 .clone();
363 let args = self.eval_positional(args, frame, fid)?;
364 let label = format!("{}.{method}", self.classes[def_class as usize].name);
365 self.invoke_method(fn_id, self_value, args, &label)
366 }
367
368 pub(crate) fn resolve_method(&self, class_id: u32, name: &str) -> Option<(usize, u32)> {
371 let mut current = Some(class_id);
372 let mut guard = 0;
373 while let Some(cid) = current {
374 let class = &self.classes[cid as usize];
375 if let Some(fn_id) = class.methods.get(name) {
376 return Some((*fn_id, cid));
377 }
378 guard += 1;
379 if guard > self.classes.len() {
380 break;
381 }
382 current = class.parent;
383 }
384 None
385 }
386
387 fn construct(
390 &mut self,
391 class_id: u32,
392 args: Vec<Value>,
393 kwargs: Vec<(String, Value)>,
394 label: &str,
395 ) -> DogeResult<Value> {
396 let class = self.classes[class_id as usize].clone();
397 let object = Value::object(class_id, &class.name);
398 match self.resolve_method(class_id, "init") {
399 Some((fn_id, _)) => {
400 self.invoke_method_kw(fn_id, object.clone(), args, kwargs, label)?;
401 }
402 None => {
403 self.bind_args(&dc::Params::default(), args, kwargs, label, class.file_id)?;
405 }
406 }
407 Ok(object)
408 }
409
410 fn invoke_method_kw(
413 &mut self,
414 fn_id: usize,
415 recv: Value,
416 args: Vec<Value>,
417 kwargs: Vec<(String, Value)>,
418 label: &str,
419 ) -> DogeResult<Value> {
420 let callable = self.callables[fn_id].clone();
421 let Callable::User(template) = callable.as_ref() else {
422 unreachable!("interp bug: a method id points at a native");
423 };
424 self.call_user(template, &[], args, kwargs, Some(recv), label)
425 }
426
427 #[allow(clippy::too_many_arguments)]
430 fn call_module_member(
431 &mut self,
432 module: crate::ModuleRef,
433 base: &str,
434 member: &str,
435 args: &[dc::Expr],
436 kwargs: &[(String, dc::Expr)],
437 frame: &Scope,
438 fid: u32,
439 ) -> DogeResult<Value> {
440 match module {
441 crate::ModuleRef::Stdlib(m) => {
442 let args = self.eval_positional(args, frame, fid)?;
443 match self
444 .module_fn_ids
445 .get(&(m.name.to_string(), member.to_string()))
446 {
447 Some(id) => self.call_id(*id, Vec::new(), args, Vec::new(), member),
448 None => Err(DogeError::attr_error(format!(
449 "{base} has no member {member}"
450 ))),
451 }
452 }
453 crate::ModuleRef::User(mfid) => {
454 let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
455 if let Some(class_id) = self.class_id_in(mfid, member) {
456 let label = format!("{base}.{member}");
457 return self.construct(class_id, args, kwargs, &label);
458 }
459 let value = self
460 .lookup(&self.globals(mfid), mfid, member)
461 .map(|c| c.borrow().clone())
462 .ok_or_else(|| {
463 DogeError::attr_error(format!("{base} has no member {member}"))
464 })?;
465 self.call_value(value, args, kwargs)
466 }
467 }
468 }
469}