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 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) => call_native(native, args),
170 Callable::User(template) => {
171 self.call_user(template, &captures, args, kwargs, None, label)
172 }
173 Callable::Ctor(class_id) => self.construct(*class_id, args, kwargs, label),
175 }
176 }
177
178 fn call_user(
182 &mut self,
183 template: &Template,
184 captures: &[crate::Cell],
185 args: Vec<Value>,
186 kwargs: Vec<(String, Value)>,
187 self_value: Option<Value>,
188 label: &str,
189 ) -> DogeResult<Value> {
190 enter_call(&mut self.depth)?;
191 let saved_class = self.current_method_class;
192 self.current_method_class = template.method_class;
193 let result = self.user_body(template, captures, args, kwargs, self_value, label);
194 self.current_method_class = saved_class;
195 exit_call(&mut self.depth);
196 result
197 }
198
199 fn user_body(
200 &mut self,
201 template: &Template,
202 captures: &[crate::Cell],
203 args: Vec<Value>,
204 kwargs: Vec<(String, Value)>,
205 self_value: Option<Value>,
206 label: &str,
207 ) -> DogeResult<Value> {
208 let bound = self.bind_args(&template.params, args, kwargs, label, template.file_id)?;
209
210 let frame = scope();
211 {
212 let mut f = frame.borrow_mut();
213 for (name, captured) in template.capture_names.iter().zip(captures) {
214 f.insert(name.clone(), captured.clone());
215 }
216 if let Some(self_value) = self_value {
217 f.insert("self".to_string(), cell(self_value));
218 }
219 for (name, value) in template.params.binding_names().iter().zip(bound) {
220 f.insert(name.clone(), cell(value));
221 }
222 }
223 for name in dc::hoisted_names(&template.body) {
226 frame
227 .borrow_mut()
228 .entry(name)
229 .or_insert_with(|| cell(Value::None));
230 }
231
232 match self.exec_stmts(&template.body, &frame, template.file_id)? {
233 Flow::Return(value) => Ok(value),
234 _ => Ok(Value::None),
236 }
237 }
238
239 fn bind_args(
244 &mut self,
245 params: &dc::Params,
246 args: Vec<Value>,
247 kwargs: Vec<(String, Value)>,
248 label: &str,
249 fid: u32,
250 ) -> DogeResult<Vec<Value>> {
251 let n = params.params.len();
252 let has_vararg = params.has_vararg();
253 let required = params.required();
254 let max = params.max_positional();
255 let total = args.len() + kwargs.len();
256
257 if !has_vararg && args.len() > n {
258 return Err(function_arity_error(label, required, max, total));
259 }
260
261 let mut slot: Vec<Option<Value>> = vec![None; n];
262 let mut extras: Vec<Value> = Vec::new();
263 for (i, arg) in args.into_iter().enumerate() {
264 if i < n {
265 slot[i] = Some(arg);
266 } else {
267 extras.push(arg);
268 }
269 }
270 for (name, value) in kwargs {
271 match params.params.iter().position(|p| p.name == name) {
272 Some(idx) if slot[idx].is_none() => slot[idx] = Some(value),
273 Some(_) => {
274 return Err(DogeError::type_error(format!(
275 "{label} got parameter {name} twice"
276 )))
277 }
278 None => {
279 return Err(DogeError::type_error(format!(
280 "{label} has no parameter {name}"
281 )))
282 }
283 }
284 }
285
286 let mut out = Vec::with_capacity(n + has_vararg as usize);
287 for (i, filled) in slot.into_iter().enumerate() {
288 match filled {
289 Some(value) => out.push(value),
290 None => match ¶ms.params[i].default {
291 Some(default) => out.push(self.eval(default, &scope(), fid)?),
292 None => return Err(function_arity_error(label, required, max, total)),
293 },
294 }
295 }
296 if has_vararg {
297 out.push(Value::list(extras));
298 }
299 Ok(out)
300 }
301
302 fn call_method(&mut self, recv: Value, name: &str, args: Vec<Value>) -> DogeResult<Value> {
306 if !matches!(recv, Value::Object(_)) {
307 return builtin_method(&recv, name, args);
308 }
309 let class_id = object_class_id(&recv)?;
310 match self.resolve_method(class_id, name) {
311 Some((fn_id, def_class)) => {
312 let label = format!("{}.{name}", self.classes[def_class as usize].name);
313 self.invoke_method(fn_id, recv, args, &label)
314 }
315 None => Err(doge_runtime::no_such_method(&recv, name)),
316 }
317 }
318
319 fn invoke_method(
321 &mut self,
322 fn_id: usize,
323 recv: Value,
324 args: Vec<Value>,
325 label: &str,
326 ) -> DogeResult<Value> {
327 let callable = self.callables[fn_id].clone();
328 let Callable::User(template) = callable.as_ref() else {
329 unreachable!("interp bug: a method id points at a native");
330 };
331 self.call_user(template, &[], args, Vec::new(), Some(recv), label)
332 }
333
334 pub(crate) fn eval_super_call(
337 &mut self,
338 method: &str,
339 args: &[dc::Expr],
340 frame: &Scope,
341 fid: u32,
342 ) -> DogeResult<Value> {
343 let class_id = self
344 .current_method_class
345 .expect("checker guarantees super is inside a method");
346 let parent = self.classes[class_id as usize]
347 .parent
348 .expect("checker guarantees the class has a parent");
349 let (fn_id, def_class) = self
350 .resolve_method(parent, method)
351 .expect("checker guarantees a parent defines the method");
352 let self_value = self
353 .lookup(frame, fid, "self")
354 .expect("a method frame always binds self")
355 .borrow()
356 .clone();
357 let args = self.eval_positional(args, frame, fid)?;
358 let label = format!("{}.{method}", self.classes[def_class as usize].name);
359 self.invoke_method(fn_id, self_value, args, &label)
360 }
361
362 pub(crate) fn resolve_method(&self, class_id: u32, name: &str) -> Option<(usize, u32)> {
365 let mut current = Some(class_id);
366 let mut guard = 0;
367 while let Some(cid) = current {
368 let class = &self.classes[cid as usize];
369 if let Some(fn_id) = class.methods.get(name) {
370 return Some((*fn_id, cid));
371 }
372 guard += 1;
373 if guard > self.classes.len() {
374 break;
375 }
376 current = class.parent;
377 }
378 None
379 }
380
381 fn construct(
384 &mut self,
385 class_id: u32,
386 args: Vec<Value>,
387 kwargs: Vec<(String, Value)>,
388 label: &str,
389 ) -> DogeResult<Value> {
390 let class = self.classes[class_id as usize].clone();
391 let object = Value::object(class_id, &class.name);
392 match self.resolve_method(class_id, "init") {
393 Some((fn_id, _)) => {
394 self.invoke_method_kw(fn_id, object.clone(), args, kwargs, label)?;
395 }
396 None => {
397 self.bind_args(&dc::Params::default(), args, kwargs, label, class.file_id)?;
399 }
400 }
401 Ok(object)
402 }
403
404 fn invoke_method_kw(
407 &mut self,
408 fn_id: usize,
409 recv: Value,
410 args: Vec<Value>,
411 kwargs: Vec<(String, Value)>,
412 label: &str,
413 ) -> DogeResult<Value> {
414 let callable = self.callables[fn_id].clone();
415 let Callable::User(template) = callable.as_ref() else {
416 unreachable!("interp bug: a method id points at a native");
417 };
418 self.call_user(template, &[], args, kwargs, Some(recv), label)
419 }
420
421 #[allow(clippy::too_many_arguments)]
424 fn call_module_member(
425 &mut self,
426 module: crate::ModuleRef,
427 base: &str,
428 member: &str,
429 args: &[dc::Expr],
430 kwargs: &[(String, dc::Expr)],
431 frame: &Scope,
432 fid: u32,
433 ) -> DogeResult<Value> {
434 match module {
435 crate::ModuleRef::Stdlib(m) => {
436 let args = self.eval_positional(args, frame, fid)?;
437 match self
438 .module_fn_ids
439 .get(&(m.name.to_string(), member.to_string()))
440 {
441 Some(id) => self.call_id(*id, Vec::new(), args, Vec::new(), member),
442 None => Err(DogeError::attr_error(format!(
443 "{base} has no member {member}"
444 ))),
445 }
446 }
447 crate::ModuleRef::User(mfid) => {
448 let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
449 if let Some(class_id) = self.class_id_in(mfid, member) {
450 let label = format!("{base}.{member}");
451 return self.construct(class_id, args, kwargs, &label);
452 }
453 let value = self
454 .lookup(&self.globals(mfid), mfid, member)
455 .map(|c| c.borrow().clone())
456 .ok_or_else(|| {
457 DogeError::attr_error(format!("{base} has no member {member}"))
458 })?;
459 self.call_value(value, args, kwargs)
460 }
461 }
462 }
463}