1#![allow(
2 clippy::await_holding_lock,
3 clippy::enum_variant_names,
4 clippy::get_first,
5 clippy::io_other_error,
6 clippy::needless_range_loop,
7 clippy::redundant_closure,
8 clippy::result_large_err,
9 clippy::too_many_arguments,
10 clippy::useless_conversion
11)]
12#![cfg_attr(target_arch = "wasm32", allow(dead_code))]
13
14use crate::builtins::common::format::format_variadic;
15use runmat_builtins::Value;
16#[cfg(not(target_arch = "wasm32"))]
17use runmat_gc_api::GcPtr;
18
19pub mod dispatcher;
20
21pub mod callsite;
22pub mod console;
23pub mod data;
24pub mod interaction;
25pub mod interrupt;
26pub mod output_context;
27pub mod output_count;
28pub mod source_context;
29
30pub mod arrays;
31pub mod builtins;
32pub mod comparison;
33pub mod concatenation;
34pub mod elementwise;
35pub mod indexing;
36pub mod matrix;
37pub mod plotting_hooks;
38pub mod replay;
39pub mod runtime_error;
40pub mod user_functions;
41pub mod warning_store;
42pub mod workspace;
43
44pub type BuiltinResult<T> = Result<T, RuntimeError>;
46
47pub use runtime_error::{
48 build_runtime_error, replay_error, replay_error_with_source, CallFrame, ErrorContext,
49 ReplayErrorKind, RuntimeError, RuntimeErrorBuilder,
50};
51
52#[cfg(feature = "blas-lapack")]
53pub mod blas;
54#[cfg(feature = "blas-lapack")]
55pub mod lapack;
56
57#[cfg(all(feature = "blas-lapack", target_os = "macos"))]
59#[link(name = "Accelerate", kind = "framework")]
60extern "C" {}
61
62#[cfg(all(feature = "blas-lapack", not(target_os = "macos")))]
64extern crate openblas_src;
65
66pub use dispatcher::{
67 call_builtin, call_builtin_async, call_builtin_async_with_outputs, gather_if_needed,
68 gather_if_needed_async, is_gpu_value, value_contains_gpu,
69};
70
71#[cfg(feature = "plot-core")]
72pub use builtins::plotting::{
73 export_figure_scene as runtime_plot_export_figure_scene,
74 import_figure_scene_async as runtime_plot_import_figure_scene_async,
75 import_figure_scene_from_path_async as runtime_plot_import_figure_scene_from_path_async,
76};
77pub use replay::{
78 runtime_export_workspace_state, runtime_import_workspace_state, WorkspaceReplayMode,
79};
80
81pub use runmat_macros::{register_fusion_spec, register_gpu_spec};
82
83pub use arrays::create_range;
86pub use concatenation::create_matrix_from_values;
87pub use elementwise::{elementwise_div, elementwise_mul, elementwise_neg, elementwise_pow, power};
88pub use indexing::perform_indexing;
89#[cfg(feature = "blas-lapack")]
95pub use blas::*;
96#[cfg(feature = "blas-lapack")]
97pub use lapack::*;
98
99pub fn make_cell_with_shape(values: Vec<Value>, shape: Vec<usize>) -> Result<Value, String> {
100 #[cfg(target_arch = "wasm32")]
101 {
102 let ca = runmat_builtins::CellArray::new_with_shape(values, shape)
103 .map_err(|e| format!("Cell creation error: {e}"))?;
104 Ok(Value::Cell(ca))
105 }
106
107 #[cfg(not(target_arch = "wasm32"))]
108 {
109 let handles: Vec<GcPtr<Value>> = values
110 .into_iter()
111 .map(|v| runmat_gc::gc_allocate(v).map_err(|e| format!("Cell creation error: {e}")))
112 .collect::<Result<_, _>>()?;
113 let ca = runmat_builtins::CellArray::new_handles_with_shape(handles, shape)
114 .map_err(|e| format!("Cell creation error: {e}"))?;
115 Ok(Value::Cell(ca))
116 }
117}
118
119pub(crate) fn make_cell(values: Vec<Value>, rows: usize, cols: usize) -> Result<Value, String> {
120 make_cell_with_shape(values, vec![rows, cols])
121}
122
123#[runmat_macros::runtime_builtin(name = "__make_cell", builtin_path = "crate")]
125async fn make_cell_builtin(rest: Vec<Value>) -> crate::BuiltinResult<Value> {
126 let rows = 1usize;
127 let cols = rest.len();
128 make_cell(rest, rows, cols).map_err(Into::into)
129}
130
131fn to_string_scalar(v: &Value) -> Result<String, String> {
132 let s: String = v.try_into()?;
133 Ok(s)
134}
135
136fn to_string_array(v: &Value) -> Result<runmat_builtins::StringArray, String> {
137 match v {
138 Value::String(s) => runmat_builtins::StringArray::new(vec![s.clone()], vec![1, 1])
139 .map_err(|e| e.to_string()),
140 Value::StringArray(sa) => Ok(sa.clone()),
141 Value::CharArray(ca) => {
142 let mut out: Vec<String> = Vec::with_capacity(ca.rows);
144 for r in 0..ca.rows {
145 let mut s = String::with_capacity(ca.cols);
146 for c in 0..ca.cols {
147 s.push(ca.data[r * ca.cols + c]);
148 }
149 out.push(s);
150 }
151 runmat_builtins::StringArray::new(out, vec![ca.rows, 1]).map_err(|e| e.to_string())
152 }
153 other => Err(format!("cannot convert to string array: {other:?}")),
154 }
155}
156
157#[runmat_macros::runtime_builtin(name = "strtrim", builtin_path = "crate")]
158async fn strtrim_builtin(a: Value) -> crate::BuiltinResult<Value> {
159 let s = to_string_scalar(&a)?;
160 Ok(Value::String(s.trim().to_string()))
161}
162
163#[runmat_macros::runtime_builtin(name = "strjoin", builtin_path = "crate")]
165async fn strjoin_rowwise(a: Value, delim: Value) -> crate::BuiltinResult<Value> {
166 let d = to_string_scalar(&delim)?;
167 let sa = to_string_array(&a)?;
168 let rows = *sa.shape.first().unwrap_or(&sa.data.len());
169 let cols = *sa.shape.get(1).unwrap_or(&1);
170 if rows == 0 || cols == 0 {
171 return Ok(Value::StringArray(
172 runmat_builtins::StringArray::new(Vec::new(), vec![0, 0]).unwrap(),
173 ));
174 }
175 let mut out: Vec<String> = Vec::with_capacity(rows);
176 for r in 0..rows {
177 let mut s = String::new();
178 for c in 0..cols {
179 if c > 0 {
180 s.push_str(&d);
181 }
182 s.push_str(&sa.data[r + c * rows]);
183 }
184 out.push(s);
185 }
186 Ok(Value::StringArray(
187 runmat_builtins::StringArray::new(out, vec![rows, 1])
188 .map_err(|e| format!("strjoin: {e}"))?,
189 ))
190}
191
192#[runmat_macros::runtime_builtin(name = "deal", builtin_path = "crate")]
194async fn deal_builtin(rest: Vec<Value>) -> crate::BuiltinResult<Value> {
195 if let Some(out_count) = crate::output_count::current_output_count() {
196 return Ok(crate::output_count::output_list_with_padding(
197 out_count, rest,
198 ));
199 }
200 let cols = rest.len();
202 make_cell(rest, 1, cols).map_err(Into::into)
203}
204
205#[runmat_macros::runtime_builtin(name = "rethrow", builtin_path = "crate")]
208async fn rethrow_builtin(e: Value) -> crate::BuiltinResult<Value> {
209 match e {
210 Value::MException(me) => Err(build_runtime_error(me.message)
211 .with_identifier(me.identifier)
212 .build()),
213 Value::String(s) => Err(build_runtime_error(s).build()),
214 other => Err(build_runtime_error(format!("RunMat:error: {other:?}")).build()),
215 }
216}
217
218#[runmat_macros::runtime_builtin(name = "call_method", builtin_path = "crate")]
219async fn call_method_builtin(
220 base: Value,
221 method: String,
222 rest: Vec<Value>,
223) -> crate::BuiltinResult<Value> {
224 match base {
225 Value::Object(obj) => {
226 let qualified = format!("{}.{}", obj.class_name, method);
228 let mut args = Vec::with_capacity(1 + rest.len());
230 args.push(Value::Object(obj.clone()));
231 args.extend(rest);
232 if let Ok(v) = crate::call_builtin_async(&qualified, &args).await {
233 return Ok(v);
234 }
235 Ok(crate::call_builtin_async(&method, &args).await?)
237 }
238 Value::HandleObject(h) => {
239 let target = unsafe { &*h.target.as_raw() };
241 let class_name = match target {
242 Value::Object(o) => o.class_name.clone(),
243 Value::Struct(_) => h.class_name.clone(),
244 _ => h.class_name.clone(),
245 };
246 let qualified = format!("{class_name}.{method}");
247 let mut args = Vec::with_capacity(1 + rest.len());
248 args.push(Value::HandleObject(h.clone()));
249 args.extend(rest);
250 if let Ok(v) = crate::call_builtin_async(&qualified, &args).await {
251 return Ok(v);
252 }
253 Ok(crate::call_builtin_async(&method, &args).await?)
254 }
255 other => {
256 Err((format!("call_method unsupported on {other:?} for method '{method}'")).into())
257 }
258 }
259}
260
261#[runmat_macros::runtime_builtin(name = "subsasgn", builtin_path = "crate")]
263async fn subsasgn_dispatch(
264 obj: Value,
265 kind: String,
266 payload: Value,
267 rhs: Value,
268) -> crate::BuiltinResult<Value> {
269 match &obj {
270 Value::Object(o) => {
271 let qualified = format!("{}.subsasgn", o.class_name);
272 Ok(
273 crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload, rhs])
274 .await?,
275 )
276 }
277 Value::HandleObject(h) => {
278 let target = unsafe { &*h.target.as_raw() };
279 let class_name = match target {
280 Value::Object(o) => o.class_name.clone(),
281 _ => h.class_name.clone(),
282 };
283 let qualified = format!("{class_name}.subsasgn");
284 Ok(
285 crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload, rhs])
286 .await?,
287 )
288 }
289 other => Err((format!("subsasgn: receiver must be object, got {other:?}")).into()),
290 }
291}
292
293#[runmat_macros::runtime_builtin(name = "subsref", builtin_path = "crate")]
294async fn subsref_dispatch(obj: Value, kind: String, payload: Value) -> crate::BuiltinResult<Value> {
295 match &obj {
296 Value::Object(o) => {
297 let qualified = format!("{}.subsref", o.class_name);
298 Ok(crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload]).await?)
299 }
300 Value::HandleObject(h) => {
301 let target = unsafe { &*h.target.as_raw() };
302 let class_name = match target {
303 Value::Object(o) => o.class_name.clone(),
304 _ => h.class_name.clone(),
305 };
306 let qualified = format!("{class_name}.subsref");
307 Ok(crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload]).await?)
308 }
309 other => Err((format!("subsref: receiver must be object, got {other:?}")).into()),
310 }
311}
312
313#[runmat_macros::runtime_builtin(name = "new_handle_object", builtin_path = "crate")]
316async fn new_handle_object_builtin(class_name: String) -> crate::BuiltinResult<Value> {
317 let obj = new_object_builtin(class_name.clone()).await?;
319 let gc = runmat_gc::gc_allocate(obj).map_err(|e| format!("gc: {e}"))?;
320 Ok(Value::HandleObject(runmat_builtins::HandleRef {
321 class_name,
322 target: gc,
323 valid: true,
324 }))
325}
326
327#[runmat_macros::runtime_builtin(name = "isvalid", builtin_path = "crate")]
328async fn isvalid_builtin(v: Value) -> crate::BuiltinResult<Value> {
329 match v {
330 Value::HandleObject(h) => Ok(Value::Bool(h.valid)),
331 Value::Listener(l) => Ok(Value::Bool(l.valid && l.enabled)),
332 _ => Ok(Value::Bool(false)),
333 }
334}
335
336use std::sync::{Mutex, OnceLock};
337
338#[derive(Default)]
339struct EventRegistry {
340 next_id: u64,
341 listeners: std::collections::HashMap<(usize, String), Vec<runmat_builtins::Listener>>,
342}
343
344static EVENT_REGISTRY: OnceLock<Mutex<EventRegistry>> = OnceLock::new();
345
346fn events() -> &'static Mutex<EventRegistry> {
347 EVENT_REGISTRY.get_or_init(|| Mutex::new(EventRegistry::default()))
348}
349
350#[runmat_macros::runtime_builtin(name = "addlistener", builtin_path = "crate")]
351async fn addlistener_builtin(
352 target: Value,
353 event_name: String,
354 callback: Value,
355) -> crate::BuiltinResult<Value> {
356 let key_ptr: usize = match &target {
357 Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
358 Value::Object(o) => o as *const _ as usize,
359 _ => return Err(("addlistener: target must be handle or object".to_string()).into()),
360 };
361 let mut reg = events().lock().unwrap();
362 let id = {
363 reg.next_id += 1;
364 reg.next_id
365 };
366 let tgt_gc = match target {
367 Value::HandleObject(h) => h.target,
368 Value::Object(o) => {
369 runmat_gc::gc_allocate(Value::Object(o)).map_err(|e| format!("gc: {e}"))?
370 }
371 _ => unreachable!(),
372 };
373 let cb_gc = runmat_gc::gc_allocate(callback).map_err(|e| format!("gc: {e}"))?;
374 let listener = runmat_builtins::Listener {
375 id,
376 target: tgt_gc,
377 event_name: event_name.clone(),
378 callback: cb_gc,
379 enabled: true,
380 valid: true,
381 };
382 reg.listeners
383 .entry((key_ptr, event_name))
384 .or_default()
385 .push(listener.clone());
386 Ok(Value::Listener(listener))
387}
388
389#[runmat_macros::runtime_builtin(name = "notify", builtin_path = "crate")]
390async fn notify_builtin(
391 target: Value,
392 event_name: String,
393 rest: Vec<Value>,
394) -> crate::BuiltinResult<Value> {
395 let key_ptr: usize = match &target {
396 Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
397 Value::Object(o) => o as *const _ as usize,
398 _ => return Err(("notify: target must be handle or object".to_string()).into()),
399 };
400 let mut to_call: Vec<runmat_builtins::Listener> = Vec::new();
401 {
402 let reg = events().lock().unwrap();
403 if let Some(list) = reg.listeners.get(&(key_ptr, event_name.clone())) {
404 for l in list {
405 if l.valid && l.enabled {
406 to_call.push(l.clone());
407 }
408 }
409 }
410 }
411 for l in to_call {
412 let mut args = Vec::new();
414 args.push(target.clone());
415 args.extend(rest.iter().cloned());
416 let cbv: Value = (*l.callback).clone();
417 match &cbv {
418 Value::String(s) if s.starts_with('@') => {
419 let mut a = vec![Value::String(s.clone())];
420 a.extend(args.into_iter());
421 let _ = crate::call_builtin_async("feval", &a).await?;
422 }
423 Value::FunctionHandle(name) => {
424 let mut a = vec![Value::FunctionHandle(name.clone())];
425 a.extend(args.into_iter());
426 let _ = crate::call_builtin_async("feval", &a).await?;
427 }
428 Value::Closure(_) => {
429 let mut a = vec![cbv.clone()];
430 a.extend(args.into_iter());
431 let _ = crate::call_builtin_async("feval", &a).await?;
432 }
433 _ => {}
434 }
435 }
436 Ok(Value::Num(0.0))
437}
438
439#[runmat_macros::runtime_builtin(name = "get.p", builtin_path = "crate")]
443async fn get_p_builtin(obj: Value) -> crate::BuiltinResult<Value> {
444 match obj {
445 Value::Object(o) => {
446 if let Some(v) = o.properties.get("p_backing") {
447 Ok(v.clone())
448 } else {
449 Ok(Value::Num(0.0))
450 }
451 }
452 other => Err((format!("get.p requires object, got {other:?}")).into()),
453 }
454}
455
456#[runmat_macros::runtime_builtin(name = "set.p", builtin_path = "crate")]
457async fn set_p_builtin(obj: Value, val: Value) -> crate::BuiltinResult<Value> {
458 match obj {
459 Value::Object(mut o) => {
460 o.properties.insert("p_backing".to_string(), val);
461 Ok(Value::Object(o))
462 }
463 other => Err((format!("set.p requires object, got {other:?}")).into()),
464 }
465}
466
467#[runmat_macros::runtime_builtin(name = "make_handle", builtin_path = "crate")]
468async fn make_handle_builtin(name: String) -> crate::BuiltinResult<Value> {
469 Ok(Value::FunctionHandle(name))
470}
471
472#[runmat_macros::runtime_builtin(name = "make_anon", builtin_path = "crate")]
473async fn make_anon_builtin(params: String, body: String) -> crate::BuiltinResult<Value> {
474 Ok(Value::String(format!("@anon({params}) {body}")))
475}
476
477#[runmat_macros::runtime_builtin(name = "new_object", builtin_path = "crate")]
478pub(crate) async fn new_object_builtin(class_name: String) -> crate::BuiltinResult<Value> {
479 if let Some(def) = runmat_builtins::get_class(&class_name) {
480 let mut chain: Vec<runmat_builtins::ClassDef> = Vec::new();
482 let mut cursor: Option<String> = Some(def.name.clone());
484 while let Some(name) = cursor {
485 if let Some(cd) = runmat_builtins::get_class(&name) {
486 chain.push(cd.clone());
487 cursor = cd.parent.clone();
488 } else {
489 break;
490 }
491 }
492 chain.reverse();
494 let mut obj = runmat_builtins::ObjectInstance::new(def.name.clone());
495 for cd in chain {
497 for (k, p) in cd.properties.iter() {
498 if !p.is_static {
499 if let Some(v) = &p.default_value {
500 obj.properties.insert(k.clone(), v.clone());
501 }
502 }
503 }
504 }
505 Ok(Value::Object(obj))
506 } else {
507 Ok(Value::Object(runmat_builtins::ObjectInstance::new(
508 class_name,
509 )))
510 }
511}
512
513#[runmat_macros::runtime_builtin(name = "classref", builtin_path = "crate")]
516async fn classref_builtin(class_name: String) -> crate::BuiltinResult<Value> {
517 Ok(Value::ClassRef(class_name))
518}
519
520#[runmat_macros::runtime_builtin(name = "__register_test_classes", builtin_path = "crate")]
521async fn register_test_classes_builtin() -> crate::BuiltinResult<Value> {
522 use runmat_builtins::*;
523 let mut props = std::collections::HashMap::new();
524 props.insert(
525 "x".to_string(),
526 PropertyDef {
527 name: "x".to_string(),
528 is_static: false,
529 is_dependent: false,
530 get_access: Access::Public,
531 set_access: Access::Public,
532 default_value: Some(Value::Num(0.0)),
533 },
534 );
535 props.insert(
536 "y".to_string(),
537 PropertyDef {
538 name: "y".to_string(),
539 is_static: false,
540 is_dependent: false,
541 get_access: Access::Public,
542 set_access: Access::Public,
543 default_value: Some(Value::Num(0.0)),
544 },
545 );
546 props.insert(
547 "staticValue".to_string(),
548 PropertyDef {
549 name: "staticValue".to_string(),
550 is_static: true,
551 is_dependent: false,
552 get_access: Access::Public,
553 set_access: Access::Public,
554 default_value: Some(Value::Num(42.0)),
555 },
556 );
557 props.insert(
558 "secret".to_string(),
559 PropertyDef {
560 name: "secret".to_string(),
561 is_static: false,
562 is_dependent: false,
563 get_access: Access::Private,
564 set_access: Access::Private,
565 default_value: Some(Value::Num(99.0)),
566 },
567 );
568 let mut methods = std::collections::HashMap::new();
569 methods.insert(
570 "move".to_string(),
571 MethodDef {
572 name: "move".to_string(),
573 is_static: false,
574 access: Access::Public,
575 function_name: "Point.move".to_string(),
576 },
577 );
578 methods.insert(
579 "origin".to_string(),
580 MethodDef {
581 name: "origin".to_string(),
582 is_static: true,
583 access: Access::Public,
584 function_name: "Point.origin".to_string(),
585 },
586 );
587 runmat_builtins::register_class(ClassDef {
588 name: "Point".to_string(),
589 parent: None,
590 properties: props,
591 methods,
592 });
593
594 let mut ns_props = std::collections::HashMap::new();
596 ns_props.insert(
597 "x".to_string(),
598 PropertyDef {
599 name: "x".to_string(),
600 is_static: false,
601 is_dependent: false,
602 get_access: Access::Public,
603 set_access: Access::Public,
604 default_value: Some(Value::Num(1.0)),
605 },
606 );
607 ns_props.insert(
608 "y".to_string(),
609 PropertyDef {
610 name: "y".to_string(),
611 is_static: false,
612 is_dependent: false,
613 get_access: Access::Public,
614 set_access: Access::Public,
615 default_value: Some(Value::Num(2.0)),
616 },
617 );
618 let ns_methods = std::collections::HashMap::new();
619 runmat_builtins::register_class(ClassDef {
620 name: "pkg.PointNS".to_string(),
621 parent: None,
622 properties: ns_props,
623 methods: ns_methods,
624 });
625
626 let shape_props = std::collections::HashMap::new();
628 let mut shape_methods = std::collections::HashMap::new();
629 shape_methods.insert(
630 "area".to_string(),
631 MethodDef {
632 name: "area".to_string(),
633 is_static: false,
634 access: Access::Public,
635 function_name: "Shape.area".to_string(),
636 },
637 );
638 runmat_builtins::register_class(ClassDef {
639 name: "Shape".to_string(),
640 parent: None,
641 properties: shape_props,
642 methods: shape_methods,
643 });
644
645 let mut circle_props = std::collections::HashMap::new();
646 circle_props.insert(
647 "r".to_string(),
648 PropertyDef {
649 name: "r".to_string(),
650 is_static: false,
651 is_dependent: false,
652 get_access: Access::Public,
653 set_access: Access::Public,
654 default_value: Some(Value::Num(0.0)),
655 },
656 );
657 let mut circle_methods = std::collections::HashMap::new();
658 circle_methods.insert(
659 "area".to_string(),
660 MethodDef {
661 name: "area".to_string(),
662 is_static: false,
663 access: Access::Public,
664 function_name: "Circle.area".to_string(),
665 },
666 );
667 runmat_builtins::register_class(ClassDef {
668 name: "Circle".to_string(),
669 parent: Some("Shape".to_string()),
670 properties: circle_props,
671 methods: circle_methods,
672 });
673
674 let ctor_props = std::collections::HashMap::new();
676 let mut ctor_methods = std::collections::HashMap::new();
677 ctor_methods.insert(
678 "Ctor".to_string(),
679 MethodDef {
680 name: "Ctor".to_string(),
681 is_static: true,
682 access: Access::Public,
683 function_name: "Ctor.Ctor".to_string(),
684 },
685 );
686 runmat_builtins::register_class(ClassDef {
687 name: "Ctor".to_string(),
688 parent: None,
689 properties: ctor_props,
690 methods: ctor_methods,
691 });
692
693 let overidx_props = std::collections::HashMap::new();
695 let mut overidx_methods = std::collections::HashMap::new();
696 overidx_methods.insert(
697 "subsref".to_string(),
698 MethodDef {
699 name: "subsref".to_string(),
700 is_static: false,
701 access: Access::Public,
702 function_name: "OverIdx.subsref".to_string(),
703 },
704 );
705 overidx_methods.insert(
706 "subsasgn".to_string(),
707 MethodDef {
708 name: "subsasgn".to_string(),
709 is_static: false,
710 access: Access::Public,
711 function_name: "OverIdx.subsasgn".to_string(),
712 },
713 );
714 runmat_builtins::register_class(ClassDef {
715 name: "OverIdx".to_string(),
716 parent: None,
717 properties: overidx_props,
718 methods: overidx_methods,
719 });
720 Ok(Value::Num(1.0))
721}
722
723#[cfg(feature = "test-classes")]
724pub async fn test_register_classes() {
725 let _ = register_test_classes_builtin().await;
726}
727
728#[runmat_macros::runtime_builtin(name = "Point.move", builtin_path = "crate")]
730async fn point_move_method(obj: Value, dx: f64, dy: f64) -> crate::BuiltinResult<Value> {
731 match obj {
732 Value::Object(mut o) => {
733 let mut x = 0.0;
734 let mut y = 0.0;
735 if let Some(Value::Num(v)) = o.properties.get("x") {
736 x = *v;
737 }
738 if let Some(Value::Num(v)) = o.properties.get("y") {
739 y = *v;
740 }
741 o.properties.insert("x".to_string(), Value::Num(x + dx));
742 o.properties.insert("y".to_string(), Value::Num(y + dy));
743 Ok(Value::Object(o))
744 }
745 other => Err((format!("Point.move requires object receiver, got {other:?}")).into()),
746 }
747}
748
749#[runmat_macros::runtime_builtin(name = "Point.origin", builtin_path = "crate")]
750async fn point_origin_method() -> crate::BuiltinResult<Value> {
751 let mut o = runmat_builtins::ObjectInstance::new("Point".to_string());
752 o.properties.insert("x".to_string(), Value::Num(0.0));
753 o.properties.insert("y".to_string(), Value::Num(0.0));
754 Ok(Value::Object(o))
755}
756
757#[runmat_macros::runtime_builtin(name = "Shape.area", builtin_path = "crate")]
758async fn shape_area_method(_obj: Value) -> crate::BuiltinResult<Value> {
759 Ok(Value::Num(0.0))
760}
761
762#[runmat_macros::runtime_builtin(name = "Circle.area", builtin_path = "crate")]
763async fn circle_area_method(obj: Value) -> crate::BuiltinResult<Value> {
764 match obj {
765 Value::Object(o) => {
766 let r = if let Some(Value::Num(v)) = o.properties.get("r") {
767 *v
768 } else {
769 0.0
770 };
771 Ok(Value::Num(std::f64::consts::PI * r * r))
772 }
773 other => Err((format!("Circle.area requires object receiver, got {other:?}")).into()),
774 }
775}
776
777#[runmat_macros::runtime_builtin(name = "Ctor.Ctor", builtin_path = "crate")]
779async fn ctor_ctor_method(x: f64) -> crate::BuiltinResult<Value> {
780 let mut o = runmat_builtins::ObjectInstance::new("Ctor".to_string());
782 o.properties.insert("x".to_string(), Value::Num(x));
783 Ok(Value::Object(o))
784}
785
786#[runmat_macros::runtime_builtin(name = "PkgF.foo", builtin_path = "crate")]
788async fn pkgf_foo() -> crate::BuiltinResult<Value> {
789 Ok(Value::Num(10.0))
790}
791
792#[runmat_macros::runtime_builtin(name = "PkgG.foo", builtin_path = "crate")]
793async fn pkgg_foo() -> crate::BuiltinResult<Value> {
794 Ok(Value::Num(20.0))
795}
796
797#[runmat_macros::runtime_builtin(name = "OverIdx.subsref", builtin_path = "crate")]
798async fn overidx_subsref(obj: Value, kind: String, payload: Value) -> crate::BuiltinResult<Value> {
799 match (obj, kind.as_str(), payload) {
801 (Value::Object(_), "()", Value::Cell(_)) => Ok(Value::Num(99.0)),
802 (Value::Object(o), "{}", Value::Cell(_)) => {
803 if let Some(v) = o.properties.get("lastCell") {
804 Ok(v.clone())
805 } else {
806 Ok(Value::Num(0.0))
807 }
808 }
809 (Value::Object(o), ".", Value::String(field)) => {
810 if let Some(v) = o.properties.get(&field) {
812 Ok(v.clone())
813 } else {
814 Ok(Value::Num(77.0))
815 }
816 }
817 (Value::Object(o), ".", Value::CharArray(ca)) => {
818 let field: String = ca.data.iter().collect();
819 if let Some(v) = o.properties.get(&field) {
820 Ok(v.clone())
821 } else {
822 Ok(Value::Num(77.0))
823 }
824 }
825 _ => Err(("subsref: unsupported payload".to_string()).into()),
826 }
827}
828
829#[runmat_macros::runtime_builtin(name = "OverIdx.subsasgn", builtin_path = "crate")]
830async fn overidx_subsasgn(
831 mut obj: Value,
832 kind: String,
833 payload: Value,
834 rhs: Value,
835) -> crate::BuiltinResult<Value> {
836 match (&mut obj, kind.as_str(), payload) {
837 (Value::Object(o), "()", Value::Cell(_)) => {
838 o.properties.insert("last".to_string(), rhs);
840 Ok(Value::Object(o.clone()))
841 }
842 (Value::Object(o), "{}", Value::Cell(_)) => {
843 o.properties.insert("lastCell".to_string(), rhs);
844 Ok(Value::Object(o.clone()))
845 }
846 (Value::Object(o), ".", Value::String(field)) => {
847 o.properties.insert(field, rhs);
848 Ok(Value::Object(o.clone()))
849 }
850 (Value::Object(o), ".", Value::CharArray(ca)) => {
851 let field: String = ca.data.iter().collect();
852 o.properties.insert(field, rhs);
853 Ok(Value::Object(o.clone()))
854 }
855 _ => Err(("subsasgn: unsupported payload".to_string()).into()),
856 }
857}
858
859#[runmat_macros::runtime_builtin(name = "OverIdx.plus", builtin_path = "crate")]
861async fn overidx_plus(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
862 let o = match obj {
863 Value::Object(o) => o,
864 _ => return Err(("OverIdx.plus: receiver must be object".to_string()).into()),
865 };
866 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
867 *v
868 } else {
869 0.0
870 };
871 let r: f64 = (&rhs).try_into()?;
872 Ok(Value::Num(k + r))
873}
874
875#[runmat_macros::runtime_builtin(name = "OverIdx.times", builtin_path = "crate")]
876async fn overidx_times(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
877 let o = match obj {
878 Value::Object(o) => o,
879 _ => return Err(("OverIdx.times: receiver must be object".to_string()).into()),
880 };
881 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
882 *v
883 } else {
884 0.0
885 };
886 let r: f64 = (&rhs).try_into()?;
887 Ok(Value::Num(k * r))
888}
889
890#[runmat_macros::runtime_builtin(name = "OverIdx.mtimes", builtin_path = "crate")]
891async fn overidx_mtimes(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
892 let o = match obj {
893 Value::Object(o) => o,
894 _ => return Err(("OverIdx.mtimes: receiver must be object".to_string()).into()),
895 };
896 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
897 *v
898 } else {
899 0.0
900 };
901 let r: f64 = (&rhs).try_into()?;
902 Ok(Value::Num(k * r))
903}
904
905#[runmat_macros::runtime_builtin(name = "OverIdx.lt", builtin_path = "crate")]
906async fn overidx_lt(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
907 let o = match obj {
908 Value::Object(o) => o,
909 _ => return Err(("OverIdx.lt: receiver must be object".to_string()).into()),
910 };
911 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
912 *v
913 } else {
914 0.0
915 };
916 let r: f64 = (&rhs).try_into()?;
917 Ok(Value::Num(if k < r { 1.0 } else { 0.0 }))
918}
919
920#[runmat_macros::runtime_builtin(name = "OverIdx.gt", builtin_path = "crate")]
921async fn overidx_gt(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
922 let o = match obj {
923 Value::Object(o) => o,
924 _ => return Err(("OverIdx.gt: receiver must be object".to_string()).into()),
925 };
926 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
927 *v
928 } else {
929 0.0
930 };
931 let r: f64 = (&rhs).try_into()?;
932 Ok(Value::Num(if k > r { 1.0 } else { 0.0 }))
933}
934
935#[runmat_macros::runtime_builtin(name = "OverIdx.eq", builtin_path = "crate")]
936async fn overidx_eq(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
937 let o = match obj {
938 Value::Object(o) => o,
939 _ => return Err(("OverIdx.eq: receiver must be object".to_string()).into()),
940 };
941 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
942 *v
943 } else {
944 0.0
945 };
946 let r: f64 = (&rhs).try_into()?;
947 Ok(Value::Num(if (k - r).abs() < 1e-12 { 1.0 } else { 0.0 }))
948}
949
950#[runmat_macros::runtime_builtin(name = "OverIdx.uplus", builtin_path = "crate")]
951async fn overidx_uplus(obj: Value) -> crate::BuiltinResult<Value> {
952 Ok(obj)
954}
955
956#[runmat_macros::runtime_builtin(name = "OverIdx.rdivide", builtin_path = "crate")]
957async fn overidx_rdivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
958 let o = match obj {
959 Value::Object(o) => o,
960 _ => return Err(("OverIdx.rdivide: receiver must be object".to_string()).into()),
961 };
962 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
963 *v
964 } else {
965 0.0
966 };
967 let r: f64 = (&rhs).try_into()?;
968 Ok(Value::Num(k / r))
969}
970
971#[runmat_macros::runtime_builtin(name = "OverIdx.mrdivide", builtin_path = "crate")]
972async fn overidx_mrdivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
973 overidx_rdivide(obj, rhs).await
974}
975
976#[runmat_macros::runtime_builtin(name = "OverIdx.ldivide", builtin_path = "crate")]
977async fn overidx_ldivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
978 let o = match obj {
979 Value::Object(o) => o,
980 _ => return Err(("OverIdx.ldivide: receiver must be object".to_string()).into()),
981 };
982 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
983 *v
984 } else {
985 0.0
986 };
987 let r: f64 = (&rhs).try_into()?;
988 Ok(Value::Num(r / k))
989}
990
991#[runmat_macros::runtime_builtin(name = "OverIdx.mldivide", builtin_path = "crate")]
992async fn overidx_mldivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
993 overidx_ldivide(obj, rhs).await
994}
995
996#[runmat_macros::runtime_builtin(name = "OverIdx.and", builtin_path = "crate")]
997async fn overidx_and(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
998 let o = match obj {
999 Value::Object(o) => o,
1000 _ => return Err(("OverIdx.and: receiver must be object".to_string()).into()),
1001 };
1002 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
1003 *v
1004 } else {
1005 0.0
1006 };
1007 let r: f64 = (&rhs).try_into()?;
1008 Ok(Value::Num(if (k != 0.0) && (r != 0.0) { 1.0 } else { 0.0 }))
1009}
1010
1011#[runmat_macros::runtime_builtin(name = "OverIdx.or", builtin_path = "crate")]
1012async fn overidx_or(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
1013 let o = match obj {
1014 Value::Object(o) => o,
1015 _ => return Err(("OverIdx.or: receiver must be object".to_string()).into()),
1016 };
1017 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
1018 *v
1019 } else {
1020 0.0
1021 };
1022 let r: f64 = (&rhs).try_into()?;
1023 Ok(Value::Num(if (k != 0.0) || (r != 0.0) { 1.0 } else { 0.0 }))
1024}
1025
1026#[runmat_macros::runtime_builtin(name = "OverIdx.xor", builtin_path = "crate")]
1027async fn overidx_xor(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
1028 let o = match obj {
1029 Value::Object(o) => o,
1030 _ => return Err(("OverIdx.xor: receiver must be object".to_string()).into()),
1031 };
1032 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
1033 *v
1034 } else {
1035 0.0
1036 };
1037 let r: f64 = (&rhs).try_into()?;
1038 let a = k != 0.0;
1039 let b = r != 0.0;
1040 Ok(Value::Num(if a ^ b { 1.0 } else { 0.0 }))
1041}
1042
1043#[runmat_macros::runtime_builtin(name = "feval", builtin_path = "crate")]
1044async fn feval_builtin(f: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1045 async fn call_by_name(name: &str, args: &[Value]) -> crate::BuiltinResult<Value> {
1046 if let Some(result) = crate::user_functions::try_call_user_function(name, args).await {
1047 match result {
1048 Ok(value) => return Ok(value),
1049 Err(err) => {
1050 let identifier = err.identifier().unwrap_or("").to_ascii_lowercase();
1051 let message = err.message().to_ascii_lowercase();
1052 let is_undefined = identifier.contains("undefinedfunction")
1053 || message.contains("undefined function");
1054 if !is_undefined {
1055 return Err(err);
1056 }
1057 }
1058 }
1059 }
1060 crate::call_builtin_async(name, args).await
1061 }
1062
1063 match f {
1064 Value::String(s) => {
1066 if let Some(name) = s.strip_prefix('@') {
1067 call_by_name(name, &rest).await
1068 } else {
1069 Err(
1070 (format!("feval: expected function handle string starting with '@', got {s}"))
1071 .into(),
1072 )
1073 }
1074 }
1075 Value::CharArray(ca) => {
1077 if ca.rows == 1 {
1078 let s: String = ca.data.iter().collect();
1079 if let Some(name) = s.strip_prefix('@') {
1080 call_by_name(name, &rest).await
1081 } else {
1082 Err((format!(
1083 "feval: expected function handle string starting with '@', got {s}"
1084 ))
1085 .into())
1086 }
1087 } else {
1088 Err(("feval: function handle char array must be a row vector".to_string()).into())
1089 }
1090 }
1091 Value::FunctionHandle(name) => call_by_name(&name, &rest).await,
1092 Value::Closure(c) => {
1093 let mut args = c.captures.clone();
1094 args.extend(rest);
1095 call_by_name(&c.function_name, &args).await
1096 }
1097 other => Err((format!("feval: unsupported function value {other:?}")).into()),
1098 }
1099}
1100
1101#[allow(dead_code)]
1104fn tensor_sum_all(t: &runmat_builtins::Tensor) -> f64 {
1105 t.data.iter().sum()
1106}
1107
1108fn tensor_prod_all(t: &runmat_builtins::Tensor) -> f64 {
1109 t.data.iter().product()
1110}
1111
1112fn prod_all_or_cols(a: Value) -> Result<Value, String> {
1113 match a {
1114 Value::Tensor(t) => {
1115 let rows = t.rows();
1116 let cols = t.cols();
1117 if rows > 1 && cols > 1 {
1118 let mut out = vec![1.0f64; cols];
1119 for (c, oc) in out.iter_mut().enumerate().take(cols) {
1120 let mut p = 1.0;
1121 for r in 0..rows {
1122 p *= t.data[r + c * rows];
1123 }
1124 *oc = p;
1125 }
1126 Ok(Value::Tensor(
1127 runmat_builtins::Tensor::new(out, vec![1, cols])
1128 .map_err(|e| format!("prod: {e}"))?,
1129 ))
1130 } else {
1131 Ok(Value::Num(tensor_prod_all(&t)))
1132 }
1133 }
1134 _ => Err("prod: expected tensor".to_string()),
1135 }
1136}
1137
1138fn prod_dim(a: Value, dim: f64) -> Result<Value, String> {
1139 let t = match a {
1140 Value::Tensor(t) => t,
1141 _ => return Err("prod: expected tensor".to_string()),
1142 };
1143 let dim = if dim < 1.0 { 1usize } else { dim as usize };
1144 let rows = t.rows();
1145 let cols = t.cols();
1146 if dim == 1 {
1147 let mut out = vec![1.0f64; cols];
1148 for (c, oc) in out.iter_mut().enumerate().take(cols) {
1149 let mut p = 1.0;
1150 for r in 0..rows {
1151 p *= t.data[r + c * rows];
1152 }
1153 *oc = p;
1154 }
1155 Ok(Value::Tensor(
1156 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("prod: {e}"))?,
1157 ))
1158 } else if dim == 2 {
1159 let mut out = vec![1.0f64; rows];
1160 for (r, orow) in out.iter_mut().enumerate().take(rows) {
1161 let mut p = 1.0;
1162 for c in 0..cols {
1163 p *= t.data[r + c * rows];
1164 }
1165 *orow = p;
1166 }
1167 Ok(Value::Tensor(
1168 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("prod: {e}"))?,
1169 ))
1170 } else {
1171 Err("prod: dim out of range".to_string())
1172 }
1173}
1174
1175#[runmat_macros::runtime_builtin(name = "prod", builtin_path = "crate")]
1176async fn prod_var_builtin(a: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1177 if rest.is_empty() {
1178 return (prod_all_or_cols(a)).map_err(Into::into);
1179 }
1180 if rest.len() == 1 {
1181 match &rest[0] {
1182 Value::Num(d) => return (prod_dim(a, *d)).map_err(Into::into),
1183 Value::Int(i) => return (prod_dim(a, i.to_i64() as f64)).map_err(Into::into),
1184 _ => {}
1185 }
1186 }
1187 Err(("prod: unsupported arguments".to_string()).into())
1188}
1189
1190fn any_all_or_cols(a: Value) -> Result<Value, String> {
1191 match a {
1192 Value::Tensor(t) => {
1193 let rows = t.rows();
1194 let cols = t.cols();
1195 if rows > 1 && cols > 1 {
1196 let mut out = vec![0.0f64; cols];
1197 for (c, oc) in out.iter_mut().enumerate().take(cols) {
1198 let mut v = 0.0;
1199 for r in 0..rows {
1200 if t.data[r + c * rows] != 0.0 {
1201 v = 1.0;
1202 break;
1203 }
1204 }
1205 *oc = v;
1206 }
1207 Ok(Value::Tensor(
1208 runmat_builtins::Tensor::new(out, vec![1, cols])
1209 .map_err(|e| format!("any: {e}"))?,
1210 ))
1211 } else {
1212 Ok(Value::Num(if t.data.iter().any(|&x| x != 0.0) {
1213 1.0
1214 } else {
1215 0.0
1216 }))
1217 }
1218 }
1219 _ => Err("any: expected tensor".to_string()),
1220 }
1221}
1222
1223fn any_dim(a: Value, dim: f64) -> Result<Value, String> {
1224 let t = match a {
1225 Value::Tensor(t) => t,
1226 _ => return Err("any: expected tensor".to_string()),
1227 };
1228 let dim = if dim < 1.0 { 1usize } else { dim as usize };
1229 let rows = t.rows();
1230 let cols = t.cols();
1231 if dim == 1 {
1232 let mut out = vec![0.0f64; cols];
1233 for (c, oc) in out.iter_mut().enumerate().take(cols) {
1234 let mut v = 0.0;
1235 for r in 0..rows {
1236 if t.data[r + c * rows] != 0.0 {
1237 v = 1.0;
1238 break;
1239 }
1240 }
1241 *oc = v;
1242 }
1243 Ok(Value::Tensor(
1244 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("any: {e}"))?,
1245 ))
1246 } else if dim == 2 {
1247 let mut out = vec![0.0f64; rows];
1248 for (r, orow) in out.iter_mut().enumerate().take(rows) {
1249 let mut v = 0.0;
1250 for c in 0..cols {
1251 if t.data[r + c * rows] != 0.0 {
1252 v = 1.0;
1253 break;
1254 }
1255 }
1256 *orow = v;
1257 }
1258 Ok(Value::Tensor(
1259 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("any: {e}"))?,
1260 ))
1261 } else {
1262 Err("any: dim out of range".to_string())
1263 }
1264}
1265
1266#[runmat_macros::runtime_builtin(name = "any", builtin_path = "crate")]
1267async fn any_var_builtin(a: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1268 if rest.is_empty() {
1269 return (any_all_or_cols(a)).map_err(Into::into);
1270 }
1271 if rest.len() == 1 {
1272 match &rest[0] {
1273 Value::Num(d) => return (any_dim(a, *d)).map_err(Into::into),
1274 Value::Int(i) => return (any_dim(a, i.to_i64() as f64)).map_err(Into::into),
1275 _ => {}
1276 }
1277 }
1278 Err(("any: unsupported arguments".to_string()).into())
1279}
1280
1281fn all_all_or_cols(a: Value) -> Result<Value, String> {
1282 match a {
1283 Value::Tensor(t) => {
1284 let rows = t.rows();
1285 let cols = t.cols();
1286 if rows > 1 && cols > 1 {
1287 let mut out = vec![0.0f64; cols];
1288 for (c, oc) in out.iter_mut().enumerate().take(cols) {
1289 let mut v = 1.0;
1290 for r in 0..rows {
1291 if t.data[r + c * rows] == 0.0 {
1292 v = 0.0;
1293 break;
1294 }
1295 }
1296 *oc = v;
1297 }
1298 Ok(Value::Tensor(
1299 runmat_builtins::Tensor::new(out, vec![1, cols])
1300 .map_err(|e| format!("all: {e}"))?,
1301 ))
1302 } else {
1303 Ok(Value::Num(if t.data.iter().all(|&x| x != 0.0) {
1304 1.0
1305 } else {
1306 0.0
1307 }))
1308 }
1309 }
1310 _ => Err("all: expected tensor".to_string()),
1311 }
1312}
1313
1314fn all_dim(a: Value, dim: f64) -> Result<Value, String> {
1315 let t = match a {
1316 Value::Tensor(t) => t,
1317 _ => return Err("all: expected tensor".to_string()),
1318 };
1319 let dim = if dim < 1.0 { 1usize } else { dim as usize };
1320 let rows = t.rows();
1321 let cols = t.cols();
1322 if dim == 1 {
1323 let mut out = vec![0.0f64; cols];
1324 for (c, oc) in out.iter_mut().enumerate().take(cols) {
1325 let mut v = 1.0;
1326 for r in 0..rows {
1327 if t.data[r + c * rows] == 0.0 {
1328 v = 0.0;
1329 break;
1330 }
1331 }
1332 *oc = v;
1333 }
1334 Ok(Value::Tensor(
1335 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("all: {e}"))?,
1336 ))
1337 } else if dim == 2 {
1338 let mut out = vec![0.0f64; rows];
1339 for (r, orow) in out.iter_mut().enumerate().take(rows) {
1340 let mut v = 1.0;
1341 for c in 0..cols {
1342 if t.data[r + c * rows] == 0.0 {
1343 v = 0.0;
1344 break;
1345 }
1346 }
1347 *orow = v;
1348 }
1349 Ok(Value::Tensor(
1350 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("all: {e}"))?,
1351 ))
1352 } else {
1353 Err("all: dim out of range".to_string())
1354 }
1355}
1356
1357#[runmat_macros::runtime_builtin(name = "all", builtin_path = "crate")]
1358async fn all_var_builtin(a: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1359 if rest.is_empty() {
1360 return (all_all_or_cols(a)).map_err(Into::into);
1361 }
1362 if rest.len() == 1 {
1363 match &rest[0] {
1364 Value::Num(d) => return (all_dim(a, *d)).map_err(Into::into),
1365 Value::Int(i) => return (all_dim(a, i.to_i64() as f64)).map_err(Into::into),
1366 _ => {}
1367 }
1368 }
1369 Err(("all: unsupported arguments".to_string()).into())
1370}
1371
1372#[runmat_macros::runtime_builtin(name = "warning", sink = true, builtin_path = "crate")]
1373async fn warning_builtin(fmt: String, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1374 let s = format_variadic(&fmt, &rest)?;
1375 tracing::warn!("Warning: {s}");
1376 Ok(Value::Num(0.0))
1377}
1378
1379#[runmat_macros::runtime_builtin(name = "getmethod", builtin_path = "crate")]
1380async fn getmethod_builtin(obj: Value, name: String) -> crate::BuiltinResult<Value> {
1381 match obj {
1382 Value::Object(o) => {
1383 Ok(Value::Closure(runmat_builtins::Closure {
1385 function_name: "call_method".to_string(),
1386 captures: vec![Value::Object(o), Value::String(name)],
1387 }))
1388 }
1389 Value::ClassRef(cls) => Ok(Value::String(format!("@{cls}.{name}"))),
1390 other => Err((format!("getmethod unsupported on {other:?}")).into()),
1391 }
1392}