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