1use std::cell::{Cell, RefCell};
2use std::collections::{HashMap, HashSet};
3
4use indexmap::IndexMap;
5use ndarray::Array2;
6use num_complex::Complex;
7use rand::{Rng, SeedableRng, rngs::SmallRng};
8
9use crate::env::{Env, LambdaFn, Value};
10use crate::io::IoContext;
11
12pub type FnCallHook = fn(
22 name: &str,
23 func: &Value,
24 args: &[Value],
25 caller_env: &Env,
26 io: &mut IoContext,
27) -> Result<Value, String>;
28
29thread_local! {
30 static FN_CALL_HOOK: Cell<Option<FnCallHook>> = const { Cell::new(None) };
31}
32
33pub fn set_fn_call_hook(f: FnCallHook) {
37 FN_CALL_HOOK.with(|c| c.set(Some(f)));
38}
39
40pub type AutoloadHook = fn(name: &str) -> bool;
49
50thread_local! {
51 static AUTOLOAD_HOOK: Cell<Option<AutoloadHook>> = const { Cell::new(None) };
52 static AUTOLOAD_CACHE: RefCell<Env> = RefCell::new(Env::new());
54 static AUTOLOAD_MISS_CACHE: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
57}
58
59pub fn set_autoload_hook(f: AutoloadHook) {
61 AUTOLOAD_HOOK.with(|c| c.set(Some(f)));
62}
63
64pub fn autoload_cache_insert(name: String, val: Value) {
66 AUTOLOAD_CACHE.with(|c| c.borrow_mut().insert(name, val));
67}
68
69pub fn clear_autoload_miss_cache() {
74 AUTOLOAD_MISS_CACHE.with(|c| c.borrow_mut().clear());
75}
76
77pub fn resolve_autoloaded(name: &str) -> Option<Value> {
83 let cached = AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned());
84 if cached.is_some() {
85 return cached;
86 }
87 if AUTOLOAD_MISS_CACHE.with(|c| c.borrow().contains(name)) {
88 return None;
89 }
90 let hook = AUTOLOAD_HOOK.with(|c| c.get());
91 if let Some(f) = hook {
92 f(name);
93 }
94 let found = AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned());
95 if found.is_none() {
96 AUTOLOAD_MISS_CACHE.with(|c| c.borrow_mut().insert(name.to_string()));
97 }
98 found
99}
100
101pub type EvalStrHook = fn(code: &str, env: &Env) -> Result<Value, String>;
110
111thread_local! {
112 static EVAL_STR_HOOK: Cell<Option<EvalStrHook>> = const { Cell::new(None) };
113}
114
115pub fn set_eval_str_hook(f: EvalStrHook) {
119 EVAL_STR_HOOK.with(|c| c.set(Some(f)));
120}
121
122fn call_eval_str_hook(code: &str, env: &Env) -> Result<Value, String> {
123 match EVAL_STR_HOOK.with(|c| c.get()) {
124 Some(hook) => hook(code, env),
125 None => Err("eval: exec::init() not called".to_string()),
126 }
127}
128
129thread_local! {
132 static TIC_TIME: Cell<Option<std::time::Instant>> = const { Cell::new(None) };
134}
135
136thread_local! {
139 static LAST_ERR: RefCell<String> = const { RefCell::new(String::new()) };
140}
141
142pub fn set_last_err(msg: &str) {
144 LAST_ERR.with(|e| *e.borrow_mut() = msg.to_string());
145}
146
147pub fn get_last_err() -> String {
149 LAST_ERR.with(|e| e.borrow().clone())
150}
151
152thread_local! {
155 static NARGOUT: Cell<usize> = const { Cell::new(1) };
156}
157
158pub fn set_nargout(n: usize) {
164 NARGOUT.with(|c| c.set(n));
165}
166
167fn get_nargout() -> usize {
168 NARGOUT.with(|c| c.get())
169}
170
171thread_local! {
174 static DISPLAY_FMT: RefCell<FormatMode> = const { RefCell::new(FormatMode::Short) };
175 static DISPLAY_BASE: Cell<Base> = const { Cell::new(Base::Dec) };
176 static DISPLAY_COMPACT: Cell<bool> = const { Cell::new(false) };
177}
178
179pub fn set_display_ctx(fmt: &FormatMode, base: Base, compact: bool) {
184 DISPLAY_FMT.with(|f| *f.borrow_mut() = fmt.clone());
185 DISPLAY_BASE.with(|b| b.set(base));
186 DISPLAY_COMPACT.with(|c| c.set(compact));
187}
188
189pub fn get_display_fmt() -> FormatMode {
191 DISPLAY_FMT.with(|f| f.borrow().clone())
192}
193
194pub fn get_display_base() -> Base {
196 DISPLAY_BASE.with(|b| b.get())
197}
198
199pub fn get_display_compact() -> bool {
201 DISPLAY_COMPACT.with(|c| c.get())
202}
203
204thread_local! {
207 static GLOBAL_ENV: RefCell<Env> = RefCell::new(Env::new());
212
213 static GLOBAL_NAMES_STACK: RefCell<Vec<HashSet<String>>> =
218 RefCell::new(vec![HashSet::new()]);
219}
220
221pub fn global_frame_push() {
223 GLOBAL_NAMES_STACK.with(|s| s.borrow_mut().push(HashSet::new()));
224}
225
226pub fn global_frame_pop() {
228 GLOBAL_NAMES_STACK.with(|s| {
229 s.borrow_mut().pop();
230 });
231}
232
233pub fn global_declare(name: &str) {
235 GLOBAL_NAMES_STACK.with(|s| {
236 if let Some(frame) = s.borrow_mut().last_mut() {
237 frame.insert(name.to_string());
238 }
239 });
240}
241
242pub fn is_global(name: &str) -> bool {
244 GLOBAL_NAMES_STACK.with(|s| s.borrow().last().is_some_and(|f| f.contains(name)))
245}
246
247pub fn global_get(name: &str) -> Option<Value> {
249 GLOBAL_ENV.with(|e| e.borrow().get(name).cloned())
250}
251
252pub fn global_set(name: &str, val: Value) {
254 GLOBAL_ENV.with(|e| e.borrow_mut().insert(name.to_string(), val));
255}
256
257pub fn global_init_if_absent(name: &str) {
259 GLOBAL_ENV.with(|e| {
260 e.borrow_mut()
261 .entry(name.to_string())
262 .or_insert(Value::Scalar(0.0));
263 });
264}
265
266pub fn global_refresh_into_env(env: &mut crate::env::Env) {
271 GLOBAL_NAMES_STACK.with(|s| {
272 GLOBAL_ENV.with(|ge| {
273 if let Some(frame) = s.borrow().last() {
274 let store = ge.borrow();
275 for name in frame {
276 if let Some(val) = store.get(name) {
277 env.insert(name.clone(), val.clone());
278 }
279 }
280 }
281 });
282 });
283}
284
285thread_local! {
288 static PERSISTENT_STORE: RefCell<HashMap<String, Value>> =
293 RefCell::new(HashMap::new());
294
295 static FUNC_NAME_STACK: RefCell<Vec<String>> =
300 RefCell::new(vec![String::new()]);
301
302 static PERSISTENT_NAMES_STACK: RefCell<Vec<HashSet<String>>> =
304 RefCell::new(vec![HashSet::new()]);
305}
306
307pub fn persistent_frame_push(func_name: &str) {
309 FUNC_NAME_STACK.with(|s| s.borrow_mut().push(func_name.to_string()));
310 PERSISTENT_NAMES_STACK.with(|s| s.borrow_mut().push(HashSet::new()));
311}
312
313pub fn persistent_frame_pop() -> (String, HashSet<String>) {
315 let func_name = FUNC_NAME_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default());
316 let names = PERSISTENT_NAMES_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default());
317 (func_name, names)
318}
319
320pub fn persistent_declare(name: &str) {
322 PERSISTENT_NAMES_STACK.with(|s| {
323 if let Some(frame) = s.borrow_mut().last_mut() {
324 frame.insert(name.to_string());
325 }
326 });
327}
328
329pub fn persistent_load(func_name: &str, var_name: &str) -> Option<Value> {
331 let key = format!("{func_name}\x00{var_name}");
332 PERSISTENT_STORE.with(|s| s.borrow().get(&key).cloned())
333}
334
335pub fn persistent_save(func_name: &str, var_name: &str, val: Value) {
337 let key = format!("{func_name}\x00{var_name}");
338 PERSISTENT_STORE.with(|s| s.borrow_mut().insert(key, val));
339}
340
341pub fn current_func_name() -> String {
345 FUNC_NAME_STACK.with(|s| s.borrow().last().cloned().unwrap_or_default())
346}
347
348pub fn is_persistent(name: &str) -> bool {
350 PERSISTENT_NAMES_STACK.with(|s| s.borrow().last().is_some_and(|frame| frame.contains(name)))
351}
352
353thread_local! {
356 static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
360}
361
362pub fn rng_seed(seed: u64) {
364 RNG.with(|r| *r.borrow_mut() = SmallRng::seed_from_u64(seed));
365}
366
367pub fn rng_shuffle() {
369 RNG.with(|r| *r.borrow_mut() = SmallRng::from_entropy());
370}
371
372fn rand_uniform() -> f64 {
374 RNG.with(|r| r.borrow_mut().gen_range(0.0_f64..1.0))
375}
376
377fn rand_normal() -> f64 {
379 let u1 = rand_uniform().max(f64::EPSILON);
380 let u2 = rand_uniform();
381 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
382}
383
384#[derive(Debug, Clone)]
390pub enum Expr {
391 Number(f64),
393 Var(String),
395 UnaryMinus(Box<Expr>),
397 UnaryNot(Box<Expr>),
399 BinOp(Box<Expr>, Op, Box<Expr>),
401 Call(String, Vec<Expr>),
406 Matrix(Vec<Vec<Expr>>),
408 Transpose(Box<Expr>),
410 Range(Box<Expr>, Option<Box<Expr>>, Box<Expr>),
413 Colon,
416 StrLiteral(String),
418 StringObjLiteral(String),
420 Lambda {
425 params: Vec<String>,
427 body: Box<Expr>,
429 source: String,
431 },
432 PlainTranspose(Box<Expr>),
437 CellLiteral(Vec<Expr>),
441 CellIndex(Box<Expr>, Box<Expr>),
446 FuncHandle(String),
451 FieldGet(Box<Expr>, String),
455 DynFieldGet(Box<Expr>, Box<Expr>),
459 DotCall(Vec<String>, Vec<Expr>),
469 NaT,
471}
472
473#[derive(Debug, Clone)]
475pub enum Op {
476 Add,
478 Sub,
480 Mul,
482 Div,
484 Pow,
486 ElemMul,
488 ElemDiv,
490 ElemPow,
492 Eq,
495 NotEq,
497 Lt,
499 Gt,
501 LtEq,
503 GtEq,
505 And,
508 Or,
510 ElemAnd,
513 ElemOr,
515 LDiv,
517}
518
519#[derive(Debug, Clone, Copy, PartialEq, Default)]
521pub enum Base {
522 #[default]
524 Dec,
525 Hex,
527 Bin,
529 Oct,
531}
532
533#[derive(Debug, Clone, PartialEq)]
535pub enum FormatMode {
536 Short,
538 Long,
540 ShortE,
542 LongE,
544 ShortG,
546 LongG,
548 Bank,
550 Rat,
552 Hex,
554 Plus,
556 Custom(usize),
558}
559
560impl Default for FormatMode {
561 fn default() -> Self {
562 FormatMode::Custom(10)
563 }
564}
565
566impl FormatMode {
567 pub fn name(&self) -> String {
569 match self {
570 FormatMode::Short => "short".to_string(),
571 FormatMode::Long => "long".to_string(),
572 FormatMode::ShortE => "shortE".to_string(),
573 FormatMode::LongE => "longE".to_string(),
574 FormatMode::ShortG => "shortG".to_string(),
575 FormatMode::LongG => "longG".to_string(),
576 FormatMode::Bank => "bank".to_string(),
577 FormatMode::Rat => "rat".to_string(),
578 FormatMode::Hex => "hex".to_string(),
579 FormatMode::Plus => "+".to_string(),
580 FormatMode::Custom(n) => format!("custom({n})"),
581 }
582 }
583}
584
585pub fn eval(expr: &Expr, env: &Env) -> Result<Value, String> {
588 eval_inner(expr, env, None)
589}
590
591pub fn eval_with_io(expr: &Expr, env: &Env, io: &mut IoContext) -> Result<Value, String> {
594 eval_inner(expr, env, Some(io))
595}
596
597fn eval_inner(expr: &Expr, env: &Env, mut io: Option<&mut IoContext>) -> Result<Value, String> {
598 match expr {
599 Expr::Number(n) => Ok(Value::Scalar(*n)),
600 Expr::Var(name) => env.get(name).cloned().ok_or(()).or_else(|_| {
601 if is_global(name)
603 && let Some(val) = global_get(name)
604 {
605 return Ok(val);
606 }
607 if name == "e" {
609 return Ok(Value::Scalar(std::f64::consts::E));
610 }
611 if let Ok(val) = call_builtin(name, &[], env, io.as_deref_mut()) {
613 return Ok(val);
614 }
615 let hint = suggest_similar(name, env);
616 match hint {
617 Some(s) => Err(format!("Undefined variable '{name}'; did you mean '{s}'?")),
618 None => Err(format!("Undefined variable: '{name}'")),
619 }
620 }),
621 Expr::UnaryMinus(e) => match eval_inner(e, env, io)? {
622 Value::Void => Err("Unary minus is not applicable to void".to_string()),
623 Value::Scalar(n) => Ok(Value::Scalar(-n)),
624 Value::Matrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|x| -x)))),
625 Value::Complex(re, im) => Ok(Value::Complex(-re, -im)),
626 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(Box::new(m.mapv(|c| -c)))),
627 Value::Str(s) => match str_to_numeric(&s) {
628 Value::Scalar(n) => Ok(Value::Scalar(-n)),
629 Value::Matrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|x| -x)))),
630 _ => unreachable!(),
631 },
632 Value::StringObj(_) => {
633 Err("Unary minus is not applicable to string objects".to_string())
634 }
635 Value::Lambda(_)
636 | Value::Function(_)
637 | Value::Tuple(_)
638 | Value::Cell(_)
639 | Value::Struct(_)
640 | Value::StructArray(_)
641 | Value::DateTime(_)
642 | Value::Duration(_)
643 | Value::DateTimeArray(_)
644 | Value::DurationArray(_)
645 | Value::Map(_) => Err("Unary minus is not applicable to this type".to_string()),
646 },
647 Expr::UnaryNot(e) => match eval_inner(e, env, io)? {
648 Value::Void => Err("Logical NOT is not applicable to void".to_string()),
649 Value::Scalar(n) => Ok(Value::Scalar(if n == 0.0 { 1.0 } else { 0.0 })),
650 Value::Matrix(m) => Ok(Value::Matrix(Box::new(
651 m.mapv(|x| if x == 0.0 { 1.0 } else { 0.0 }),
652 ))),
653 Value::Complex(re, im) => Ok(Value::Scalar(if re == 0.0 && im == 0.0 {
654 1.0
655 } else {
656 0.0
657 })),
658 Value::ComplexMatrix(m) => {
659 Ok(Value::Matrix(Box::new(m.mapv(|c| {
660 if c.re == 0.0 && c.im == 0.0 { 1.0 } else { 0.0 }
661 }))))
662 }
663 Value::Str(s) => match str_to_numeric(&s) {
664 Value::Scalar(n) => Ok(Value::Scalar(if n == 0.0 { 1.0 } else { 0.0 })),
665 Value::Matrix(m) => Ok(Value::Matrix(Box::new(
666 m.mapv(|x| if x == 0.0 { 1.0 } else { 0.0 }),
667 ))),
668 _ => unreachable!(),
669 },
670 Value::StringObj(_) => {
671 Err("Logical NOT is not applicable to string objects".to_string())
672 }
673 Value::Lambda(_)
674 | Value::Function(_)
675 | Value::Tuple(_)
676 | Value::Cell(_)
677 | Value::Struct(_)
678 | Value::StructArray(_)
679 | Value::DateTime(_)
680 | Value::Duration(_)
681 | Value::DateTimeArray(_)
682 | Value::DurationArray(_)
683 | Value::Map(_) => Err("Logical NOT is not applicable to this type".to_string()),
684 },
685 Expr::BinOp(left, op, right) => {
686 let l = eval_inner(left, env, io.as_deref_mut())?;
687 let r = eval_inner(right, env, io)?;
688 eval_binop(l, op, r)
689 }
690 Expr::Call(name, args) => {
691 if name == "try" && args.len() == 2 {
694 return match eval_inner(&args[0], env, io.as_deref_mut()) {
695 Ok(v) => Ok(v),
696 Err(msg) => {
697 set_last_err(&msg);
698 eval_inner(&args[1], env, io.as_deref_mut())
699 }
700 };
701 }
702
703 if let Some(env_val) = env.get(name) {
710 if !matches!(env_val, Value::Lambda(_) | Value::Function(_)) {
711 return eval_index(env_val, args, env);
712 }
713 let val = env_val.clone();
715 match &val {
716 Value::Lambda(f) => {
717 let mut evaled = Vec::with_capacity(args.len().max(1));
720 for a in args {
721 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
722 }
723 if evaled.is_empty() {
724 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
725 }
726 let f = f.clone();
727 return f.0(&evaled, io);
728 }
729 Value::Function(_) => {
730 let mut evaled = Vec::with_capacity(args.len());
734 for a in args {
735 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
736 }
737 return match io.as_deref_mut() {
738 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
739 Some(hook) => hook(name, &val, &evaled, env, io_ref),
740 None => Err(format!(
741 "'{name}': user function execution not initialized \
742 (call exec::init() first)"
743 )),
744 }),
745 None => {
746 let mut tmp_io = IoContext::new();
749 FN_CALL_HOOK.with(|c| match c.get() {
750 Some(hook) => hook(name, &val, &evaled, env, &mut tmp_io),
751 None => Err(format!(
752 "'{name}': user function execution not initialized"
753 )),
754 })
755 }
756 };
757 }
758 _ => unreachable!(),
759 }
760 }
761 let autoloaded_val = AUTOLOAD_CACHE
766 .with(|c| c.borrow().get(name).cloned())
767 .or_else(|| {
768 if AUTOLOAD_MISS_CACHE.with(|c| c.borrow().contains(name.as_str())) {
769 return None;
770 }
771 let loaded = AUTOLOAD_HOOK
772 .with(|c| c.get())
773 .is_some_and(|hook| hook(name));
774 if loaded {
775 AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned())
776 } else {
777 AUTOLOAD_MISS_CACHE.with(|c| c.borrow_mut().insert(name.to_string()));
778 None
779 }
780 });
781 if let Some(val) = autoloaded_val {
782 let mut evaled = Vec::with_capacity(args.len());
783 for a in args {
784 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
785 }
786 return match io.as_deref_mut() {
787 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
788 Some(hook) => hook(name, &val, &evaled, env, io_ref),
789 None => Err(format!("'{name}': exec::init() not called")),
790 }),
791 None => {
792 let mut tmp_io = IoContext::new();
793 FN_CALL_HOOK.with(|c| match c.get() {
794 Some(hook) => hook(name, &val, &evaled, env, &mut tmp_io),
795 None => Err(format!("'{name}': exec::init() not called")),
796 })
797 }
798 };
799 }
800
801 let mut evaled = Vec::with_capacity(args.len().max(1));
803 for a in args {
804 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
805 }
806 let no_ans_inject = matches!(
809 name.as_str(),
810 "struct"
811 | "fieldnames"
812 | "isfield"
813 | "rmfield"
814 | "isstruct"
815 | "cell"
816 | "iscell"
817 | "isempty"
818 | "cellfun"
819 | "error"
820 | "warning"
821 | "lasterr"
822 | "pcall"
823 | "rand"
824 | "randn"
825 | "rng"
826 | "tic"
827 | "toc"
828 );
829 if evaled.is_empty() && !no_ans_inject {
830 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
831 }
832 call_builtin(name, &evaled, env, io)
833 }
834
835 Expr::Lambda {
836 params,
837 body,
838 source,
839 } => {
840 let captured_env = env.clone();
843 let captured_params = params.clone();
844 let captured_body = *body.clone();
845 let src = source.clone();
846 let lambda = LambdaFn(
847 std::rc::Rc::new(move |args: &[Value], io: Option<&mut IoContext>| {
848 let effective = if args.len() > captured_params.len() {
850 if args.len() > captured_params.len() + 1 {
851 return Err(format!(
852 "Lambda: too many arguments (expected at most {}, got {})",
853 captured_params.len(),
854 args.len()
855 ));
856 }
857 &args[..captured_params.len()]
858 } else {
859 args
860 };
861 let mut local_env = captured_env.clone();
862 for (p, a) in captured_params.iter().zip(effective.iter()) {
863 local_env.insert(p.clone(), a.clone());
864 }
865 local_env.insert("nargin".to_string(), Value::Scalar(effective.len() as f64));
866 eval_inner(&captured_body, &local_env, io)
867 }),
868 src,
869 );
870 Ok(Value::Lambda(Box::new(lambda)))
871 }
872 Expr::CellLiteral(elems) => {
873 let mut vals = Vec::with_capacity(elems.len());
874 for e in elems {
875 vals.push(eval_inner(e, env, io.as_deref_mut())?);
876 }
877 Ok(Value::Cell(Box::new(vals)))
878 }
879 Expr::CellIndex(cell_expr, idx_expr) => {
880 let cell = eval_inner(cell_expr, env, io.as_deref_mut())?;
881 let idx = eval_inner(idx_expr, env, io)?;
882 match (cell, idx) {
883 (Value::Cell(v), Value::Scalar(i)) => {
884 let i = i as isize;
885 if i < 1 || i as usize > v.len() {
886 Err(format!("Cell index {} out of range (1..{})", i, v.len()))
887 } else {
888 Ok(v[(i - 1) as usize].clone())
889 }
890 }
891 (Value::Cell(_), _) => Err("Cell index must be a scalar integer".to_string()),
892 _ => Err("Brace indexing '{}' is only valid on cell arrays".to_string()),
893 }
894 }
895 Expr::DynFieldGet(base_expr, field_expr) => {
896 let base_val = eval_inner(base_expr, env, io.as_deref_mut())?;
897 let field_val = eval_inner(field_expr, env, io)?;
898 let field = match &field_val {
899 Value::Str(s) | Value::StringObj(s) => s.clone(),
900 _ => return Err("Dynamic field name must be a string".to_string()),
901 };
902 match base_val {
903 Value::Struct(map) => map
904 .get(&field)
905 .cloned()
906 .ok_or_else(|| format!("No field '{field}' in struct")),
907 _ => Err(format!(
908 "Cannot access field '{field}' on a non-struct value"
909 )),
910 }
911 }
912 Expr::FieldGet(base_expr, field) => {
913 let base_val = eval_inner(base_expr, env, io)?;
914 match base_val {
915 Value::Map(ref map) if field == "Count" => Ok(Value::Scalar(map.len() as f64)),
916 Value::Map(_) => Err(format!(
917 "Map has no property '{field}'; use 'Count', isKey(), keys(), values()"
918 )),
919 Value::Struct(map) => map
920 .get(field)
921 .cloned()
922 .ok_or_else(|| format!("No field '{field}' in struct")),
923 Value::StructArray(arr) => {
925 let mut values: Vec<Value> = Vec::with_capacity(arr.len());
926 for (idx, elem) in arr.iter().enumerate() {
927 let v = elem.get(field).cloned().ok_or_else(|| {
928 format!("No field '{field}' in struct array element {}", idx + 1)
929 })?;
930 values.push(v);
931 }
932 let all_scalar = values.iter().all(|v| matches!(v, Value::Scalar(_)));
934 if all_scalar {
935 let nums: Vec<f64> = values
936 .into_iter()
937 .map(|v| {
938 if let Value::Scalar(n) = v {
939 n
940 } else {
941 unreachable!()
942 }
943 })
944 .collect();
945 let n = nums.len();
946 Ok(Value::Matrix(Box::new(
947 Array2::from_shape_vec((1, n), nums).unwrap(),
948 )))
949 } else {
950 Ok(Value::Cell(Box::new(values)))
951 }
952 }
953 _ => Err(format!(
954 "Cannot access field '{field}' on a non-struct value"
955 )),
956 }
957 }
958 Expr::DotCall(segs, args) => {
959 let qualified = segs.join(".");
960 if segs == &["containers", "Map"] {
962 return make_containers_map(args, env, io);
963 }
964 if let Some(head_val) = env.get(&segs[0]).cloned() {
966 let mut val = head_val;
967 for field in &segs[1..] {
968 val = match val {
969 Value::Struct(ref map) => map
970 .get(field)
971 .cloned()
972 .ok_or_else(|| format!("No field '{field}' in struct"))?,
973 _ => {
974 return Err(format!(
975 "Cannot access field '{field}' on a non-struct value"
976 ));
977 }
978 };
979 }
980 let mut evaled = Vec::with_capacity(args.len());
981 for a in args {
982 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
983 }
984 return match val {
985 Value::Lambda(f) => {
986 if evaled.is_empty() {
987 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
988 }
989 f.0(&evaled, io)
990 }
991 Value::Function(_) => match io.as_deref_mut() {
992 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
993 Some(hook) => hook(&qualified, &val, &evaled, env, io_ref),
994 None => Err(format!("'{qualified}': exec::init() not called")),
995 }),
996 None => {
997 let mut tmp_io = IoContext::new();
998 FN_CALL_HOOK.with(|c| match c.get() {
999 Some(hook) => hook(&qualified, &val, &evaled, env, &mut tmp_io),
1000 None => Err(format!("'{qualified}': exec::init() not called")),
1001 })
1002 }
1003 },
1004 _ => Err(format!("'{qualified}': not a callable")),
1005 };
1006 }
1007 let cached = AUTOLOAD_CACHE.with(|c| c.borrow().get(&qualified).cloned());
1009 let autoloaded_val = cached.or_else(|| {
1010 let loaded = AUTOLOAD_HOOK
1011 .with(|c| c.get())
1012 .is_some_and(|hook| hook(&qualified));
1013 if loaded {
1014 AUTOLOAD_CACHE.with(|c| c.borrow().get(&qualified).cloned())
1015 } else {
1016 None
1017 }
1018 });
1019 if let Some(val) = autoloaded_val {
1020 let mut evaled = Vec::with_capacity(args.len());
1021 for a in args {
1022 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
1023 }
1024 return match io.as_deref_mut() {
1025 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
1026 Some(hook) => hook(&qualified, &val, &evaled, env, io_ref),
1027 None => Err(format!("'{qualified}': exec::init() not called")),
1028 }),
1029 None => {
1030 let mut tmp_io = IoContext::new();
1031 FN_CALL_HOOK.with(|c| match c.get() {
1032 Some(hook) => hook(&qualified, &val, &evaled, env, &mut tmp_io),
1033 None => Err(format!("'{qualified}': exec::init() not called")),
1034 })
1035 }
1036 };
1037 }
1038 Err(format!("Unknown package function: '{qualified}'"))
1039 }
1040 Expr::FuncHandle(name) => {
1041 let name = name.clone();
1042 let captured_env = env.clone();
1043 let src = format!("@{name}");
1044 let lambda = LambdaFn(
1045 std::rc::Rc::new(move |args: &[Value], io: Option<&mut IoContext>| {
1046 if let Some(f) = captured_env.get(&name) {
1048 let f = f.clone();
1049 call_function_value(&f, args, io)
1050 } else {
1051 call_builtin(&name, args, &captured_env, io)
1052 }
1053 }),
1054 src,
1055 );
1056 Ok(Value::Lambda(Box::new(lambda)))
1057 }
1058 Expr::PlainTranspose(e) => match eval_inner(e, env, io)? {
1059 Value::Void => Err("Transpose is not applicable to void".to_string()),
1060 Value::Scalar(n) => Ok(Value::Scalar(n)),
1061 Value::Matrix(m) => Ok(Value::Matrix(Box::new(m.t().to_owned()))),
1062 Value::Complex(re, im) => Ok(Value::Complex(re, im)),
1064 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(Box::new(m.t().to_owned()))),
1066 Value::Str(s) => Ok(Value::Str(s)),
1067 Value::StringObj(s) => Ok(Value::StringObj(s)),
1068 v @ (Value::DateTimeArray(_) | Value::DurationArray(_)) => Ok(v),
1070 Value::Lambda(_)
1071 | Value::Function(_)
1072 | Value::Tuple(_)
1073 | Value::Cell(_)
1074 | Value::Struct(_)
1075 | Value::StructArray(_)
1076 | Value::DateTime(_)
1077 | Value::Duration(_)
1078 | Value::Map(_) => Err("Transpose is not applicable to this type".to_string()),
1079 },
1080 Expr::Colon => Err("':' is only valid inside index expressions".to_string()),
1081 Expr::NaT => Ok(Value::DateTime(f64::NAN)),
1082 Expr::Matrix(rows) => {
1083 if rows.is_empty() {
1084 return Ok(Value::Matrix(Box::new(Array2::<f64>::zeros((0, 0)))));
1085 }
1086
1087 let mut evaluated: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
1089 for row in rows {
1090 if row.is_empty() {
1091 continue;
1092 }
1093 let mut ev_row: Vec<Value> = Vec::with_capacity(row.len());
1094 for elem_expr in row {
1095 ev_row.push(eval_inner(elem_expr, env, io.as_deref_mut())?);
1096 }
1097 evaluated.push(ev_row);
1098 }
1099 if evaluated.is_empty() {
1100 return Ok(Value::Matrix(Box::new(Array2::<f64>::zeros((0, 0)))));
1101 }
1102
1103 let has_complex = evaluated
1105 .iter()
1106 .flat_map(|row| row.iter())
1107 .any(|v| matches!(v, Value::Complex(_, _) | Value::ComplexMatrix(_)));
1108
1109 enum MatKind {
1111 ComplexNumeric,
1112 Numeric,
1113 DateTime,
1114 Duration,
1115 Str,
1116 }
1117 let kind = if has_complex {
1118 MatKind::ComplexNumeric
1119 } else {
1120 match &evaluated[0][0] {
1121 Value::Scalar(_) | Value::Matrix(_) => MatKind::Numeric,
1122 Value::DateTime(_) | Value::DateTimeArray(_) => MatKind::DateTime,
1123 Value::Duration(_) | Value::DurationArray(_) => MatKind::Duration,
1124 Value::Str(_) | Value::StringObj(_) => MatKind::Str,
1125 Value::Void => {
1126 return Err("Void value cannot be used in matrix literal".to_string());
1127 }
1128 Value::Lambda(_)
1129 | Value::Function(_)
1130 | Value::Tuple(_)
1131 | Value::Cell(_)
1132 | Value::Struct(_)
1133 | Value::StructArray(_)
1134 | Value::Map(_) => {
1135 return Err("This type cannot be used in matrix literals".to_string());
1136 }
1137 Value::Complex(_, _) | Value::ComplexMatrix(_) => unreachable!(),
1139 }
1140 };
1141
1142 match kind {
1143 MatKind::ComplexNumeric => {
1144 let mut row_blocks: Vec<Array2<Complex<f64>>> =
1147 Vec::with_capacity(evaluated.len());
1148 for ev_row in &evaluated {
1149 let mut elem_mats: Vec<Array2<Complex<f64>>> =
1150 Vec::with_capacity(ev_row.len());
1151 for val in ev_row {
1152 let block: Array2<Complex<f64>> = match val {
1153 Value::Scalar(n) => {
1154 Array2::from_elem((1, 1), Complex::new(*n, 0.0))
1155 }
1156 Value::Complex(re, im) => {
1157 Array2::from_elem((1, 1), Complex::new(*re, *im))
1158 }
1159 Value::Matrix(m) => cm_from_real(m),
1160 Value::ComplexMatrix(m) => (**m).clone(),
1161 _ => {
1162 return Err(
1163 "This type cannot be used in a complex matrix literal"
1164 .to_string(),
1165 );
1166 }
1167 };
1168 elem_mats.push(block);
1169 }
1170 let nrows = elem_mats[0].nrows();
1171 for (i, m) in elem_mats.iter().enumerate().skip(1) {
1172 if m.nrows() != nrows {
1173 return Err(format!(
1174 "Matrix row height mismatch: expected {} rows, element {} has {} rows",
1175 nrows,
1176 i + 1,
1177 m.nrows()
1178 ));
1179 }
1180 }
1181 let ncols: usize = elem_mats.iter().map(|m| m.ncols()).sum();
1182 let mut flat: Vec<Complex<f64>> = Vec::with_capacity(nrows * ncols);
1183 for r in 0..nrows {
1184 for m in &elem_mats {
1185 flat.extend(m.row(r).iter().copied());
1186 }
1187 }
1188 row_blocks.push(
1189 Array2::from_shape_vec((nrows, ncols), flat)
1190 .map_err(|e| format!("Matrix shape error: {e}"))?,
1191 );
1192 }
1193 if row_blocks.is_empty() {
1194 return Ok(Value::ComplexMatrix(Box::new(Array2::zeros((0, 0)))));
1195 }
1196 let ncols = row_blocks[0].ncols();
1197 for (i, blk) in row_blocks.iter().enumerate().skip(1) {
1198 if blk.ncols() != ncols {
1199 return Err(format!(
1200 "Matrix column count mismatch: expected {} columns, row {} has {} columns",
1201 ncols,
1202 i + 1,
1203 blk.ncols()
1204 ));
1205 }
1206 }
1207 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1208 let mut flat: Vec<Complex<f64>> = Vec::with_capacity(total_rows * ncols);
1209 for blk in &row_blocks {
1210 flat.extend(blk.iter().copied());
1211 }
1212 let m = Array2::from_shape_vec((total_rows, ncols), flat)
1213 .map_err(|e| format!("Matrix shape error: {e}"))?;
1214 Ok(Value::ComplexMatrix(Box::new(m)))
1215 }
1216 MatKind::DateTime => {
1217 let mut ts: Vec<f64> = Vec::new();
1218 for ev_row in &evaluated {
1219 for val in ev_row {
1220 match val {
1221 Value::DateTime(t) => ts.push(*t),
1222 Value::DateTimeArray(v) => ts.extend_from_slice(v),
1223 _ => {
1224 return Err(
1225 "Matrix literal: cannot mix datetime with other types"
1226 .to_string(),
1227 );
1228 }
1229 }
1230 }
1231 }
1232 Ok(Value::DateTimeArray(ts))
1233 }
1234 MatKind::Duration => {
1235 let mut sv: Vec<f64> = Vec::new();
1236 for ev_row in &evaluated {
1237 for val in ev_row {
1238 match val {
1239 Value::Duration(s) => sv.push(*s),
1240 Value::DurationArray(v) => sv.extend_from_slice(v),
1241 _ => {
1242 return Err(
1243 "Matrix literal: cannot mix duration with other types"
1244 .to_string(),
1245 );
1246 }
1247 }
1248 }
1249 }
1250 Ok(Value::DurationArray(sv))
1251 }
1252 MatKind::Numeric => {
1253 let mut row_blocks: Vec<Array2<f64>> = Vec::with_capacity(evaluated.len());
1256 for ev_row in &evaluated {
1257 let mut elem_mats: Vec<Array2<f64>> = Vec::with_capacity(ev_row.len());
1258 for val in ev_row {
1259 match val {
1260 Value::Scalar(n) => {
1261 elem_mats.push(Array2::from_elem((1, 1), *n));
1262 }
1263 Value::Matrix(m) => elem_mats.push((**m).clone()),
1264 Value::Void => {
1265 return Err(
1266 "Void value cannot be used in matrix literal".to_string()
1267 );
1268 }
1269 Value::Str(s) | Value::StringObj(s) => {
1272 let codes: Vec<f64> =
1273 s.chars().map(|c| c as u32 as f64).collect();
1274 let mat = if codes.is_empty() {
1275 Array2::<f64>::zeros((1, 0))
1276 } else {
1277 Array2::from_shape_vec((1, codes.len()), codes)
1278 .map_err(|e| format!("Matrix shape error: {e}"))?
1279 };
1280 elem_mats.push(mat);
1281 }
1282 _ => {
1283 return Err(
1284 "This type cannot be used in matrix literals".to_string()
1285 );
1286 }
1287 }
1288 }
1289 let nrows = elem_mats[0].nrows();
1290 for (i, m) in elem_mats.iter().enumerate().skip(1) {
1291 if m.nrows() != nrows {
1292 return Err(format!(
1293 "Matrix row height mismatch: expected {} rows, element {} has {} rows",
1294 nrows,
1295 i + 1,
1296 m.nrows()
1297 ));
1298 }
1299 }
1300 let ncols: usize = elem_mats.iter().map(|m| m.ncols()).sum();
1301 let mut flat: Vec<f64> = Vec::with_capacity(nrows * ncols);
1302 for r in 0..nrows {
1303 for m in &elem_mats {
1304 flat.extend(m.row(r).iter().copied());
1305 }
1306 }
1307 row_blocks.push(
1308 Array2::from_shape_vec((nrows, ncols), flat)
1309 .map_err(|e| format!("Matrix shape error: {e}"))?,
1310 );
1311 }
1312 if row_blocks.is_empty() {
1313 return Ok(Value::Matrix(Box::new(Array2::<f64>::zeros((0, 0)))));
1314 }
1315 let ncols = row_blocks[0].ncols();
1316 if ncols == 0 {
1317 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1318 return Ok(Value::Matrix(Box::new(Array2::zeros((total_rows, 0)))));
1319 }
1320 for (i, blk) in row_blocks.iter().enumerate().skip(1) {
1321 if blk.ncols() != ncols {
1322 return Err(format!(
1323 "Matrix column count mismatch: expected {} columns, row {} has {} columns",
1324 ncols,
1325 i + 1,
1326 blk.ncols()
1327 ));
1328 }
1329 }
1330 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1331 let mut flat: Vec<f64> = Vec::with_capacity(total_rows * ncols);
1332 for blk in &row_blocks {
1333 flat.extend(blk.iter().copied());
1334 }
1335 let m = Array2::from_shape_vec((total_rows, ncols), flat)
1336 .map_err(|e| format!("Matrix shape error: {e}"))?;
1337 Ok(Value::Matrix(Box::new(m)))
1338 }
1339 MatKind::Str => {
1340 if evaluated.len() > 1 {
1341 return Err("Multi-row char-array literals are not supported".to_string());
1342 }
1343 let mut out = String::new();
1344 for val in &evaluated[0] {
1345 match val {
1346 Value::Str(s) | Value::StringObj(s) => out.push_str(s),
1347 Value::Scalar(n) => {
1348 let code = n.round();
1349 out.push(
1350 char::from_u32(code as u32)
1351 .ok_or_else(|| format!("char: invalid code {n}"))?,
1352 );
1353 }
1354 Value::Matrix(m) => {
1355 for &n in m.iter() {
1356 out.push(
1357 char::from_u32(n.round() as u32)
1358 .ok_or_else(|| format!("char: invalid code {n}"))?,
1359 );
1360 }
1361 }
1362 _ => {
1363 return Err(
1364 "This type cannot be used in a char-array literal".to_string()
1365 );
1366 }
1367 }
1368 }
1369 Ok(Value::Str(out))
1370 }
1371 }
1372 }
1373 Expr::Transpose(e) => match eval_inner(e, env, io)? {
1374 Value::Void => Err("Transpose is not applicable to void".to_string()),
1375 Value::Scalar(n) => Ok(Value::Scalar(n)),
1376 Value::Matrix(m) => Ok(Value::Matrix(Box::new(m.t().to_owned()))),
1377 Value::Complex(re, im) => Ok(Value::Complex(re, -im)),
1378 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(Box::new(m.t().mapv(|c| c.conj())))),
1380 Value::Str(s) => Ok(Value::Str(s)),
1382 Value::StringObj(s) => Ok(Value::StringObj(s)),
1383 v @ (Value::DateTimeArray(_) | Value::DurationArray(_)) => Ok(v),
1385 Value::Lambda(_)
1386 | Value::Function(_)
1387 | Value::Tuple(_)
1388 | Value::Cell(_)
1389 | Value::Struct(_)
1390 | Value::StructArray(_)
1391 | Value::DateTime(_)
1392 | Value::Duration(_)
1393 | Value::Map(_) => Err("Transpose is not applicable to this type".to_string()),
1394 },
1395 Expr::StrLiteral(s) => Ok(Value::Str(s.clone())),
1396 Expr::StringObjLiteral(s) => Ok(Value::StringObj(s.clone())),
1397 Expr::Range(start_expr, step_expr, stop_expr) => {
1398 let start = match eval_inner(start_expr, env, io.as_deref_mut())? {
1399 Value::Scalar(n) => n,
1400 _ => return Err("Range bounds must be real scalars".to_string()),
1401 };
1402 let stop = match eval_inner(stop_expr, env, io.as_deref_mut())? {
1403 Value::Scalar(n) => n,
1404 _ => return Err("Range bounds must be real scalars".to_string()),
1405 };
1406 let step = match step_expr {
1407 None => 1.0,
1408 Some(s) => match eval_inner(s, env, io)? {
1409 Value::Scalar(n) => n,
1410 _ => return Err("Range step must be a real scalar".to_string()),
1411 },
1412 };
1413 if step == 0.0 {
1414 return Err("Range step cannot be zero".to_string());
1415 }
1416 let n_float = (stop - start) / step;
1417 if n_float < -1e-10 {
1418 return Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))));
1420 }
1421 let n = (n_float + 1e-10).floor() as usize + 1;
1422 let vals: Vec<f64> = (0..n).map(|i| start + i as f64 * step).collect();
1423 let m =
1424 Array2::from_shape_vec((1, n), vals).map_err(|e| format!("Range error: {e}"))?;
1425 Ok(Value::Matrix(Box::new(m)))
1426 }
1427 }
1428}
1429
1430fn eval_binop(l: Value, op: &Op, r: Value) -> Result<Value, String> {
1431 match (l, r) {
1432 (Value::Void, _) | (_, Value::Void) => {
1433 Err("Cannot apply operator to void value".to_string())
1434 }
1435 (Value::StringObj(a), Value::StringObj(b)) => match op {
1437 Op::Add => Ok(Value::StringObj(a + &b)),
1438 Op::Eq => Ok(Value::Scalar(bool_to_f64(a == b))),
1439 Op::NotEq => Ok(Value::Scalar(bool_to_f64(a != b))),
1440 _ => Err("Operator not supported on string objects".to_string()),
1441 },
1442 (Value::Str(s), r) => eval_binop(str_to_numeric(&s), op, r),
1444 (l, Value::Str(s)) => eval_binop(l, op, str_to_numeric(&s)),
1445 (Value::StringObj(_), _) | (_, Value::StringObj(_)) => {
1447 Err("String object cannot be combined with non-string values".to_string())
1448 }
1449 (Value::Lambda(_), _)
1451 | (_, Value::Lambda(_))
1452 | (Value::Function(_), _)
1453 | (_, Value::Function(_))
1454 | (Value::Tuple(_), _)
1455 | (_, Value::Tuple(_))
1456 | (Value::Cell(_), _)
1457 | (_, Value::Cell(_))
1458 | (Value::Struct(_), _)
1459 | (_, Value::Struct(_))
1460 | (Value::StructArray(_), _)
1461 | (_, Value::StructArray(_))
1462 | (Value::Map(_), _)
1463 | (_, Value::Map(_)) => Err("Cannot apply operator to a Map value".to_string()),
1464 (Value::DateTime(t), Value::Duration(d)) => match op {
1467 Op::Add => Ok(Value::DateTime(t + d)),
1468 Op::Sub => Ok(Value::DateTime(t - d)),
1469 _ => Err("Unsupported operator between datetime and duration".to_string()),
1470 },
1471 (Value::Duration(d), Value::DateTime(t)) => match op {
1473 Op::Add => Ok(Value::DateTime(t + d)),
1474 _ => Err("Unsupported operator between duration and datetime".to_string()),
1475 },
1476 (Value::DateTime(t1), Value::DateTime(t2)) => match op {
1478 Op::Sub => Ok(Value::Duration(t1 - t2)),
1479 Op::Eq => Ok(Value::Scalar(bool_to_f64(
1480 (t1 - t2).abs() < 1e-9 || (t1.is_nan() && t2.is_nan()),
1481 ))),
1482 Op::NotEq => Ok(Value::Scalar(bool_to_f64(
1483 (t1 - t2).abs() >= 1e-9 && !(t1.is_nan() && t2.is_nan()),
1484 ))),
1485 Op::Lt => Ok(Value::Scalar(bool_to_f64(t1 < t2))),
1486 Op::Gt => Ok(Value::Scalar(bool_to_f64(t1 > t2))),
1487 Op::LtEq => Ok(Value::Scalar(bool_to_f64(t1 <= t2))),
1488 Op::GtEq => Ok(Value::Scalar(bool_to_f64(t1 >= t2))),
1489 _ => Err("Unsupported operator between two datetimes".to_string()),
1490 },
1491 (Value::Duration(d1), Value::Duration(d2)) => match op {
1493 Op::Add => Ok(Value::Duration(d1 + d2)),
1494 Op::Sub => Ok(Value::Duration(d1 - d2)),
1495 Op::Div | Op::ElemDiv => Ok(Value::Scalar(d1 / d2)),
1496 Op::Eq => Ok(Value::Scalar(bool_to_f64((d1 - d2).abs() < 1e-9))),
1497 Op::NotEq => Ok(Value::Scalar(bool_to_f64((d1 - d2).abs() >= 1e-9))),
1498 Op::Lt => Ok(Value::Scalar(bool_to_f64(d1 < d2))),
1499 Op::Gt => Ok(Value::Scalar(bool_to_f64(d1 > d2))),
1500 Op::LtEq => Ok(Value::Scalar(bool_to_f64(d1 <= d2))),
1501 Op::GtEq => Ok(Value::Scalar(bool_to_f64(d1 >= d2))),
1502 _ => Err("Unsupported operator between two durations".to_string()),
1503 },
1504 (Value::Duration(d), Value::Scalar(s)) => match op {
1505 Op::Mul | Op::ElemMul => Ok(Value::Duration(d * s)),
1506 Op::Div | Op::ElemDiv => Ok(Value::Duration(d / s)),
1507 _ => Err("Unsupported operator between duration and scalar".to_string()),
1508 },
1509 (Value::Scalar(s), Value::Duration(d)) => match op {
1510 Op::Mul | Op::ElemMul => Ok(Value::Duration(s * d)),
1511 _ => Err("Unsupported operator between scalar and duration".to_string()),
1512 },
1513 (Value::DateTime(t), Value::DurationArray(dv)) => match op {
1515 Op::Add => Ok(Value::DateTimeArray(dv.iter().map(|d| t + d).collect())),
1516 Op::Sub => Ok(Value::DateTimeArray(dv.iter().map(|d| t - d).collect())),
1517 _ => Err("Unsupported operator between datetime and duration array".to_string()),
1518 },
1519 (Value::DurationArray(dv), Value::DateTime(t)) => match op {
1520 Op::Add => Ok(Value::DateTimeArray(dv.iter().map(|d| t + d).collect())),
1521 _ => Err("Unsupported operator between duration array and datetime".to_string()),
1522 },
1523 (Value::DateTimeArray(tv), Value::Duration(d)) => match op {
1524 Op::Add => Ok(Value::DateTimeArray(tv.iter().map(|t| t + d).collect())),
1525 Op::Sub => Ok(Value::DateTimeArray(tv.iter().map(|t| t - d).collect())),
1526 _ => Err("Unsupported operator between datetime array and duration".to_string()),
1527 },
1528 (Value::DateTimeArray(tv), Value::DurationArray(dv)) => match op {
1529 Op::Add if tv.len() == dv.len() => Ok(Value::DateTimeArray(
1530 tv.iter().zip(&dv).map(|(t, d)| t + d).collect(),
1531 )),
1532 Op::Sub if tv.len() == dv.len() => Ok(Value::DateTimeArray(
1533 tv.iter().zip(&dv).map(|(t, d)| t - d).collect(),
1534 )),
1535 _ => Err("Unsupported or mismatched datetime/duration array operation".to_string()),
1536 },
1537 (Value::DateTimeArray(tv1), Value::DateTimeArray(tv2)) => match op {
1538 Op::Sub if tv1.len() == tv2.len() => Ok(Value::DurationArray(
1539 tv1.iter().zip(&tv2).map(|(a, b)| a - b).collect(),
1540 )),
1541 _ => Err("Unsupported operator between two datetime arrays".to_string()),
1542 },
1543 (Value::DurationArray(dv), Value::Scalar(s)) => match op {
1544 Op::Mul | Op::ElemMul => Ok(Value::DurationArray(dv.iter().map(|d| d * s).collect())),
1545 Op::Div | Op::ElemDiv => Ok(Value::DurationArray(dv.iter().map(|d| d / s).collect())),
1546 _ => Err("Unsupported operator between duration array and scalar".to_string()),
1547 },
1548 (Value::Scalar(s), Value::DurationArray(dv)) => match op {
1549 Op::Mul | Op::ElemMul => Ok(Value::DurationArray(dv.iter().map(|d| s * d).collect())),
1550 _ => Err("Unsupported operator between scalar and duration array".to_string()),
1551 },
1552 (Value::DateTime(_), _)
1554 | (_, Value::DateTime(_))
1555 | (Value::Duration(_), _)
1556 | (_, Value::Duration(_))
1557 | (Value::DateTimeArray(_), _)
1558 | (_, Value::DateTimeArray(_))
1559 | (Value::DurationArray(_), _)
1560 | (_, Value::DurationArray(_)) => {
1561 Err("Unsupported operation on datetime or duration value".to_string())
1562 }
1563 (Value::Complex(re1, im1), Value::Complex(re2, im2)) => {
1565 complex_binop(re1, im1, op, re2, im2)
1566 }
1567 (Value::Complex(re, im), Value::Scalar(s)) => complex_binop(re, im, op, s, 0.0),
1568 (Value::Scalar(s), Value::Complex(re, im)) => complex_binop(s, 0.0, op, re, im),
1569 (Value::Complex(re, im), Value::Matrix(m)) => {
1571 complex_binop_cm(re, im, op, cm_from_real(&m))
1572 }
1573 (Value::Matrix(m), Value::Complex(re, im)) => {
1574 cm_binop_complex(cm_from_real(&m), op, re, im)
1575 }
1576 (Value::ComplexMatrix(a), Value::ComplexMatrix(b)) => complex_matrix_binop(*a, op, *b),
1578 (Value::ComplexMatrix(cm), Value::Matrix(m)) => {
1579 complex_matrix_binop(*cm, op, cm_from_real(&m))
1580 }
1581 (Value::Matrix(m), Value::ComplexMatrix(cm)) => {
1582 complex_matrix_binop(cm_from_real(&m), op, *cm)
1583 }
1584 (Value::ComplexMatrix(cm), Value::Scalar(s)) => cm_binop_scalar(*cm, op, s),
1585 (Value::Scalar(s), Value::ComplexMatrix(cm)) => scalar_binop_cm(s, op, *cm),
1586 (Value::ComplexMatrix(cm), Value::Complex(re, im)) => cm_binop_complex(*cm, op, re, im),
1587 (Value::Complex(re, im), Value::ComplexMatrix(cm)) => complex_binop_cm(re, im, op, *cm),
1588 (Value::Scalar(lv), Value::Scalar(rv)) => {
1589 let result = match op {
1590 Op::Add => lv + rv,
1591 Op::Sub => lv - rv,
1592 Op::Mul | Op::ElemMul => lv * rv,
1593 Op::Div | Op::ElemDiv => lv / rv,
1594 Op::LDiv => rv / lv,
1595 Op::Pow | Op::ElemPow => lv.powf(rv),
1596 Op::Eq => bool_to_f64(lv == rv),
1597 Op::NotEq => bool_to_f64(lv != rv),
1598 Op::Lt => bool_to_f64(lv < rv),
1599 Op::Gt => bool_to_f64(lv > rv),
1600 Op::LtEq => bool_to_f64(lv <= rv),
1601 Op::GtEq => bool_to_f64(lv >= rv),
1602 Op::And | Op::ElemAnd => bool_to_f64(lv != 0.0 && rv != 0.0),
1603 Op::Or | Op::ElemOr => bool_to_f64(lv != 0.0 || rv != 0.0),
1604 };
1605 Ok(Value::Scalar(result))
1606 }
1607 (Value::Matrix(lm), Value::Matrix(rm)) => match op {
1608 Op::Add => {
1609 check_same_shape(&lm, &rm)?;
1610 Ok(Value::Matrix(Box::new(&*lm + &*rm)))
1611 }
1612 Op::Sub => {
1613 check_same_shape(&lm, &rm)?;
1614 Ok(Value::Matrix(Box::new(&*lm - &*rm)))
1615 }
1616 Op::Mul => {
1617 if lm.ncols() != rm.nrows() {
1618 return Err(format!(
1619 "Inner dimensions must agree: {}x{} * {}x{}",
1620 lm.nrows(),
1621 lm.ncols(),
1622 rm.nrows(),
1623 rm.ncols()
1624 ));
1625 }
1626 Ok(Value::Matrix(Box::new(lm.dot(&*rm))))
1627 }
1628 Op::ElemMul => {
1629 check_same_shape(&lm, &rm)?;
1630 Ok(Value::Matrix(Box::new(&*lm * &*rm)))
1631 }
1632 Op::ElemDiv => {
1633 check_same_shape(&lm, &rm)?;
1634 Ok(Value::Matrix(Box::new(&*lm / &*rm)))
1635 }
1636 Op::ElemPow => {
1637 check_same_shape(&lm, &rm)?;
1638 Ok(Value::Matrix(Box::new(
1639 ndarray::Zip::from(&*lm)
1640 .and(&*rm)
1641 .map_collect(|a, b| a.powf(*b)),
1642 )))
1643 }
1644 Op::Eq | Op::NotEq | Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1645 check_same_shape(&lm, &rm)?;
1646 Ok(Value::Matrix(Box::new(
1647 ndarray::Zip::from(&*lm)
1648 .and(&*rm)
1649 .map_collect(|a, b| bool_to_f64(cmp_op(op, *a, *b))),
1650 )))
1651 }
1652 Op::And | Op::Or | Op::ElemAnd | Op::ElemOr => {
1653 check_same_shape(&lm, &rm)?;
1654 Ok(Value::Matrix(Box::new(
1655 ndarray::Zip::from(&*lm)
1656 .and(&*rm)
1657 .map_collect(|a, b| bool_to_f64(cmp_op(op, *a, *b))),
1658 )))
1659 }
1660 Op::Div => Err("Matrix / Matrix: use inv(B)*A or A*inv(B)".to_string()),
1661 Op::LDiv => Ok(Value::Matrix(Box::new(solve_linear(&lm, &rm)?))),
1662 Op::Pow => Err("Matrix ^ Matrix: not supported".to_string()),
1663 },
1664 (Value::Scalar(s), Value::Matrix(m)) => match op {
1665 Op::Add => Ok(Value::Matrix(Box::new(s + &*m))),
1666 Op::Sub => Ok(Value::Matrix(Box::new(m.mapv(|x| s - x)))),
1667 Op::Mul | Op::ElemMul => Ok(Value::Matrix(Box::new(s * &*m))),
1668 Op::Div => Err("Scalar / Matrix: not supported".to_string()),
1669 Op::ElemDiv => Err("Scalar ./ Matrix: not supported".to_string()),
1670 Op::LDiv => {
1671 if s == 0.0 {
1672 return Err("Left division by zero (a \\ B requires a ≠ 0)".to_string());
1673 }
1674 Ok(Value::Matrix(Box::new(m.mapv(|x| x / s))))
1675 }
1676 Op::Pow | Op::ElemPow => Ok(Value::Matrix(Box::new(m.mapv(|x| s.powf(x))))),
1677 Op::Eq
1678 | Op::NotEq
1679 | Op::Lt
1680 | Op::Gt
1681 | Op::LtEq
1682 | Op::GtEq
1683 | Op::And
1684 | Op::Or
1685 | Op::ElemAnd
1686 | Op::ElemOr => Ok(Value::Matrix(Box::new(
1687 m.mapv(|x| bool_to_f64(cmp_op(op, s, x))),
1688 ))),
1689 },
1690 (Value::Matrix(m), Value::Scalar(s)) => match op {
1691 Op::Add => Ok(Value::Matrix(Box::new(&*m + s))),
1692 Op::Sub => Ok(Value::Matrix(Box::new(&*m - s))),
1693 Op::Mul | Op::ElemMul => Ok(Value::Matrix(Box::new(&*m * s))),
1694 Op::Div | Op::ElemDiv => Ok(Value::Matrix(Box::new(m.mapv(|x| x / s)))),
1695 Op::LDiv => {
1696 let b = Array2::from_elem((m.nrows(), 1), s);
1697 Ok(Value::Matrix(Box::new(solve_linear(&m, &b)?)))
1698 }
1699 Op::Pow | Op::ElemPow => Ok(Value::Matrix(Box::new(m.mapv(|x| x.powf(s))))),
1700 Op::Eq
1701 | Op::NotEq
1702 | Op::Lt
1703 | Op::Gt
1704 | Op::LtEq
1705 | Op::GtEq
1706 | Op::And
1707 | Op::Or
1708 | Op::ElemAnd
1709 | Op::ElemOr => Ok(Value::Matrix(Box::new(
1710 m.mapv(|x| bool_to_f64(cmp_op(op, x, s))),
1711 ))),
1712 },
1713 }
1714}
1715
1716#[inline]
1717fn bool_to_f64(b: bool) -> f64 {
1718 if b { 1.0 } else { 0.0 }
1719}
1720
1721fn cmp_op(op: &Op, a: f64, b: f64) -> bool {
1723 match op {
1724 Op::Eq => a == b,
1725 Op::NotEq => a != b,
1726 Op::Lt => a < b,
1727 Op::Gt => a > b,
1728 Op::LtEq => a <= b,
1729 Op::GtEq => a >= b,
1730 Op::And | Op::ElemAnd => a != 0.0 && b != 0.0,
1731 Op::Or | Op::ElemOr => a != 0.0 || b != 0.0,
1732 _ => unreachable!(),
1733 }
1734}
1735
1736fn complex_binop(re1: f64, im1: f64, op: &Op, re2: f64, im2: f64) -> Result<Value, String> {
1738 match op {
1739 Op::Add => Ok(make_complex(re1 + re2, im1 + im2)),
1740 Op::Sub => Ok(make_complex(re1 - re2, im1 - im2)),
1741 Op::Mul | Op::ElemMul => {
1742 Ok(make_complex(re1 * re2 - im1 * im2, re1 * im2 + im1 * re2))
1744 }
1745 Op::Div | Op::ElemDiv => {
1746 let denom = re2 * re2 + im2 * im2;
1749 if denom == 0.0 {
1750 return Ok(make_complex(re1 / 0.0_f64, im1 / 0.0_f64));
1751 }
1752 Ok(make_complex(
1753 (re1 * re2 + im1 * im2) / denom,
1754 (im1 * re2 - re1 * im2) / denom,
1755 ))
1756 }
1757 Op::Pow | Op::ElemPow => {
1758 let r1 = (re1 * re1 + im1 * im1).sqrt();
1759 if r1 == 0.0 {
1760 if re2 > 0.0 {
1761 return Ok(Value::Scalar(0.0));
1762 }
1763 return Ok(Value::Complex(f64::NAN, f64::NAN));
1764 }
1765 if im2 == 0.0 && re2.fract() == 0.0 && re2.abs() < 1_000_000.0 {
1768 let n = re2 as i64;
1769 if n == 0 {
1770 return Ok(Value::Scalar(1.0));
1771 }
1772 let abs_n = n.unsigned_abs();
1774 let (mut rr, mut ri) = (1.0_f64, 0.0_f64);
1775 let (mut br, mut bi) = (re1, im1);
1776 let mut exp = abs_n;
1777 while exp > 0 {
1778 if exp & 1 == 1 {
1779 let nr = rr * br - ri * bi;
1780 let ni = rr * bi + ri * br;
1781 rr = nr;
1782 ri = ni;
1783 }
1784 let nr = br * br - bi * bi;
1785 let ni = 2.0 * br * bi;
1786 br = nr;
1787 bi = ni;
1788 exp >>= 1;
1789 }
1790 if n < 0 {
1791 let denom = rr * rr + ri * ri;
1793 return Ok(make_complex(rr / denom, -ri / denom));
1794 }
1795 return Ok(make_complex(rr, ri));
1796 }
1797 let theta1 = im1.atan2(re1);
1799 let ln_r1 = r1.ln();
1800 let exp_re = re2 * ln_r1 - im2 * theta1;
1801 let exp_im = im2 * ln_r1 + re2 * theta1;
1802 let mag = exp_re.exp();
1803 Ok(make_complex(mag * exp_im.cos(), mag * exp_im.sin()))
1804 }
1805 Op::Eq => Ok(Value::Scalar(bool_to_f64(re1 == re2 && im1 == im2))),
1806 Op::NotEq => Ok(Value::Scalar(bool_to_f64(re1 != re2 || im1 != im2))),
1807 Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1808 Err("Ordering is not defined for complex numbers".to_string())
1809 }
1810 Op::And | Op::ElemAnd => Ok(Value::Scalar(bool_to_f64(
1811 (re1 != 0.0 || im1 != 0.0) && (re2 != 0.0 || im2 != 0.0),
1812 ))),
1813 Op::Or | Op::ElemOr => Ok(Value::Scalar(bool_to_f64(
1814 re1 != 0.0 || im1 != 0.0 || re2 != 0.0 || im2 != 0.0,
1815 ))),
1816 Op::LDiv => Err("Left division (\\) is not supported for complex numbers".to_string()),
1817 }
1818}
1819
1820#[inline]
1822fn make_complex(re: f64, im: f64) -> Value {
1823 if im == 0.0 {
1824 Value::Scalar(re)
1825 } else {
1826 Value::Complex(re, im)
1827 }
1828}
1829
1830#[inline]
1832fn cm_from_real(m: &Array2<f64>) -> Array2<Complex<f64>> {
1833 m.mapv(|x| Complex::new(x, 0.0))
1834}
1835
1836fn complex_matrix_binop(
1838 a: Array2<Complex<f64>>,
1839 op: &Op,
1840 b: Array2<Complex<f64>>,
1841) -> Result<Value, String> {
1842 let same_shape = || {
1843 if a.shape() != b.shape() {
1844 Err(format!(
1845 "Matrix dimensions must agree: {}×{} vs {}×{}",
1846 a.nrows(),
1847 a.ncols(),
1848 b.nrows(),
1849 b.ncols()
1850 ))
1851 } else {
1852 Ok(())
1853 }
1854 };
1855 match op {
1856 Op::Add => {
1857 same_shape()?;
1858 Ok(Value::ComplexMatrix(Box::new(a + b)))
1859 }
1860 Op::Sub => {
1861 same_shape()?;
1862 Ok(Value::ComplexMatrix(Box::new(a - b)))
1863 }
1864 Op::Mul => {
1865 if a.ncols() != b.nrows() {
1866 return Err(format!(
1867 "Inner dimensions must agree: {}×{} * {}×{}",
1868 a.nrows(),
1869 a.ncols(),
1870 b.nrows(),
1871 b.ncols()
1872 ));
1873 }
1874 Ok(Value::ComplexMatrix(Box::new(a.dot(&b))))
1875 }
1876 Op::ElemMul => {
1877 same_shape()?;
1878 Ok(Value::ComplexMatrix(Box::new(a * b)))
1879 }
1880 Op::ElemDiv => {
1881 same_shape()?;
1882 Ok(Value::ComplexMatrix(Box::new(a / b)))
1883 }
1884 Op::ElemPow => {
1885 same_shape()?;
1886 Ok(Value::ComplexMatrix(Box::new(
1887 ndarray::Zip::from(&a)
1888 .and(&b)
1889 .map_collect(|x, y| x.powc(*y)),
1890 )))
1891 }
1892 Op::Pow => Err(
1893 "ComplexMatrix ^ ComplexMatrix: not supported; use .^ for element-wise power"
1894 .to_string(),
1895 ),
1896 Op::Div | Op::LDiv => {
1897 Err("Complex matrix / and \\ not supported; use inv(A)*B".to_string())
1898 }
1899 Op::Eq => {
1900 same_shape()?;
1901 Ok(Value::Matrix(Box::new(
1902 ndarray::Zip::from(&a)
1903 .and(&b)
1904 .map_collect(|x, y| bool_to_f64(x == y)),
1905 )))
1906 }
1907 Op::NotEq => {
1908 same_shape()?;
1909 Ok(Value::Matrix(Box::new(
1910 ndarray::Zip::from(&a)
1911 .and(&b)
1912 .map_collect(|x, y| bool_to_f64(x != y)),
1913 )))
1914 }
1915 Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1916 Err("Ordering comparison not defined for complex matrices".to_string())
1917 }
1918 Op::And | Op::ElemAnd => {
1919 same_shape()?;
1920 Ok(Value::Matrix(Box::new(
1921 ndarray::Zip::from(&a).and(&b).map_collect(|x, y| {
1922 bool_to_f64((x.re != 0.0 || x.im != 0.0) && (y.re != 0.0 || y.im != 0.0))
1923 }),
1924 )))
1925 }
1926 Op::Or | Op::ElemOr => {
1927 same_shape()?;
1928 Ok(Value::Matrix(Box::new(
1929 ndarray::Zip::from(&a).and(&b).map_collect(|x, y| {
1930 bool_to_f64(x.re != 0.0 || x.im != 0.0 || y.re != 0.0 || y.im != 0.0)
1931 }),
1932 )))
1933 }
1934 }
1935}
1936
1937fn cm_binop_scalar(cm: Array2<Complex<f64>>, op: &Op, s: f64) -> Result<Value, String> {
1939 let c = Complex::new(s, 0.0);
1940 match op {
1941 Op::Add => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x + c)))),
1942 Op::Sub => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x - c)))),
1943 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x * c)))),
1944 Op::Div | Op::ElemDiv => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x / c)))),
1945 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x.powf(s))))),
1946 Op::Eq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(x == c))))),
1947 Op::NotEq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(x != c))))),
1948 _ => Err("Unsupported operator between complex matrix and scalar".to_string()),
1949 }
1950}
1951
1952fn scalar_binop_cm(s: f64, op: &Op, cm: Array2<Complex<f64>>) -> Result<Value, String> {
1954 let c = Complex::new(s, 0.0);
1955 match op {
1956 Op::Add => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c + x)))),
1957 Op::Sub => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c - x)))),
1958 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c * x)))),
1959 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c.powc(x))))),
1960 Op::Eq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(c == x))))),
1961 Op::NotEq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(c != x))))),
1962 _ => Err("Unsupported operator between scalar and complex matrix".to_string()),
1963 }
1964}
1965
1966fn cm_binop_complex(cm: Array2<Complex<f64>>, op: &Op, re: f64, im: f64) -> Result<Value, String> {
1968 let c = Complex::new(re, im);
1969 match op {
1970 Op::Add => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x + c)))),
1971 Op::Sub => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x - c)))),
1972 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x * c)))),
1973 Op::Div | Op::ElemDiv => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x / c)))),
1974 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| x.powc(c))))),
1975 Op::Eq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(x == c))))),
1976 Op::NotEq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(x != c))))),
1977 _ => Err("Unsupported operator between complex matrix and complex scalar".to_string()),
1978 }
1979}
1980
1981fn complex_binop_cm(re: f64, im: f64, op: &Op, cm: Array2<Complex<f64>>) -> Result<Value, String> {
1983 let c = Complex::new(re, im);
1984 match op {
1985 Op::Add => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c + x)))),
1986 Op::Sub => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c - x)))),
1987 Op::Mul | Op::ElemMul => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c * x)))),
1988 Op::Pow | Op::ElemPow => Ok(Value::ComplexMatrix(Box::new(cm.mapv(|x| c.powc(x))))),
1989 Op::Eq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(c == x))))),
1990 Op::NotEq => Ok(Value::Matrix(Box::new(cm.mapv(|x| bool_to_f64(c != x))))),
1991 _ => Err("Unsupported operator between complex scalar and complex matrix".to_string()),
1992 }
1993}
1994
1995fn str_to_numeric(s: &str) -> Value {
1998 let codes: Vec<f64> = s.chars().map(|c| c as u32 as f64).collect();
1999 match codes.len() {
2000 0 => Value::Matrix(Box::new(Array2::zeros((1, 0)))),
2001 1 => Value::Scalar(codes[0]),
2002 n => Value::Matrix(Box::new(Array2::from_shape_vec((1, n), codes).unwrap())),
2003 }
2004}
2005
2006fn string_arg<'a>(v: &'a Value, fname: &str, pos: usize) -> Result<&'a str, String> {
2008 match v {
2009 Value::Str(s) | Value::StringObj(s) => Ok(s.as_str()),
2010 _ => Err(format!(
2011 "Function '{fname}' argument {pos} must be a string"
2012 )),
2013 }
2014}
2015
2016fn check_same_shape(lm: &Array2<f64>, rm: &Array2<f64>) -> Result<(), String> {
2017 if lm.shape() != rm.shape() {
2018 return Err(format!(
2019 "Matrix size mismatch: {}x{} vs {}x{}",
2020 lm.nrows(),
2021 lm.ncols(),
2022 rm.nrows(),
2023 rm.ncols()
2024 ));
2025 }
2026 Ok(())
2027}
2028
2029fn scalar_arg(v: &Value, fname: &str, pos: usize) -> Result<f64, String> {
2030 match v {
2031 Value::Void => Err(format!(
2032 "Function '{fname}' argument {pos} must be a scalar, got void"
2033 )),
2034 Value::Scalar(n) => Ok(*n),
2035 Value::Complex(re, im) if *im == 0.0 => Ok(*re),
2036 Value::Complex(_, _) => Err(format!(
2037 "Function '{fname}' argument {pos} must be real, got a complex number"
2038 )),
2039 Value::Matrix(_) => Err(format!(
2040 "Function '{fname}' argument {pos} must be a scalar, got a matrix"
2041 )),
2042 Value::ComplexMatrix(_) => Err(format!(
2043 "Function '{fname}' argument {pos} must be a scalar, got a complex matrix"
2044 )),
2045 Value::Str(s) if s.chars().count() == 1 => Ok(s.chars().next().unwrap() as u32 as f64),
2046 Value::Str(_) | Value::StringObj(_) => Err(format!(
2047 "Function '{fname}' argument {pos} must be a scalar, got a string"
2048 )),
2049 Value::Lambda(_)
2050 | Value::Function(_)
2051 | Value::Tuple(_)
2052 | Value::Cell(_)
2053 | Value::Struct(_)
2054 | Value::StructArray(_)
2055 | Value::DateTime(_)
2056 | Value::Duration(_)
2057 | Value::DateTimeArray(_)
2058 | Value::DurationArray(_)
2059 | Value::Map(_) => Err(format!(
2060 "Function '{fname}' argument {pos} must be a scalar, got a non-numeric value"
2061 )),
2062 }
2063}
2064
2065fn size_arg(v: &Value, fname: &str) -> Result<(usize, usize), String> {
2075 match v {
2076 Value::Scalar(n) => Ok((*n as usize, *n as usize)),
2077 Value::Matrix(m) => {
2078 let elems: Vec<f64> = m.iter().copied().collect();
2079 match elems.as_slice() {
2080 [n] => Ok((*n as usize, *n as usize)),
2081 [r, c] => Ok((*r as usize, *c as usize)),
2082 _ => Err(format!(
2083 "{fname}: size argument must be a scalar or a 1×2 vector, \
2084 got a {}×{} matrix",
2085 m.nrows(),
2086 m.ncols()
2087 )),
2088 }
2089 }
2090 _ => Err(format!(
2091 "{fname}: size argument must be a scalar or a [rows cols] vector"
2092 )),
2093 }
2094}
2095
2096fn randi_range(v: &Value) -> Result<(i64, i64), String> {
2101 match v {
2102 Value::Scalar(n) => {
2103 let hi = *n as i64;
2104 if hi < 1 {
2105 return Err("randi: max must be a positive integer".to_string());
2106 }
2107 Ok((1, hi))
2108 }
2109 Value::Matrix(m) if m.len() == 2 => {
2110 let vals: Vec<f64> = m.iter().copied().collect();
2111 let lo = vals[0] as i64;
2112 let hi = vals[1] as i64;
2113 if lo > hi {
2114 return Err("randi: [min, max] range is empty".to_string());
2115 }
2116 Ok((lo, hi))
2117 }
2118 _ => Err("randi: first argument must be a scalar max or a [min, max] vector".to_string()),
2119 }
2120}
2121
2122fn numeric_vec(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
2126 match v {
2127 Value::Scalar(n) => Ok(vec![*n]),
2128 Value::Matrix(m) => Ok(m.iter().copied().collect()),
2129 _ => Err(format!("{fname}: argument must be numeric")),
2130 }
2131}
2132
2133fn stat_var_vec(vals: &[f64], population: bool) -> f64 {
2136 let n = vals.len();
2137 if n == 0 {
2138 return f64::NAN;
2139 }
2140 if n == 1 {
2141 return 0.0;
2142 }
2143 let mean = vals.iter().sum::<f64>() / n as f64;
2144 let ss: f64 = vals.iter().map(|&x| (x - mean).powi(2)).sum();
2145 let denom = if population { n as f64 } else { (n - 1) as f64 };
2146 ss / denom
2147}
2148
2149fn apply_stat<F>(v: &Value, mut f: F, fname: &str) -> Result<Value, String>
2155where
2156 F: FnMut(&[f64]) -> f64,
2157{
2158 match v {
2159 Value::Scalar(n) => Ok(Value::Scalar(f(&[*n]))),
2160 Value::Matrix(m) => {
2161 if m.nrows() == 1 || m.ncols() == 1 {
2162 let vals: Vec<f64> = m.iter().copied().collect();
2163 Ok(Value::Scalar(f(&vals)))
2164 } else {
2165 let ncols = m.ncols();
2166 let result: Vec<f64> = (0..ncols)
2167 .map(|c| {
2168 let col: Vec<f64> = m.column(c).iter().copied().collect();
2169 f(&col)
2170 })
2171 .collect();
2172 Ok(Value::Matrix(Box::new(
2173 Array2::from_shape_vec((1, ncols), result).unwrap(),
2174 )))
2175 }
2176 }
2177 _ => Err(format!("{fname}: argument must be numeric")),
2178 }
2179}
2180
2181fn percentile_sorted(sorted: &[f64], p: f64) -> f64 {
2183 let n = sorted.len();
2184 if n == 0 {
2185 return f64::NAN;
2186 }
2187 if n == 1 {
2188 return sorted[0];
2189 }
2190 let p = p.clamp(0.0, 100.0);
2191 let idx = (p / 100.0 * n as f64 - 0.5).max(0.0).min((n - 1) as f64);
2193 let lo = idx.floor() as usize;
2194 let hi = idx.ceil() as usize;
2195 let frac = idx - lo as f64;
2196 sorted[lo] * (1.0 - frac) + sorted[hi] * frac
2197}
2198
2199fn apply_elem<F: Fn(f64) -> f64>(v: &Value, f: F) -> Result<Value, String> {
2200 match v {
2201 Value::Void => Err("Element-wise function not applicable to void".to_string()),
2202 Value::Scalar(n) => Ok(Value::Scalar(f(*n))),
2203 Value::Matrix(m) => Ok(Value::Matrix(Box::new(m.mapv(f)))),
2204 Value::Complex(re, im) if *im == 0.0 => Ok(Value::Scalar(f(*re))),
2205 Value::Complex(_, _) => {
2206 Err("Element-wise real function not applicable to complex values".to_string())
2207 }
2208 Value::ComplexMatrix(_) => {
2209 Err("Element-wise real function not applicable to complex matrices".to_string())
2210 }
2211 Value::Str(_) | Value::StringObj(_) => {
2212 Err("Element-wise function not applicable to strings".to_string())
2213 }
2214 Value::Lambda(_)
2215 | Value::Function(_)
2216 | Value::Tuple(_)
2217 | Value::Cell(_)
2218 | Value::Struct(_)
2219 | Value::StructArray(_)
2220 | Value::DateTime(_)
2221 | Value::Duration(_)
2222 | Value::DateTimeArray(_)
2223 | Value::DurationArray(_)
2224 | Value::Map(_) => Err("Element-wise function not applicable to this type".to_string()),
2225 }
2226}
2227
2228fn apply_reduction<F>(v: &Value, f: F) -> Result<Value, String>
2234where
2235 F: Fn(&[f64]) -> f64,
2236{
2237 match v {
2238 Value::Void => Err("Reduction not applicable to void".to_string()),
2239 Value::Scalar(n) => Ok(Value::Scalar(f(&[*n]))),
2240 Value::Complex(_, _) => Err("Reduction not applicable to complex values".to_string()),
2241 Value::ComplexMatrix(_) => Err("Reduction not applicable to complex matrices".to_string()),
2242 Value::Str(_) | Value::StringObj(_) => {
2243 Err("Reduction not applicable to strings".to_string())
2244 }
2245 Value::Lambda(_)
2246 | Value::Function(_)
2247 | Value::Tuple(_)
2248 | Value::Cell(_)
2249 | Value::Struct(_)
2250 | Value::StructArray(_)
2251 | Value::DateTime(_)
2252 | Value::Duration(_)
2253 | Value::DateTimeArray(_)
2254 | Value::DurationArray(_)
2255 | Value::Map(_) => Err("Reduction not applicable to this type".to_string()),
2256 Value::Matrix(m) => {
2257 if m.nrows() == 1 || m.ncols() == 1 {
2258 let vals: Vec<f64> = m.iter().copied().collect();
2259 Ok(Value::Scalar(f(&vals)))
2260 } else {
2261 let ncols = m.ncols();
2262 let result: Vec<f64> = (0..ncols)
2263 .map(|c| {
2264 let col: Vec<f64> = m.column(c).iter().copied().collect();
2265 f(&col)
2266 })
2267 .collect();
2268 Ok(Value::Matrix(Box::new(
2269 Array2::from_shape_vec((1, ncols), result).unwrap(),
2270 )))
2271 }
2272 }
2273 }
2274}
2275
2276fn apply_cm_reduction<F>(v: &Value, f: F) -> Result<Value, String>
2281where
2282 F: Fn(&[Complex<f64>]) -> Complex<f64>,
2283{
2284 let make_scalar = |c: Complex<f64>| -> Value {
2285 if c.im == 0.0 {
2286 Value::Scalar(c.re)
2287 } else {
2288 Value::Complex(c.re, c.im)
2289 }
2290 };
2291 match v {
2292 Value::Scalar(n) => Ok(make_scalar(f(&[Complex::new(*n, 0.0)]))),
2293 Value::Complex(re, im) => Ok(make_scalar(f(&[Complex::new(*re, *im)]))),
2294 Value::Matrix(m) => {
2295 if m.nrows() == 1 || m.ncols() == 1 {
2296 let vals: Vec<Complex<f64>> = m.iter().map(|&x| Complex::new(x, 0.0)).collect();
2297 Ok(make_scalar(f(&vals)))
2298 } else {
2299 let ncols = m.ncols();
2300 let result: Vec<Complex<f64>> = (0..ncols)
2301 .map(|c| {
2302 let col: Vec<Complex<f64>> =
2303 m.column(c).iter().map(|&x| Complex::new(x, 0.0)).collect();
2304 f(&col)
2305 })
2306 .collect();
2307 if result.iter().all(|c| c.im == 0.0) {
2308 let reals: Vec<f64> = result.iter().map(|c| c.re).collect();
2309 Ok(Value::Matrix(Box::new(
2310 Array2::from_shape_vec((1, ncols), reals).unwrap(),
2311 )))
2312 } else {
2313 Ok(Value::ComplexMatrix(Box::new(
2314 Array2::from_shape_vec((1, ncols), result).unwrap(),
2315 )))
2316 }
2317 }
2318 }
2319 Value::ComplexMatrix(m) => {
2320 if m.nrows() == 1 || m.ncols() == 1 {
2321 let vals: Vec<Complex<f64>> = m.iter().copied().collect();
2322 Ok(make_scalar(f(&vals)))
2323 } else {
2324 let ncols = m.ncols();
2325 let result: Vec<Complex<f64>> = (0..ncols)
2326 .map(|c| {
2327 let col: Vec<Complex<f64>> = m.column(c).iter().copied().collect();
2328 f(&col)
2329 })
2330 .collect();
2331 if result.iter().all(|c| c.im == 0.0) {
2332 let reals: Vec<f64> = result.iter().map(|c| c.re).collect();
2333 Ok(Value::Matrix(Box::new(
2334 Array2::from_shape_vec((1, ncols), reals).unwrap(),
2335 )))
2336 } else {
2337 Ok(Value::ComplexMatrix(Box::new(
2338 Array2::from_shape_vec((1, ncols), result).unwrap(),
2339 )))
2340 }
2341 }
2342 }
2343 _ => Err("Reduction not applicable to this type".to_string()),
2344 }
2345}
2346
2347fn apply_cumulative<F>(v: &Value, combine: F) -> Result<Value, String>
2351where
2352 F: Fn(f64, f64) -> f64,
2353{
2354 match v {
2355 Value::Void => Err("Cumulative reduction not applicable to void".to_string()),
2356 Value::Scalar(n) => Ok(Value::Scalar(*n)),
2357 Value::Complex(_, _) => {
2358 Err("Cumulative reduction not applicable to complex values".to_string())
2359 }
2360 Value::ComplexMatrix(_) => {
2361 Err("Cumulative reduction not applicable to complex matrices".to_string())
2362 }
2363 Value::Str(_) | Value::StringObj(_) => {
2364 Err("Cumulative reduction not applicable to strings".to_string())
2365 }
2366 Value::Lambda(_)
2367 | Value::Function(_)
2368 | Value::Tuple(_)
2369 | Value::Cell(_)
2370 | Value::Struct(_)
2371 | Value::StructArray(_)
2372 | Value::DateTime(_)
2373 | Value::Duration(_)
2374 | Value::DateTimeArray(_)
2375 | Value::DurationArray(_)
2376 | Value::Map(_) => Err("Cumulative reduction not applicable to this type".to_string()),
2377 Value::Matrix(m) => {
2378 let initial = combine(0.0, 0.0); let identity = if (combine(1.0, 1.0) - 1.0).abs() < 1e-15 && initial == 0.0 {
2382 1.0 } else {
2384 0.0 };
2386 let (nrows, ncols) = (m.nrows(), m.ncols());
2387 let mut result = m.clone();
2388 if nrows == 1 || ncols == 1 {
2389 let mut acc = identity;
2391 for v in result.iter_mut() {
2392 acc = combine(acc, *v);
2393 *v = acc;
2394 }
2395 } else {
2396 for c in 0..ncols {
2398 let mut acc = identity;
2399 for r in 0..nrows {
2400 acc = combine(acc, result[[r, c]]);
2401 result[[r, c]] = acc;
2402 }
2403 }
2404 }
2405 Ok(Value::Matrix(result))
2406 }
2407 }
2408}
2409
2410fn find_nonzero(v: &Value, max_k: usize) -> Result<Value, String> {
2412 match v {
2413 Value::Void => Err("find: not applicable to void".to_string()),
2414 Value::ComplexMatrix(_) => Err("find: not applicable to complex matrices".to_string()),
2415 Value::Str(_) | Value::StringObj(_) => Err("find: not applicable to strings".to_string()),
2416 Value::Lambda(_)
2417 | Value::Function(_)
2418 | Value::Tuple(_)
2419 | Value::Cell(_)
2420 | Value::Struct(_)
2421 | Value::StructArray(_)
2422 | Value::DateTime(_)
2423 | Value::Duration(_)
2424 | Value::DateTimeArray(_)
2425 | Value::DurationArray(_)
2426 | Value::Map(_) => Err("find: not applicable to this type".to_string()),
2427 Value::Complex(re, im) => {
2428 if (*re != 0.0 || *im != 0.0) && max_k >= 1 {
2429 Ok(Value::Matrix(Box::new(
2430 Array2::from_shape_vec((1, 1), vec![1.0]).unwrap(),
2431 )))
2432 } else {
2433 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
2434 }
2435 }
2436 Value::Scalar(n) => {
2437 if *n != 0.0 && max_k >= 1 {
2438 Ok(Value::Matrix(Box::new(
2439 Array2::from_shape_vec((1, 1), vec![1.0]).unwrap(),
2440 )))
2441 } else {
2442 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
2443 }
2444 }
2445 Value::Matrix(m) => {
2446 let nrows = m.nrows();
2447 let total = m.len();
2448 let mut idxs: Vec<f64> = Vec::new();
2449 for i in 0..total {
2450 if idxs.len() >= max_k {
2451 break;
2452 }
2453 let row = i % nrows;
2454 let col = i / nrows;
2455 if m[[row, col]] != 0.0 {
2456 idxs.push((i + 1) as f64);
2457 }
2458 }
2459 let n = idxs.len();
2460 if n == 0 {
2461 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
2462 } else {
2463 Ok(Value::Matrix(Box::new(
2464 Array2::from_shape_vec((1, n), idxs).unwrap(),
2465 )))
2466 }
2467 }
2468 }
2469}
2470
2471pub fn format_printf(fmt: &str, args: &[Value]) -> Result<String, String> {
2485 let mut result = String::new();
2486 let mut arg_idx = 0;
2487
2488 loop {
2489 let consumed_before = arg_idx;
2490 let mut chars = fmt.chars().peekable();
2491
2492 while let Some(c) = chars.next() {
2493 if c == '\\' {
2494 match chars.next() {
2495 Some('n') => result.push('\n'),
2496 Some('t') => result.push('\t'),
2497 Some('\\') => result.push('\\'),
2498 Some('\'') => result.push('\''),
2499 Some('"') => result.push('"'),
2500 Some(other) => {
2501 result.push('\\');
2502 result.push(other);
2503 }
2504 None => result.push('\\'),
2505 }
2506 continue;
2507 }
2508
2509 if c != '%' {
2510 result.push(c);
2511 continue;
2512 }
2513
2514 if chars.peek() == Some(&'%') {
2516 chars.next();
2517 result.push('%');
2518 continue;
2519 }
2520
2521 let mut flag_minus = false;
2523 let mut flag_plus = false;
2524 let mut flag_zero = false;
2525 let mut flag_space = false;
2526 loop {
2527 match chars.peek() {
2528 Some('-') => {
2529 flag_minus = true;
2530 chars.next();
2531 }
2532 Some('+') => {
2533 flag_plus = true;
2534 chars.next();
2535 }
2536 Some('0') => {
2537 flag_zero = true;
2538 chars.next();
2539 }
2540 Some(' ') => {
2541 flag_space = true;
2542 chars.next();
2543 }
2544 _ => break,
2545 }
2546 }
2547
2548 let mut width_str = String::new();
2550 while let Some(&d) = chars.peek() {
2551 if d.is_ascii_digit() {
2552 width_str.push(d);
2553 chars.next();
2554 } else {
2555 break;
2556 }
2557 }
2558 let width: usize = width_str.parse().unwrap_or(0);
2559
2560 let mut precision: Option<usize> = None;
2562 if chars.peek() == Some(&'.') {
2563 chars.next();
2564 let mut p = String::new();
2565 while let Some(&d) = chars.peek() {
2566 if d.is_ascii_digit() {
2567 p.push(d);
2568 chars.next();
2569 } else {
2570 break;
2571 }
2572 }
2573 precision = Some(p.parse().unwrap_or(0));
2574 }
2575
2576 let spec = match chars.next() {
2578 Some(s) => s,
2579 None => {
2580 return Err("fprintf: incomplete format specifier at end of string".to_string());
2581 }
2582 };
2583
2584 if arg_idx >= args.len() {
2586 continue;
2587 }
2588
2589 let arg = &args[arg_idx];
2590 arg_idx += 1;
2591
2592 let formatted = match spec {
2593 'd' | 'i' => {
2594 let n = printf_scalar(arg, spec)?;
2595 let i = n.trunc() as i64;
2596 let s = printf_sign_str(i >= 0, flag_plus, flag_space, format!("{}", i.abs()));
2597 printf_pad(s, width, flag_minus, flag_zero)
2598 }
2599 'f' => {
2600 let n = printf_scalar(arg, spec)?;
2601 let prec = precision.unwrap_or(6);
2602 let s = printf_sign_str(
2603 n >= 0.0,
2604 flag_plus,
2605 flag_space,
2606 format!("{:.prec$}", n.abs(), prec = prec),
2607 );
2608 printf_pad(s, width, flag_minus, flag_zero)
2609 }
2610 'e' | 'E' => {
2611 let n = printf_scalar(arg, spec)?;
2612 let prec = precision.unwrap_or(6);
2613 let s = printf_format_sci(n, prec, flag_plus, flag_space, spec == 'E');
2614 printf_pad(s, width, flag_minus, flag_zero)
2615 }
2616 'g' | 'G' => {
2617 let n = printf_scalar(arg, spec)?;
2618 let prec = precision.unwrap_or(6).max(1);
2619 let s = printf_format_g(n, prec, flag_plus, flag_space, spec == 'G');
2620 printf_pad(s, width, flag_minus, flag_zero)
2621 }
2622 'x' | 'X' => {
2623 let n = printf_scalar(arg, spec)?;
2624 let i = n.trunc() as u64;
2625 let hex = if spec == 'X' {
2626 format!("{:X}", i)
2627 } else {
2628 format!("{:x}", i)
2629 };
2630 printf_pad(hex, width, flag_minus, flag_zero)
2631 }
2632 's' => {
2633 let s = printf_string(arg)?;
2634 let s = if let Some(max_len) = precision {
2635 s.chars().take(max_len).collect::<String>()
2636 } else {
2637 s
2638 };
2639 printf_pad(s, width, flag_minus, false)
2640 }
2641 other => return Err(format!("fprintf: unknown format specifier '%{other}'")),
2642 };
2643
2644 result.push_str(&formatted);
2645 }
2646
2647 if arg_idx >= args.len() || arg_idx == consumed_before {
2649 break;
2650 }
2651 }
2652
2653 Ok(result)
2654}
2655
2656fn printf_scalar(v: &Value, spec: char) -> Result<f64, String> {
2658 match v {
2659 Value::Scalar(n) => Ok(*n),
2660 Value::Complex(re, im) if *im == 0.0 => Ok(*re),
2661 Value::Str(s) if s.chars().count() == 1 => Ok(s.chars().next().unwrap() as u32 as f64),
2662 _ => Err(format!(
2663 "fprintf: expected numeric argument for '%{spec}', got {:?}",
2664 std::mem::discriminant(v)
2665 )),
2666 }
2667}
2668
2669fn printf_string(v: &Value) -> Result<String, String> {
2671 match v {
2672 Value::Str(s) | Value::StringObj(s) => Ok(s.clone()),
2673 Value::Scalar(n) => Ok(format_number(*n)),
2674 Value::Complex(re, im) => Ok(format_complex(*re, *im, &FormatMode::Custom(6))),
2675 Value::Void => Err("fprintf: cannot format void as string".to_string()),
2676 Value::Matrix(_) => Err("fprintf: cannot format matrix as string".to_string()),
2677 Value::ComplexMatrix(_) => {
2678 Err("fprintf: cannot format complex matrix as string".to_string())
2679 }
2680 Value::DateTime(ts) => Ok(crate::datetime::format_datetime(*ts)),
2681 Value::Duration(s) => Ok(crate::datetime::format_duration(*s)),
2682 Value::Lambda(_)
2683 | Value::Function(_)
2684 | Value::Tuple(_)
2685 | Value::Cell(_)
2686 | Value::Struct(_)
2687 | Value::StructArray(_)
2688 | Value::DateTimeArray(_)
2689 | Value::DurationArray(_)
2690 | Value::Map(_) => Err("fprintf: cannot format this type as string".to_string()),
2691 }
2692}
2693
2694fn printf_sign_str(positive: bool, flag_plus: bool, flag_space: bool, digits: String) -> String {
2696 if positive {
2697 if flag_plus {
2698 format!("+{digits}")
2699 } else if flag_space {
2700 format!(" {digits}")
2701 } else {
2702 digits
2703 }
2704 } else {
2705 format!("-{digits}")
2706 }
2707}
2708
2709fn printf_pad(s: String, width: usize, left_align: bool, zero_pad: bool) -> String {
2711 if s.len() >= width {
2712 return s;
2713 }
2714 let pad_len = width - s.len();
2715 if left_align {
2716 format!("{s}{}", " ".repeat(pad_len))
2717 } else if zero_pad {
2718 let (prefix, rest) = if s.starts_with(['+', '-', ' ']) {
2720 s.split_at(1)
2721 } else {
2722 ("", s.as_str())
2723 };
2724 format!("{prefix}{}{rest}", "0".repeat(pad_len))
2725 } else {
2726 format!("{}{s}", " ".repeat(pad_len))
2727 }
2728}
2729
2730fn printf_format_sci(
2733 n: f64,
2734 prec: usize,
2735 flag_plus: bool,
2736 flag_space: bool,
2737 upper: bool,
2738) -> String {
2739 if n == 0.0 {
2740 let zeros = "0".repeat(prec);
2741 let sep = if prec > 0 {
2742 format!(".{zeros}")
2743 } else {
2744 String::new()
2745 };
2746 let e_char = if upper { 'E' } else { 'e' };
2747 let sign = if flag_plus {
2748 "+"
2749 } else if flag_space {
2750 " "
2751 } else {
2752 ""
2753 };
2754 return format!("{sign}0{sep}{e_char}+00");
2755 }
2756
2757 let neg = n < 0.0;
2758 let abs_n = n.abs();
2759 let exp = abs_n.log10().floor() as i32;
2760 let mantissa = abs_n / 10f64.powi(exp);
2761 let man_str = format!("{:.prec$}", mantissa, prec = prec);
2762
2763 let e_char = if upper { 'E' } else { 'e' };
2764 let exp_sign = if exp >= 0 { '+' } else { '-' };
2765 let exp_abs = exp.unsigned_abs();
2766 let exp_str = if exp_abs < 10 {
2767 format!("{e_char}{exp_sign}0{exp_abs}")
2768 } else {
2769 format!("{e_char}{exp_sign}{exp_abs}")
2770 };
2771
2772 let sign_str = if neg {
2773 "-"
2774 } else if flag_plus {
2775 "+"
2776 } else if flag_space {
2777 " "
2778 } else {
2779 ""
2780 };
2781 format!("{sign_str}{man_str}{exp_str}")
2782}
2783
2784fn printf_format_g(n: f64, prec: usize, flag_plus: bool, flag_space: bool, upper: bool) -> String {
2787 if n == 0.0 {
2788 let sign = if flag_plus {
2789 "+"
2790 } else if flag_space {
2791 " "
2792 } else {
2793 ""
2794 };
2795 return format!("{sign}0");
2796 }
2797 let abs_n = n.abs();
2798 let exp = abs_n.log10().floor() as i32;
2799 if exp < -4 || exp >= prec as i32 {
2800 let s = printf_format_sci(n, prec.saturating_sub(1), flag_plus, flag_space, upper);
2801 trim_g_sci(s, upper)
2802 } else {
2803 let decimal_places = (prec as i32 - 1 - exp).max(0) as usize;
2804 let neg = n < 0.0;
2805 let s = format!("{:.prec$}", abs_n, prec = decimal_places);
2806 let s = if s.contains('.') {
2807 s.trim_end_matches('0').trim_end_matches('.').to_string()
2808 } else {
2809 s
2810 };
2811 let sign = if neg {
2812 "-"
2813 } else if flag_plus {
2814 "+"
2815 } else if flag_space {
2816 " "
2817 } else {
2818 ""
2819 };
2820 format!("{sign}{s}")
2821 }
2822}
2823
2824fn trim_g_sci(s: String, upper: bool) -> String {
2826 let e_char = if upper { 'E' } else { 'e' };
2827 if let Some(e_pos) = s.find(e_char) {
2828 let mantissa = &s[..e_pos];
2829 let exp_part = &s[e_pos..];
2830 let trimmed = if mantissa.contains('.') {
2831 mantissa.trim_end_matches('0').trim_end_matches('.')
2832 } else {
2833 mantissa
2834 };
2835 format!("{trimmed}{exp_part}")
2836 } else {
2837 s
2838 }
2839}
2840
2841fn call_function_value(
2846 f: &Value,
2847 args: &[Value],
2848 io: Option<&mut IoContext>,
2849) -> Result<Value, String> {
2850 match f {
2851 Value::Lambda(lf) => {
2852 let lf = lf.clone();
2853 lf.0(args, io)
2854 }
2855 Value::Function(_) => {
2856 let empty_env = Env::new();
2860 match io {
2861 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
2862 Some(hook) => hook("<anonymous>", f, args, &empty_env, io_ref),
2863 None => Err("User function execution not initialized".to_string()),
2864 }),
2865 None => {
2866 let mut tmp_io = IoContext::new();
2867 FN_CALL_HOOK.with(|c| match c.get() {
2868 Some(hook) => hook("<anonymous>", f, args, &empty_env, &mut tmp_io),
2869 None => Err("User function execution not initialized".to_string()),
2870 })
2871 }
2872 }
2873 }
2874 _ => Err("cellfun/arrayfun: first argument must be a function or lambda (@fn)".to_string()),
2875 }
2876}
2877
2878pub fn builtin_names() -> &'static [&'static str] {
2882 &[
2883 "abs",
2884 "acos",
2885 "all",
2886 "angle",
2887 "any",
2888 "arrayfun",
2889 "asin",
2890 "assert",
2891 "atan",
2892 "atan2",
2893 "bitand",
2894 "bitnot",
2895 "bitor",
2896 "bitshift",
2897 "bitxor",
2898 "ceil",
2899 "cell",
2900 "cellfun",
2901 "chol",
2902 "complex",
2903 "cond",
2904 "cross",
2905 "conj",
2906 "contains",
2907 "conv",
2908 "cos",
2909 "cov",
2910 "cumprod",
2911 "cumsum",
2912 "datenum",
2913 "datestr",
2914 "datevec",
2915 "datetime",
2916 "day",
2917 "days",
2918 "deconv",
2919 "det",
2920 "diag",
2921 "diff",
2922 "dir",
2923 "dot",
2924 "disp",
2925 "dlmread",
2926 "dlmwrite",
2927 "eig",
2928 "endsWith",
2929 "erf",
2930 "eval",
2931 "erfc",
2932 "exist",
2933 "exp",
2934 "eye",
2935 "fclose",
2936 "fft",
2937 "fftfreq",
2938 "fftshift",
2939 "fgetl",
2940 "fgets",
2941 "fieldnames",
2942 "ifft",
2943 "ifftshift",
2944 "figure",
2945 "find",
2946 "fliplr",
2947 "flipud",
2948 "floor",
2949 "fopen",
2950 "fprintf",
2951 "genpath",
2952 "histc",
2953 "hour",
2954 "hours",
2955 "hypot",
2956 "imag",
2957 "ind2sub",
2958 "int2str",
2959 "interp1",
2960 "intersect",
2961 "inv",
2962 "iqr",
2963 "iscell",
2964 "ismember",
2965 "ischar",
2966 "isdatetime",
2967 "isduration",
2968 "isempty",
2969 "isfield",
2970 "isfile",
2971 "isfinite",
2972 "isfolder",
2973 "isinf",
2974 "isnan",
2975 "isnat",
2976 "isreal",
2977 "isKey",
2978 "isstring",
2979 "isstruct",
2980 "jsonencode",
2981 "jsondecode",
2982 "keys",
2983 "kron",
2984 "kurtosis",
2985 "lasterr",
2986 "length",
2987 "linspace",
2988 "load",
2989 "log",
2990 "log10",
2991 "log2",
2992 "lower",
2993 "lu",
2994 "mat2str",
2995 "max",
2996 "mean",
2997 "median",
2998 "meshgrid",
2999 "milliseconds",
3000 "min",
3001 "minute",
3002 "minutes",
3003 "mod",
3004 "mode",
3005 "month",
3006 "nan",
3007 "norm",
3008 "normcdf",
3009 "normpdf",
3010 "not",
3011 "null",
3012 "num2str",
3013 "numel",
3014 "ones",
3015 "orth",
3016 "pinv",
3017 "poly",
3018 "polyfit",
3019 "polyval",
3020 "posixtime",
3021 "prctile",
3022 "prod",
3023 "qr",
3024 "rand",
3025 "randi",
3026 "randn",
3027 "rank",
3028 "readmatrix",
3029 "readtable",
3030 "real",
3031 "regexp",
3032 "regexpi",
3033 "regexprep",
3034 "rem",
3035 "repelem",
3036 "remove",
3037 "repmat",
3038 "reshape",
3039 "rmfield",
3040 "rng",
3041 "roots",
3042 "round",
3043 "setdiff",
3044 "second",
3045 "seconds",
3046 "sign",
3047 "sin",
3048 "size",
3049 "skewness",
3050 "sort",
3051 "sprintf",
3052 "sqrt",
3053 "startsWith",
3054 "std",
3055 "str2double",
3056 "str2num",
3057 "strcmp",
3058 "strcmpi",
3059 "strjoin",
3060 "strrep",
3061 "strsplit",
3062 "strtrim",
3063 "sub2ind",
3064 "sum",
3065 "svd",
3066 "tan",
3067 "tic",
3068 "toc",
3069 "trace",
3070 "tril",
3071 "triu",
3072 "union",
3073 "unique",
3074 "upper",
3075 "values",
3076 "var",
3077 "writetable",
3078 "xor",
3079 "year",
3080 "years",
3081 "zeros",
3082 "zscore",
3083 ]
3084}
3085
3086fn levenshtein(a: &str, b: &str) -> usize {
3088 let a: Vec<char> = a.chars().collect();
3089 let b: Vec<char> = b.chars().collect();
3090 let (m, n) = (a.len(), b.len());
3091 let mut row: Vec<usize> = (0..=n).collect();
3092 for i in 1..=m {
3093 let mut prev = row[0];
3094 row[0] = i;
3095 for j in 1..=n {
3096 let next = if a[i - 1] == b[j - 1] {
3097 prev
3098 } else {
3099 1 + prev.min(row[j]).min(row[j - 1])
3100 };
3101 prev = row[j];
3102 row[j] = next;
3103 }
3104 }
3105 row[n]
3106}
3107
3108fn suggest_similar(name: &str, env: &Env) -> Option<String> {
3110 const MAX_DIST: usize = 2;
3111 let mut best: Option<(String, usize)> = None;
3112 let mut update = |candidate: &str| {
3113 let d = levenshtein(name, candidate);
3114 if d <= MAX_DIST && best.as_ref().is_none_or(|(_, bd)| d < *bd) {
3115 best = Some((candidate.to_string(), d));
3116 }
3117 };
3118 for key in env.keys() {
3119 update(key);
3120 }
3121 for &bname in builtin_names() {
3122 update(bname);
3123 }
3124 best.map(|(s, _)| s)
3125}
3126
3127fn assert_values_equal(a: &Value, b: &Value, tol: Option<f64>) -> Result<Value, String> {
3129 match (a, b) {
3130 (Value::Scalar(x), Value::Scalar(y)) => {
3131 let ok = match tol {
3132 None => x == y,
3133 Some(t) => (x - y).abs() <= t,
3134 };
3135 if ok {
3136 Ok(Value::Void)
3137 } else if let Some(t) = tol {
3138 Err(format!(
3139 "assert: |{x} - {y}| = {} exceeds tolerance {t}",
3140 (x - y).abs()
3141 ))
3142 } else {
3143 Err(format!("assert: {x} ~= {y}"))
3144 }
3145 }
3146 (Value::Matrix(ma), Value::Matrix(mb)) => {
3147 if ma.shape() != mb.shape() {
3148 return Err(format!(
3149 "assert: size mismatch [{}×{}] vs [{}×{}]",
3150 ma.nrows(),
3151 ma.ncols(),
3152 mb.nrows(),
3153 mb.ncols()
3154 ));
3155 }
3156 for (x, y) in ma.iter().zip(mb.iter()) {
3157 let ok = match tol {
3158 None => x == y,
3159 Some(t) => (x - y).abs() <= t,
3160 };
3161 if !ok {
3162 if let Some(t) = tol {
3163 return Err(format!(
3164 "assert: difference {} exceeds tolerance {t}",
3165 (x - y).abs()
3166 ));
3167 } else {
3168 return Err(format!("assert: {x} ~= {y}"));
3169 }
3170 }
3171 }
3172 Ok(Value::Void)
3173 }
3174 _ => {
3175 if tol.is_some() {
3176 return Err("assert: tolerance requires numeric arguments".to_string());
3177 }
3178 if a == b {
3179 Ok(Value::Void)
3180 } else {
3181 Err("assert: values not equal".to_string())
3182 }
3183 }
3184 }
3185}
3186
3187pub(crate) fn call_builtin(
3188 name: &str,
3189 args: &[Value],
3190 env: &Env,
3191 mut io: Option<&mut IoContext>,
3192) -> Result<Value, String> {
3193 if let Some(result) = crate::plugin::call_plugin(name, args, env) {
3195 return result;
3196 }
3197
3198 match (name, args.len()) {
3199 ("sqrt", 1) => match &args[0] {
3201 Value::Scalar(x) if *x < 0.0 => Ok(make_complex(0.0, (-x).sqrt())),
3202 Value::Complex(re, im) => {
3203 let mag = (*re * *re + *im * *im).sqrt();
3204 let sqrt_mag = mag.sqrt();
3205 let arg = (*im).atan2(*re) / 2.0;
3206 Ok(make_complex(sqrt_mag * arg.cos(), sqrt_mag * arg.sin()))
3207 }
3208 _ => apply_elem(&args[0], |x| x.sqrt()),
3209 },
3210 ("floor", 1) => apply_elem(&args[0], |x| x.floor()),
3211 ("ceil", 1) => apply_elem(&args[0], |x| x.ceil()),
3212 ("round", 1) => apply_elem(&args[0], |x| x.round()),
3213 ("sign", 1) => apply_elem(&args[0], |x| x.signum()),
3214 ("log", 1) => apply_elem(&args[0], |x| x.ln()),
3215 ("log2", 1) => apply_elem(&args[0], |x| x.log2()),
3216 ("log10", 1) => apply_elem(&args[0], |x| x.log10()),
3217 ("exp", 1) => match &args[0] {
3218 Value::Complex(re, im) => {
3219 let e = re.exp();
3220 Ok(make_complex(e * im.cos(), e * im.sin()))
3221 }
3222 _ => apply_elem(&args[0], |x| x.exp()),
3223 },
3224 ("sin", 1) => apply_elem(&args[0], |x| x.sin()),
3225 ("cos", 1) => apply_elem(&args[0], |x| x.cos()),
3226 ("tan", 1) => apply_elem(&args[0], |x| x.tan()),
3227 ("asin", 1) => apply_elem(&args[0], |x| x.asin()),
3228 ("acos", 1) => apply_elem(&args[0], |x| x.acos()),
3229 ("atan", 1) => apply_elem(&args[0], |x| x.atan()),
3230 ("erf", 1) => apply_elem(&args[0], libm::erf),
3232 ("erfc", 1) => apply_elem(&args[0], libm::erfc),
3233 ("normcdf", 1) => apply_elem(&args[0], |x| {
3234 0.5 * (1.0 + libm::erf(x / std::f64::consts::SQRT_2))
3235 }),
3236 ("normcdf", 3) => {
3237 let mu = scalar_arg(&args[1], name, 2)?;
3238 let s = scalar_arg(&args[2], name, 3)?;
3239 if s <= 0.0 {
3240 return Err("normcdf: sigma must be positive".to_string());
3241 }
3242 apply_elem(&args[0], move |x| {
3243 0.5 * (1.0 + libm::erf((x - mu) / (s * std::f64::consts::SQRT_2)))
3244 })
3245 }
3246 ("normpdf", 1) => apply_elem(&args[0], |x| {
3247 (-0.5 * x * x).exp() / (2.0 * std::f64::consts::PI).sqrt()
3248 }),
3249 ("normpdf", 3) => {
3250 let mu = scalar_arg(&args[1], name, 2)?;
3251 let s = scalar_arg(&args[2], name, 3)?;
3252 if s <= 0.0 {
3253 return Err("normpdf: sigma must be positive".to_string());
3254 }
3255 apply_elem(&args[0], move |x| {
3256 let z = (x - mu) / s;
3257 (-0.5 * z * z).exp() / (s * (2.0 * std::f64::consts::PI).sqrt())
3258 })
3259 }
3260 ("atan2", 2) => Ok(Value::Scalar(
3262 scalar_arg(&args[0], name, 1)?.atan2(scalar_arg(&args[1], name, 2)?),
3263 )),
3264 ("mod", 2) => {
3265 let a = scalar_arg(&args[0], name, 1)?;
3266 let b = scalar_arg(&args[1], name, 2)?;
3267 Ok(Value::Scalar(a - b * (a / b).floor()))
3268 }
3269 ("rem", 2) => {
3270 let a = scalar_arg(&args[0], name, 1)?;
3271 let b = scalar_arg(&args[1], name, 2)?;
3272 Ok(Value::Scalar(a - b * (a / b).trunc()))
3273 }
3274 ("max", 2) => Ok(Value::Scalar(
3275 scalar_arg(&args[0], name, 1)?.max(scalar_arg(&args[1], name, 2)?),
3276 )),
3277 ("min", 2) => Ok(Value::Scalar(
3278 scalar_arg(&args[0], name, 1)?.min(scalar_arg(&args[1], name, 2)?),
3279 )),
3280 ("hypot", 2) => Ok(Value::Scalar(
3281 scalar_arg(&args[0], name, 1)?.hypot(scalar_arg(&args[1], name, 2)?),
3282 )),
3283 ("log", 2) => Ok(Value::Scalar(
3284 scalar_arg(&args[0], name, 1)?.log(scalar_arg(&args[1], name, 2)?),
3285 )),
3286 ("zeros", 1) => {
3288 let (r, c) = size_arg(&args[0], name)?;
3289 Ok(Value::Matrix(Box::new(Array2::zeros((r, c)))))
3290 }
3291 ("zeros", 2) => {
3292 let r = scalar_arg(&args[0], name, 1)? as usize;
3293 let c = scalar_arg(&args[1], name, 2)? as usize;
3294 Ok(Value::Matrix(Box::new(Array2::zeros((r, c)))))
3295 }
3296 ("ones", 1) => {
3297 let (r, c) = size_arg(&args[0], name)?;
3298 Ok(Value::Matrix(Box::new(Array2::ones((r, c)))))
3299 }
3300 ("ones", 2) => {
3301 let r = scalar_arg(&args[0], name, 1)? as usize;
3302 let c = scalar_arg(&args[1], name, 2)? as usize;
3303 Ok(Value::Matrix(Box::new(Array2::ones((r, c)))))
3304 }
3305 ("eye", 1) => {
3306 let n = scalar_arg(&args[0], name, 1)? as usize;
3307 let mut m = Array2::<f64>::zeros((n, n));
3308 for i in 0..n {
3309 m[[i, i]] = 1.0;
3310 }
3311 Ok(Value::Matrix(Box::new(m)))
3312 }
3313 ("size", 1) => match &args[0] {
3315 Value::Void => Err("size: not applicable to void".to_string()),
3316 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Matrix(
3317 Box::new(Array2::from_shape_vec((1, 2), vec![1.0, 1.0]).unwrap()),
3318 )),
3319 Value::Matrix(m) => Ok(Value::Matrix(Box::new(
3320 Array2::from_shape_vec((1, 2), vec![m.nrows() as f64, m.ncols() as f64]).unwrap(),
3321 ))),
3322 Value::ComplexMatrix(m) => Ok(Value::Matrix(Box::new(
3323 Array2::from_shape_vec((1, 2), vec![m.nrows() as f64, m.ncols() as f64]).unwrap(),
3324 ))),
3325 Value::Str(s) => Ok(Value::Matrix(Box::new(
3326 Array2::from_shape_vec((1, 2), vec![1.0, s.chars().count() as f64]).unwrap(),
3327 ))),
3328 Value::StringObj(s) => Ok(Value::Matrix(Box::new(
3329 Array2::from_shape_vec((1, 2), vec![1.0, s.chars().count() as f64]).unwrap(),
3330 ))),
3331 Value::Cell(v) => Ok(Value::Matrix(Box::new(
3332 Array2::from_shape_vec((1, 2), vec![1.0, v.len() as f64]).unwrap(),
3333 ))),
3334 Value::StructArray(arr) => Ok(Value::Matrix(Box::new(
3335 Array2::from_shape_vec((1, 2), vec![1.0, arr.len() as f64]).unwrap(),
3336 ))),
3337 Value::Lambda(_)
3338 | Value::Function(_)
3339 | Value::Tuple(_)
3340 | Value::DateTime(_)
3341 | Value::Duration(_)
3342 | Value::DateTimeArray(_)
3343 | Value::DurationArray(_)
3344 | Value::Map(_) => Err("size: not applicable to this type".to_string()),
3345 },
3346 ("size", 2) => {
3347 let dim = scalar_arg(&args[1], name, 2)? as usize;
3348 match &args[0] {
3349 Value::Void => Err("size: not applicable to void".to_string()),
3350 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => {
3351 Ok(Value::Scalar(1.0))
3352 }
3353 Value::Matrix(m) => match dim {
3354 1 => Ok(Value::Scalar(m.nrows() as f64)),
3355 2 => Ok(Value::Scalar(m.ncols() as f64)),
3356 _ => Err(format!("size: invalid dimension {dim}, must be 1 or 2")),
3357 },
3358 Value::ComplexMatrix(m) => match dim {
3359 1 => Ok(Value::Scalar(m.nrows() as f64)),
3360 2 => Ok(Value::Scalar(m.ncols() as f64)),
3361 _ => Err(format!("size: invalid dimension {dim}, must be 1 or 2")),
3362 },
3363 Value::Str(s) => match dim {
3364 1 => Ok(Value::Scalar(1.0)),
3365 2 => Ok(Value::Scalar(s.chars().count() as f64)),
3366 _ => Err(format!("size: invalid dimension {dim}")),
3367 },
3368 Value::StringObj(s) => match dim {
3369 1 => Ok(Value::Scalar(1.0)),
3370 2 => Ok(Value::Scalar(s.chars().count() as f64)),
3371 _ => Err(format!("size: invalid dimension {dim}")),
3372 },
3373 Value::Cell(v) => match dim {
3374 1 => Ok(Value::Scalar(1.0)),
3375 2 => Ok(Value::Scalar(v.len() as f64)),
3376 _ => Err(format!("size: invalid dimension {dim}")),
3377 },
3378 Value::StructArray(arr) => match dim {
3379 1 => Ok(Value::Scalar(1.0)),
3380 2 => Ok(Value::Scalar(arr.len() as f64)),
3381 _ => Err(format!("size: invalid dimension {dim}")),
3382 },
3383 Value::Lambda(_)
3384 | Value::Function(_)
3385 | Value::Tuple(_)
3386 | Value::DateTime(_)
3387 | Value::Duration(_)
3388 | Value::DateTimeArray(_)
3389 | Value::DurationArray(_)
3390 | Value::Map(_) => Err("size: not applicable to this type".to_string()),
3391 }
3392 }
3393 ("length", 1) => match &args[0] {
3394 Value::Void => Err("length: not applicable to void".to_string()),
3395 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Scalar(1.0)),
3396 Value::Matrix(m) => Ok(Value::Scalar(m.nrows().max(m.ncols()) as f64)),
3397 Value::ComplexMatrix(m) => Ok(Value::Scalar(m.nrows().max(m.ncols()) as f64)),
3398 Value::Str(s) => Ok(Value::Scalar(s.chars().count() as f64)),
3399 Value::StringObj(s) => Ok(Value::Scalar(s.chars().count() as f64)),
3400 Value::Cell(v) => Ok(Value::Scalar(v.len() as f64)),
3401 Value::StructArray(arr) => Ok(Value::Scalar(arr.len() as f64)),
3402 Value::DateTimeArray(v) | Value::DurationArray(v) => Ok(Value::Scalar(v.len() as f64)),
3403 Value::DateTime(_) | Value::Duration(_) => Ok(Value::Scalar(1.0)),
3404 Value::Map(m) => Ok(Value::Scalar(m.len() as f64)),
3405 Value::Lambda(_) | Value::Function(_) | Value::Tuple(_) => {
3406 Err("length: not applicable to function values".to_string())
3407 }
3408 },
3409 ("numel", 1) => match &args[0] {
3410 Value::Void => Err("numel: not applicable to void".to_string()),
3411 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Scalar(1.0)),
3412 Value::Matrix(m) => Ok(Value::Scalar(m.len() as f64)),
3413 Value::ComplexMatrix(m) => Ok(Value::Scalar(m.len() as f64)),
3414 Value::Str(s) => Ok(Value::Scalar(s.chars().count() as f64)),
3415 Value::StringObj(s) => Ok(Value::Scalar(s.chars().count() as f64)),
3416 Value::Cell(v) => Ok(Value::Scalar(v.len() as f64)),
3417 Value::StructArray(arr) => Ok(Value::Scalar(arr.len() as f64)),
3418 Value::DateTimeArray(v) | Value::DurationArray(v) => Ok(Value::Scalar(v.len() as f64)),
3419 Value::DateTime(_) | Value::Duration(_) => Ok(Value::Scalar(1.0)),
3420 Value::Map(m) => Ok(Value::Scalar(m.len() as f64)),
3421 Value::Lambda(_) | Value::Function(_) | Value::Tuple(_) => {
3422 Err("numel: not applicable to function values".to_string())
3423 }
3424 },
3425 ("trace", 1) => match &args[0] {
3426 Value::Void => Err("trace: not applicable to void".to_string()),
3427 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3428 Value::Complex(re, _) => Ok(Value::Scalar(*re)),
3429 Value::Matrix(m) => {
3430 let n = m.nrows().min(m.ncols());
3431 Ok(Value::Scalar((0..n).map(|i| m[[i, i]]).sum()))
3432 }
3433 Value::ComplexMatrix(m) => {
3434 let n = m.nrows().min(m.ncols());
3435 let s: Complex<f64> = (0..n).map(|i| m[[i, i]]).sum();
3436 Ok(if s.im == 0.0 {
3437 Value::Scalar(s.re)
3438 } else {
3439 Value::Complex(s.re, s.im)
3440 })
3441 }
3442 Value::Str(_)
3443 | Value::StringObj(_)
3444 | Value::Lambda(_)
3445 | Value::Function(_)
3446 | Value::Tuple(_)
3447 | Value::Cell(_)
3448 | Value::Struct(_)
3449 | Value::StructArray(_)
3450 | Value::DateTime(_)
3451 | Value::Duration(_)
3452 | Value::DateTimeArray(_)
3453 | Value::DurationArray(_)
3454 | Value::Map(_) => Err("trace: not applicable to non-numeric values".to_string()),
3455 },
3456 ("det", 1) => match &args[0] {
3457 Value::Void => Err("det: not applicable to void".to_string()),
3458 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3459 Value::Complex(_, _) => Err("det: not applicable to complex scalars".to_string()),
3460 Value::ComplexMatrix(_) => Err("det: not supported for complex matrices".to_string()),
3461 Value::Matrix(m) => Ok(Value::Scalar(det_matrix(m)?)),
3462 Value::Str(_)
3463 | Value::StringObj(_)
3464 | Value::Lambda(_)
3465 | Value::Function(_)
3466 | Value::Tuple(_)
3467 | Value::Cell(_)
3468 | Value::Struct(_)
3469 | Value::StructArray(_)
3470 | Value::DateTime(_)
3471 | Value::Duration(_)
3472 | Value::DateTimeArray(_)
3473 | Value::DurationArray(_)
3474 | Value::Map(_) => Err("det: not applicable to non-numeric values".to_string()),
3475 },
3476 ("inv", 1) => match &args[0] {
3477 Value::Void => Err("inv: not applicable to void".to_string()),
3478 Value::Scalar(n) => {
3479 if *n == 0.0 {
3480 Err("inv: singular (zero scalar)".to_string())
3481 } else {
3482 Ok(Value::Scalar(1.0 / n))
3483 }
3484 }
3485 Value::Complex(re, im) => {
3486 let denom = re * re + im * im;
3488 if denom == 0.0 {
3489 Err("inv: singular (zero complex)".to_string())
3490 } else {
3491 Ok(make_complex(re / denom, -im / denom))
3492 }
3493 }
3494 Value::Matrix(m) => Ok(Value::Matrix(Box::new(inv_matrix(m)?))),
3495 Value::ComplexMatrix(_) => Err("inv: not supported for complex matrices".to_string()),
3496 Value::Str(_)
3497 | Value::StringObj(_)
3498 | Value::Lambda(_)
3499 | Value::Function(_)
3500 | Value::Tuple(_)
3501 | Value::Cell(_)
3502 | Value::Struct(_)
3503 | Value::StructArray(_)
3504 | Value::DateTime(_)
3505 | Value::Duration(_)
3506 | Value::DateTimeArray(_)
3507 | Value::DurationArray(_)
3508 | Value::Map(_) => Err("inv: not applicable to non-numeric values".to_string()),
3509 },
3510 ("linspace", 3) => {
3512 let a = scalar_arg(&args[0], name, 1)?;
3513 let b = scalar_arg(&args[1], name, 2)?;
3514 let n = scalar_arg(&args[2], name, 3)? as usize;
3515 if n == 0 {
3516 return Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))));
3517 }
3518 if n == 1 {
3519 return Ok(Value::Matrix(Box::new(
3520 Array2::from_shape_vec((1, 1), vec![b]).unwrap(),
3521 )));
3522 }
3523 let vals: Vec<f64> = (0..n)
3524 .map(|i| a + (b - a) * i as f64 / (n - 1) as f64)
3525 .collect();
3526 Ok(Value::Matrix(Box::new(
3527 Array2::from_shape_vec((1, n), vals).unwrap(),
3528 )))
3529 }
3530 ("bitand", 2) => {
3534 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3535 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
3536 Ok(Value::Scalar((a & b) as f64))
3537 }
3538 ("bitor", 2) => {
3539 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3540 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
3541 Ok(Value::Scalar((a | b) as f64))
3542 }
3543 ("bitxor", 2) => {
3544 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3545 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
3546 Ok(Value::Scalar((a ^ b) as f64))
3547 }
3548 ("bitshift", 2) => {
3551 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3552 let n = scalar_arg(&args[1], name, 2)?;
3553 if n.fract() != 0.0 {
3554 return Err("bitshift: shift amount must be an integer".to_string());
3555 }
3556 let n = n as i64;
3557 let result: u64 = if n >= 64 || n <= -64 {
3558 0
3559 } else if n >= 0 {
3560 a.wrapping_shl(n as u32)
3561 } else {
3562 a.wrapping_shr((-n) as u32)
3563 };
3564 Ok(Value::Scalar(result as f64))
3565 }
3566 ("bitnot", 1) => {
3569 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3570 let mask: u64 = 0xFFFF_FFFF;
3571 Ok(Value::Scalar(((a ^ mask) & mask) as f64))
3572 }
3573 ("bitnot", 2) => {
3574 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
3575 let bits = scalar_arg(&args[1], name, 2)?;
3576 if bits.fract() != 0.0 || !(1.0..=53.0).contains(&bits) {
3577 return Err(format!(
3578 "bitnot: bit-width must be an integer in [1, 53], got {bits}"
3579 ));
3580 }
3581 let mask: u64 = (1u64 << bits as u32) - 1;
3582 Ok(Value::Scalar(((a ^ mask) & mask) as f64))
3583 }
3584 ("isnan", 1) => apply_elem(&args[0], |x| if x.is_nan() { 1.0 } else { 0.0 }),
3586 ("isinf", 1) => apply_elem(&args[0], |x| if x.is_infinite() { 1.0 } else { 0.0 }),
3587 ("isfinite", 1) => apply_elem(&args[0], |x| if x.is_finite() { 1.0 } else { 0.0 }),
3588 ("nan", 1) => {
3590 let (r, c) = size_arg(&args[0], name)?;
3591 Ok(Value::Matrix(Box::new(Array2::from_elem((r, c), f64::NAN))))
3592 }
3593 ("nan", 2) => {
3594 let r = scalar_arg(&args[0], name, 1)? as usize;
3595 let c = scalar_arg(&args[1], name, 2)? as usize;
3596 Ok(Value::Matrix(Box::new(Array2::from_elem((r, c), f64::NAN))))
3597 }
3598 ("rand", 0) => Ok(Value::Scalar(rand_uniform())),
3600 ("rand", 1) => {
3601 let (r, c) = size_arg(&args[0], name)?;
3602 let data: Vec<f64> = (0..r * c).map(|_| rand_uniform()).collect();
3603 Ok(Value::Matrix(Box::new(
3604 Array2::from_shape_vec((r, c), data).unwrap(),
3605 )))
3606 }
3607 ("rand", 2) => {
3608 let r = scalar_arg(&args[0], name, 1)? as usize;
3609 let c = scalar_arg(&args[1], name, 2)? as usize;
3610 let data: Vec<f64> = (0..r * c).map(|_| rand_uniform()).collect();
3611 Ok(Value::Matrix(Box::new(
3612 Array2::from_shape_vec((r, c), data).unwrap(),
3613 )))
3614 }
3615 ("randn", 0) => Ok(Value::Scalar(rand_normal())),
3616 ("randn", 1) => {
3617 let (r, c) = size_arg(&args[0], name)?;
3618 let data: Vec<f64> = (0..r * c).map(|_| rand_normal()).collect();
3619 Ok(Value::Matrix(Box::new(
3620 Array2::from_shape_vec((r, c), data).unwrap(),
3621 )))
3622 }
3623 ("randn", 2) => {
3624 let r = scalar_arg(&args[0], name, 1)? as usize;
3625 let c = scalar_arg(&args[1], name, 2)? as usize;
3626 let data: Vec<f64> = (0..r * c).map(|_| rand_normal()).collect();
3627 Ok(Value::Matrix(Box::new(
3628 Array2::from_shape_vec((r, c), data).unwrap(),
3629 )))
3630 }
3631 ("randi", 1) => {
3632 let (lo, hi) = randi_range(&args[0])?;
3633 let v = RNG.with(|r| r.borrow_mut().gen_range(lo..=hi)) as f64;
3634 Ok(Value::Scalar(v))
3635 }
3636 ("randi", 2) => {
3637 let (lo, hi) = randi_range(&args[0])?;
3638 let n = scalar_arg(&args[1], name, 2)? as usize;
3639 let data: Vec<f64> = (0..n * n)
3640 .map(|_| RNG.with(|r| r.borrow_mut().gen_range(lo..=hi)) as f64)
3641 .collect();
3642 Ok(Value::Matrix(Box::new(
3643 Array2::from_shape_vec((n, n), data).unwrap(),
3644 )))
3645 }
3646 ("randi", 3) => {
3647 let (lo, hi) = randi_range(&args[0])?;
3648 let r = scalar_arg(&args[1], name, 2)? as usize;
3649 let c = scalar_arg(&args[2], name, 3)? as usize;
3650 let data: Vec<f64> = (0..r * c)
3651 .map(|_| RNG.with(|rng| rng.borrow_mut().gen_range(lo..=hi)) as f64)
3652 .collect();
3653 Ok(Value::Matrix(Box::new(
3654 Array2::from_shape_vec((r, c), data).unwrap(),
3655 )))
3656 }
3657 ("rng", 1) => match &args[0] {
3658 Value::Scalar(n) => {
3659 rng_seed(*n as u64);
3660 Ok(Value::Void)
3661 }
3662 Value::Str(s) | Value::StringObj(s) if s == "shuffle" => {
3663 rng_shuffle();
3664 Ok(Value::Void)
3665 }
3666 _ => Err("rng: argument must be a numeric seed or 'shuffle'".to_string()),
3667 },
3668 ("sum", 1) => {
3672 if matches!(&args[0], Value::Complex(_, _) | Value::ComplexMatrix(_)) {
3673 apply_cm_reduction(&args[0], |v| v.iter().copied().sum())
3674 } else {
3675 apply_reduction(&args[0], |v| v.iter().copied().sum())
3676 }
3677 }
3678 ("prod", 1) => {
3679 if matches!(&args[0], Value::Complex(_, _) | Value::ComplexMatrix(_)) {
3680 apply_cm_reduction(&args[0], |v| v.iter().copied().product())
3681 } else {
3682 apply_reduction(&args[0], |v| v.iter().copied().product())
3683 }
3684 }
3685 ("any", 1) => apply_reduction(&args[0], |v| {
3686 if v.iter().any(|&x| x != 0.0) {
3687 1.0
3688 } else {
3689 0.0
3690 }
3691 }),
3692 ("all", 1) => apply_reduction(&args[0], |v| {
3693 if v.iter().all(|&x| x != 0.0) {
3694 1.0
3695 } else {
3696 0.0
3697 }
3698 }),
3699 ("mean", 1) => {
3700 if matches!(&args[0], Value::Complex(_, _) | Value::ComplexMatrix(_)) {
3701 apply_cm_reduction(&args[0], |v| {
3702 if v.is_empty() {
3703 Complex::new(f64::NAN, 0.0)
3704 } else {
3705 v.iter().copied().sum::<Complex<f64>>() / v.len() as f64
3706 }
3707 })
3708 } else {
3709 apply_reduction(&args[0], |v| {
3710 if v.is_empty() {
3711 f64::NAN
3712 } else {
3713 v.iter().copied().sum::<f64>() / v.len() as f64
3714 }
3715 })
3716 }
3717 }
3718 ("min", 1) => apply_reduction(&args[0], |v| {
3721 v.iter().copied().fold(f64::INFINITY, f64::min)
3722 }),
3723 ("max", 1) => apply_reduction(&args[0], |v| {
3724 v.iter().copied().fold(f64::NEG_INFINITY, f64::max)
3725 }),
3726 ("norm", 1) => match &args[0] {
3728 Value::Void => Err("norm: not applicable to void".to_string()),
3729 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
3730 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt())),
3731 Value::Matrix(m) => {
3732 if m.nrows() <= 1 || m.ncols() <= 1 {
3733 Ok(Value::Scalar(m.iter().map(|x| x * x).sum::<f64>().sqrt()))
3735 } else {
3736 let (_, s, _) = svd_compute(m)?;
3738 Ok(Value::Scalar(s.first().copied().unwrap_or(0.0)))
3739 }
3740 }
3741 Value::ComplexMatrix(m) => Ok(Value::Scalar(
3742 m.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt(),
3743 )),
3744 Value::Str(_)
3745 | Value::StringObj(_)
3746 | Value::Lambda(_)
3747 | Value::Function(_)
3748 | Value::Tuple(_)
3749 | Value::Cell(_)
3750 | Value::Struct(_)
3751 | Value::StructArray(_)
3752 | Value::DateTime(_)
3753 | Value::Duration(_)
3754 | Value::DateTimeArray(_)
3755 | Value::DurationArray(_)
3756 | Value::Map(_) => Err("norm: not applicable to non-numeric values".to_string()),
3757 },
3758 ("norm", 2) => match &args[1] {
3759 Value::Str(s) | Value::StringObj(s) => match s.as_str() {
3760 "fro" => match &args[0] {
3761 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
3762 Value::Matrix(m) => {
3763 Ok(Value::Scalar(m.iter().map(|x| x * x).sum::<f64>().sqrt()))
3764 }
3765 _ => Err("norm: first argument must be numeric".to_string()),
3766 },
3767 other => Err(format!("norm: unknown norm type '{other}'")),
3768 },
3769 _ => {
3770 let p = scalar_arg(&args[1], name, 2)?;
3771 match &args[0] {
3772 Value::Void => Err("norm: not applicable to void".to_string()),
3773 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
3774 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt().powf(p))),
3775 Value::Matrix(m) => {
3776 if m.nrows() > 1 && m.ncols() > 1 {
3777 if (p - 2.0).abs() < 1e-15 {
3779 let (_, s, _) = svd_compute(m)?;
3780 return Ok(Value::Scalar(s.first().copied().unwrap_or(0.0)));
3781 } else if (p - 1.0).abs() < 1e-15 {
3782 let v = (0..m.ncols())
3784 .map(|j| m.column(j).iter().map(|&x| x.abs()).sum::<f64>())
3785 .fold(0.0_f64, f64::max);
3786 return Ok(Value::Scalar(v));
3787 } else if p == f64::INFINITY {
3788 let v = (0..m.nrows())
3790 .map(|i| m.row(i).iter().map(|&x| x.abs()).sum::<f64>())
3791 .fold(0.0_f64, f64::max);
3792 return Ok(Value::Scalar(v));
3793 }
3794 }
3795 if p == f64::INFINITY {
3797 Ok(Value::Scalar(
3798 m.iter().copied().fold(0.0_f64, |acc, x| acc.max(x.abs())),
3799 ))
3800 } else {
3801 Ok(Value::Scalar(
3802 m.iter().map(|x| x.abs().powf(p)).sum::<f64>().powf(1.0 / p),
3803 ))
3804 }
3805 }
3806 Value::ComplexMatrix(m) => Ok(Value::Scalar(
3807 m.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt().powf(p),
3808 )),
3809 Value::Str(_)
3810 | Value::StringObj(_)
3811 | Value::Lambda(_)
3812 | Value::Function(_)
3813 | Value::Tuple(_)
3814 | Value::Cell(_)
3815 | Value::Struct(_)
3816 | Value::StructArray(_)
3817 | Value::DateTime(_)
3818 | Value::Duration(_)
3819 | Value::DateTimeArray(_)
3820 | Value::DurationArray(_)
3821 | Value::Map(_) => {
3822 Err("norm: not applicable to non-numeric values".to_string())
3823 }
3824 }
3825 }
3826 },
3827 ("cumsum", 1) => apply_cumulative(&args[0], |acc, x| acc + x),
3829 ("cumprod", 1) => apply_cumulative(&args[0], |acc, x| acc * x),
3830 ("sort", 1) => match &args[0] {
3832 Value::Void => Err("sort: not applicable to void".to_string()),
3833 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3834 Value::Complex(_, _) => Err("sort: not applicable to complex values".to_string()),
3835 Value::ComplexMatrix(_) => Err("sort: not applicable to complex values".to_string()),
3836 Value::Str(_)
3837 | Value::StringObj(_)
3838 | Value::Lambda(_)
3839 | Value::Function(_)
3840 | Value::Tuple(_)
3841 | Value::Cell(_)
3842 | Value::Struct(_)
3843 | Value::StructArray(_)
3844 | Value::DateTime(_)
3845 | Value::Duration(_)
3846 | Value::DateTimeArray(_)
3847 | Value::DurationArray(_)
3848 | Value::Map(_) => Err("sort: not applicable to non-numeric values".to_string()),
3849 Value::Matrix(m) => {
3850 if m.nrows() > 1 && m.ncols() > 1 {
3851 return Err("sort: input must be a vector".to_string());
3852 }
3853 let mut vals: Vec<f64> = m.iter().copied().collect();
3854 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3855 Ok(Value::Matrix(Box::new(
3856 Array2::from_shape_vec(m.raw_dim(), vals).unwrap(),
3857 )))
3858 }
3859 },
3860 ("reshape", 3) => {
3862 let r = scalar_arg(&args[1], name, 2)? as usize;
3863 let c = scalar_arg(&args[2], name, 3)? as usize;
3864 match &args[0] {
3865 Value::Void => Err("reshape: not applicable to void".to_string()),
3866 Value::Scalar(n) => {
3867 if r * c != 1 {
3868 return Err(format!("reshape: cannot reshape 1 element into {r}x{c}"));
3869 }
3870 Ok(Value::Matrix(Box::new(
3871 Array2::from_shape_vec((1, 1), vec![*n]).unwrap(),
3872 )))
3873 }
3874 Value::Complex(_, _) => {
3875 Err("reshape: not applicable to complex values".to_string())
3876 }
3877 Value::ComplexMatrix(_) => {
3878 Err("reshape: not supported for complex matrices".to_string())
3879 }
3880 Value::Str(_)
3881 | Value::StringObj(_)
3882 | Value::Lambda(_)
3883 | Value::Function(_)
3884 | Value::Tuple(_)
3885 | Value::Cell(_)
3886 | Value::Struct(_)
3887 | Value::StructArray(_)
3888 | Value::DateTime(_)
3889 | Value::Duration(_)
3890 | Value::DateTimeArray(_)
3891 | Value::DurationArray(_)
3892 | Value::Map(_) => Err("reshape: not applicable to non-numeric values".to_string()),
3893 Value::Matrix(m) => {
3894 let total = m.len();
3895 if r * c != total {
3896 return Err(format!(
3897 "reshape: cannot reshape {total} elements into {r}x{c}"
3898 ));
3899 }
3900 let flat: Vec<f64> = (0..m.ncols())
3902 .flat_map(|col| (0..m.nrows()).map(move |row| m[[row, col]]))
3903 .collect();
3904 let mut result = Array2::<f64>::zeros((r, c));
3905 for (i, &v) in flat.iter().enumerate() {
3906 result[[i % r, i / r]] = v;
3907 }
3908 Ok(Value::Matrix(Box::new(result)))
3909 }
3910 }
3911 }
3912 ("fliplr", 1) => match &args[0] {
3914 Value::Void => Err(format!("{name}: not applicable to void")),
3915 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3916 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
3917 Value::ComplexMatrix(_) => Err(format!("{name}: not supported for complex matrices")),
3918 Value::Str(_)
3919 | Value::StringObj(_)
3920 | Value::Lambda(_)
3921 | Value::Function(_)
3922 | Value::Tuple(_)
3923 | Value::Cell(_)
3924 | Value::Struct(_)
3925 | Value::StructArray(_)
3926 | Value::DateTime(_)
3927 | Value::Duration(_)
3928 | Value::DateTimeArray(_)
3929 | Value::DurationArray(_)
3930 | Value::Map(_) => Err(format!("{name}: not applicable to non-numeric values")),
3931 Value::Matrix(m) => {
3932 let (nrows, ncols) = (m.nrows(), m.ncols());
3933 let mut result = m.clone();
3934 for r in 0..nrows {
3935 for c in 0..ncols / 2 {
3936 let tmp = result[[r, c]];
3937 result[[r, c]] = result[[r, ncols - 1 - c]];
3938 result[[r, ncols - 1 - c]] = tmp;
3939 }
3940 }
3941 Ok(Value::Matrix(result))
3942 }
3943 },
3944 ("flipud", 1) => match &args[0] {
3945 Value::Void => Err(format!("{name}: not applicable to void")),
3946 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3947 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
3948 Value::ComplexMatrix(_) => Err(format!("{name}: not supported for complex matrices")),
3949 Value::Str(_)
3950 | Value::StringObj(_)
3951 | Value::Lambda(_)
3952 | Value::Function(_)
3953 | Value::Tuple(_)
3954 | Value::Cell(_)
3955 | Value::Struct(_)
3956 | Value::StructArray(_)
3957 | Value::DateTime(_)
3958 | Value::Duration(_)
3959 | Value::DateTimeArray(_)
3960 | Value::DurationArray(_)
3961 | Value::Map(_) => Err(format!("{name}: not applicable to non-numeric values")),
3962 Value::Matrix(m) => {
3963 let (nrows, ncols) = (m.nrows(), m.ncols());
3964 let mut result = m.clone();
3965 for c in 0..ncols {
3966 for r in 0..nrows / 2 {
3967 let tmp = result[[r, c]];
3968 result[[r, c]] = result[[nrows - 1 - r, c]];
3969 result[[nrows - 1 - r, c]] = tmp;
3970 }
3971 }
3972 Ok(Value::Matrix(result))
3973 }
3974 },
3975 ("find", 1) => find_nonzero(&args[0], usize::MAX),
3977 ("find", 2) => {
3978 let k = scalar_arg(&args[1], name, 2)?;
3979 if k < 0.0 {
3980 return Err("find: k must be non-negative".to_string());
3981 }
3982 find_nonzero(&args[0], k as usize)
3983 }
3984 ("unique", 1) => match &args[0] {
3986 Value::Void => Err("unique: not applicable to void".to_string()),
3987 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3988 Value::Matrix(m) => {
3989 let mut vals: Vec<f64> = m.iter().copied().collect();
3990 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3991 let mut unique: Vec<f64> = Vec::new();
3992 for v in vals {
3993 if unique.last().is_none_or(|&last| last != v) {
3994 unique.push(v);
3995 }
3996 }
3997 let n = unique.len();
3998 Ok(Value::Matrix(Box::new(
3999 Array2::from_shape_vec((1, n), unique).unwrap(),
4000 )))
4001 }
4002 Value::Complex(_, _) => Err("unique: not applicable to complex values".to_string()),
4003 Value::ComplexMatrix(_) => Err("unique: not applicable to complex values".to_string()),
4004 Value::Str(_)
4005 | Value::StringObj(_)
4006 | Value::Lambda(_)
4007 | Value::Function(_)
4008 | Value::Tuple(_)
4009 | Value::Cell(_)
4010 | Value::Struct(_)
4011 | Value::StructArray(_)
4012 | Value::DateTime(_)
4013 | Value::Duration(_)
4014 | Value::DateTimeArray(_)
4015 | Value::DurationArray(_)
4016 | Value::Map(_) => Err("unique: not applicable to non-numeric values".to_string()),
4017 },
4018 ("std", 1) => apply_stat(&args[0], |s| stat_var_vec(s, false).sqrt(), "std"),
4020 ("std", 2) => {
4021 let w = scalar_arg(&args[1], name, 2)?;
4022 let population = w != 0.0;
4023 apply_stat(&args[0], |s| stat_var_vec(s, population).sqrt(), "std")
4024 }
4025 ("var", 1) => apply_stat(&args[0], |s| stat_var_vec(s, false), "var"),
4026 ("var", 2) => {
4027 let w = scalar_arg(&args[1], name, 2)?;
4028 let population = w != 0.0;
4029 apply_stat(&args[0], |s| stat_var_vec(s, population), "var")
4030 }
4031 ("cov", 1) => match &args[0] {
4032 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
4033 Value::Matrix(m) => {
4034 if m.nrows() == 1 || m.ncols() == 1 {
4035 let vals: Vec<f64> = m.iter().copied().collect();
4036 Ok(Value::Scalar(stat_var_vec(&vals, false)))
4037 } else {
4038 let (nobs, nvars) = (m.nrows(), m.ncols());
4039 if nobs < 2 {
4040 return Err("cov: need at least 2 observations".to_string());
4041 }
4042 let mut centered = m.clone();
4043 for c in 0..nvars {
4044 let col_mean: f64 = m.column(c).iter().sum::<f64>() / nobs as f64;
4045 for r in 0..nobs {
4046 centered[[r, c]] -= col_mean;
4047 }
4048 }
4049 let denom = (nobs - 1) as f64;
4050 let mut cov_mat = Array2::<f64>::zeros((nvars, nvars));
4051 for i in 0..nvars {
4052 for j in 0..nvars {
4053 let dot: f64 =
4054 (0..nobs).map(|r| centered[[r, i]] * centered[[r, j]]).sum();
4055 cov_mat[[i, j]] = dot / denom;
4056 }
4057 }
4058 Ok(Value::Matrix(Box::new(cov_mat)))
4059 }
4060 }
4061 _ => Err("cov: argument must be numeric".to_string()),
4062 },
4063 ("median", 1) => apply_stat(
4064 &args[0],
4065 |s| {
4066 if s.is_empty() {
4067 return f64::NAN;
4068 }
4069 let mut v = s.to_vec();
4070 v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4071 let n = v.len();
4072 if n % 2 == 0 {
4073 (v[n / 2 - 1] + v[n / 2]) / 2.0
4074 } else {
4075 v[n / 2]
4076 }
4077 },
4078 "median",
4079 ),
4080 ("mode", 1) => apply_stat(
4081 &args[0],
4082 |s| {
4083 if s.is_empty() {
4084 return f64::NAN;
4085 }
4086 let mut counts: std::collections::HashMap<u64, usize> =
4087 std::collections::HashMap::new();
4088 for &x in s {
4089 *counts.entry(x.to_bits()).or_insert(0) += 1;
4090 }
4091 let max_count = counts.values().copied().max().unwrap_or(0);
4092 let mut candidates: Vec<f64> = counts
4093 .iter()
4094 .filter(|&(_, &c)| c == max_count)
4095 .map(|(&bits, _)| f64::from_bits(bits))
4096 .collect();
4097 candidates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4098 candidates[0]
4099 },
4100 "mode",
4101 ),
4102 ("skewness", 1) => apply_stat(
4103 &args[0],
4104 |s| {
4105 let n = s.len();
4106 if n == 0 {
4107 return f64::NAN;
4108 }
4109 if n == 1 {
4110 return 0.0;
4111 }
4112 let mean = s.iter().sum::<f64>() / n as f64;
4113 let m2 = s.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
4114 if m2 == 0.0 {
4115 return f64::NAN;
4116 }
4117 let m3 = s.iter().map(|&x| (x - mean).powi(3)).sum::<f64>() / n as f64;
4118 m3 / m2.powf(1.5)
4119 },
4120 "skewness",
4121 ),
4122 ("kurtosis", 1) => apply_stat(
4123 &args[0],
4124 |s| {
4125 let n = s.len();
4126 if n < 2 {
4127 return f64::NAN;
4128 }
4129 let mean = s.iter().sum::<f64>() / n as f64;
4130 let m2 = s.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
4131 if m2 == 0.0 {
4132 return f64::NAN;
4133 }
4134 let m4 = s.iter().map(|&x| (x - mean).powi(4)).sum::<f64>() / n as f64;
4135 m4 / m2.powi(2)
4136 },
4137 "kurtosis",
4138 ),
4139 ("histc", 2) => {
4140 let vals = numeric_vec(&args[0], name)?;
4141 let edges = numeric_vec(&args[1], name)?;
4142 if edges.is_empty() {
4143 return Err("histc: edges must not be empty".to_string());
4144 }
4145 let n_edges = edges.len();
4146 let mut counts = vec![0.0f64; n_edges];
4147 for &v in &vals {
4148 let last = n_edges - 1;
4150 if v == edges[last] {
4151 counts[last] += 1.0;
4152 } else {
4153 for i in 0..last {
4154 if v >= edges[i] && v < edges[i + 1] {
4155 counts[i] += 1.0;
4156 break;
4157 }
4158 }
4159 }
4160 }
4161 Ok(Value::Matrix(Box::new(
4162 Array2::from_shape_vec((1, n_edges), counts).unwrap(),
4163 )))
4164 }
4165 ("prctile", 2) => {
4167 let p_vals = numeric_vec(&args[1], name)?;
4168 let n_p = p_vals.len();
4169
4170 let compute_col = |vals: &[f64]| -> Vec<f64> {
4172 let mut s = vals.to_vec();
4173 s.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4174 p_vals.iter().map(|&p| percentile_sorted(&s, p)).collect()
4175 };
4176
4177 match &args[0] {
4178 Value::Scalar(n) => {
4179 let pr = compute_col(&[*n]);
4180 if n_p == 1 {
4181 Ok(Value::Scalar(pr[0]))
4182 } else {
4183 Ok(Value::Matrix(Box::new(
4184 Array2::from_shape_vec((1, n_p), pr).unwrap(),
4185 )))
4186 }
4187 }
4188 Value::Matrix(m) if m.nrows() == 1 || m.ncols() == 1 => {
4189 let vals: Vec<f64> = m.iter().copied().collect();
4190 let pr = compute_col(&vals);
4191 if n_p == 1 {
4192 Ok(Value::Scalar(pr[0]))
4193 } else {
4194 Ok(Value::Matrix(Box::new(
4195 Array2::from_shape_vec((1, n_p), pr).unwrap(),
4196 )))
4197 }
4198 }
4199 Value::Matrix(m) => {
4200 let ncols = m.ncols();
4202 let mut result = Array2::<f64>::zeros((n_p, ncols));
4203 for j in 0..ncols {
4204 let col: Vec<f64> = m.column(j).iter().copied().collect();
4205 let pr = compute_col(&col);
4206 for (i, &v) in pr.iter().enumerate() {
4207 result[[i, j]] = v;
4208 }
4209 }
4210 if n_p == 1 {
4211 let row: Vec<f64> = result.row(0).iter().copied().collect();
4212 Ok(Value::Matrix(Box::new(
4213 Array2::from_shape_vec((1, ncols), row).unwrap(),
4214 )))
4215 } else {
4216 Ok(Value::Matrix(Box::new(result)))
4217 }
4218 }
4219 _ => Err("prctile: first argument must be numeric".to_string()),
4220 }
4221 }
4222 ("iqr", 1) => apply_stat(
4223 &args[0],
4224 |s| {
4225 let mut sorted = s.to_vec();
4226 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
4227 percentile_sorted(&sorted, 75.0) - percentile_sorted(&sorted, 25.0)
4228 },
4229 "iqr",
4230 ),
4231 ("zscore", 1) => match &args[0] {
4232 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
4233 Value::Matrix(m) => {
4234 if m.nrows() == 1 || m.ncols() == 1 {
4235 let vals: Vec<f64> = m.iter().copied().collect();
4236 let n = vals.len() as f64;
4237 let mean = vals.iter().sum::<f64>() / n;
4238 let s = stat_var_vec(&vals, false).sqrt();
4239 let result: Vec<f64> = vals
4240 .iter()
4241 .map(|&x| if s == 0.0 { 0.0 } else { (x - mean) / s })
4242 .collect();
4243 Ok(Value::Matrix(Box::new(
4244 Array2::from_shape_vec(m.raw_dim(), result).unwrap(),
4245 )))
4246 } else {
4247 let (nrows, ncols) = (m.nrows(), m.ncols());
4248 let mut result = m.clone();
4249 for j in 0..ncols {
4250 let col: Vec<f64> = m.column(j).iter().copied().collect();
4251 let mean = col.iter().sum::<f64>() / col.len() as f64;
4252 let s = stat_var_vec(&col, false).sqrt();
4253 for i in 0..nrows {
4254 result[[i, j]] = if s == 0.0 {
4255 0.0
4256 } else {
4257 (m[[i, j]] - mean) / s
4258 };
4259 }
4260 }
4261 Ok(Value::Matrix(result))
4262 }
4263 }
4264 _ => Err("zscore: argument must be numeric".to_string()),
4265 },
4266 ("diag", 1) => match &args[0] {
4268 Value::Scalar(n) => Ok(Value::Matrix(Box::new(Array2::from_elem((1, 1), *n)))),
4269 Value::Matrix(m) => {
4270 let (rows, cols) = (m.nrows(), m.ncols());
4271 if rows == 1 || cols == 1 {
4272 let v: Vec<f64> = m.iter().copied().collect();
4274 let n = v.len();
4275 let mut result = Array2::<f64>::zeros((n, n));
4276 for (i, &val) in v.iter().enumerate() {
4277 result[[i, i]] = val;
4278 }
4279 Ok(Value::Matrix(Box::new(result)))
4280 } else {
4281 let n = rows.min(cols);
4283 let d: Vec<f64> = (0..n).map(|i| m[[i, i]]).collect();
4284 Ok(Value::Matrix(Box::new(
4285 Array2::from_shape_vec((n, 1), d).unwrap(),
4286 )))
4287 }
4288 }
4289 Value::Void => Err("diag: not applicable to void".to_string()),
4290 Value::Complex(re, im) => {
4291 let mut result = Array2::<Complex<f64>>::zeros((1, 1));
4292 result[[0, 0]] = Complex::new(*re, *im);
4293 Ok(Value::ComplexMatrix(Box::new(result)))
4294 }
4295 Value::ComplexMatrix(m) => {
4296 let (rows, cols) = (m.nrows(), m.ncols());
4297 if rows == 1 || cols == 1 {
4298 let v: Vec<Complex<f64>> = m.iter().copied().collect();
4299 let n = v.len();
4300 let mut result = Array2::<Complex<f64>>::zeros((n, n));
4301 for (i, &val) in v.iter().enumerate() {
4302 result[[i, i]] = val;
4303 }
4304 Ok(Value::ComplexMatrix(Box::new(result)))
4305 } else {
4306 let n = rows.min(cols);
4307 let d: Vec<Complex<f64>> = (0..n).map(|i| m[[i, i]]).collect();
4308 Ok(Value::ComplexMatrix(Box::new(
4309 Array2::from_shape_vec((n, 1), d).unwrap(),
4310 )))
4311 }
4312 }
4313 Value::Str(_)
4314 | Value::StringObj(_)
4315 | Value::Lambda(_)
4316 | Value::Function(_)
4317 | Value::Tuple(_)
4318 | Value::Cell(_)
4319 | Value::Struct(_)
4320 | Value::StructArray(_)
4321 | Value::DateTime(_)
4322 | Value::Duration(_)
4323 | Value::DateTimeArray(_)
4324 | Value::DurationArray(_)
4325 | Value::Map(_) => Err("diag: not applicable to non-numeric values".to_string()),
4326 },
4327
4328 ("real", 1) => match &args[0] {
4331 Value::Void => Err("real: not applicable to void".to_string()),
4332 Value::Scalar(n) => Ok(Value::Scalar(*n)),
4333 Value::Complex(re, _) => Ok(Value::Scalar(*re)),
4334 Value::Matrix(m) => Ok(Value::Matrix(m.clone())),
4335 Value::ComplexMatrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|c| c.re)))),
4336 Value::Str(_)
4337 | Value::StringObj(_)
4338 | Value::Lambda(_)
4339 | Value::Function(_)
4340 | Value::Tuple(_)
4341 | Value::Cell(_)
4342 | Value::Struct(_)
4343 | Value::StructArray(_)
4344 | Value::DateTime(_)
4345 | Value::Duration(_)
4346 | Value::DateTimeArray(_)
4347 | Value::DurationArray(_)
4348 | Value::Map(_) => Err("real: not applicable to non-numeric values".to_string()),
4349 },
4350 ("imag", 1) => match &args[0] {
4352 Value::Void => Err("imag: not applicable to void".to_string()),
4353 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
4354 Value::Complex(_, im) => Ok(Value::Scalar(*im)),
4355 Value::Matrix(m) => Ok(Value::Matrix(Box::new(Array2::zeros(m.raw_dim())))),
4356 Value::ComplexMatrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|c| c.im)))),
4357 Value::Str(_)
4358 | Value::StringObj(_)
4359 | Value::Lambda(_)
4360 | Value::Function(_)
4361 | Value::Tuple(_)
4362 | Value::Cell(_)
4363 | Value::Struct(_)
4364 | Value::StructArray(_)
4365 | Value::DateTime(_)
4366 | Value::Duration(_)
4367 | Value::DateTimeArray(_)
4368 | Value::DurationArray(_)
4369 | Value::Map(_) => Err("imag: not applicable to non-numeric values".to_string()),
4370 },
4371 ("abs", 1) => match &args[0] {
4373 Value::Void => Err("abs: not applicable to void".to_string()),
4374 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
4375 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt())),
4376 Value::Matrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|x| x.abs())))),
4377 Value::ComplexMatrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|c| c.norm())))),
4378 Value::Str(_)
4379 | Value::StringObj(_)
4380 | Value::Lambda(_)
4381 | Value::Function(_)
4382 | Value::Tuple(_)
4383 | Value::Cell(_)
4384 | Value::Struct(_)
4385 | Value::StructArray(_)
4386 | Value::DateTime(_)
4387 | Value::Duration(_)
4388 | Value::DateTimeArray(_)
4389 | Value::DurationArray(_)
4390 | Value::Map(_) => Err("abs: not applicable to non-numeric values".to_string()),
4391 },
4392 ("angle", 1) => match &args[0] {
4394 Value::Void => Err("angle: not applicable to void".to_string()),
4395 Value::Scalar(n) => Ok(Value::Scalar(if *n >= 0.0 {
4396 0.0
4397 } else {
4398 std::f64::consts::PI
4399 })),
4400 Value::Complex(re, im) => Ok(Value::Scalar(im.atan2(*re))),
4401 Value::Matrix(m) => {
4402 Ok(Value::Matrix(Box::new(m.mapv(|x| {
4403 if x >= 0.0 { 0.0 } else { std::f64::consts::PI }
4404 }))))
4405 }
4406 Value::ComplexMatrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|c| c.im.atan2(c.re))))),
4407 Value::Str(_)
4408 | Value::StringObj(_)
4409 | Value::Lambda(_)
4410 | Value::Function(_)
4411 | Value::Tuple(_)
4412 | Value::Cell(_)
4413 | Value::Struct(_)
4414 | Value::StructArray(_)
4415 | Value::DateTime(_)
4416 | Value::Duration(_)
4417 | Value::DateTimeArray(_)
4418 | Value::DurationArray(_)
4419 | Value::Map(_) => Err("angle: not applicable to non-numeric values".to_string()),
4420 },
4421 ("conj", 1) => match &args[0] {
4423 Value::Void => Err("conj: not applicable to void".to_string()),
4424 Value::Scalar(n) => Ok(Value::Scalar(*n)),
4425 Value::Complex(re, im) => Ok(make_complex(*re, -*im)),
4426 Value::Matrix(m) => Ok(Value::Matrix(m.clone())),
4427 Value::ComplexMatrix(m) => Ok(Value::ComplexMatrix(Box::new(m.mapv(|c| c.conj())))),
4428 Value::Str(_)
4429 | Value::StringObj(_)
4430 | Value::Lambda(_)
4431 | Value::Function(_)
4432 | Value::Tuple(_)
4433 | Value::Cell(_)
4434 | Value::Struct(_)
4435 | Value::StructArray(_)
4436 | Value::DateTime(_)
4437 | Value::Duration(_)
4438 | Value::DateTimeArray(_)
4439 | Value::DurationArray(_)
4440 | Value::Map(_) => Err("conj: not applicable to non-numeric values".to_string()),
4441 },
4442 ("complex", 2) => {
4444 let re = scalar_arg(&args[0], name, 1)?;
4445 let im = scalar_arg(&args[1], name, 2)?;
4446 Ok(make_complex(re, im))
4447 }
4448 ("isreal", 1) => match &args[0] {
4450 Value::Void => Ok(Value::Scalar(0.0)),
4451 Value::Scalar(_) => Ok(Value::Scalar(1.0)),
4452 Value::Complex(_, im) => Ok(Value::Scalar(if *im == 0.0 { 1.0 } else { 0.0 })),
4453 Value::Matrix(_) => Ok(Value::Scalar(1.0)),
4454 Value::ComplexMatrix(_) => Ok(Value::Scalar(0.0)),
4455 Value::Str(_) | Value::StringObj(_) => Ok(Value::Scalar(0.0)),
4457 Value::Lambda(_)
4458 | Value::Function(_)
4459 | Value::Tuple(_)
4460 | Value::Cell(_)
4461 | Value::Struct(_)
4462 | Value::StructArray(_)
4463 | Value::DateTime(_)
4464 | Value::Duration(_)
4465 | Value::DateTimeArray(_)
4466 | Value::DurationArray(_)
4467 | Value::Map(_) => Ok(Value::Scalar(0.0)),
4468 },
4469 ("num2str", 1) => match &args[0] {
4472 Value::Void => Err("num2str: not applicable to void".to_string()),
4473 Value::Str(s) => Ok(Value::Str(s.clone())),
4474 Value::StringObj(s) => Ok(Value::Str(s.clone())),
4475 Value::Scalar(n) => Ok(Value::Str(fmt_auto_sig(*n, 5))),
4476 Value::Complex(re, im) => Ok(Value::Str(format_complex(*re, *im, &FormatMode::Short))),
4477 Value::Matrix(m) => {
4478 let s = m
4479 .iter()
4480 .map(|x| fmt_auto_sig(*x, 5))
4481 .collect::<Vec<_>>()
4482 .join(" ");
4483 Ok(Value::Str(s))
4484 }
4485 Value::ComplexMatrix(_) => {
4486 Err("num2str: not supported for complex matrices".to_string())
4487 }
4488 Value::Lambda(_)
4489 | Value::Function(_)
4490 | Value::Tuple(_)
4491 | Value::Cell(_)
4492 | Value::Struct(_)
4493 | Value::StructArray(_)
4494 | Value::DateTime(_)
4495 | Value::Duration(_)
4496 | Value::DateTimeArray(_)
4497 | Value::DurationArray(_)
4498 | Value::Map(_) => Err("num2str: not applicable to this type".to_string()),
4499 },
4500 ("num2str", 2) => {
4502 let n = scalar_arg(&args[1], name, 2)? as usize;
4503 match &args[0] {
4504 Value::Void => Err("num2str: not applicable to void".to_string()),
4505 Value::Str(s) => Ok(Value::Str(s.clone())),
4506 Value::StringObj(s) => Ok(Value::Str(s.clone())),
4507 Value::Scalar(v) => Ok(Value::Str(fmt_auto_sig(*v, n))),
4508 Value::Complex(re, im) => {
4509 Ok(Value::Str(format_complex(*re, *im, &FormatMode::Custom(n))))
4510 }
4511 Value::Matrix(m) => {
4512 let s = m
4513 .iter()
4514 .map(|x| fmt_auto_sig(*x, n))
4515 .collect::<Vec<_>>()
4516 .join(" ");
4517 Ok(Value::Str(s))
4518 }
4519 Value::ComplexMatrix(_) => {
4520 Err("num2str: not supported for complex matrices".to_string())
4521 }
4522 Value::Lambda(_)
4523 | Value::Function(_)
4524 | Value::Tuple(_)
4525 | Value::Cell(_)
4526 | Value::Struct(_)
4527 | Value::StructArray(_)
4528 | Value::DateTime(_)
4529 | Value::Duration(_)
4530 | Value::DateTimeArray(_)
4531 | Value::DurationArray(_)
4532 | Value::Map(_) => Err("num2str: not applicable to this type".to_string()),
4533 }
4534 }
4535 ("str2double", 1) => {
4537 let s = string_arg(&args[0], name, 1)?;
4538 match s.trim().parse::<f64>() {
4539 Ok(n) => Ok(Value::Scalar(n)),
4540 Err(_) => Ok(Value::Scalar(f64::NAN)),
4541 }
4542 }
4543 ("str2num", 1) => {
4545 let s = string_arg(&args[0], name, 1)?;
4546 s.trim()
4547 .parse::<f64>()
4548 .map(Value::Scalar)
4549 .map_err(|_| format!("str2num: cannot convert '{}' to number", s.trim()))
4550 }
4551 ("strcat", n) if n >= 2 => {
4553 let mut result = String::new();
4554 let mut any_obj = false;
4555 for (i, arg) in args.iter().enumerate() {
4556 match arg {
4557 Value::Str(s) => result.push_str(s.trim_end()),
4558 Value::StringObj(s) => {
4559 result.push_str(s);
4560 any_obj = true;
4561 }
4562 _ => return Err(format!("strcat: argument {} must be a string", i + 1)),
4563 }
4564 }
4565 if any_obj {
4566 Ok(Value::StringObj(result))
4567 } else {
4568 Ok(Value::Str(result))
4569 }
4570 }
4571 ("ischar", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::Str(_)) {
4573 1.0
4574 } else {
4575 0.0
4576 })),
4577 ("isstring", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::StringObj(_)) {
4579 1.0
4580 } else {
4581 0.0
4582 })),
4583 ("struct", _) => {
4586 if !args.len().is_multiple_of(2) {
4587 return Err(
4588 "struct: requires an even number of arguments (name, value, ...)".to_string(),
4589 );
4590 }
4591 let mut map = IndexMap::new();
4592 for pair in args.chunks(2) {
4593 let key = match &pair[0] {
4594 Value::Str(s) | Value::StringObj(s) => s.clone(),
4595 _ => return Err("struct: field names must be strings".to_string()),
4596 };
4597 map.insert(key, pair[1].clone());
4598 }
4599 Ok(Value::Struct(Box::new(map)))
4600 }
4601 ("fieldnames", 1) => match &args[0] {
4603 Value::Struct(map) => {
4604 let names: Vec<Value> = map.keys().map(|k| Value::Str(k.clone())).collect();
4605 Ok(Value::Cell(Box::new(names)))
4606 }
4607 Value::StructArray(arr) => {
4608 let names: Vec<Value> = arr
4610 .first()
4611 .map(|m| m.keys().map(|k| Value::Str(k.clone())).collect())
4612 .unwrap_or_default();
4613 Ok(Value::Cell(Box::new(names)))
4614 }
4615 _ => Err("fieldnames: argument must be a struct".to_string()),
4616 },
4617 ("isfield", 2) => {
4619 let field = match &args[1] {
4620 Value::Str(s) | Value::StringObj(s) => s.clone(),
4621 _ => return Err("isfield: second argument must be a string".to_string()),
4622 };
4623 Ok(Value::Scalar(match &args[0] {
4624 Value::Struct(map) if map.contains_key(&field) => 1.0,
4625 Value::StructArray(arr) if arr.first().is_some_and(|m| m.contains_key(&field)) => {
4626 1.0
4627 }
4628 _ => 0.0,
4629 }))
4630 }
4631 ("isKey", 2) => {
4633 let key = match &args[1] {
4634 Value::Str(s) | Value::StringObj(s) => s.clone(),
4635 _ => return Err("isKey: second argument must be a string key".to_string()),
4636 };
4637 match &args[0] {
4638 Value::Map(map) => Ok(Value::Scalar(if map.contains_key(&key) {
4639 1.0
4640 } else {
4641 0.0
4642 })),
4643 _ => Err("isKey: first argument must be a containers.Map".to_string()),
4644 }
4645 }
4646 ("keys", 1) => match &args[0] {
4647 Value::Map(map) => {
4648 let mut sorted_keys: Vec<&String> = map.keys().collect();
4649 sorted_keys.sort();
4650 Ok(Value::Cell(Box::new(
4651 sorted_keys
4652 .into_iter()
4653 .map(|k| Value::Str(k.clone()))
4654 .collect(),
4655 )))
4656 }
4657 _ => Err("keys: argument must be a containers.Map".to_string()),
4658 },
4659 ("values", 1) => match &args[0] {
4660 Value::Map(map) => {
4661 let mut pairs: Vec<(&String, &Value)> = map.iter().collect();
4662 pairs.sort_by_key(|(k, _)| *k);
4663 Ok(Value::Cell(Box::new(
4664 pairs.into_iter().map(|(_, v)| v.clone()).collect(),
4665 )))
4666 }
4667 _ => Err("values: argument must be a containers.Map".to_string()),
4668 },
4669 ("rmfield", 2) => {
4671 let field = match &args[1] {
4672 Value::Str(s) | Value::StringObj(s) => s.clone(),
4673 _ => return Err("rmfield: second argument must be a string".to_string()),
4674 };
4675 match &args[0] {
4676 Value::Struct(map) => {
4677 if !map.contains_key(&field) {
4678 return Err(format!("rmfield: field '{field}' does not exist"));
4679 }
4680 let mut updated = map.clone();
4681 updated.shift_remove(&field);
4682 Ok(Value::Struct(updated))
4683 }
4684 Value::StructArray(arr) => {
4685 let updated: Result<Vec<_>, _> = arr
4686 .iter()
4687 .map(|m| {
4688 if !m.contains_key(&field) {
4689 return Err(format!("rmfield: field '{field}' does not exist"));
4690 }
4691 let mut m2 = m.clone();
4692 m2.shift_remove(&field);
4693 Ok(m2)
4694 })
4695 .collect();
4696 Ok(Value::StructArray(Box::new(updated?)))
4697 }
4698 _ => Err("rmfield: first argument must be a struct".to_string()),
4699 }
4700 }
4701 ("isstruct", 1) => Ok(Value::Scalar(
4703 if matches!(&args[0], Value::Struct(_) | Value::StructArray(_)) {
4704 1.0
4705 } else {
4706 0.0
4707 },
4708 )),
4709 ("isempty", 1) => {
4713 let empty = match &args[0] {
4714 Value::Matrix(m) => m.is_empty(),
4715 Value::Str(s) | Value::StringObj(s) => s.is_empty(),
4716 Value::Cell(v) => v.is_empty(),
4717 Value::Void => true,
4718 _ => false,
4719 };
4720 Ok(Value::Scalar(if empty { 1.0 } else { 0.0 }))
4721 }
4722 ("iscell", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::Cell(_)) {
4724 1.0
4725 } else {
4726 0.0
4727 })),
4728 ("cell", 1) => {
4730 let n = scalar_arg(&args[0], name, 1)? as usize;
4731 Ok(Value::Cell(Box::new(vec![Value::Scalar(0.0); n])))
4732 }
4733 ("cell", 2) => {
4735 let m = scalar_arg(&args[0], name, 1)? as usize;
4736 let n = scalar_arg(&args[1], name, 2)? as usize;
4737 Ok(Value::Cell(Box::new(vec![Value::Scalar(0.0); m * n])))
4738 }
4739 ("cellfun", 2) => {
4742 let f = args[0].clone();
4743 match &args[1] {
4744 Value::Cell(elems) => {
4745 let elems: Vec<Value> = (**elems).clone();
4746 let mut results = Vec::with_capacity(elems.len());
4747 for elem in &elems {
4748 let result =
4749 call_function_value(&f, std::slice::from_ref(elem), io.as_deref_mut())?;
4750 results.push(result);
4751 }
4752 let all_scalar = results.iter().all(|v| matches!(v, Value::Scalar(_)));
4754 if all_scalar {
4755 let vals: Vec<f64> = results
4756 .iter()
4757 .map(|v| {
4758 if let Value::Scalar(n) = v {
4759 *n
4760 } else {
4761 unreachable!()
4762 }
4763 })
4764 .collect();
4765 let n = vals.len();
4766 if n == 0 {
4767 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
4768 } else {
4769 Ok(Value::Matrix(Box::new(
4770 Array2::from_shape_vec((1, n), vals).unwrap(),
4771 )))
4772 }
4773 } else {
4774 Ok(Value::Cell(Box::new(results)))
4775 }
4776 }
4777 _ => Err("cellfun: second argument must be a cell array".to_string()),
4778 }
4779 }
4780 ("arrayfun", 2) => {
4783 let f = args[0].clone();
4784 match &args[1] {
4785 Value::Matrix(m) => {
4786 let m = m.clone();
4787 let mut flat = Vec::with_capacity(m.len());
4788 for col in 0..m.ncols() {
4790 for row in 0..m.nrows() {
4791 let elem = Value::Scalar(m[[row, col]]);
4792 let result = call_function_value(&f, &[elem], io.as_deref_mut())?;
4793 match result {
4794 Value::Scalar(n) => flat.push(n),
4795 _ => {
4796 return Err(
4797 "arrayfun: function must return a scalar".to_string()
4798 );
4799 }
4800 }
4801 }
4802 }
4803 Ok(Value::Matrix(Box::new(
4804 Array2::from_shape_vec((m.nrows(), m.ncols()), flat).unwrap(),
4805 )))
4806 }
4807 Value::Scalar(n) => {
4808 let elem = Value::Scalar(*n);
4809 let result = call_function_value(&f, &[elem], io.as_deref_mut())?;
4810 Ok(result)
4811 }
4812 _ => {
4813 Err("arrayfun: second argument must be a numeric matrix or scalar".to_string())
4814 }
4815 }
4816 }
4817 ("lower", 1) => match &args[0] {
4819 Value::Str(s) => Ok(Value::Str(s.to_lowercase())),
4820 Value::StringObj(s) => Ok(Value::StringObj(s.to_lowercase())),
4821 _ => Err("lower: argument must be a string".to_string()),
4822 },
4823 ("upper", 1) => match &args[0] {
4825 Value::Str(s) => Ok(Value::Str(s.to_uppercase())),
4826 Value::StringObj(s) => Ok(Value::StringObj(s.to_uppercase())),
4827 _ => Err("upper: argument must be a string".to_string()),
4828 },
4829 ("strtrim", 1) => match &args[0] {
4831 Value::Str(s) => Ok(Value::Str(s.trim().to_string())),
4832 Value::StringObj(s) => Ok(Value::StringObj(s.trim().to_string())),
4833 _ => Err("strtrim: argument must be a string".to_string()),
4834 },
4835 ("strrep", 3) => {
4837 let s = string_arg(&args[0], name, 1)?.to_string();
4838 let old = string_arg(&args[1], name, 2)?;
4839 let new = string_arg(&args[2], name, 3)?;
4840 let result = s.replace(old, new);
4841 match &args[0] {
4842 Value::StringObj(_) => Ok(Value::StringObj(result)),
4843 _ => Ok(Value::Str(result)),
4844 }
4845 }
4846 ("strcmp", 2) => {
4848 let a = string_arg(&args[0], name, 1)?;
4849 let b = string_arg(&args[1], name, 2)?;
4850 Ok(Value::Scalar(bool_to_f64(a == b)))
4851 }
4852 ("strcmpi", 2) => {
4854 let a = string_arg(&args[0], name, 1)?.to_lowercase();
4855 let b = string_arg(&args[1], name, 2)?.to_lowercase();
4856 Ok(Value::Scalar(bool_to_f64(a == b)))
4857 }
4858 ("disp", 1) => {
4860 use std::io::Write;
4861 let mode = get_display_fmt();
4862 let output = match &args[0] {
4863 Value::Str(s) | Value::StringObj(s) => format!("{s}\n"),
4864 v => match format_value_full(v, &mode) {
4865 Some(block) => format!("{block}\n\n"),
4866 None => format!("{}\n", format_value(v, get_display_base(), &mode)),
4867 },
4868 };
4869 match io {
4870 Some(ctx) => ctx.write_to_fd(1, &output)?,
4871 None => {
4872 print!("{output}");
4873 if output.contains('\n') {
4874 std::io::stdout().flush().ok();
4875 }
4876 }
4877 }
4878 Ok(Value::Void)
4879 }
4880 ("sprintf", n) if n >= 1 => {
4882 let fmt = string_arg(&args[0], name, 1)?.to_string();
4883 let result = format_printf(&fmt, &args[1..])?;
4884 Ok(Value::Str(result))
4885 }
4886 ("fprintf", n) if n >= 1 => {
4888 let (fd, fmt_idx) = match &args[0] {
4890 Value::Scalar(n) => (*n as i32, 1),
4891 _ => (1, 0),
4892 };
4893 if fmt_idx >= args.len() {
4894 return Err("fprintf: missing format string".to_string());
4895 }
4896 let fmt = string_arg(&args[fmt_idx], name, fmt_idx + 1)?.to_string();
4897 let output = format_printf(&fmt, &args[fmt_idx + 1..])?;
4898 match io {
4899 Some(ctx) => ctx.write_to_fd(fd, &output)?,
4900 None => {
4901 if fd == 1 {
4903 use std::io::Write;
4904 print!("{output}");
4905 if output.contains('\n') {
4906 std::io::stdout().flush().ok();
4907 }
4908 } else {
4909 return Err("fprintf: file I/O not available in this context".to_string());
4910 }
4911 }
4912 }
4913 Ok(Value::Void)
4914 }
4915 ("fopen", 2) => {
4917 let path = string_arg(&args[0], name, 1)?;
4918 let mode = string_arg(&args[1], name, 2)?;
4919 match io {
4920 Some(ctx) => Ok(Value::Scalar(ctx.fopen(path, mode) as f64)),
4921 None => Err("fopen: file I/O not available in this context".to_string()),
4922 }
4923 }
4924 ("fclose", 1) => match &args[0] {
4926 Value::Str(s) if s == "all" => {
4927 if let Some(ctx) = io {
4928 ctx.fclose_all();
4929 }
4930 Ok(Value::Scalar(0.0))
4931 }
4932 _ => {
4933 let fd = scalar_arg(&args[0], name, 1)? as i32;
4934 match io {
4935 Some(ctx) => Ok(Value::Scalar(ctx.fclose(fd) as f64)),
4936 None => Err("fclose: file I/O not available in this context".to_string()),
4937 }
4938 }
4939 },
4940 ("fgetl", 1) => {
4942 let fd = scalar_arg(&args[0], name, 1)? as i32;
4943 match io {
4944 Some(ctx) => match ctx.fgetl(fd) {
4945 Some(line) => Ok(Value::Str(line)),
4946 None => Ok(Value::Scalar(-1.0)),
4947 },
4948 None => Err("fgetl: file I/O not available in this context".to_string()),
4949 }
4950 }
4951 ("fgets", 1) => {
4953 let fd = scalar_arg(&args[0], name, 1)? as i32;
4954 match io {
4955 Some(ctx) => match ctx.fgets(fd) {
4956 Some(line) => Ok(Value::Str(line)),
4957 None => Ok(Value::Scalar(-1.0)),
4958 },
4959 None => Err("fgets: file I/O not available in this context".to_string()),
4960 }
4961 }
4962 ("isfile", 1) => {
4964 let path = string_arg(&args[0], name, 1)?;
4965 let is_file = std::fs::metadata(path)
4966 .map(|m| m.is_file())
4967 .unwrap_or(false);
4968 Ok(Value::Scalar(bool_to_f64(is_file)))
4969 }
4970 ("isfolder", 1) => {
4972 let path = string_arg(&args[0], name, 1)?;
4973 let is_dir = std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false);
4974 Ok(Value::Scalar(bool_to_f64(is_dir)))
4975 }
4976 ("dir", _) => {
4978 let path = if args.is_empty() {
4979 "."
4980 } else {
4981 string_arg(&args[0], "dir", 1)?
4982 };
4983 Ok(dir_impl(path))
4984 }
4985 ("genpath", 1) => {
4987 let root = string_arg(&args[0], name, 1)?;
4988 let sep = if cfg!(windows) { ';' } else { ':' };
4989 let mut dirs: Vec<String> = Vec::new();
4990 let mut stack = vec![std::path::PathBuf::from(root)];
4991 while let Some(dir) = stack.pop() {
4992 if !dir.is_dir() {
4993 continue;
4994 }
4995 dirs.push(dir.to_string_lossy().into_owned());
4996 if let Ok(entries) = std::fs::read_dir(&dir) {
4997 let mut children: Vec<std::path::PathBuf> = entries
4998 .filter_map(|e| e.ok())
4999 .map(|e| e.path())
5000 .filter(|p| p.is_dir())
5001 .collect();
5002 children.sort();
5003 children.reverse();
5004 stack.extend(children);
5005 }
5006 }
5007 Ok(Value::Str(dirs.join(&sep.to_string())))
5008 }
5009 ("pwd", _) => {
5011 let cwd = std::env::current_dir()
5012 .map(|p| p.to_string_lossy().into_owned())
5013 .unwrap_or_default();
5014 Ok(Value::Str(cwd))
5015 }
5016 ("exist", 1) => {
5018 let name_arg = string_arg(&args[0], name, 1)?;
5019 if env.contains_key(name_arg) {
5020 Ok(Value::Scalar(1.0))
5021 } else if std::path::Path::new(name_arg).is_file() {
5022 Ok(Value::Scalar(2.0))
5023 } else {
5024 Ok(Value::Scalar(0.0))
5025 }
5026 }
5027 ("exist", 2) => {
5029 let name_arg = string_arg(&args[0], name, 1)?;
5030 let kind = string_arg(&args[1], name, 2)?;
5031 match kind {
5032 "var" => Ok(Value::Scalar(if env.contains_key(name_arg) {
5033 1.0
5034 } else {
5035 0.0
5036 })),
5037 "file" => Ok(Value::Scalar(if std::path::Path::new(name_arg).is_file() {
5038 2.0
5039 } else {
5040 0.0
5041 })),
5042 other => Err(format!(
5043 "exist: unknown type '{other}', expected 'var' or 'file'"
5044 )),
5045 }
5046 }
5047 ("dlmread", 1) => {
5049 let path = string_arg(&args[0], name, 1)?.to_string();
5050 dlmread_impl(&path, None)
5051 }
5052 ("dlmread", 2) => {
5053 let path = string_arg(&args[0], name, 1)?.to_string();
5054 let delim = interpret_delim(string_arg(&args[1], name, 2)?);
5055 dlmread_impl(&path, Some(delim))
5056 }
5057 ("dlmwrite", 2) => {
5059 let path = string_arg(&args[0], name, 1)?.to_string();
5060 dlmwrite_impl(&path, &args[1], None)
5061 }
5062 ("dlmwrite", 3) => {
5063 let path = string_arg(&args[0], name, 1)?.to_string();
5064 let delim = interpret_delim(string_arg(&args[2], name, 3)?);
5065 dlmwrite_impl(&path, &args[1], Some(delim))
5066 }
5067 ("readmatrix", n) if n == 1 || n == 3 => {
5069 let path = string_arg(&args[0], name, 1)?.to_string();
5070 let delim = parse_delimiter_opt(name, args, 1)?;
5071 readmatrix_impl(&path, delim)
5072 }
5073 ("readtable", n) if n == 1 || n == 3 => {
5075 let path = string_arg(&args[0], name, 1)?.to_string();
5076 let delim = parse_delimiter_opt(name, args, 1)?;
5077 readtable_impl(&path, delim)
5078 }
5079 ("writetable", n) if n == 2 || n == 4 => {
5081 let path = string_arg(&args[1], name, 2)?.to_string();
5082 let delim = parse_delimiter_opt(name, args, 2)?;
5083 writetable_impl(&args[0], &path, delim)
5084 }
5085 ("xor", 2) => {
5087 let a = &args[0];
5088 let b = &args[1];
5089 match (a, b) {
5090 (Value::Scalar(x), Value::Scalar(y)) => {
5091 Ok(Value::Scalar(bool_to_f64((*x != 0.0) ^ (*y != 0.0))))
5092 }
5093 (Value::Matrix(mx), Value::Matrix(my)) => {
5094 if mx.shape() != my.shape() {
5095 return Err("xor: matrices must have the same dimensions".to_string());
5096 }
5097 Ok(Value::Matrix(Box::new(
5098 ndarray::Zip::from(&**mx)
5099 .and(&**my)
5100 .map_collect(|a, b| bool_to_f64((*a != 0.0) ^ (*b != 0.0))),
5101 )))
5102 }
5103 (Value::Scalar(s), Value::Matrix(m)) => {
5104 let sv = *s != 0.0;
5105 Ok(Value::Matrix(Box::new(
5106 m.mapv(|x| bool_to_f64(sv ^ (x != 0.0))),
5107 )))
5108 }
5109 (Value::Matrix(m), Value::Scalar(s)) => {
5110 let sv = *s != 0.0;
5111 Ok(Value::Matrix(Box::new(
5112 m.mapv(|x| bool_to_f64((x != 0.0) ^ sv)),
5113 )))
5114 }
5115 _ => Err("xor: arguments must be numeric".to_string()),
5116 }
5117 }
5118 ("not", 1) => apply_elem(&args[0], |x| if x == 0.0 { 1.0 } else { 0.0 }),
5120 ("int2str", 1) => match &args[0] {
5122 Value::Scalar(n) => Ok(Value::Str(format!("{}", n.round() as i64))),
5123 Value::Matrix(m) => {
5124 let parts: Vec<String> =
5125 m.iter().map(|x| format!("{}", x.round() as i64)).collect();
5126 Ok(Value::Str(parts.join(" ")))
5127 }
5128 _ => Err("int2str: argument must be numeric".to_string()),
5129 },
5130 ("mat2str", 1) => match &args[0] {
5132 Value::Scalar(n) => Ok(Value::Str(format!("{n}"))),
5133 Value::Matrix(m) => {
5134 if m.nrows() == 0 || m.ncols() == 0 {
5135 return Ok(Value::Str("[]".to_string()));
5136 }
5137 let mut s = String::from("[");
5138 for (r, row) in m.rows().into_iter().enumerate() {
5139 if r > 0 {
5140 s.push(';');
5141 }
5142 for (c, val) in row.iter().enumerate() {
5143 if c > 0 {
5144 s.push(' ');
5145 }
5146 s.push_str(&format!("{val}"));
5147 }
5148 }
5149 s.push(']');
5150 Ok(Value::Str(s))
5151 }
5152 _ => Err("mat2str: argument must be numeric".to_string()),
5153 },
5154 ("strsplit", 2) => {
5156 let s = string_arg(&args[0], name, 1)?.to_string();
5157 let delim = string_arg(&args[1], name, 2)?.to_string();
5158 let parts: Vec<Value> = s
5159 .split(delim.as_str())
5160 .map(|p| Value::Str(p.to_string()))
5161 .collect();
5162 Ok(Value::Cell(Box::new(parts)))
5163 }
5164 ("strsplit", 1) => {
5166 let s = string_arg(&args[0], name, 1)?.to_string();
5167 let parts: Vec<Value> = s
5168 .split_whitespace()
5169 .map(|p| Value::Str(p.to_string()))
5170 .collect();
5171 Ok(Value::Cell(Box::new(parts)))
5172 }
5173 ("strjoin", n) if n == 1 || n == 2 => {
5175 let cells = match &args[0] {
5176 Value::Cell(v) => v,
5177 _ => {
5178 return Err(
5179 "strjoin: first argument must be a cell array of strings".to_string()
5180 );
5181 }
5182 };
5183 let delim = if n == 2 {
5184 string_arg(&args[1], name, 2)?.to_string()
5185 } else {
5186 " ".to_string()
5187 };
5188 let mut parts: Vec<String> = Vec::with_capacity(cells.len());
5189 for (i, v) in cells.iter().enumerate() {
5190 match v {
5191 Value::Str(s) | Value::StringObj(s) => parts.push(s.clone()),
5192 _ => return Err(format!("strjoin: element {} must be a string", i + 1)),
5193 }
5194 }
5195 Ok(Value::Str(parts.join(&delim)))
5196 }
5197 ("contains", 2) => {
5199 let s = string_arg(&args[0], name, 1)?;
5200 let pat = string_arg(&args[1], name, 2)?;
5201 Ok(Value::Scalar(bool_to_f64(s.contains(pat))))
5202 }
5203 ("contains", 4) => {
5204 let s = string_arg(&args[0], name, 1)?;
5205 let pat = string_arg(&args[1], name, 2)?;
5206 let key = string_arg(&args[2], name, 3)?;
5207 if key != "IgnoreCase" {
5208 return Err(format!(
5209 "contains: unknown option '{key}'; expected 'IgnoreCase'"
5210 ));
5211 }
5212 let ignore = match &args[3] {
5213 Value::Scalar(n) => *n != 0.0,
5214 _ => return Err("contains: 'IgnoreCase' value must be a scalar".to_string()),
5215 };
5216 if ignore {
5217 Ok(Value::Scalar(bool_to_f64(
5218 s.to_lowercase().contains(&pat.to_lowercase()),
5219 )))
5220 } else {
5221 Ok(Value::Scalar(bool_to_f64(s.contains(pat))))
5222 }
5223 }
5224 ("startsWith", 2) => {
5226 let s = string_arg(&args[0], name, 1)?;
5227 let pat = string_arg(&args[1], name, 2)?;
5228 Ok(Value::Scalar(bool_to_f64(s.starts_with(pat))))
5229 }
5230 ("endsWith", 2) => {
5232 let s = string_arg(&args[0], name, 1)?;
5233 let pat = string_arg(&args[1], name, 2)?;
5234 Ok(Value::Scalar(bool_to_f64(s.ends_with(pat))))
5235 }
5236 ("regexp", 2) => {
5238 let s = string_arg(&args[0], name, 1)?.to_string();
5239 let pat = string_arg(&args[1], name, 2)?.to_string();
5240 regexp_impl("regexp", &s, &pat, false, false)
5241 }
5242 ("regexp", 3) => {
5243 let s = string_arg(&args[0], name, 1)?.to_string();
5244 let pat = string_arg(&args[1], name, 2)?.to_string();
5245 let opt = string_arg(&args[2], name, 3)?;
5246 if opt != "match" {
5247 return Err(format!("regexp: unknown option '{opt}'; expected 'match'"));
5248 }
5249 regexp_impl("regexp", &s, &pat, false, true)
5250 }
5251 ("regexpi", 2) => {
5253 let s = string_arg(&args[0], name, 1)?.to_string();
5254 let pat = string_arg(&args[1], name, 2)?.to_string();
5255 regexp_impl("regexpi", &s, &pat, true, false)
5256 }
5257 ("regexpi", 3) => {
5258 let s = string_arg(&args[0], name, 1)?.to_string();
5259 let pat = string_arg(&args[1], name, 2)?.to_string();
5260 let opt = string_arg(&args[2], name, 3)?;
5261 if opt != "match" {
5262 return Err(format!("regexpi: unknown option '{opt}'; expected 'match'"));
5263 }
5264 regexp_impl("regexpi", &s, &pat, true, true)
5265 }
5266 ("regexprep", 3) => {
5268 let s = string_arg(&args[0], name, 1)?.to_string();
5269 let pat = string_arg(&args[1], name, 2)?.to_string();
5270 let rep = string_arg(&args[2], name, 3)?.to_string();
5271 regexprep_impl(&s, &pat, &rep)
5272 }
5273 ("error", _) if !args.is_empty() => {
5275 let fmt_str = match &args[0] {
5276 Value::Str(s) | Value::StringObj(s) => s.clone(),
5277 _ => return Err("error: first argument must be a format string".to_string()),
5278 };
5279 let msg = format_printf(&fmt_str, &args[1..])?;
5280 Err(msg)
5281 }
5282 ("warning", _) if !args.is_empty() => {
5284 let fmt_str = match &args[0] {
5285 Value::Str(s) | Value::StringObj(s) => s.clone(),
5286 _ => return Err("warning: first argument must be a format string".to_string()),
5287 };
5288 let msg = format_printf(&fmt_str, &args[1..])?;
5289 eprintln!("warning: {msg}");
5290 Ok(Value::Void)
5291 }
5292 ("lasterr", 0) => Ok(Value::Str(get_last_err())),
5294 ("lasterr", 1) => {
5295 let prev = get_last_err();
5296 let new_msg = match &args[0] {
5297 Value::Str(s) | Value::StringObj(s) => s.clone(),
5298 _ => return Err("lasterr: argument must be a string".to_string()),
5299 };
5300 set_last_err(&new_msg);
5301 Ok(Value::Str(prev))
5302 }
5303 ("pcall", _) if !args.is_empty() => {
5305 let callable = args[0].clone();
5306 let call_args = &args[1..];
5307 let result = match &callable {
5308 Value::Lambda(f) => {
5309 let f = f.clone();
5310 f.0(call_args, io)
5311 }
5312 Value::Function(_) => match io {
5313 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
5314 Some(hook) => hook("<pcall>", &callable, call_args, env, io_ref),
5315 None => Err("pcall: function execution not initialized".to_string()),
5316 }),
5317 None => {
5318 let mut tmp_io = IoContext::new();
5319 FN_CALL_HOOK.with(|c| match c.get() {
5320 Some(hook) => hook("<pcall>", &callable, call_args, env, &mut tmp_io),
5321 None => Err("pcall: function execution not initialized".to_string()),
5322 })
5323 }
5324 },
5325 _ => {
5326 return Err(
5327 "pcall: first argument must be a function handle (@func)".to_string()
5328 );
5329 }
5330 };
5331 match result {
5332 Ok(v) => Ok(Value::Tuple(vec![Value::Scalar(1.0), v])),
5333 Err(msg) => {
5334 set_last_err(&msg);
5335 Ok(Value::Tuple(vec![Value::Scalar(0.0), Value::Str(msg)]))
5336 }
5337 }
5338 }
5339 ("eig", 1) => match &args[0] {
5343 Value::Scalar(n) => {
5344 if get_nargout() <= 1 {
5345 Ok(Value::Matrix(Box::new(
5346 Array2::from_shape_vec((1, 1), vec![*n]).unwrap(),
5347 )))
5348 } else {
5349 Ok(Value::Tuple(vec![
5350 Value::Matrix(Box::new(Array2::eye(1))),
5351 Value::Matrix(Box::new(Array2::from_elem((1, 1), *n))),
5352 ]))
5353 }
5354 }
5355 Value::Matrix(m) => {
5356 let (evals, evecs) = eig_compute(m)?;
5357 let nn = evals.len();
5358 let has_imag = evals.iter().any(|c| c.im.abs() > 1e-14);
5359 if get_nargout() <= 1 {
5360 if has_imag {
5361 Ok(Value::ComplexMatrix(Box::new(
5362 Array2::from_shape_vec((nn, 1), evals).unwrap(),
5363 )))
5364 } else {
5365 let reals: Vec<f64> = evals.iter().map(|c| c.re).collect();
5366 Ok(Value::Matrix(Box::new(
5367 Array2::from_shape_vec((nn, 1), reals).unwrap(),
5368 )))
5369 }
5370 } else if has_imag {
5371 Err("eig: [V,D] form not supported when eigenvalues are complex".to_string())
5372 } else {
5373 let reals: Vec<f64> = evals.iter().map(|c| c.re).collect();
5374 let mut d = Array2::<f64>::zeros((nn, nn));
5375 for (i, &e) in reals.iter().enumerate() {
5376 d[[i, i]] = e;
5377 }
5378 Ok(Value::Tuple(vec![
5379 Value::Matrix(Box::new(evecs)),
5380 Value::Matrix(Box::new(d)),
5381 ]))
5382 }
5383 }
5384 _ => Err("eig: argument must be a real numeric matrix".to_string()),
5385 },
5386
5387 ("svd", 1) => match &args[0] {
5390 Value::Scalar(n) => {
5391 let sv = n.abs();
5392 if get_nargout() <= 1 {
5393 Ok(Value::Matrix(Box::new(
5394 Array2::from_shape_vec((1, 1), vec![sv]).unwrap(),
5395 )))
5396 } else {
5397 Ok(Value::Tuple(vec![
5398 Value::Matrix(Box::new(Array2::eye(1))),
5399 Value::Matrix(Box::new(Array2::from_elem((1, 1), sv))),
5400 Value::Matrix(Box::new(Array2::eye(1))),
5401 ]))
5402 }
5403 }
5404 Value::Matrix(m) => {
5405 let mm = m.nrows();
5406 let nn = m.ncols();
5407 let (u_c, s_v, v_c) = svd_compute(m)?;
5408 let k = s_v.len();
5409 if get_nargout() <= 1 {
5410 let col: Vec<f64> = s_v;
5411 Ok(Value::Matrix(Box::new(
5412 Array2::from_shape_vec((k, 1), col).unwrap(),
5413 )))
5414 } else {
5415 let u_full = complete_orthonormal_basis(&u_c);
5417 let mut s_mat = Array2::<f64>::zeros((mm, nn));
5418 for (i, &sv) in s_v.iter().enumerate() {
5419 s_mat[[i, i]] = sv;
5420 }
5421 Ok(Value::Tuple(vec![
5422 Value::Matrix(Box::new(u_full)),
5423 Value::Matrix(Box::new(s_mat)),
5424 Value::Matrix(Box::new(v_c)),
5425 ]))
5426 }
5427 }
5428 _ => Err("svd: argument must be a real numeric matrix".to_string()),
5429 },
5430 ("svd", 2) => match (&args[0], &args[1]) {
5431 (Value::Matrix(m), Value::Str(opt) | Value::StringObj(opt)) if opt == "econ" => {
5432 let (u_c, s_v, v_c) = svd_compute(m)?;
5433 let k = s_v.len();
5434 let mut s_mat = Array2::<f64>::zeros((k, k));
5435 for (i, &sv) in s_v.iter().enumerate() {
5436 s_mat[[i, i]] = sv;
5437 }
5438 Ok(Value::Tuple(vec![
5439 Value::Matrix(Box::new(u_c)),
5440 Value::Matrix(Box::new(s_mat)),
5441 Value::Matrix(Box::new(v_c)),
5442 ]))
5443 }
5444 _ => Err("svd: expected svd(A, 'econ')".to_string()),
5445 },
5446
5447 ("lu", 1) => match &args[0] {
5449 Value::Scalar(n) => {
5450 if get_nargout() <= 1 {
5451 Ok(Value::Scalar(*n))
5452 } else {
5453 Ok(Value::Tuple(vec![
5454 Value::Matrix(Box::new(Array2::eye(1))),
5455 Value::Matrix(Box::new(Array2::from_elem((1, 1), *n))),
5456 Value::Matrix(Box::new(Array2::eye(1))),
5457 ]))
5458 }
5459 }
5460 Value::Matrix(m) => {
5461 let (l, u, p) = lu_decompose(m)?;
5462 if get_nargout() <= 1 {
5463 Ok(Value::Matrix(Box::new(u)))
5464 } else {
5465 Ok(Value::Tuple(vec![
5466 Value::Matrix(Box::new(l)),
5467 Value::Matrix(Box::new(u)),
5468 Value::Matrix(Box::new(p)),
5469 ]))
5470 }
5471 }
5472 _ => Err("lu: argument must be a real numeric matrix".to_string()),
5473 },
5474
5475 ("qr", 1) => match &args[0] {
5477 Value::Scalar(n) => {
5478 if get_nargout() <= 1 {
5479 Ok(Value::Scalar(*n))
5480 } else {
5481 Ok(Value::Tuple(vec![
5482 Value::Matrix(Box::new(Array2::from_elem(
5483 (1, 1),
5484 if *n >= 0.0 { 1.0 } else { -1.0 },
5485 ))),
5486 Value::Matrix(Box::new(Array2::from_elem((1, 1), n.abs()))),
5487 ]))
5488 }
5489 }
5490 Value::Matrix(m) => {
5491 let (q, r) = qr_decompose(m)?;
5492 if get_nargout() <= 1 {
5493 Ok(Value::Matrix(Box::new(r)))
5494 } else {
5495 Ok(Value::Tuple(vec![
5496 Value::Matrix(Box::new(q)),
5497 Value::Matrix(Box::new(r)),
5498 ]))
5499 }
5500 }
5501 _ => Err("qr: argument must be a real numeric matrix".to_string()),
5502 },
5503
5504 ("chol", 1) => match &args[0] {
5506 Value::Scalar(n) => {
5507 if *n < 0.0 {
5508 Err("chol: value is not positive definite".to_string())
5509 } else {
5510 Ok(Value::Scalar(n.sqrt()))
5511 }
5512 }
5513 Value::Matrix(m) => Ok(Value::Matrix(Box::new(chol_decompose(m)?))),
5514 _ => Err("chol: argument must be a real numeric matrix".to_string()),
5515 },
5516
5517 ("rank", 1) => match &args[0] {
5519 Value::Scalar(x) => Ok(Value::Scalar(if x.abs() > 1e-15 { 1.0 } else { 0.0 })),
5520 Value::Matrix(m) => {
5521 let (_, s_v, _) = svd_compute(m)?;
5522 let tol = (m.nrows().max(m.ncols())) as f64
5523 * s_v.first().copied().unwrap_or(0.0)
5524 * f64::EPSILON
5525 * 2.0;
5526 let r = s_v.iter().filter(|&&s| s > tol).count();
5527 Ok(Value::Scalar(r as f64))
5528 }
5529 _ => Err("rank: argument must be a real numeric matrix".to_string()),
5530 },
5531
5532 ("null", 1) => match &args[0] {
5534 Value::Scalar(_) => Ok(Value::Matrix(Box::new(Array2::zeros((1, 0))))),
5535 Value::Matrix(m) => {
5536 let nn = m.ncols();
5537 let (_, s_v, v_c) = svd_compute(m)?;
5538 let tol = (m.nrows().max(nn)) as f64
5539 * s_v.first().copied().unwrap_or(0.0)
5540 * f64::EPSILON
5541 * 2.0;
5542 let r = s_v.iter().filter(|&&s| s > tol).count();
5543 let null_k = nn.saturating_sub(r);
5544 if null_k == 0 {
5545 return Ok(Value::Matrix(Box::new(Array2::zeros((nn, 0)))));
5546 }
5547 let mut result = Array2::<f64>::zeros((nn, null_k));
5548 for j in 0..null_k {
5549 let col_idx = r + j;
5550 if col_idx < v_c.ncols() {
5551 for i in 0..nn {
5552 result[[i, j]] = v_c[[i, col_idx]];
5553 }
5554 }
5555 }
5556 Ok(Value::Matrix(Box::new(result)))
5557 }
5558 _ => Err("null: argument must be a real numeric matrix".to_string()),
5559 },
5560
5561 ("orth", 1) => match &args[0] {
5563 Value::Scalar(x) => {
5564 if x.abs() > 1e-15 {
5565 Ok(Value::Matrix(Box::new(Array2::from_elem((1, 1), 1.0))))
5566 } else {
5567 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
5568 }
5569 }
5570 Value::Matrix(m) => {
5571 let mm = m.nrows();
5572 let (u_c, s_v, _) = svd_compute(m)?;
5573 let tol = (mm.max(m.ncols())) as f64
5574 * s_v.first().copied().unwrap_or(0.0)
5575 * f64::EPSILON
5576 * 2.0;
5577 let r = s_v.iter().filter(|&&s| s > tol).count();
5578 if r == 0 {
5579 return Ok(Value::Matrix(Box::new(Array2::zeros((mm, 0)))));
5580 }
5581 let mut result = Array2::<f64>::zeros((mm, r));
5582 for j in 0..r {
5583 if j < u_c.ncols() {
5584 for i in 0..mm {
5585 result[[i, j]] = u_c[[i, j]];
5586 }
5587 }
5588 }
5589 Ok(Value::Matrix(Box::new(result)))
5590 }
5591 _ => Err("orth: argument must be a real numeric matrix".to_string()),
5592 },
5593
5594 ("cond", 1) => match &args[0] {
5596 Value::Scalar(x) => {
5597 if x.abs() < 1e-15 {
5598 Ok(Value::Scalar(f64::INFINITY))
5599 } else {
5600 Ok(Value::Scalar(1.0))
5601 }
5602 }
5603 Value::Matrix(m) => {
5604 let (_, s_v, _) = svd_compute(m)?;
5605 if s_v.is_empty() {
5606 return Ok(Value::Scalar(1.0));
5607 }
5608 let s_max = s_v[0];
5609 let s_min = *s_v.last().unwrap();
5610 Ok(Value::Scalar(if s_min < 1e-15 {
5611 f64::INFINITY
5612 } else {
5613 s_max / s_min
5614 }))
5615 }
5616 _ => Err("cond: argument must be a real numeric matrix".to_string()),
5617 },
5618
5619 ("pinv", 1) => match &args[0] {
5621 Value::Scalar(x) => Ok(Value::Scalar(if x.abs() < 1e-15 { 0.0 } else { 1.0 / x })),
5622 Value::Matrix(m) => {
5623 let mm = m.nrows();
5624 let nn = m.ncols();
5625 let (u_c, s_v, v_c) = svd_compute(m)?;
5626 let k = s_v.len();
5627 let tol =
5628 (mm.max(nn)) as f64 * s_v.first().copied().unwrap_or(0.0) * f64::EPSILON * 2.0;
5629 let mut result = Array2::<f64>::zeros((nn, mm));
5631 for j in 0..k {
5632 if s_v[j] > tol {
5633 let inv_s = 1.0 / s_v[j];
5634 for r in 0..nn {
5635 for c in 0..mm {
5636 result[[r, c]] += v_c[[r, j]] * inv_s * u_c[[c, j]];
5637 }
5638 }
5639 }
5640 }
5641 Ok(Value::Matrix(Box::new(result)))
5642 }
5643 _ => Err("pinv: argument must be a real numeric matrix".to_string()),
5644 },
5645
5646 ("fft", 1) => fft_call(&args[0], None),
5648 ("fft", 2) => {
5649 let n = scalar_arg(&args[1], "fft", 2)?;
5650 let n = n as usize;
5651 if n == 0 {
5652 return Err("fft: length must be positive".to_string());
5653 }
5654 fft_call(&args[0], Some(n))
5655 }
5656 ("ifft", 1) => ifft_call(&args[0]),
5657
5658 ("fftshift", 1) => match &args[0] {
5660 Value::Scalar(s) => Ok(Value::Scalar(*s)),
5661 Value::Matrix(m) => {
5662 let (nrows, ncols) = (m.nrows(), m.ncols());
5663 if nrows == 1 {
5664 let n = ncols;
5665 let shift = n / 2;
5666 let data: Vec<f64> = m.iter().copied().collect();
5667 let mut out = vec![0.0f64; n];
5668 for (i, &x) in data.iter().enumerate() {
5669 out[(i + shift) % n] = x;
5670 }
5671 Ok(Value::Matrix(Box::new(
5672 Array2::from_shape_vec((1, n), out).unwrap(),
5673 )))
5674 } else if ncols == 1 {
5675 let n = nrows;
5676 let shift = n / 2;
5677 let data: Vec<f64> = m.iter().copied().collect();
5678 let mut out = vec![0.0f64; n];
5679 for (i, &x) in data.iter().enumerate() {
5680 out[(i + shift) % n] = x;
5681 }
5682 Ok(Value::Matrix(Box::new(
5683 Array2::from_shape_vec((n, 1), out).unwrap(),
5684 )))
5685 } else {
5686 let row_shift = nrows / 2;
5687 let col_shift = ncols / 2;
5688 let mut out = Array2::<f64>::zeros((nrows, ncols));
5689 for i in 0..nrows {
5690 for j in 0..ncols {
5691 out[[(i + row_shift) % nrows, (j + col_shift) % ncols]] = m[[i, j]];
5692 }
5693 }
5694 Ok(Value::Matrix(Box::new(out)))
5695 }
5696 }
5697 _ => Err("fftshift: argument must be a numeric matrix".to_string()),
5698 },
5699
5700 ("ifftshift", 1) => match &args[0] {
5702 Value::Scalar(s) => Ok(Value::Scalar(*s)),
5703 Value::Matrix(m) => {
5704 let (nrows, ncols) = (m.nrows(), m.ncols());
5705 if nrows == 1 {
5706 let n = ncols;
5707 let shift = n.div_ceil(2);
5708 let data: Vec<f64> = m.iter().copied().collect();
5709 let mut out = vec![0.0f64; n];
5710 for (i, &x) in data.iter().enumerate() {
5711 out[(i + shift) % n] = x;
5712 }
5713 Ok(Value::Matrix(Box::new(
5714 Array2::from_shape_vec((1, n), out).unwrap(),
5715 )))
5716 } else if ncols == 1 {
5717 let n = nrows;
5718 let shift = n.div_ceil(2);
5719 let data: Vec<f64> = m.iter().copied().collect();
5720 let mut out = vec![0.0f64; n];
5721 for (i, &x) in data.iter().enumerate() {
5722 out[(i + shift) % n] = x;
5723 }
5724 Ok(Value::Matrix(Box::new(
5725 Array2::from_shape_vec((n, 1), out).unwrap(),
5726 )))
5727 } else {
5728 let row_shift = nrows.div_ceil(2);
5729 let col_shift = ncols.div_ceil(2);
5730 let mut out = Array2::<f64>::zeros((nrows, ncols));
5731 for i in 0..nrows {
5732 for j in 0..ncols {
5733 out[[(i + row_shift) % nrows, (j + col_shift) % ncols]] = m[[i, j]];
5734 }
5735 }
5736 Ok(Value::Matrix(Box::new(out)))
5737 }
5738 }
5739 _ => Err("ifftshift: argument must be a numeric matrix".to_string()),
5740 },
5741
5742 ("fftfreq", 2) => {
5744 let n = match &args[0] {
5745 Value::Scalar(s) => {
5746 let n = *s as usize;
5747 if *s < 1.0 || (*s - n as f64).abs() > 1e-9 {
5748 return Err("fftfreq: n must be a positive integer".to_string());
5749 }
5750 n
5751 }
5752 _ => return Err("fftfreq: first argument must be a scalar integer".to_string()),
5753 };
5754 let d = scalar_arg(&args[1], "fftfreq", 2)?;
5755 if d == 0.0 {
5756 return Err("fftfreq: sample spacing d must be nonzero".to_string());
5757 }
5758 let pos_count = (n - 1) / 2 + 1;
5760 let neg_count = n / 2;
5761 let factor = 1.0 / (n as f64 * d);
5762 let mut freqs = Vec::with_capacity(n);
5763 for k in 0..pos_count as i64 {
5764 freqs.push(k as f64 * factor);
5765 }
5766 let neg_start = -(neg_count as i64);
5767 for k in neg_start..0 {
5768 freqs.push(k as f64 * factor);
5769 }
5770 Ok(Value::Matrix(Box::new(
5771 Array2::from_shape_vec((1, n), freqs).unwrap(),
5772 )))
5773 }
5774
5775 ("jsondecode", 1) => jsondecode_impl(&args[0]),
5777 ("jsonencode", 1) => jsonencode_impl(&args[0]),
5778
5779 ("load", 1) => {
5781 let path = match &args[0] {
5782 Value::Str(s) | Value::StringObj(s) => s.clone(),
5783 _ => return Err("load: argument must be a string path".to_string()),
5784 };
5785 if !path.ends_with(".mat") {
5786 return Err("load: use bare 'load path' syntax for non-.mat files".to_string());
5787 }
5788 load_mat_file(&path)
5789 }
5790
5791 ("assert", 1) => {
5793 let truthy = match &args[0] {
5794 Value::Scalar(n) => *n != 0.0 && !n.is_nan(),
5795 Value::Matrix(m) => m.iter().all(|&x| x != 0.0 && !x.is_nan()),
5796 Value::Complex(re, im) => *re != 0.0 || *im != 0.0,
5797 Value::Str(s) | Value::StringObj(s) => !s.is_empty(),
5798 _ => false,
5799 };
5800 if truthy {
5801 Ok(Value::Void)
5802 } else {
5803 Err("assert: condition is false".to_string())
5804 }
5805 }
5806
5807 ("assert", 2) => assert_values_equal(&args[0], &args[1], None),
5809
5810 ("assert", 3) => {
5812 let tol = match &args[2] {
5813 Value::Scalar(t) => *t,
5814 _ => return Err("assert: tolerance must be a scalar".to_string()),
5815 };
5816 assert_values_equal(&args[0], &args[1], Some(tol))
5817 }
5818
5819 ("datetime", 1) => match &args[0] {
5821 Value::Str(s) | Value::StringObj(s) => {
5822 let s = s.as_str();
5823 if s == "now" {
5824 return Ok(Value::DateTime(crate::datetime::now_timestamp()));
5825 }
5826 if s == "today" {
5827 return Ok(Value::DateTime(crate::datetime::today_timestamp()));
5828 }
5829 crate::datetime::parse_iso8601(s).map(Value::DateTime)
5830 }
5831 _ => Err("datetime: expected a string or numeric constructor arguments".to_string()),
5832 },
5833 ("datetime", 3) if matches!(&args[1], Value::Str(_) | Value::StringObj(_)) => {
5835 let ts = scalar_arg(&args[0], "datetime", 1)?;
5836 match (&args[1], &args[2]) {
5837 (Value::Str(k) | Value::StringObj(k), Value::Str(v) | Value::StringObj(v))
5838 if k.eq_ignore_ascii_case("convertfrom")
5839 && v.eq_ignore_ascii_case("posixtime") =>
5840 {
5841 Ok(Value::DateTime(ts))
5842 }
5843 _ => Err("datetime: unsupported arguments".to_string()),
5844 }
5845 }
5846 ("datetime", 3) => {
5847 let y = scalar_arg(&args[0], "datetime", 1)? as i64;
5848 let mo = scalar_arg(&args[1], "datetime", 2)? as u32;
5849 let d = scalar_arg(&args[2], "datetime", 3)? as u32;
5850 Ok(Value::DateTime(crate::datetime::civil_to_timestamp(
5851 y, mo, d, 0, 0, 0.0,
5852 )))
5853 }
5854 ("datetime", 6) => {
5855 let y = scalar_arg(&args[0], "datetime", 1)? as i64;
5856 let mo = scalar_arg(&args[1], "datetime", 2)? as u32;
5857 let d = scalar_arg(&args[2], "datetime", 3)? as u32;
5858 let h = scalar_arg(&args[3], "datetime", 4)? as u32;
5859 let mi = scalar_arg(&args[4], "datetime", 5)? as u32;
5860 let s = scalar_arg(&args[5], "datetime", 6)?;
5861 Ok(Value::DateTime(crate::datetime::civil_to_timestamp(
5862 y, mo, d, h, mi, s,
5863 )))
5864 }
5865
5866 ("year", 1) => match &args[0] {
5868 Value::DateTime(ts) => {
5869 let (y, ..) = crate::datetime::timestamp_to_civil(*ts);
5870 Ok(Value::Scalar(y as f64))
5871 }
5872 Value::DateTimeArray(v) => {
5873 let rows: Vec<f64> = v
5874 .iter()
5875 .map(|ts| {
5876 let (y, ..) = crate::datetime::timestamp_to_civil(*ts);
5877 y as f64
5878 })
5879 .collect();
5880 Ok(Value::Matrix(Box::new(
5881 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5882 .map_err(|e| e.to_string())?,
5883 )))
5884 }
5885 _ => Err("year: argument must be a datetime".to_string()),
5886 },
5887 ("month", 1) => match &args[0] {
5888 Value::DateTime(ts) => {
5889 let (_, mo, ..) = crate::datetime::timestamp_to_civil(*ts);
5890 Ok(Value::Scalar(mo as f64))
5891 }
5892 Value::DateTimeArray(v) => {
5893 let rows: Vec<f64> = v
5894 .iter()
5895 .map(|ts| {
5896 let (_, mo, ..) = crate::datetime::timestamp_to_civil(*ts);
5897 mo as f64
5898 })
5899 .collect();
5900 Ok(Value::Matrix(Box::new(
5901 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5902 .map_err(|e| e.to_string())?,
5903 )))
5904 }
5905 _ => Err("month: argument must be a datetime".to_string()),
5906 },
5907 ("day", 1) => match &args[0] {
5908 Value::DateTime(ts) => {
5909 let (_, _, d, ..) = crate::datetime::timestamp_to_civil(*ts);
5910 Ok(Value::Scalar(d as f64))
5911 }
5912 Value::DateTimeArray(v) => {
5913 let rows: Vec<f64> = v
5914 .iter()
5915 .map(|ts| {
5916 let (_, _, d, ..) = crate::datetime::timestamp_to_civil(*ts);
5917 d as f64
5918 })
5919 .collect();
5920 Ok(Value::Matrix(Box::new(
5921 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5922 .map_err(|e| e.to_string())?,
5923 )))
5924 }
5925 _ => Err("day: argument must be a datetime".to_string()),
5926 },
5927 ("hour", 1) => match &args[0] {
5928 Value::DateTime(ts) => {
5929 let (_, _, _, h, ..) = crate::datetime::timestamp_to_civil(*ts);
5930 Ok(Value::Scalar(h as f64))
5931 }
5932 Value::DateTimeArray(v) => {
5933 let rows: Vec<f64> = v
5934 .iter()
5935 .map(|ts| {
5936 let (_, _, _, h, ..) = crate::datetime::timestamp_to_civil(*ts);
5937 h as f64
5938 })
5939 .collect();
5940 Ok(Value::Matrix(Box::new(
5941 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5942 .map_err(|e| e.to_string())?,
5943 )))
5944 }
5945 _ => Err("hour: argument must be a datetime or duration".to_string()),
5946 },
5947 ("minute", 1) => match &args[0] {
5948 Value::DateTime(ts) => {
5949 let (_, _, _, _, mi, ..) = crate::datetime::timestamp_to_civil(*ts);
5950 Ok(Value::Scalar(mi as f64))
5951 }
5952 Value::DateTimeArray(v) => {
5953 let rows: Vec<f64> = v
5954 .iter()
5955 .map(|ts| {
5956 let (_, _, _, _, mi, ..) = crate::datetime::timestamp_to_civil(*ts);
5957 mi as f64
5958 })
5959 .collect();
5960 Ok(Value::Matrix(Box::new(
5961 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5962 .map_err(|e| e.to_string())?,
5963 )))
5964 }
5965 _ => Err("minute: argument must be a datetime or duration".to_string()),
5966 },
5967 ("second", 1) => match &args[0] {
5968 Value::DateTime(ts) => {
5969 let (_, _, _, _, _, s) = crate::datetime::timestamp_to_civil(*ts);
5970 Ok(Value::Scalar(s))
5971 }
5972 Value::DateTimeArray(v) => {
5973 let rows: Vec<f64> = v
5974 .iter()
5975 .map(|ts| {
5976 let (_, _, _, _, _, s) = crate::datetime::timestamp_to_civil(*ts);
5977 s
5978 })
5979 .collect();
5980 Ok(Value::Matrix(Box::new(
5981 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
5982 .map_err(|e| e.to_string())?,
5983 )))
5984 }
5985 _ => Err("second: argument must be a datetime or duration".to_string()),
5986 },
5987
5988 ("isdatetime", 1) => Ok(Value::Scalar(bool_to_f64(matches!(
5990 &args[0],
5991 Value::DateTime(_) | Value::DateTimeArray(_)
5992 )))),
5993 ("isduration", 1) => Ok(Value::Scalar(bool_to_f64(matches!(
5994 &args[0],
5995 Value::Duration(_) | Value::DurationArray(_)
5996 )))),
5997 ("isnat", 1) => match &args[0] {
5998 Value::DateTime(ts) => Ok(Value::Scalar(bool_to_f64(ts.is_nan()))),
5999 Value::DateTimeArray(v) => {
6000 let rows: Vec<f64> = v
6001 .iter()
6002 .map(|ts| if ts.is_nan() { 1.0 } else { 0.0 })
6003 .collect();
6004 Ok(Value::Matrix(Box::new(
6005 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
6006 .map_err(|e| e.to_string())?,
6007 )))
6008 }
6009 _ => Ok(Value::Scalar(0.0)),
6010 },
6011
6012 ("hours", 1) => match &args[0] {
6014 Value::Duration(s) => Ok(Value::Scalar(*s / 3600.0)),
6015 Value::DurationArray(v) => {
6016 let rows: Vec<f64> = v.iter().map(|s| s / 3600.0).collect();
6017 Ok(Value::Matrix(Box::new(
6018 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
6019 .map_err(|e| e.to_string())?,
6020 )))
6021 }
6022 _ => {
6023 let s = scalar_arg(&args[0], "hours", 1)?;
6024 Ok(Value::Duration(s * 3600.0))
6025 }
6026 },
6027 ("minutes", 1) => match &args[0] {
6028 Value::Duration(s) => Ok(Value::Scalar(*s / 60.0)),
6029 Value::DurationArray(v) => {
6030 let rows: Vec<f64> = v.iter().map(|s| s / 60.0).collect();
6031 Ok(Value::Matrix(Box::new(
6032 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
6033 .map_err(|e| e.to_string())?,
6034 )))
6035 }
6036 _ => {
6037 let s = scalar_arg(&args[0], "minutes", 1)?;
6038 Ok(Value::Duration(s * 60.0))
6039 }
6040 },
6041 ("seconds", 1) => match &args[0] {
6042 Value::Duration(s) => Ok(Value::Scalar(*s)),
6043 Value::DurationArray(v) => {
6044 let rows = v.to_vec();
6045 Ok(Value::Matrix(Box::new(
6046 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
6047 .map_err(|e| e.to_string())?,
6048 )))
6049 }
6050 _ => {
6051 let s = scalar_arg(&args[0], "seconds", 1)?;
6052 Ok(Value::Duration(s))
6053 }
6054 },
6055 ("days", 1) => match &args[0] {
6056 Value::Duration(s) => Ok(Value::Scalar(*s / 86400.0)),
6057 Value::DurationArray(v) => {
6058 let rows: Vec<f64> = v.iter().map(|s| s / 86400.0).collect();
6059 Ok(Value::Matrix(Box::new(
6060 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
6061 .map_err(|e| e.to_string())?,
6062 )))
6063 }
6064 _ => {
6065 let s = scalar_arg(&args[0], "days", 1)?;
6066 Ok(Value::Duration(s * 86400.0))
6067 }
6068 },
6069 ("milliseconds", 1) => match &args[0] {
6070 Value::Duration(s) => Ok(Value::Scalar(*s * 1000.0)),
6071 Value::DurationArray(v) => {
6072 let rows: Vec<f64> = v.iter().map(|s| s * 1000.0).collect();
6073 Ok(Value::Matrix(Box::new(
6074 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
6075 .map_err(|e| e.to_string())?,
6076 )))
6077 }
6078 _ => {
6079 let s = scalar_arg(&args[0], "milliseconds", 1)?;
6080 Ok(Value::Duration(s / 1000.0))
6081 }
6082 },
6083 ("years", 1) => match &args[0] {
6084 Value::Duration(s) => Ok(Value::Scalar(*s / (365.2425 * 86400.0))),
6085 Value::DurationArray(v) => {
6086 let rows: Vec<f64> = v.iter().map(|s| s / (365.2425 * 86400.0)).collect();
6087 Ok(Value::Matrix(Box::new(
6088 ndarray::Array2::from_shape_vec((rows.len(), 1), rows)
6089 .map_err(|e| e.to_string())?,
6090 )))
6091 }
6092 _ => {
6093 let s = scalar_arg(&args[0], "years", 1)?;
6094 Ok(Value::Duration(s * 365.2425 * 86400.0))
6095 }
6096 },
6097 ("duration", 3) => {
6099 let h = scalar_arg(&args[0], "duration", 1)?;
6100 let m = scalar_arg(&args[1], "duration", 2)?;
6101 let s = scalar_arg(&args[2], "duration", 3)?;
6102 Ok(Value::Duration(h * 3600.0 + m * 60.0 + s))
6103 }
6104
6105 ("datestr", 1) => match &args[0] {
6107 Value::DateTime(ts) => {
6108 let s = crate::datetime::format_datestr(*ts, "dd-MMM-yyyy HH:mm:ss");
6109 Ok(Value::Str(s))
6110 }
6111 Value::DateTimeArray(v) => Ok(Value::Cell(Box::new(
6112 v.iter()
6113 .map(|ts| {
6114 Value::Str(crate::datetime::format_datestr(*ts, "dd-MMM-yyyy HH:mm:ss"))
6115 })
6116 .collect(),
6117 ))),
6118 _ => Err("datestr: argument must be a datetime".to_string()),
6119 },
6120 ("datestr", 2) => {
6121 let fmt_str = match &args[1] {
6122 Value::Str(s) | Value::StringObj(s) => s.clone(),
6123 _ => return Err("datestr: second argument must be a format string".to_string()),
6124 };
6125 match &args[0] {
6126 Value::DateTime(ts) => {
6127 Ok(Value::Str(crate::datetime::format_datestr(*ts, &fmt_str)))
6128 }
6129 Value::DateTimeArray(v) => Ok(Value::Cell(Box::new(
6130 v.iter()
6131 .map(|ts| Value::Str(crate::datetime::format_datestr(*ts, &fmt_str)))
6132 .collect(),
6133 ))),
6134 _ => Err("datestr: first argument must be a datetime".to_string()),
6135 }
6136 }
6137 ("datevec", 1) => match &args[0] {
6138 Value::DateTime(ts) => {
6139 let (y, mo, d, h, mi, s) = crate::datetime::timestamp_to_civil(*ts);
6140 let sec_i = s.floor() as u32;
6141 let data = vec![
6142 y as f64,
6143 mo as f64,
6144 d as f64,
6145 h as f64,
6146 mi as f64,
6147 sec_i as f64,
6148 ];
6149 Ok(Value::Matrix(Box::new(
6150 ndarray::Array2::from_shape_vec((1, 6), data).map_err(|e| e.to_string())?,
6151 )))
6152 }
6153 _ => Err("datevec: argument must be a datetime".to_string()),
6154 },
6155 ("datenum", 1) => match &args[0] {
6156 Value::DateTime(ts) => Ok(Value::Scalar(crate::datetime::to_datenum(*ts))),
6157 _ => Err("datenum: argument must be a datetime".to_string()),
6158 },
6159 ("datenum", 3) => {
6160 let y = scalar_arg(&args[0], "datenum", 1)? as i64;
6161 let mo = scalar_arg(&args[1], "datenum", 2)? as u32;
6162 let d = scalar_arg(&args[2], "datenum", 3)? as u32;
6163 let ts = crate::datetime::civil_to_timestamp(y, mo, d, 0, 0, 0.0);
6164 Ok(Value::Scalar(crate::datetime::to_datenum(ts)))
6165 }
6166 ("posixtime", 1) => match &args[0] {
6167 Value::DateTime(ts) => Ok(Value::Scalar(*ts)),
6168 _ => Err("posixtime: argument must be a datetime".to_string()),
6169 },
6170
6171 ("diff", 1) => match &args[0] {
6173 Value::DateTimeArray(v) if v.len() >= 2 => {
6174 let diffs: Vec<f64> = v.windows(2).map(|w| w[1] - w[0]).collect();
6175 Ok(Value::DurationArray(diffs))
6176 }
6177 Value::DurationArray(v) if v.len() >= 2 => {
6178 let diffs: Vec<f64> = v.windows(2).map(|w| w[1] - w[0]).collect();
6179 Ok(Value::DurationArray(diffs))
6180 }
6181 Value::Matrix(m) => {
6182 let (nrows, ncols) = (m.nrows(), m.ncols());
6184 if ncols > 1 && nrows == 1 {
6185 let data: Vec<f64> =
6187 (0..ncols - 1).map(|j| m[[0, j + 1]] - m[[0, j]]).collect();
6188 Ok(Value::Matrix(Box::new(
6189 ndarray::Array2::from_shape_vec((1, data.len()), data)
6190 .map_err(|e| e.to_string())?,
6191 )))
6192 } else if nrows > 1 {
6193 let data: Vec<f64> = (0..nrows - 1)
6195 .flat_map(|i| (0..ncols).map(move |j| m[[i + 1, j]] - m[[i, j]]))
6196 .collect();
6197 Ok(Value::Matrix(Box::new(
6198 ndarray::Array2::from_shape_vec((nrows - 1, ncols), data)
6199 .map_err(|e| e.to_string())?,
6200 )))
6201 } else {
6202 Err("diff: input must have at least 2 elements".to_string())
6203 }
6204 }
6205 _ => Err("diff: unsupported argument type".to_string()),
6206 },
6207
6208 ("triu", 1) => match &args[0] {
6210 Value::Matrix(m) => {
6211 let mut r = m.clone();
6212 for i in 0..m.nrows() {
6213 for j in 0..m.ncols() {
6214 if (j as isize) < (i as isize) {
6215 r[[i, j]] = 0.0;
6216 }
6217 }
6218 }
6219 Ok(Value::Matrix(r))
6220 }
6221 Value::Scalar(n) => Ok(Value::Scalar(*n)),
6222 _ => Err("triu: argument must be a numeric matrix".to_string()),
6223 },
6224 ("triu", 2) => match (&args[0], &args[1]) {
6225 (Value::Matrix(m), Value::Scalar(k)) => {
6226 let k = *k as isize;
6227 let mut r = m.clone();
6228 for i in 0..m.nrows() {
6229 for j in 0..m.ncols() {
6230 if (j as isize) - (i as isize) < k {
6231 r[[i, j]] = 0.0;
6232 }
6233 }
6234 }
6235 Ok(Value::Matrix(r))
6236 }
6237 _ => Err("triu: expects (matrix, scalar)".to_string()),
6238 },
6239
6240 ("tril", 1) => match &args[0] {
6241 Value::Matrix(m) => {
6242 let mut r = m.clone();
6243 for i in 0..m.nrows() {
6244 for j in 0..m.ncols() {
6245 if (j as isize) > (i as isize) {
6246 r[[i, j]] = 0.0;
6247 }
6248 }
6249 }
6250 Ok(Value::Matrix(r))
6251 }
6252 Value::Scalar(n) => Ok(Value::Scalar(*n)),
6253 _ => Err("tril: argument must be a numeric matrix".to_string()),
6254 },
6255 ("tril", 2) => match (&args[0], &args[1]) {
6256 (Value::Matrix(m), Value::Scalar(k)) => {
6257 let k = *k as isize;
6258 let mut r = m.clone();
6259 for i in 0..m.nrows() {
6260 for j in 0..m.ncols() {
6261 if (j as isize) - (i as isize) > k {
6262 r[[i, j]] = 0.0;
6263 }
6264 }
6265 }
6266 Ok(Value::Matrix(r))
6267 }
6268 _ => Err("tril: expects (matrix, scalar)".to_string()),
6269 },
6270
6271 ("repmat", 3) => match (&args[0], &args[1], &args[2]) {
6272 (Value::Matrix(a), Value::Scalar(rm), Value::Scalar(cn)) => {
6273 let rm = *rm as usize;
6274 let cn = *cn as usize;
6275 if rm == 0 || cn == 0 {
6276 return Ok(Value::Matrix(Box::new(Array2::zeros((0, 0)))));
6277 }
6278 let row_tile: Vec<Array2<f64>> = std::iter::repeat_n(a.view(), cn)
6279 .map(|v| v.to_owned())
6280 .collect();
6281 let row_block = ndarray::concatenate(
6282 ndarray::Axis(1),
6283 &row_tile.iter().map(|m| m.view()).collect::<Vec<_>>(),
6284 )
6285 .map_err(|e| e.to_string())?;
6286 let col_tiles: Vec<Array2<f64>> = std::iter::repeat_n(row_block.view(), rm)
6287 .map(|v| v.to_owned())
6288 .collect();
6289 let result = ndarray::concatenate(
6290 ndarray::Axis(0),
6291 &col_tiles.iter().map(|m| m.view()).collect::<Vec<_>>(),
6292 )
6293 .map_err(|e| e.to_string())?;
6294 Ok(Value::Matrix(Box::new(result)))
6295 }
6296 (Value::Scalar(s), Value::Scalar(rm), Value::Scalar(cn)) => {
6297 let rm = *rm as usize;
6298 let cn = *cn as usize;
6299 Ok(Value::Matrix(Box::new(Array2::from_elem((rm, cn), *s))))
6300 }
6301 _ => Err("repmat: expects (matrix, m, n)".to_string()),
6302 },
6303
6304 ("kron", 2) => match (&args[0], &args[1]) {
6305 (Value::Matrix(a), Value::Matrix(b)) => {
6306 let (ra, ca) = (a.nrows(), a.ncols());
6307 let (rb, cb) = (b.nrows(), b.ncols());
6308 let mut result = Array2::<f64>::zeros((ra * rb, ca * cb));
6309 for i in 0..ra {
6310 for j in 0..ca {
6311 let aij = a[[i, j]];
6312 for p in 0..rb {
6313 for q in 0..cb {
6314 result[[i * rb + p, j * cb + q]] = aij * b[[p, q]];
6315 }
6316 }
6317 }
6318 }
6319 Ok(Value::Matrix(Box::new(result)))
6320 }
6321 (Value::Scalar(s), Value::Matrix(b)) => Ok(Value::Matrix(Box::new(b.mapv(|x| x * s)))),
6322 (Value::Matrix(a), Value::Scalar(s)) => Ok(Value::Matrix(Box::new(a.mapv(|x| x * s)))),
6323 (Value::Scalar(a), Value::Scalar(b)) => Ok(Value::Scalar(a * b)),
6324 _ => Err("kron: arguments must be numeric matrices".to_string()),
6325 },
6326
6327 ("meshgrid", 1) => {
6329 let xv = numeric_vec(&args[0], "meshgrid")?;
6330 let n = xv.len();
6331 let x_mat = Array2::from_shape_fn((n, n), |(_r, c)| xv[c]);
6332 let y_mat = Array2::from_shape_fn((n, n), |(r, _c)| xv[r]);
6333 if get_nargout() >= 2 {
6334 Ok(Value::Tuple(vec![
6335 Value::Matrix(Box::new(x_mat)),
6336 Value::Matrix(Box::new(y_mat)),
6337 ]))
6338 } else {
6339 Ok(Value::Matrix(Box::new(x_mat)))
6340 }
6341 }
6342 ("meshgrid", 2) => {
6343 let xv = numeric_vec(&args[0], "meshgrid")?;
6344 let yv = numeric_vec(&args[1], "meshgrid")?;
6345 let n_rows = yv.len();
6346 let n_cols = xv.len();
6347 let x_mat = Array2::from_shape_fn((n_rows, n_cols), |(_r, c)| xv[c]);
6348 let y_mat = Array2::from_shape_fn((n_rows, n_cols), |(r, _c)| yv[r]);
6349 if get_nargout() >= 2 {
6350 Ok(Value::Tuple(vec![
6351 Value::Matrix(Box::new(x_mat)),
6352 Value::Matrix(Box::new(y_mat)),
6353 ]))
6354 } else {
6355 Ok(Value::Matrix(Box::new(x_mat)))
6356 }
6357 }
6358
6359 ("cross", 2) => {
6361 fn to_vec3(v: &Value, argn: usize) -> Result<[f64; 3], String> {
6362 match v {
6363 Value::Matrix(m) => {
6364 let flat: Vec<f64> = m.iter().copied().collect();
6365 if flat.len() != 3 {
6366 Err(format!(
6367 "cross: argument {} must have exactly 3 elements",
6368 argn
6369 ))
6370 } else {
6371 Ok([flat[0], flat[1], flat[2]])
6372 }
6373 }
6374 _ => Err(format!(
6375 "cross: argument {} must be a 3-element vector",
6376 argn
6377 )),
6378 }
6379 }
6380 let a = to_vec3(&args[0], 1)?;
6381 let b = to_vec3(&args[1], 2)?;
6382 let cx = a[1] * b[2] - a[2] * b[1];
6383 let cy = a[2] * b[0] - a[0] * b[2];
6384 let cz = a[0] * b[1] - a[1] * b[0];
6385 let result = match &args[0] {
6387 Value::Matrix(m) if m.nrows() == 1 => {
6388 Array2::from_shape_vec((1, 3), vec![cx, cy, cz]).unwrap()
6389 }
6390 _ => Array2::from_shape_vec((3, 1), vec![cx, cy, cz]).unwrap(),
6391 };
6392 Ok(Value::Matrix(Box::new(result)))
6393 }
6394
6395 ("dot", 2) => {
6396 fn to_flat(v: &Value, argn: usize) -> Result<Vec<f64>, String> {
6397 match v {
6398 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6399 Value::Scalar(s) => Ok(vec![*s]),
6400 _ => Err(format!("dot: argument {} must be a numeric vector", argn)),
6401 }
6402 }
6403 let a = to_flat(&args[0], 1)?;
6404 let b = to_flat(&args[1], 2)?;
6405 if a.len() != b.len() {
6406 return Err(format!(
6407 "dot: vectors must have the same length ({} vs {})",
6408 a.len(),
6409 b.len()
6410 ));
6411 }
6412 let s: f64 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
6413 Ok(Value::Scalar(s))
6414 }
6415
6416 ("intersect", 2) => {
6418 fn to_sorted_vec(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6419 match v {
6420 Value::Matrix(m) => {
6421 let mut vals: Vec<f64> = m.iter().copied().collect();
6422 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6423 Ok(vals)
6424 }
6425 Value::Scalar(s) => Ok(vec![*s]),
6426 _ => Err(format!("{fname}: arguments must be numeric vectors")),
6427 }
6428 }
6429 let a = to_sorted_vec(&args[0], "intersect")?;
6430 let b = to_sorted_vec(&args[1], "intersect")?;
6431 let b_set: std::collections::HashSet<u64> = b
6432 .iter()
6433 .filter(|x| !x.is_nan())
6434 .map(|x| x.to_bits())
6435 .collect();
6436 let mut result: Vec<f64> = Vec::new();
6437 for x in &a {
6438 if !x.is_nan()
6439 && b_set.contains(&x.to_bits())
6440 && result.last().is_none_or(|&last| last != *x)
6441 {
6442 result.push(*x);
6443 }
6444 }
6445 let n = result.len();
6446 if n == 0 {
6447 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
6448 } else {
6449 Ok(Value::Matrix(Box::new(
6450 Array2::from_shape_vec((1, n), result).unwrap(),
6451 )))
6452 }
6453 }
6454
6455 ("union", 2) => {
6456 fn collect_vals(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6457 match v {
6458 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6459 Value::Scalar(s) => Ok(vec![*s]),
6460 _ => Err(format!("{fname}: arguments must be numeric vectors")),
6461 }
6462 }
6463 let mut combined = collect_vals(&args[0], "union")?;
6464 combined.extend(collect_vals(&args[1], "union")?);
6465 combined.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
6466 let mut result: Vec<f64> = Vec::new();
6467 for x in combined {
6468 if result.last().is_none_or(|&last| last != x) {
6469 result.push(x);
6470 }
6471 }
6472 let n = result.len();
6473 if n == 0 {
6474 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
6475 } else {
6476 Ok(Value::Matrix(Box::new(
6477 Array2::from_shape_vec((1, n), result).unwrap(),
6478 )))
6479 }
6480 }
6481
6482 ("setdiff", 2) => {
6483 fn collect_vals2(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6484 match v {
6485 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6486 Value::Scalar(s) => Ok(vec![*s]),
6487 _ => Err(format!("{fname}: arguments must be numeric vectors")),
6488 }
6489 }
6490 let a = collect_vals2(&args[0], "setdiff")?;
6491 let b = collect_vals2(&args[1], "setdiff")?;
6492 let b_set: std::collections::HashSet<u64> = b
6493 .iter()
6494 .filter(|x| !x.is_nan())
6495 .map(|x| x.to_bits())
6496 .collect();
6497 let mut a_sorted = a.clone();
6498 a_sorted.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
6499 let mut result: Vec<f64> = Vec::new();
6500 for x in a_sorted {
6501 if !x.is_nan()
6502 && !b_set.contains(&x.to_bits())
6503 && result.last().is_none_or(|&last| last != x)
6504 {
6505 result.push(x);
6506 }
6507 }
6508 let n = result.len();
6509 if n == 0 {
6510 Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))))
6511 } else {
6512 Ok(Value::Matrix(Box::new(
6513 Array2::from_shape_vec((1, n), result).unwrap(),
6514 )))
6515 }
6516 }
6517
6518 ("ismember", 2) => {
6519 fn collect_vals3(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
6520 match v {
6521 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6522 Value::Scalar(s) => Ok(vec![*s]),
6523 _ => Err(format!("{fname}: arguments must be numeric")),
6524 }
6525 }
6526 let set: std::collections::HashSet<u64> = collect_vals3(&args[1], "ismember")?
6527 .into_iter()
6528 .filter(|x| !x.is_nan())
6529 .map(|x| x.to_bits())
6530 .collect();
6531 match &args[0] {
6532 Value::Scalar(s) => {
6533 let found = !s.is_nan() && set.contains(&s.to_bits());
6534 Ok(Value::Scalar(if found { 1.0 } else { 0.0 }))
6535 }
6536 Value::Matrix(m) => {
6537 let result: Vec<f64> = m
6538 .iter()
6539 .map(|x| {
6540 if !x.is_nan() && set.contains(&x.to_bits()) {
6541 1.0
6542 } else {
6543 0.0
6544 }
6545 })
6546 .collect();
6547 let shape = m.raw_dim();
6548 Ok(Value::Matrix(Box::new(
6549 Array2::from_shape_vec(shape, result).unwrap(),
6550 )))
6551 }
6552 _ => Err("ismember: first argument must be numeric".to_string()),
6553 }
6554 }
6555
6556 ("sub2ind", 3) => {
6558 let sz = match &args[0] {
6559 Value::Matrix(m) if m.len() == 2 => (m[[0, 0]] as usize, m[[0, 1]] as usize),
6560 _ => return Err("sub2ind: first argument must be [rows cols]".to_string()),
6561 };
6562 let rows = sz.0;
6563 fn idx_vals(v: &Value, argn: usize) -> Result<Vec<f64>, String> {
6564 match v {
6565 Value::Scalar(s) => Ok(vec![*s]),
6566 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6567 _ => Err(format!("sub2ind: argument {} must be numeric", argn)),
6568 }
6569 }
6570 let r = idx_vals(&args[1], 2)?;
6571 let c = idx_vals(&args[2], 3)?;
6572 if r.len() != c.len() {
6573 return Err(
6574 "sub2ind: row and column index vectors must have the same length".to_string(),
6575 );
6576 }
6577 if r.len() == 1 {
6578 let idx = (c[0] as usize - 1) * rows + r[0] as usize;
6579 Ok(Value::Scalar(idx as f64))
6580 } else {
6581 let vals: Vec<f64> = r
6582 .iter()
6583 .zip(c.iter())
6584 .map(|(&ri, &ci)| ((ci as usize - 1) * rows + ri as usize) as f64)
6585 .collect();
6586 let n = vals.len();
6587 Ok(Value::Matrix(Box::new(
6588 Array2::from_shape_vec((1, n), vals).unwrap(),
6589 )))
6590 }
6591 }
6592
6593 ("ind2sub", 2) => {
6594 let sz = match &args[0] {
6595 Value::Matrix(m) if m.len() == 2 => (m[[0, 0]] as usize, m[[0, 1]] as usize),
6596 _ => return Err("ind2sub: first argument must be [rows cols]".to_string()),
6597 };
6598 let rows = sz.0;
6599 fn idx_vals2(v: &Value, argn: usize) -> Result<Vec<f64>, String> {
6600 match v {
6601 Value::Scalar(s) => Ok(vec![*s]),
6602 Value::Matrix(m) => Ok(m.iter().copied().collect()),
6603 _ => Err(format!("ind2sub: argument {} must be numeric", argn)),
6604 }
6605 }
6606 let indices = idx_vals2(&args[1], 2)?;
6607 if indices.len() == 1 {
6608 let idx = indices[0] as usize;
6609 let r = ((idx - 1) % rows + 1) as f64;
6610 let c = ((idx - 1) / rows + 1) as f64;
6611 Ok(Value::Tuple(vec![Value::Scalar(r), Value::Scalar(c)]))
6612 } else {
6613 let n = indices.len();
6614 let rs: Vec<f64> = indices
6615 .iter()
6616 .map(|&idx| ((idx as usize - 1) % rows + 1) as f64)
6617 .collect();
6618 let cs: Vec<f64> = indices
6619 .iter()
6620 .map(|&idx| ((idx as usize - 1) / rows + 1) as f64)
6621 .collect();
6622 let rm = Value::Matrix(Box::new(Array2::from_shape_vec((1, n), rs).unwrap()));
6623 let cm = Value::Matrix(Box::new(Array2::from_shape_vec((1, n), cs).unwrap()));
6624 Ok(Value::Tuple(vec![rm, cm]))
6625 }
6626 }
6627
6628 ("repelem", 2) => match (&args[0], &args[1]) {
6629 (Value::Matrix(a), Value::Scalar(n)) => {
6630 let n = *n as usize;
6631 let flat: Vec<f64> = a.iter().flat_map(|&x| std::iter::repeat_n(x, n)).collect();
6632 let total = flat.len();
6633 Ok(Value::Matrix(Box::new(
6634 Array2::from_shape_vec((1, total), flat).unwrap(),
6635 )))
6636 }
6637 (Value::Matrix(a), Value::Matrix(ns)) => {
6638 let av: Vec<f64> = a.iter().copied().collect();
6639 let nv: Vec<f64> = ns.iter().copied().collect();
6640 if av.len() != nv.len() {
6641 return Err(
6642 "repelem: element count vector must match source vector length".to_string(),
6643 );
6644 }
6645 let flat: Vec<f64> = av
6646 .iter()
6647 .zip(nv.iter())
6648 .flat_map(|(&x, &n)| std::iter::repeat_n(x, n as usize))
6649 .collect();
6650 let total = flat.len();
6651 Ok(Value::Matrix(Box::new(
6652 Array2::from_shape_vec((1, total), flat).unwrap(),
6653 )))
6654 }
6655 (Value::Scalar(s), Value::Scalar(n)) => {
6656 let n = *n as usize;
6657 Ok(Value::Matrix(Box::new(Array2::from_elem((1, n), *s))))
6658 }
6659 _ => Err("repelem: unsupported argument types".to_string()),
6660 },
6661 ("repelem", 3) => match (&args[0], &args[1], &args[2]) {
6662 (Value::Matrix(a), Value::Scalar(rm), Value::Scalar(cn)) => {
6663 let rm = *rm as usize;
6664 let cn = *cn as usize;
6665 let (nrows, ncols) = (a.nrows(), a.ncols());
6666 let mut result = Array2::<f64>::zeros((nrows * rm, ncols * cn));
6667 for i in 0..nrows {
6668 for j in 0..ncols {
6669 let v = a[[i, j]];
6670 for di in 0..rm {
6671 for dj in 0..cn {
6672 result[[i * rm + di, j * cn + dj]] = v;
6673 }
6674 }
6675 }
6676 }
6677 Ok(Value::Matrix(Box::new(result)))
6678 }
6679 (Value::Scalar(s), Value::Scalar(rm), Value::Scalar(cn)) => Ok(Value::Matrix(
6680 Box::new(Array2::from_elem((*rm as usize, *cn as usize), *s)),
6681 )),
6682 _ => Err("repelem: expects (matrix, m, n) for 2D repetition".to_string()),
6683 },
6684
6685 ("polyval", 2) => {
6687 let coeffs = poly_coeffs(&args[0], "polyval")?;
6688 if coeffs.is_empty() {
6689 return Err("polyval: polynomial vector is empty".to_string());
6690 }
6691 match &args[1] {
6692 Value::Scalar(x) => Ok(Value::Scalar(horner(&coeffs, *x))),
6693 Value::Matrix(m) => Ok(Value::Matrix(Box::new(m.mapv(|x| horner(&coeffs, x))))),
6694 _ => Err("polyval: second argument must be a real numeric value".to_string()),
6695 }
6696 }
6697
6698 ("polyfit", 3) => {
6699 let xv = poly_coeffs(&args[0], "polyfit")?;
6700 let yv = poly_coeffs(&args[1], "polyfit")?;
6701 let deg = match &args[2] {
6702 Value::Scalar(n) => {
6703 let d = *n as usize;
6704 if *n < 0.0 || (*n - d as f64).abs() > 1e-9 {
6705 return Err("polyfit: degree must be a non-negative integer".to_string());
6706 }
6707 d
6708 }
6709 _ => return Err("polyfit: degree must be a scalar".to_string()),
6710 };
6711 if xv.len() != yv.len() {
6712 return Err("polyfit: x and y must have the same length".to_string());
6713 }
6714 let m = xv.len();
6715 let ncols = deg + 1;
6716 if ncols > m {
6717 return Err(format!(
6718 "polyfit: not enough data points ({m}) for degree-{deg} fit"
6719 ));
6720 }
6721 let mut vander = Array2::<f64>::zeros((m, ncols));
6723 for (i, &xi) in xv.iter().enumerate() {
6724 for j in 0..ncols {
6725 vander[[i, j]] = xi.powi((deg - j) as i32);
6726 }
6727 }
6728 let (q, r) = qr_decompose(&vander)?;
6730 let qty: Vec<f64> = (0..ncols)
6731 .map(|i| (0..m).map(|k| q[[k, i]] * yv[k]).sum())
6732 .collect();
6733 let mut r_sq = Array2::<f64>::zeros((ncols, ncols));
6735 for i in 0..ncols {
6736 for j in 0..ncols {
6737 r_sq[[i, j]] = r[[i, j]];
6738 }
6739 }
6740 let coeffs = poly_back_sub(&r_sq, &qty)?;
6741 let result = Array2::from_shape_vec((1, ncols), coeffs)
6742 .map_err(|e| format!("polyfit: internal error: {e}"))?;
6743 Ok(Value::Matrix(Box::new(result)))
6744 }
6745
6746 ("roots", 1) => {
6747 let raw = poly_coeffs(&args[0], "roots")?;
6748 let start = raw.iter().position(|&c| c != 0.0).unwrap_or(raw.len());
6750 let coeffs = &raw[start..];
6751 if coeffs.len() <= 1 {
6752 return Ok(Value::Matrix(Box::new(Array2::zeros((0, 1)))));
6753 }
6754 let roots = durand_kerner(coeffs)?;
6755 Ok(roots_to_value(&roots))
6756 }
6757
6758 ("poly", 1) => match &args[0] {
6759 Value::Scalar(r) => {
6760 let data = vec![1.0, -*r];
6761 Ok(Value::Matrix(Box::new(
6762 Array2::from_shape_vec((1, 2), data).unwrap(),
6763 )))
6764 }
6765 Value::Matrix(m) => {
6766 if m.nrows() == 1 || m.ncols() == 1 {
6767 let roots: Vec<f64> = if m.nrows() == 1 {
6769 m.row(0).iter().copied().collect()
6770 } else {
6771 m.column(0).iter().copied().collect()
6772 };
6773 let mut p = vec![1.0_f64];
6774 for &r in &roots {
6775 p = poly_conv(&p, &[1.0, -r]);
6776 }
6777 let ncols = p.len();
6778 Ok(Value::Matrix(Box::new(
6779 Array2::from_shape_vec((1, ncols), p).unwrap(),
6780 )))
6781 } else {
6782 let coeffs = characteristic_poly(m)?;
6784 let ncols = coeffs.len();
6785 Ok(Value::Matrix(Box::new(
6786 Array2::from_shape_vec((1, ncols), coeffs).unwrap(),
6787 )))
6788 }
6789 }
6790 _ => Err("poly: argument must be a numeric vector or square matrix".to_string()),
6791 },
6792
6793 ("conv", 2) => {
6795 let a = poly_coeffs(&args[0], "conv")?;
6796 let b = poly_coeffs(&args[1], "conv")?;
6797 if a.is_empty() || b.is_empty() {
6798 return Ok(Value::Matrix(Box::new(Array2::zeros((1, 0)))));
6799 }
6800 let c = poly_conv(&a, &b);
6801 let len = c.len();
6802 Ok(Value::Matrix(Box::new(
6803 Array2::from_shape_vec((1, len), c).unwrap(),
6804 )))
6805 }
6806
6807 ("deconv", 2) => {
6808 let c = poly_coeffs(&args[0], "deconv")?;
6809 let b = poly_coeffs(&args[1], "deconv")?;
6810 let (q, r) = poly_deconv(&c, &b)?;
6811 let qn = q.len();
6812 let rn = r.len();
6813 let q_val = Value::Matrix(Box::new(Array2::from_shape_vec((1, qn), q).unwrap()));
6814 let r_val = Value::Matrix(Box::new(Array2::from_shape_vec((1, rn), r).unwrap()));
6815 Ok(Value::Tuple(vec![q_val, r_val]))
6816 }
6817
6818 ("interp1", 3) => {
6819 let xv = poly_coeffs(&args[0], "interp1")?;
6820 let yv = poly_coeffs(&args[1], "interp1")?;
6821 if xv.len() != yv.len() {
6822 return Err("interp1: x and y must have the same length".to_string());
6823 }
6824 if xv.len() < 2 {
6825 return Err("interp1: requires at least two knot points".to_string());
6826 }
6827 match &args[2] {
6828 Value::Scalar(xi) => Ok(Value::Scalar(interp1_at(&xv, &yv, *xi, "linear"))),
6829 Value::Matrix(xi_m) => Ok(Value::Matrix(Box::new(
6830 xi_m.mapv(|xi| interp1_at(&xv, &yv, xi, "linear")),
6831 ))),
6832 _ => Err("interp1: query points must be numeric".to_string()),
6833 }
6834 }
6835
6836 ("interp1", 4) => {
6837 let xv = poly_coeffs(&args[0], "interp1")?;
6838 let yv = poly_coeffs(&args[1], "interp1")?;
6839 let method = match &args[3] {
6840 Value::Str(s) | Value::StringObj(s) => s.clone(),
6841 _ => return Err("interp1: method argument must be a string".to_string()),
6842 };
6843 if !matches!(method.as_str(), "linear" | "nearest" | "previous" | "next") {
6844 return Err(format!(
6845 "interp1: unknown method '{method}'; supported: linear nearest previous next"
6846 ));
6847 }
6848 if xv.len() != yv.len() {
6849 return Err("interp1: x and y must have the same length".to_string());
6850 }
6851 if xv.len() < 2 {
6852 return Err("interp1: requires at least two knot points".to_string());
6853 }
6854 match &args[2] {
6855 Value::Scalar(xi) => Ok(Value::Scalar(interp1_at(&xv, &yv, *xi, &method))),
6856 Value::Matrix(xi_m) => {
6857 let m_str = method.as_str();
6858 Ok(Value::Matrix(Box::new(
6859 xi_m.mapv(|xi| interp1_at(&xv, &yv, xi, m_str)),
6860 )))
6861 }
6862 _ => Err("interp1: query points must be numeric".to_string()),
6863 }
6864 }
6865
6866 ("tic", 0) => {
6868 TIC_TIME.with(|t| t.set(Some(std::time::Instant::now())));
6869 Ok(Value::Void)
6870 }
6871 ("toc", 0) => {
6872 let elapsed = TIC_TIME.with(|t| t.get().map(|s| s.elapsed().as_secs_f64()));
6873 match elapsed {
6874 Some(t) => Ok(Value::Scalar(t)),
6875 None => Err("toc: tic must be called before toc".to_string()),
6876 }
6877 }
6878
6879 ("eval", 1) => {
6881 let code = match &args[0] {
6882 Value::Str(s) | Value::StringObj(s) => s.clone(),
6883 _ => return Err("eval: argument must be a string".to_string()),
6884 };
6885 call_eval_str_hook(&code, env)
6886 }
6887 ("eval", 2) => {
6888 let code = match &args[0] {
6889 Value::Str(s) | Value::StringObj(s) => s.clone(),
6890 _ => return Err("eval: argument must be a string".to_string()),
6891 };
6892 match call_eval_str_hook(&code, env) {
6893 Err(e) => {
6894 set_last_err(&e);
6895 let catch = match &args[1] {
6896 Value::Str(s) | Value::StringObj(s) => s.clone(),
6897 _ => return Err("eval: catch argument must be a string".to_string()),
6898 };
6899 call_eval_str_hook(&catch, env)
6900 }
6901 ok => ok,
6902 }
6903 }
6904
6905 _ => {
6906 let hint = suggest_similar(name, env);
6907 match hint {
6908 Some(s) => Err(format!("Unknown function '{name}'; did you mean '{s}'?")),
6909 None => Err(format!("Unknown function: '{name}'")),
6910 }
6911 }
6912 }
6913}
6914
6915fn interpret_delim(s: &str) -> String {
6918 match s {
6919 r"\t" => "\t".to_string(),
6920 r"\n" => "\n".to_string(),
6921 other => other.to_string(),
6922 }
6923}
6924
6925fn delim_consistent(lines: &[&str], delim: char) -> bool {
6927 let counts: Vec<usize> = lines.iter().map(|l| l.split(delim).count()).collect();
6928 counts.iter().all(|&c| c > 1) && counts.windows(2).all(|w| w[0] == w[1])
6929}
6930
6931fn dlmread_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
6933 let content =
6934 std::fs::read_to_string(path).map_err(|e| format!("dlmread: cannot read '{path}': {e}"))?;
6935
6936 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
6937
6938 if lines.is_empty() {
6939 return Ok(Value::Matrix(Box::new(Array2::zeros((0, 0)))));
6940 }
6941
6942 let delim: Option<String> = match explicit_delim {
6944 Some(d) => Some(d),
6945 None => {
6946 if delim_consistent(&lines, ',') {
6947 Some(",".to_string())
6948 } else if delim_consistent(&lines, '\t') {
6949 Some("\t".to_string())
6950 } else {
6951 None }
6953 }
6954 };
6955
6956 let mut rows: Vec<Vec<f64>> = Vec::new();
6957 for (line_num, line) in lines.iter().enumerate() {
6958 let fields: Vec<&str> = match &delim {
6959 Some(d) => line.split(d.as_str()).collect(),
6960 None => line.split_whitespace().collect(),
6961 };
6962 let mut row_vals: Vec<f64> = Vec::with_capacity(fields.len());
6963 for field in &fields {
6964 let trimmed = field.trim();
6965 if trimmed.is_empty() {
6966 row_vals.push(0.0);
6967 } else {
6968 row_vals.push(trimmed.parse::<f64>().map_err(|_| {
6969 format!(
6970 "dlmread: non-numeric value '{trimmed}' on line {}",
6971 line_num + 1
6972 )
6973 })?);
6974 }
6975 }
6976 if !row_vals.is_empty() {
6977 rows.push(row_vals);
6978 }
6979 }
6980
6981 if rows.is_empty() {
6982 return Ok(Value::Matrix(Box::new(Array2::zeros((0, 0)))));
6983 }
6984
6985 let ncols = rows[0].len();
6986 for (i, row) in rows.iter().enumerate() {
6987 if row.len() != ncols {
6988 return Err(format!(
6989 "dlmread: row {} has {} fields, expected {ncols}",
6990 i + 1,
6991 row.len()
6992 ));
6993 }
6994 }
6995
6996 let nrows = rows.len();
6997 let flat: Vec<f64> = rows.into_iter().flatten().collect();
6998 Array2::from_shape_vec((nrows, ncols), flat)
6999 .map_err(|e| format!("dlmread: shape error: {e}"))
7000 .map(|m| Value::Matrix(Box::new(m)))
7001}
7002
7003fn fmt_dlm_number(n: f64) -> String {
7006 if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
7007 format!("{}", n as i64)
7008 } else {
7009 format!("{n}")
7010 }
7011}
7012
7013fn dlmwrite_impl(path: &str, val: &Value, explicit_delim: Option<String>) -> Result<Value, String> {
7015 let delim = explicit_delim.unwrap_or_else(|| ",".to_string());
7016
7017 let content = match val {
7018 Value::Scalar(n) => format!("{}\n", fmt_dlm_number(*n)),
7019 Value::Matrix(m) => {
7020 let mut out = String::new();
7021 for row in m.rows() {
7022 let parts: Vec<String> = row.iter().map(|n| fmt_dlm_number(*n)).collect();
7023 out.push_str(&parts.join(&delim));
7024 out.push('\n');
7025 }
7026 out
7027 }
7028 _ => {
7029 return Err("dlmwrite: second argument must be a numeric scalar or matrix".to_string());
7030 }
7031 };
7032
7033 std::fs::write(path, content).map_err(|e| format!("dlmwrite: cannot write '{path}': {e}"))?;
7034 Ok(Value::Void)
7035}
7036
7037fn auto_detect_delim(lines: &[&str]) -> Option<String> {
7043 let comma_counts: Vec<usize> = lines.iter().map(|l| split_csv_row(l, ",").len()).collect();
7045 if comma_counts.iter().all(|&c| c > 1) && comma_counts.windows(2).all(|w| w[0] == w[1]) {
7046 return Some(",".to_string());
7047 }
7048 if delim_consistent(lines, '\t') {
7049 Some("\t".to_string())
7050 } else {
7051 None
7052 }
7053}
7054
7055fn split_csv_row(line: &str, delim: &str) -> Vec<String> {
7059 if delim.chars().count() != 1 {
7060 return line.split(delim).map(str::to_string).collect();
7061 }
7062 let delim_char = delim.chars().next().unwrap();
7063 let chars: Vec<char> = line.chars().collect();
7064 let mut fields: Vec<String> = Vec::new();
7065 let mut field = String::new();
7066 let mut i = 0;
7067 let mut in_quotes = false;
7068 while i < chars.len() {
7069 let c = chars[i];
7070 if in_quotes {
7071 if c == '"' && i + 1 < chars.len() && chars[i + 1] == '"' {
7072 field.push('"');
7073 i += 2;
7074 continue;
7075 } else if c == '"' {
7076 in_quotes = false;
7077 } else {
7078 field.push(c);
7079 }
7080 } else if c == '"' {
7081 in_quotes = true;
7082 } else if c == delim_char {
7083 fields.push(std::mem::take(&mut field));
7084 } else {
7085 field.push(c);
7086 }
7087 i += 1;
7088 }
7089 fields.push(field);
7090 fields
7091}
7092
7093fn split_csv_row_opt(line: &str, delim: &Option<String>) -> Vec<String> {
7095 match delim {
7096 None => line.split_whitespace().map(str::to_string).collect(),
7097 Some(d) => split_csv_row(line, d),
7098 }
7099}
7100
7101fn row_is_header(fields: &[String]) -> bool {
7103 fields
7104 .iter()
7105 .any(|f| !f.trim().is_empty() && f.trim().parse::<f64>().is_err())
7106}
7107
7108fn sanitize_header(s: &str, col_1based: usize) -> String {
7112 let s = s.trim();
7113 if s.is_empty() {
7114 return format!("x{col_1based}");
7115 }
7116 let mut out = String::new();
7117 for c in s.chars() {
7118 if c.is_alphanumeric() || c == '_' {
7119 out.push(c);
7120 } else if !out.ends_with('_') {
7121 out.push('_');
7122 }
7123 }
7124 let out = out.trim_end_matches('_').to_string();
7125 if out.is_empty() {
7126 return format!("x{col_1based}");
7127 }
7128 if out.chars().next().unwrap().is_ascii_digit() {
7129 format!("x{out}")
7130 } else {
7131 out
7132 }
7133}
7134
7135fn deduplicate_headers(headers: Vec<String>) -> Vec<String> {
7138 let mut count: HashMap<String, usize> = HashMap::new();
7139 for h in &headers {
7140 *count.entry(h.clone()).or_insert(0) += 1;
7141 }
7142 let mut seen: HashMap<String, usize> = HashMap::new();
7143 headers
7144 .into_iter()
7145 .map(|h| {
7146 if *count.get(&h).unwrap() == 1 {
7147 h
7148 } else {
7149 let idx = seen.entry(h.clone()).or_insert(0);
7150 *idx += 1;
7151 format!("{h}_{idx}")
7152 }
7153 })
7154 .collect()
7155}
7156
7157fn parse_delimiter_opt(
7160 fn_name: &str,
7161 args: &[Value],
7162 start: usize,
7163) -> Result<Option<String>, String> {
7164 if args.len() <= start {
7165 return Ok(None);
7166 }
7167 let key = string_arg(&args[start], fn_name, start + 1)?;
7168 if !key.eq_ignore_ascii_case("delimiter") {
7169 return Err(format!(
7170 "{fn_name}: expected 'Delimiter' option at argument {}, got '{key}'",
7171 start + 1
7172 ));
7173 }
7174 if args.len() <= start + 1 {
7175 return Err(format!("{fn_name}: 'Delimiter' option requires a value"));
7176 }
7177 let val = interpret_delim(string_arg(&args[start + 1], fn_name, start + 2)?);
7178 Ok(Some(val))
7179}
7180
7181fn readmatrix_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
7187 let content = std::fs::read_to_string(path)
7188 .map_err(|e| format!("readmatrix: cannot read '{path}': {e}"))?;
7189
7190 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
7191 if lines.is_empty() {
7192 return Ok(Value::Matrix(Box::new(Array2::<f64>::zeros((0, 0)))));
7193 }
7194
7195 let delim = match explicit_delim {
7196 Some(d) => Some(d),
7197 None => auto_detect_delim(&lines),
7198 };
7199
7200 let first_fields = split_csv_row_opt(lines[0], &delim);
7201 let skip_header = row_is_header(&first_fields);
7202 let data_lines = if skip_header { &lines[1..] } else { &lines[..] };
7203
7204 if data_lines.is_empty() {
7205 return Ok(Value::Matrix(Box::new(Array2::<f64>::zeros((0, 0)))));
7206 }
7207
7208 let mut rows: Vec<Vec<f64>> = Vec::new();
7209 for (i, line) in data_lines.iter().enumerate() {
7210 let fields = split_csv_row_opt(line, &delim);
7211 let mut row: Vec<f64> = Vec::with_capacity(fields.len());
7212 for f in &fields {
7213 let t = f.trim();
7214 if t.is_empty() {
7215 row.push(f64::NAN);
7216 } else {
7217 row.push(t.parse::<f64>().map_err(|_| {
7218 format!(
7219 "readmatrix: non-numeric value '{t}' on line {}",
7220 i + 1 + usize::from(skip_header)
7221 )
7222 })?);
7223 }
7224 }
7225 rows.push(row);
7226 }
7227
7228 if rows.is_empty() {
7229 return Ok(Value::Matrix(Box::new(Array2::<f64>::zeros((0, 0)))));
7230 }
7231
7232 let ncols = rows[0].len();
7233 for (i, row) in rows.iter().enumerate() {
7234 if row.len() != ncols {
7235 return Err(format!(
7236 "readmatrix: row {} has {} fields, expected {ncols}",
7237 i + 1,
7238 row.len()
7239 ));
7240 }
7241 }
7242
7243 let nrows = rows.len();
7244 let flat: Vec<f64> = rows.into_iter().flatten().collect();
7245 Array2::from_shape_vec((nrows, ncols), flat)
7246 .map_err(|e| format!("readmatrix: shape error: {e}"))
7247 .map(|m| Value::Matrix(Box::new(m)))
7248}
7249
7250fn readtable_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
7256 let content = std::fs::read_to_string(path)
7257 .map_err(|e| format!("readtable: cannot read '{path}': {e}"))?;
7258
7259 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
7260 if lines.is_empty() {
7261 return Ok(Value::Struct(Box::new(IndexMap::new())));
7262 }
7263
7264 let delim = match explicit_delim {
7265 Some(d) => Some(d),
7266 None => auto_detect_delim(&lines),
7267 };
7268
7269 let raw_headers = split_csv_row_opt(lines[0], &delim);
7270 let ncols = raw_headers.len();
7271 let headers: Vec<String> = deduplicate_headers(
7272 raw_headers
7273 .iter()
7274 .enumerate()
7275 .map(|(i, h)| sanitize_header(h.trim(), i + 1))
7276 .collect(),
7277 );
7278
7279 let data_lines = &lines[1..];
7280 if data_lines.is_empty() {
7281 let mut s: IndexMap<String, Value> = IndexMap::new();
7282 for h in &headers {
7283 s.insert(
7284 h.clone(),
7285 Value::Matrix(Box::new(Array2::<f64>::zeros((0, 1)))),
7286 );
7287 }
7288 return Ok(Value::Struct(Box::new(s)));
7289 }
7290
7291 let mut all_rows: Vec<Vec<String>> = Vec::new();
7292 for (i, line) in data_lines.iter().enumerate() {
7293 let fields = split_csv_row_opt(line, &delim);
7294 if fields.len() != ncols {
7295 return Err(format!(
7296 "readtable: row {} has {} fields, expected {ncols}",
7297 i + 2,
7298 fields.len()
7299 ));
7300 }
7301 all_rows.push(fields.into_iter().map(|f| f.trim().to_string()).collect());
7302 }
7303
7304 let nrows = all_rows.len();
7305 let mut s: IndexMap<String, Value> = IndexMap::new();
7306 for col in 0..ncols {
7307 let all_numeric = all_rows.iter().all(|row| {
7308 let t = row[col].as_str();
7309 t.is_empty() || t.parse::<f64>().is_ok()
7310 });
7311 if all_numeric {
7312 let vals: Vec<f64> = all_rows
7313 .iter()
7314 .map(|row| {
7315 let t = row[col].as_str();
7316 if t.is_empty() {
7317 f64::NAN
7318 } else {
7319 t.parse::<f64>().unwrap()
7320 }
7321 })
7322 .collect();
7323 let col_mat = Array2::from_shape_vec((nrows, 1), vals)
7324 .map_err(|e| format!("readtable: shape error: {e}"))?;
7325 s.insert(headers[col].clone(), Value::Matrix(Box::new(col_mat)));
7326 } else {
7327 let vals: Vec<Value> = all_rows
7328 .iter()
7329 .map(|row| Value::Str(row[col].clone()))
7330 .collect();
7331 s.insert(headers[col].clone(), Value::Cell(Box::new(vals)));
7332 }
7333 }
7334 Ok(Value::Struct(Box::new(s)))
7335}
7336
7337fn csv_quote_cell(s: &str, delim: &str) -> String {
7339 if s.contains('"') || s.contains('\n') || s.contains(delim) {
7340 let escaped = s.replace('"', "\"\"");
7341 format!("\"{escaped}\"")
7342 } else {
7343 s.to_string()
7344 }
7345}
7346
7347fn col_nrows(v: &Value) -> Option<usize> {
7352 match v {
7353 Value::Matrix(m) if m.ncols() == 1 || m.nrows() == 0 => Some(m.nrows()),
7354 Value::Cell(c) => Some(c.len()),
7355 Value::Scalar(_) => Some(1),
7356 Value::Str(_) | Value::StringObj(_) => Some(1),
7357 _ => None,
7358 }
7359}
7360
7361fn col_cell_str(v: &Value, row: usize, delim: &str) -> Result<String, String> {
7363 match v {
7364 Value::Matrix(m) => Ok(csv_quote_cell(&fmt_dlm_number(m[[row, 0]]), delim)),
7365 Value::Cell(c) => match &c[row] {
7366 Value::Str(s) | Value::StringObj(s) => Ok(csv_quote_cell(s, delim)),
7367 Value::Scalar(n) => Ok(csv_quote_cell(&fmt_dlm_number(*n), delim)),
7368 _ => Err(format!(
7369 "writetable: cell element at row {} has unsupported type",
7370 row + 1
7371 )),
7372 },
7373 Value::Scalar(n) => Ok(csv_quote_cell(&fmt_dlm_number(*n), delim)),
7374 Value::Str(s) | Value::StringObj(s) => Ok(csv_quote_cell(s, delim)),
7375 _ => Err(format!(
7376 "writetable: unsupported column type at row {}",
7377 row + 1
7378 )),
7379 }
7380}
7381
7382fn writetable_impl(
7388 tbl: &Value,
7389 path: &str,
7390 explicit_delim: Option<String>,
7391) -> Result<Value, String> {
7392 let delim = explicit_delim.unwrap_or_else(|| ",".to_string());
7393 let fields = match tbl {
7394 Value::Struct(m) => m,
7395 _ => return Err("writetable: first argument must be a struct".to_string()),
7396 };
7397 if fields.is_empty() {
7398 std::fs::write(path, "").map_err(|e| format!("writetable: cannot write '{path}': {e}"))?;
7399 return Ok(Value::Void);
7400 }
7401
7402 let nrows = {
7403 let (first_name, first_val) = fields.iter().next().unwrap();
7404 col_nrows(first_val).ok_or_else(|| {
7405 format!("writetable: column '{first_name}' must be a Matrix (N×1), Cell, or scalar")
7406 })?
7407 };
7408 for (cname, cval) in fields.iter() {
7409 let n = col_nrows(cval).ok_or_else(|| {
7410 format!("writetable: column '{cname}' must be a Matrix (N×1), Cell, or scalar")
7411 })?;
7412 if n != nrows {
7413 return Err(format!(
7414 "writetable: column '{cname}' has {n} rows, expected {nrows}"
7415 ));
7416 }
7417 }
7418
7419 let mut out = String::new();
7420 let header_parts: Vec<String> = fields.keys().map(|k| csv_quote_cell(k, &delim)).collect();
7421 out.push_str(&header_parts.join(&delim));
7422 out.push('\n');
7423
7424 for row in 0..nrows {
7425 let mut parts: Vec<String> = Vec::with_capacity(fields.len());
7426 for cval in fields.values() {
7427 parts.push(col_cell_str(cval, row, &delim)?);
7428 }
7429 out.push_str(&parts.join(&delim));
7430 out.push('\n');
7431 }
7432
7433 std::fs::write(path, out).map_err(|e| format!("writetable: cannot write '{path}': {e}"))?;
7434 Ok(Value::Void)
7435}
7436
7437fn glob_match_inner(pat: &[u8], name: &[u8]) -> bool {
7439 match (pat.first(), name.first()) {
7440 (None, None) => true,
7441 (Some(&b'*'), _) => {
7442 glob_match_inner(&pat[1..], name)
7443 || (!name.is_empty() && glob_match_inner(pat, &name[1..]))
7444 }
7445 (Some(&b'?'), Some(_)) => glob_match_inner(&pat[1..], &name[1..]),
7446 (Some(p), Some(n)) if p == n => glob_match_inner(&pat[1..], &name[1..]),
7447 _ => false,
7448 }
7449}
7450
7451fn glob_match(pattern: &str, name: &str) -> bool {
7453 #[cfg(windows)]
7454 let (p, n) = (pattern.to_lowercase(), name.to_lowercase());
7455 #[cfg(not(windows))]
7456 let (p, n) = (pattern.to_string(), name.to_string());
7457 glob_match_inner(p.as_bytes(), n.as_bytes())
7458}
7459
7460fn dir_impl(path_arg: &str) -> Value {
7462 let has_glob = path_arg.contains('*') || path_arg.contains('?');
7463 let p = std::path::Path::new(path_arg);
7464
7465 let (dir_path, pattern): (std::path::PathBuf, String) = if has_glob {
7466 let parent = p
7467 .parent()
7468 .filter(|d| *d != std::path::Path::new(""))
7469 .unwrap_or(std::path::Path::new("."));
7470 let pat = p
7471 .file_name()
7472 .map(|f| f.to_string_lossy().into_owned())
7473 .unwrap_or_default();
7474 (parent.to_path_buf(), pat)
7475 } else {
7476 (p.to_path_buf(), String::new())
7477 };
7478
7479 let abs = if dir_path.is_absolute() {
7483 dir_path.to_string_lossy().into_owned()
7484 } else {
7485 std::env::current_dir()
7486 .unwrap_or_else(|_| ".".into())
7487 .join(&dir_path)
7488 .to_string_lossy()
7489 .into_owned()
7490 };
7491 #[cfg(windows)]
7494 let abs = abs.replace('/', "\\");
7495 let folder_str = if abs.len() > 1 && (abs.ends_with('/') || abs.ends_with('\\')) {
7497 abs[..abs.len() - 1].to_string()
7498 } else {
7499 abs
7500 };
7501
7502 let mut entries: Vec<IndexMap<String, Value>> = Vec::new();
7503
7504 if !has_glob {
7506 for dot in &[".", ".."] {
7507 let mut row = IndexMap::new();
7508 row.insert("name".to_string(), Value::Str(dot.to_string()));
7509 row.insert("folder".to_string(), Value::Str(folder_str.clone()));
7510 row.insert("isdir".to_string(), Value::Scalar(1.0));
7511 row.insert("bytes".to_string(), Value::Scalar(0.0));
7512 entries.push(row);
7513 }
7514 }
7515
7516 let Ok(rd) = std::fs::read_dir(&dir_path) else {
7517 return Value::StructArray(Box::default());
7518 };
7519
7520 let mut file_rows: Vec<(String, IndexMap<String, Value>)> = rd
7521 .filter_map(|e| e.ok())
7522 .filter_map(|e| {
7523 let file_name = e.file_name().to_string_lossy().into_owned();
7524 if has_glob && !glob_match(&pattern, &file_name) {
7525 return None;
7526 }
7527 let meta = e.metadata().ok()?;
7528 let is_dir = if meta.is_dir() { 1.0 } else { 0.0 };
7529 let bytes = if meta.is_file() {
7530 meta.len() as f64
7531 } else {
7532 0.0
7533 };
7534 let mut row = IndexMap::new();
7535 row.insert("name".to_string(), Value::Str(file_name.clone()));
7536 row.insert("folder".to_string(), Value::Str(folder_str.clone()));
7537 row.insert("isdir".to_string(), Value::Scalar(is_dir));
7538 row.insert("bytes".to_string(), Value::Scalar(bytes));
7539 Some((file_name, row))
7540 })
7541 .collect();
7542
7543 file_rows.sort_by(|a, b| a.0.cmp(&b.0));
7544 entries.extend(file_rows.into_iter().map(|(_, row)| row));
7545 Value::StructArray(Box::new(entries))
7546}
7547
7548fn to_bits(v: f64, fname: &str, pos: usize) -> Result<u64, String> {
7551 if v < 0.0 {
7552 return Err(format!(
7553 "{fname}: argument {pos} must be non-negative, got {v}"
7554 ));
7555 }
7556 if v.fract() != 0.0 {
7557 return Err(format!(
7558 "{fname}: argument {pos} must be an integer, got {v}"
7559 ));
7560 }
7561 if v > u64::MAX as f64 {
7562 return Err(format!(
7563 "{fname}: argument {pos} is too large for bitwise operations"
7564 ));
7565 }
7566 Ok(v as u64)
7567}
7568
7569fn det_matrix(m: &Array2<f64>) -> Result<f64, String> {
7573 let n = m.nrows();
7574 if m.ncols() != n {
7575 return Err("det: matrix must be square".to_string());
7576 }
7577 if n == 0 {
7578 return Ok(1.0);
7579 }
7580 let mut a = m.clone();
7581 let mut sign: f64 = 1.0;
7582 for col in 0..n {
7583 let pivot = (col..n)
7585 .max_by(|&r1, &r2| a[[r1, col]].abs().partial_cmp(&a[[r2, col]].abs()).unwrap())
7586 .unwrap();
7587 if a[[pivot, col]].abs() < 1e-15 {
7588 return Ok(0.0); }
7590 if pivot != col {
7591 for j in 0..n {
7592 let tmp = a[[pivot, j]];
7593 a[[pivot, j]] = a[[col, j]];
7594 a[[col, j]] = tmp;
7595 }
7596 sign = -sign;
7597 }
7598 let pv = a[[col, col]];
7599 for row in (col + 1)..n {
7600 let factor = a[[row, col]] / pv;
7601 for j in col..n {
7602 let val = a[[col, j]] * factor;
7603 a[[row, j]] -= val;
7604 }
7605 }
7606 }
7607 Ok(sign * (0..n).map(|i| a[[i, i]]).product::<f64>())
7608}
7609
7610fn inv_matrix(m: &Array2<f64>) -> Result<Array2<f64>, String> {
7613 let n = m.nrows();
7614 if m.ncols() != n {
7615 return Err("inv: matrix must be square".to_string());
7616 }
7617 let cols = 2 * n;
7618 let mut aug = vec![0.0f64; n * cols];
7619 for i in 0..n {
7620 for j in 0..n {
7621 aug[i * cols + j] = m[[i, j]];
7622 }
7623 aug[i * cols + n + i] = 1.0;
7624 }
7625 for col in 0..n {
7626 let pivot = (col..n)
7628 .max_by(|&r1, &r2| {
7629 aug[r1 * cols + col]
7630 .abs()
7631 .partial_cmp(&aug[r2 * cols + col].abs())
7632 .unwrap()
7633 })
7634 .filter(|&r| aug[r * cols + col].abs() > 1e-12)
7635 .ok_or_else(|| "inv: matrix is singular".to_string())?;
7636 if pivot != col {
7637 for j in 0..cols {
7638 aug.swap(col * cols + j, pivot * cols + j);
7639 }
7640 }
7641 let pv = aug[col * cols + col];
7642 for j in 0..cols {
7643 aug[col * cols + j] /= pv;
7644 }
7645 for row in 0..n {
7646 if row == col {
7647 continue;
7648 }
7649 let factor = aug[row * cols + col];
7650 for j in 0..cols {
7651 let val = aug[col * cols + j] * factor;
7652 aug[row * cols + j] -= val;
7653 }
7654 }
7655 }
7656 let mut result = Array2::<f64>::zeros((n, n));
7657 for i in 0..n {
7658 for j in 0..n {
7659 result[[i, j]] = aug[i * cols + n + j];
7660 }
7661 }
7662 Ok(result)
7663}
7664
7665fn solve_linear(a: &Array2<f64>, b: &Array2<f64>) -> Result<Array2<f64>, String> {
7670 let n = a.nrows();
7671 if a.ncols() != n {
7672 return Err(format!(
7673 "\\: coefficient matrix must be square, got {}×{}",
7674 n,
7675 a.ncols()
7676 ));
7677 }
7678 let k = b.ncols();
7679 if b.nrows() != n {
7680 return Err(format!(
7681 "\\: size mismatch — A is {}×{} but b has {} rows",
7682 n,
7683 n,
7684 b.nrows()
7685 ));
7686 }
7687 if n == 0 {
7688 return Ok(Array2::zeros((0, k)));
7689 }
7690 let cols = n + k;
7691 let mut aug = vec![0.0f64; n * cols];
7692 for i in 0..n {
7693 for j in 0..n {
7694 aug[i * cols + j] = a[[i, j]];
7695 }
7696 for j in 0..k {
7697 aug[i * cols + n + j] = b[[i, j]];
7698 }
7699 }
7700 for col in 0..n {
7701 let pivot = (col..n)
7702 .max_by(|&r1, &r2| {
7703 aug[r1 * cols + col]
7704 .abs()
7705 .partial_cmp(&aug[r2 * cols + col].abs())
7706 .unwrap()
7707 })
7708 .filter(|&r| aug[r * cols + col].abs() > 1e-12)
7709 .ok_or_else(|| "\\: matrix is singular or nearly singular".to_string())?;
7710 if pivot != col {
7711 for j in 0..cols {
7712 aug.swap(col * cols + j, pivot * cols + j);
7713 }
7714 }
7715 let pv = aug[col * cols + col];
7716 for j in col..cols {
7717 aug[col * cols + j] /= pv;
7718 }
7719 for row in 0..n {
7720 if row == col {
7721 continue;
7722 }
7723 let factor = aug[row * cols + col];
7724 if factor == 0.0 {
7725 continue;
7726 }
7727 for j in col..cols {
7728 let val = aug[col * cols + j] * factor;
7729 aug[row * cols + j] -= val;
7730 }
7731 }
7732 }
7733 let mut result = Array2::<f64>::zeros((n, k));
7734 for i in 0..n {
7735 for j in 0..k {
7736 result[[i, j]] = aug[i * cols + n + j];
7737 }
7738 }
7739 Ok(result)
7740}
7741
7742fn qr_decompose(a: &Array2<f64>) -> Result<(Array2<f64>, Array2<f64>), String> {
7751 let m = a.nrows();
7752 let n = a.ncols();
7753 let k = m.min(n);
7754 let mut r = a.clone();
7755 let mut q = Array2::<f64>::eye(m);
7756
7757 for j in 0..k {
7758 let col_len = m - j;
7759 let mut v: Vec<f64> = (j..m).map(|i| r[[i, j]]).collect();
7760
7761 let norm_x = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
7762 if norm_x < 1e-14 {
7763 continue;
7764 }
7765 v[0] += if v[0] >= 0.0 { norm_x } else { -norm_x };
7767 let v_sq: f64 = v.iter().map(|&x| x * x).sum();
7768 if v_sq < 1e-28 {
7769 continue;
7770 }
7771
7772 for col in j..n {
7774 let dot: f64 = (0..col_len).map(|i| v[i] * r[[j + i, col]]).sum();
7775 let fac = 2.0 * dot / v_sq;
7776 for i in 0..col_len {
7777 r[[j + i, col]] -= fac * v[i];
7778 }
7779 }
7780 for row in 0..m {
7782 let dot: f64 = (0..col_len).map(|i| q[[row, j + i]] * v[i]).sum();
7783 let fac = 2.0 * dot / v_sq;
7784 for i in 0..col_len {
7785 q[[row, j + i]] -= fac * v[i];
7786 }
7787 }
7788 }
7789
7790 Ok((q, r))
7791}
7792
7793type LuResult = Result<(Array2<f64>, Array2<f64>, Array2<f64>), String>;
7799fn lu_decompose(a: &Array2<f64>) -> LuResult {
7800 let n = a.nrows();
7801 if a.ncols() != n {
7802 return Err("lu: matrix must be square".to_string());
7803 }
7804 let mut u = a.clone();
7805 let mut l = Array2::<f64>::eye(n);
7806 let mut perm: Vec<usize> = (0..n).collect();
7807
7808 for j in 0..n {
7809 let pivot = (j..n)
7810 .max_by(|&r1, &r2| {
7811 u[[r1, j]]
7812 .abs()
7813 .partial_cmp(&u[[r2, j]].abs())
7814 .unwrap_or(std::cmp::Ordering::Equal)
7815 })
7816 .unwrap();
7817
7818 if pivot != j {
7819 for col in 0..n {
7820 let tmp = u[[j, col]];
7821 u[[j, col]] = u[[pivot, col]];
7822 u[[pivot, col]] = tmp;
7823 }
7824 for col in 0..j {
7825 let tmp = l[[j, col]];
7826 l[[j, col]] = l[[pivot, col]];
7827 l[[pivot, col]] = tmp;
7828 }
7829 perm.swap(j, pivot);
7830 }
7831
7832 if u[[j, j]].abs() < 1e-15 {
7833 continue;
7834 }
7835 for i in (j + 1)..n {
7836 l[[i, j]] = u[[i, j]] / u[[j, j]];
7837 for k in j..n {
7838 let val = l[[i, j]] * u[[j, k]];
7839 u[[i, k]] -= val;
7840 }
7841 }
7842 }
7843
7844 let mut p = Array2::<f64>::zeros((n, n));
7845 for (i, &j) in perm.iter().enumerate() {
7846 p[[i, j]] = 1.0;
7847 }
7848 Ok((l, u, p))
7849}
7850
7851fn chol_decompose(a: &Array2<f64>) -> Result<Array2<f64>, String> {
7856 let n = a.nrows();
7857 if a.ncols() != n {
7858 return Err("chol: matrix must be square".to_string());
7859 }
7860 let mut r = Array2::<f64>::zeros((n, n));
7861 for j in 0..n {
7862 let mut s = a[[j, j]];
7863 for k in 0..j {
7864 s -= r[[k, j]] * r[[k, j]];
7865 }
7866 if s <= 0.0 {
7867 return Err("chol: matrix is not positive definite".to_string());
7868 }
7869 r[[j, j]] = s.sqrt();
7870 for i in (j + 1)..n {
7871 let mut t = a[[j, i]];
7872 for k in 0..j {
7873 t -= r[[k, j]] * r[[k, i]];
7874 }
7875 r[[j, i]] = t / r[[j, j]];
7876 }
7877 }
7878 Ok(r)
7879}
7880
7881type SvdResult = Result<(Array2<f64>, Vec<f64>, Array2<f64>), String>;
7890fn svd_compute(a: &Array2<f64>) -> SvdResult {
7891 let m = a.nrows();
7892 let n = a.ncols();
7893 if m < n {
7894 let (v, s, u) = svd_compute(&a.t().to_owned())?;
7895 return Ok((u, s, v));
7896 }
7897 let k = n;
7899 let mut b = a.clone();
7900 let mut v = Array2::<f64>::eye(k);
7901
7902 const MAX_ITER: usize = 200;
7903 const EPS: f64 = 1e-14;
7904
7905 'outer: for _ in 0..MAX_ITER {
7906 let mut changed = false;
7907 for p in 0..k {
7908 for q in (p + 1)..k {
7909 let alpha: f64 = (0..m).map(|i| b[[i, p]] * b[[i, p]]).sum();
7910 let beta: f64 = (0..m).map(|i| b[[i, q]] * b[[i, q]]).sum();
7911 let gamma: f64 = (0..m).map(|i| b[[i, p]] * b[[i, q]]).sum();
7912
7913 if gamma.abs() <= EPS * (alpha * beta).sqrt() {
7914 continue;
7915 }
7916 changed = true;
7917
7918 let zeta = (beta - alpha) / (2.0 * gamma);
7919 let t = zeta.signum() / (zeta.abs() + (1.0 + zeta * zeta).sqrt());
7920 let c = 1.0 / (1.0 + t * t).sqrt();
7921 let s = c * t;
7922
7923 for i in 0..m {
7924 let bp = b[[i, p]];
7925 let bq = b[[i, q]];
7926 b[[i, p]] = c * bp - s * bq;
7927 b[[i, q]] = s * bp + c * bq;
7928 }
7929 for i in 0..k {
7930 let vp = v[[i, p]];
7931 let vq = v[[i, q]];
7932 v[[i, p]] = c * vp - s * vq;
7933 v[[i, q]] = s * vp + c * vq;
7934 }
7935 }
7936 }
7937 if !changed {
7938 break 'outer;
7939 }
7940 }
7941
7942 let mut sigma: Vec<f64> = (0..k)
7943 .map(|j| (0..m).map(|i| b[[i, j]] * b[[i, j]]).sum::<f64>().sqrt())
7944 .collect();
7945 let mut u_mat = Array2::<f64>::zeros((m, k));
7946 for j in 0..k {
7947 if sigma[j] > EPS {
7948 for i in 0..m {
7949 u_mat[[i, j]] = b[[i, j]] / sigma[j];
7950 }
7951 }
7952 }
7953
7954 let mut order: Vec<usize> = (0..k).collect();
7956 order.sort_by(|&a, &b| {
7957 sigma[b]
7958 .partial_cmp(&sigma[a])
7959 .unwrap_or(std::cmp::Ordering::Equal)
7960 });
7961 let sigma_s: Vec<f64> = order.iter().map(|&i| sigma[i]).collect();
7962 let mut u_s = Array2::<f64>::zeros((m, k));
7963 let mut v_s = Array2::<f64>::zeros((n, k));
7964 for (ni, &oi) in order.iter().enumerate() {
7965 for r in 0..m {
7966 u_s[[r, ni]] = u_mat[[r, oi]];
7967 }
7968 for r in 0..k {
7969 v_s[[r, ni]] = v[[r, oi]];
7970 }
7971 }
7972 sigma = sigma_s;
7973
7974 Ok((u_s, sigma, v_s))
7975}
7976
7977fn complete_orthonormal_basis(u: &Array2<f64>) -> Array2<f64> {
7982 let m = u.nrows();
7983 let k = u.ncols();
7984 let mut basis: Vec<Vec<f64>> = (0..k).map(|j| u.column(j).to_vec()).collect();
7985
7986 let mut ei = 0usize;
7987 while basis.len() < m && ei < m {
7988 let mut v: Vec<f64> = vec![0.0; m];
7989 v[ei] = 1.0;
7990 ei += 1;
7991 for b in &basis {
7992 let dot: f64 = v.iter().zip(b.iter()).map(|(&a, &b)| a * b).sum();
7993 for (vi, &bi) in v.iter_mut().zip(b.iter()) {
7994 *vi -= dot * bi;
7995 }
7996 }
7997 let norm = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
7998 if norm > 1e-10 {
7999 for vi in &mut v {
8000 *vi /= norm;
8001 }
8002 basis.push(v);
8003 }
8004 }
8005
8006 let mut result = Array2::<f64>::zeros((m, m));
8007 for (j, b) in basis.iter().enumerate() {
8008 for (i, &val) in b.iter().enumerate() {
8009 result[[i, j]] = val;
8010 }
8011 }
8012 result
8013}
8014
8015fn eig_compute(a: &Array2<f64>) -> Result<(Vec<Complex<f64>>, Array2<f64>), String> {
8023 let n = a.nrows();
8024 if a.ncols() != n {
8025 return Err("eig: matrix must be square".to_string());
8026 }
8027 if n == 0 {
8028 return Ok((vec![], Array2::zeros((0, 0))));
8029 }
8030 if n == 1 {
8031 return Ok((vec![Complex::new(a[[0, 0]], 0.0)], Array2::eye(1)));
8032 }
8033
8034 let mut ak = a.clone();
8035 let mut evecs = Array2::<f64>::eye(n);
8036
8037 const MAX_ITER: usize = 2000;
8038 const EPS: f64 = 1e-12;
8039
8040 for _ in 0..MAX_ITER {
8041 let mu = {
8043 let d = ak[[n - 1, n - 1]];
8044 if n >= 2 {
8045 let a = ak[[n - 2, n - 2]];
8046 let b = ak[[n - 2, n - 1]];
8047 let delta = (a - d) / 2.0;
8048 if delta.abs() < 1e-30 {
8049 d - b.abs()
8050 } else {
8051 d - b * b / (delta + delta.signum() * (delta * delta + b * b).sqrt())
8052 }
8053 } else {
8054 d
8055 }
8056 };
8057
8058 for i in 0..n {
8059 ak[[i, i]] -= mu;
8060 }
8061 let (q, r) = qr_decompose(&ak)?;
8062 ak = r.dot(&q);
8063 for i in 0..n {
8064 ak[[i, i]] += mu;
8065 }
8066 evecs = evecs.dot(&q);
8067
8068 let max_sub = (0..(n - 1))
8070 .map(|i| ak[[i + 1, i]].abs())
8071 .fold(0.0_f64, f64::max);
8072 if max_sub < EPS {
8073 break;
8074 }
8075 }
8076
8077 const EPS_BLOCK: f64 = 1e-8;
8080 let mut evals: Vec<Complex<f64>> = Vec::with_capacity(n);
8081 let mut i = 0;
8082 while i < n {
8083 if i + 1 < n && ak[[i + 1, i]].abs() > EPS_BLOCK {
8084 let (a_ii, b, c, d_ii) = (
8085 ak[[i, i]],
8086 ak[[i, i + 1]],
8087 ak[[i + 1, i]],
8088 ak[[i + 1, i + 1]],
8089 );
8090 let p = (a_ii + d_ii) / 2.0;
8091 let disc = ((a_ii - d_ii) / 2.0).powi(2) + b * c;
8092 if disc < 0.0 {
8093 let q = (-disc).sqrt();
8094 evals.push(Complex::new(p, q));
8095 evals.push(Complex::new(p, -q));
8096 } else {
8097 let q = disc.sqrt();
8098 evals.push(Complex::new(p + q, 0.0));
8099 evals.push(Complex::new(p - q, 0.0));
8100 }
8101 i += 2;
8102 } else {
8103 evals.push(Complex::new(ak[[i, i]], 0.0));
8104 i += 1;
8105 }
8106 }
8107
8108 Ok((evals, evecs))
8109}
8110
8111fn env_with_end(env: &Env, dim_size: usize) -> Env {
8118 let mut e = env.clone();
8119 e.insert("end".to_string(), Value::Scalar(dim_size as f64));
8120 e
8121}
8122
8123pub(crate) fn contains_end(expr: &Expr) -> bool {
8127 match expr {
8128 Expr::Var(s) => s == "end",
8129 Expr::Number(_)
8130 | Expr::Colon
8131 | Expr::StrLiteral(_)
8132 | Expr::StringObjLiteral(_)
8133 | Expr::NaT
8134 | Expr::FuncHandle(_) => false,
8135 Expr::UnaryMinus(e)
8136 | Expr::UnaryNot(e)
8137 | Expr::Transpose(e)
8138 | Expr::PlainTranspose(e)
8139 | Expr::FieldGet(e, _) => contains_end(e),
8140 Expr::DynFieldGet(a, b) => contains_end(a) || contains_end(b),
8141 Expr::BinOp(l, _, r) => contains_end(l) || contains_end(r),
8142 Expr::Call(_, args) | Expr::DotCall(_, args) => args.iter().any(contains_end),
8143 Expr::Matrix(rows) => rows.iter().flat_map(|r| r.iter()).any(contains_end),
8144 Expr::Range(a, step, b) => {
8145 contains_end(a) || step.as_deref().is_some_and(contains_end) || contains_end(b)
8146 }
8147 Expr::Lambda { body, .. } => contains_end(body),
8148 Expr::CellLiteral(elems) => elems.iter().any(contains_end),
8149 Expr::CellIndex(a, b) => contains_end(a) || contains_end(b),
8150 }
8151}
8152
8153fn make_containers_map(
8161 args: &[Expr],
8162 env: &Env,
8163 io: Option<&mut IoContext>,
8164) -> Result<Value, String> {
8165 if args.is_empty() {
8166 return Ok(Value::Map(Box::new(IndexMap::new())));
8167 }
8168 if args.len() != 2 {
8169 return Err(
8170 "containers.Map: expected 0 or 2 arguments (keys cell, values cell)".to_string(),
8171 );
8172 }
8173 let mut io_wrap = io;
8174 let keys_val = eval_inner(&args[0], env, io_wrap.as_deref_mut())?;
8175 let vals_val = eval_inner(&args[1], env, io_wrap)?;
8176 let keys = match keys_val {
8177 Value::Cell(v) => v,
8178 _ => {
8179 return Err(
8180 "containers.Map: first argument must be a cell array of strings".to_string(),
8181 );
8182 }
8183 };
8184 let values = match vals_val {
8185 Value::Cell(v) => v,
8186 _ => {
8187 return Err(
8188 "containers.Map: second argument must be a cell array of values".to_string(),
8189 );
8190 }
8191 };
8192 if keys.len() != values.len() {
8193 return Err(format!(
8194 "containers.Map: key count ({}) does not match value count ({})",
8195 keys.len(),
8196 values.len()
8197 ));
8198 }
8199 let keys = *keys;
8200 let values = *values;
8201 let mut map = IndexMap::new();
8202 for (k, v) in keys.into_iter().zip(values) {
8203 let key = match k {
8204 Value::Str(s) | Value::StringObj(s) => s,
8205 _ => return Err("containers.Map: all keys must be strings".to_string()),
8206 };
8207 map.insert(key, v);
8208 }
8209 Ok(Value::Map(Box::new(map)))
8210}
8211
8212fn eval_index(val: &Value, args: &[Expr], env: &Env) -> Result<Value, String> {
8213 match args.len() {
8214 0 => Err("Indexing requires at least one index".to_string()),
8215 1 => {
8216 match val {
8218 Value::Void => Err("Cannot index into void".to_string()),
8219 Value::Lambda(_) | Value::Function(_) | Value::Tuple(_) => {
8220 Err("Cannot index into a function value".to_string())
8221 }
8222 Value::Cell(_) => Err("Use c{i} to index into a cell array, not c(i)".to_string()),
8223 Value::Struct(_) => {
8224 Err("Use s.field to access struct fields, not s(i)".to_string())
8225 }
8226 Value::Map(map) => {
8227 let key_val = eval_inner(&args[0], env, None)?;
8228 let key = match key_val {
8229 Value::Str(s) | Value::StringObj(s) => s,
8230 _ => return Err("Map key must be a string".to_string()),
8231 };
8232 map.get(&key)
8233 .cloned()
8234 .ok_or_else(|| format!("Map key '{key}' not found"))
8235 }
8236 Value::StructArray(arr) => {
8237 let total = arr.len();
8238 let _owned_env;
8239 let env1: &Env = if contains_end(&args[0]) {
8240 _owned_env = env_with_end(env, total);
8241 &_owned_env
8242 } else {
8243 env
8244 };
8245 match resolve_dim(&args[0], total, env1)? {
8246 DimIdx::All => {
8247 Ok(Value::StructArray(arr.clone()))
8249 }
8250 DimIdx::Indices(idxs) => {
8251 if idxs.len() == 1 {
8252 let i = idxs[0];
8253 if i >= total {
8254 return Err(format!(
8255 "Index {} out of range (1..{})",
8256 i + 1,
8257 total
8258 ));
8259 }
8260 Ok(Value::Struct(Box::new(arr[i].clone())))
8261 } else {
8262 let mut selected = Vec::with_capacity(idxs.len());
8263 for &i in &idxs {
8264 if i >= total {
8265 return Err(format!(
8266 "Index {} out of range (1..{})",
8267 i + 1,
8268 total
8269 ));
8270 }
8271 selected.push(arr[i].clone());
8272 }
8273 Ok(Value::StructArray(Box::new(selected)))
8274 }
8275 }
8276 }
8277 }
8278 Value::Scalar(n) => {
8279 let _owned_env;
8280 let env1: &Env = if contains_end(&args[0]) {
8281 _owned_env = env_with_end(env, 1);
8282 &_owned_env
8283 } else {
8284 env
8285 };
8286 match resolve_dim(&args[0], 1, env1)? {
8287 DimIdx::All | DimIdx::Indices(_) => Ok(Value::Scalar(*n)),
8288 }
8289 }
8290 Value::Complex(re, im) => {
8291 let _owned_env;
8292 let env1: &Env = if contains_end(&args[0]) {
8293 _owned_env = env_with_end(env, 1);
8294 &_owned_env
8295 } else {
8296 env
8297 };
8298 match resolve_dim(&args[0], 1, env1)? {
8299 DimIdx::All | DimIdx::Indices(_) => Ok(Value::Complex(*re, *im)),
8300 }
8301 }
8302 Value::ComplexMatrix(m) => {
8303 let total = m.nrows() * m.ncols();
8304 let _owned_env;
8305 let env1: &Env = if contains_end(&args[0]) {
8306 _owned_env = env_with_end(env, total);
8307 &_owned_env
8308 } else {
8309 env
8310 };
8311 match resolve_dim(&args[0], total, env1)? {
8312 DimIdx::All => {
8313 let mut flat: Vec<Complex<f64>> = Vec::with_capacity(total);
8315 for col in 0..m.ncols() {
8316 for row in 0..m.nrows() {
8317 flat.push(m[[row, col]]);
8318 }
8319 }
8320 Ok(Value::ComplexMatrix(Box::new(
8321 Array2::from_shape_vec((total, 1), flat).unwrap(),
8322 )))
8323 }
8324 DimIdx::Indices(idxs) => {
8325 let nrows = m.nrows();
8326 let ncols_m = m.ncols();
8327 let vals: Result<Vec<Complex<f64>>, String> = idxs
8328 .iter()
8329 .map(|&i| {
8330 let row = i % nrows;
8331 let col = i / nrows;
8332 if col >= ncols_m {
8333 Err(format!("Index {} out of range (1..{})", i + 1, total))
8334 } else {
8335 Ok(m[[row, col]])
8336 }
8337 })
8338 .collect();
8339 let vals = vals?;
8340 if vals.len() == 1 {
8341 let c = vals[0];
8342 Ok(make_complex(c.re, c.im))
8343 } else {
8344 let n = vals.len();
8345 Ok(Value::ComplexMatrix(Box::new(
8346 Array2::from_shape_vec((1, n), vals).unwrap(),
8347 )))
8348 }
8349 }
8350 }
8351 }
8352 Value::Matrix(m) => {
8353 let total = m.nrows() * m.ncols();
8354 let _owned_env;
8355 let env1: &Env = if contains_end(&args[0]) {
8356 _owned_env = env_with_end(env, total);
8357 &_owned_env
8358 } else {
8359 env
8360 };
8361 match resolve_dim(&args[0], total, env1)? {
8362 DimIdx::All => {
8363 let mut flat = Vec::with_capacity(total);
8365 for col in 0..m.ncols() {
8366 for row in 0..m.nrows() {
8367 flat.push(m[[row, col]]);
8368 }
8369 }
8370 Ok(Value::Matrix(Box::new(
8371 Array2::from_shape_vec((total, 1), flat).unwrap(),
8372 )))
8373 }
8374 DimIdx::Indices(idxs) => {
8375 let nrows = m.nrows();
8377 let ncols_m = m.ncols();
8378 let vals: Result<Vec<f64>, String> = idxs
8379 .iter()
8380 .map(|&i| {
8381 let row = i % nrows;
8383 let col = i / nrows;
8384 if col >= ncols_m {
8385 Err(format!("Index {} out of range (1..{})", i + 1, total))
8386 } else {
8387 Ok(m[[row, col]])
8388 }
8389 })
8390 .collect();
8391 let vals = vals?;
8392 if vals.len() == 1 {
8393 Ok(Value::Scalar(vals[0]))
8394 } else {
8395 let n = vals.len();
8396 Ok(Value::Matrix(Box::new(
8397 Array2::from_shape_vec((1, n), vals).unwrap(),
8398 )))
8399 }
8400 }
8401 }
8402 }
8403 Value::Str(s) => {
8404 let chars: Vec<char> = s.chars().collect();
8406 let total = chars.len();
8407 let _owned_env;
8408 let env1: &Env = if contains_end(&args[0]) {
8409 _owned_env = env_with_end(env, total);
8410 &_owned_env
8411 } else {
8412 env
8413 };
8414 match resolve_dim(&args[0], total, env1)? {
8415 DimIdx::All => {
8416 let codes: Vec<f64> = chars.iter().map(|&c| c as u32 as f64).collect();
8417 if codes.len() == 1 {
8418 Ok(Value::Scalar(codes[0]))
8419 } else {
8420 let n = codes.len();
8421 Ok(Value::Matrix(Box::new(
8422 Array2::from_shape_vec((1, n), codes).unwrap(),
8423 )))
8424 }
8425 }
8426 DimIdx::Indices(idxs) => {
8427 let mut selected = String::new();
8428 for &i in &idxs {
8429 if i >= chars.len() {
8430 return Err(format!("Index {} out of range", i + 1));
8431 }
8432 selected.push(chars[i]);
8433 }
8434 if selected.chars().count() == 1 {
8435 Ok(Value::Scalar(selected.chars().next().unwrap() as u32 as f64))
8436 } else {
8437 Ok(Value::Str(selected))
8438 }
8439 }
8440 }
8441 }
8442 Value::StringObj(s) => {
8443 let _owned_env;
8445 let env1: &Env = if contains_end(&args[0]) {
8446 _owned_env = env_with_end(env, 1);
8447 &_owned_env
8448 } else {
8449 env
8450 };
8451 match resolve_dim(&args[0], 1, env1)? {
8452 DimIdx::All | DimIdx::Indices(_) => Ok(Value::StringObj(s.clone())),
8453 }
8454 }
8455 Value::DateTimeArray(v) => {
8456 let total = v.len();
8457 let _owned_env;
8458 let env1: &Env = if contains_end(&args[0]) {
8459 _owned_env = env_with_end(env, total);
8460 &_owned_env
8461 } else {
8462 env
8463 };
8464 match resolve_dim(&args[0], total, env1)? {
8465 DimIdx::All => Ok(Value::DateTimeArray(v.clone())),
8466 DimIdx::Indices(idxs) => {
8467 if idxs.len() == 1 {
8468 let i = idxs[0];
8469 if i >= total {
8470 return Err(format!(
8471 "Index {} out of range (1..{})",
8472 i + 1,
8473 total
8474 ));
8475 }
8476 Ok(Value::DateTime(v[i]))
8477 } else {
8478 let mut sel = Vec::with_capacity(idxs.len());
8479 for &i in &idxs {
8480 if i >= total {
8481 return Err(format!(
8482 "Index {} out of range (1..{})",
8483 i + 1,
8484 total
8485 ));
8486 }
8487 sel.push(v[i]);
8488 }
8489 Ok(Value::DateTimeArray(sel))
8490 }
8491 }
8492 }
8493 }
8494 Value::DurationArray(v) => {
8495 let total = v.len();
8496 let _owned_env;
8497 let env1: &Env = if contains_end(&args[0]) {
8498 _owned_env = env_with_end(env, total);
8499 &_owned_env
8500 } else {
8501 env
8502 };
8503 match resolve_dim(&args[0], total, env1)? {
8504 DimIdx::All => Ok(Value::DurationArray(v.clone())),
8505 DimIdx::Indices(idxs) => {
8506 if idxs.len() == 1 {
8507 let i = idxs[0];
8508 if i >= total {
8509 return Err(format!(
8510 "Index {} out of range (1..{})",
8511 i + 1,
8512 total
8513 ));
8514 }
8515 Ok(Value::Duration(v[i]))
8516 } else {
8517 let mut sel = Vec::with_capacity(idxs.len());
8518 for &i in &idxs {
8519 if i >= total {
8520 return Err(format!(
8521 "Index {} out of range (1..{})",
8522 i + 1,
8523 total
8524 ));
8525 }
8526 sel.push(v[i]);
8527 }
8528 Ok(Value::DurationArray(sel))
8529 }
8530 }
8531 }
8532 }
8533 Value::DateTime(_) | Value::Duration(_) => {
8534 let _owned_env;
8536 let env1: &Env = if contains_end(&args[0]) {
8537 _owned_env = env_with_end(env, 1);
8538 &_owned_env
8539 } else {
8540 env
8541 };
8542 match resolve_dim(&args[0], 1, env1)? {
8543 DimIdx::All | DimIdx::Indices(_) => Ok(val.clone()),
8544 }
8545 }
8546 }
8547 }
8548 2 => {
8549 if matches!(
8551 val,
8552 Value::Void
8553 | Value::Str(_)
8554 | Value::StringObj(_)
8555 | Value::Lambda(_)
8556 | Value::Function(_)
8557 | Value::Tuple(_)
8558 | Value::Cell(_)
8559 | Value::Struct(_)
8560 | Value::StructArray(_)
8561 | Value::DateTime(_)
8562 | Value::Duration(_)
8563 | Value::DateTimeArray(_)
8564 | Value::DurationArray(_)
8565 ) {
8566 return Err("2D indexing not supported for this type".to_string());
8567 }
8568 let (nrows, ncols) = match val {
8570 Value::Scalar(_) | Value::Complex(_, _) => (1, 1),
8571 Value::Matrix(m) => (m.nrows(), m.ncols()),
8572 Value::ComplexMatrix(m) => (m.nrows(), m.ncols()),
8573 _ => unreachable!(),
8574 };
8575 let _owned_r;
8576 let env_r: &Env = if contains_end(&args[0]) {
8577 _owned_r = env_with_end(env, nrows);
8578 &_owned_r
8579 } else {
8580 env
8581 };
8582 let _owned_c;
8583 let env_c: &Env = if contains_end(&args[1]) {
8584 _owned_c = env_with_end(env, ncols);
8585 &_owned_c
8586 } else {
8587 env
8588 };
8589 let row_idx = resolve_dim(&args[0], nrows, env_r)?;
8590 let col_idx = resolve_dim(&args[1], ncols, env_c)?;
8591
8592 let rows: Vec<usize> = match row_idx {
8593 DimIdx::All => (0..nrows).collect(),
8594 DimIdx::Indices(v) => v,
8595 };
8596 let cols: Vec<usize> = match col_idx {
8597 DimIdx::All => (0..ncols).collect(),
8598 DimIdx::Indices(v) => v,
8599 };
8600
8601 if rows.len() == 1 && cols.len() == 1 {
8602 match val {
8603 Value::Scalar(n) => Ok(Value::Scalar(*n)),
8604 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
8605 Value::Matrix(m) => Ok(Value::Scalar(m[[rows[0], cols[0]]])),
8606 Value::ComplexMatrix(m) => {
8607 let c = m[[rows[0], cols[0]]];
8608 Ok(make_complex(c.re, c.im))
8609 }
8610 _ => unreachable!(),
8611 }
8612 } else {
8613 let out_r = rows.len();
8614 let out_c = cols.len();
8615 match val {
8616 Value::ComplexMatrix(m) => {
8617 let flat: Vec<Complex<f64>> = rows
8618 .iter()
8619 .flat_map(|&r| cols.iter().map(move |&c| m[[r, c]]))
8620 .collect();
8621 Ok(Value::ComplexMatrix(Box::new(
8622 Array2::from_shape_vec((out_r, out_c), flat).unwrap(),
8623 )))
8624 }
8625 _ => {
8626 let flat: Vec<f64> = rows
8627 .iter()
8628 .flat_map(|&r| {
8629 cols.iter().map(move |&c| match val {
8630 Value::Scalar(n) => *n,
8631 Value::Complex(re, _) => *re,
8632 Value::Matrix(m) => m[[r, c]],
8633 _ => unreachable!(),
8634 })
8635 })
8636 .collect();
8637 Ok(Value::Matrix(Box::new(
8638 Array2::from_shape_vec((out_r, out_c), flat).unwrap(),
8639 )))
8640 }
8641 }
8642 }
8643 }
8644 n => Err(format!(
8645 "Indexing with {n} indices is not supported (max 2)"
8646 )),
8647 }
8648}
8649
8650enum DimIdx {
8652 All,
8653 Indices(Vec<usize>),
8654}
8655
8656fn resolve_dim(expr: &Expr, dim_size: usize, env: &Env) -> Result<DimIdx, String> {
8662 if matches!(expr, Expr::Colon) {
8663 return Ok(DimIdx::All);
8664 }
8665 let val = eval(expr, env)?;
8666 let floats: Vec<f64> = match val {
8667 Value::Void => {
8668 return Err("Index must be numeric, not void".to_string());
8669 }
8670 Value::Scalar(n) => vec![n],
8671 Value::Complex(re, im) => {
8672 if im != 0.0 {
8673 return Err("Index must be real, not complex".to_string());
8674 }
8675 vec![re]
8676 }
8677 Value::Matrix(m) => {
8678 let total = m.nrows() * m.ncols();
8680 if m.nrows() > 1 && m.ncols() > 1 && total != dim_size {
8681 return Err("Index must be a scalar or vector, not a matrix".to_string());
8682 }
8683 if m.nrows() > 1 && m.ncols() > 1 {
8685 let mut v = Vec::with_capacity(total);
8686 for col in 0..m.ncols() {
8687 for row in 0..m.nrows() {
8688 v.push(m[[row, col]]);
8689 }
8690 }
8691 v
8692 } else {
8693 m.iter().copied().collect()
8694 }
8695 }
8696 Value::Str(_) | Value::StringObj(_) => {
8697 return Err("Index must be numeric, not a string".to_string());
8698 }
8699 Value::ComplexMatrix(_) => {
8700 return Err("Index must be real, not a complex matrix".to_string());
8701 }
8702 Value::Lambda(_)
8703 | Value::Function(_)
8704 | Value::Tuple(_)
8705 | Value::Cell(_)
8706 | Value::Struct(_)
8707 | Value::StructArray(_)
8708 | Value::DateTime(_)
8709 | Value::Duration(_)
8710 | Value::DateTimeArray(_)
8711 | Value::DurationArray(_)
8712 | Value::Map(_) => {
8713 return Err("Index must be numeric, not a function or datetime".to_string());
8714 }
8715 };
8716 if dim_size > 0 && floats.len() == dim_size && floats.iter().all(|&f| f == 0.0 || f == 1.0) {
8718 let idxs: Vec<usize> = floats
8719 .iter()
8720 .enumerate()
8721 .filter(|&(_, &f)| f == 1.0)
8722 .map(|(i, _)| i)
8723 .collect();
8724 return Ok(DimIdx::Indices(idxs));
8725 }
8726 let mut idxs = Vec::with_capacity(floats.len());
8727 for n in floats {
8728 let i = n.round() as i64;
8729 if i < 1 || i as usize > dim_size {
8730 return Err(format!("Index {i} out of range (1..{dim_size})"));
8731 }
8732 idxs.push(i as usize - 1);
8733 }
8734 Ok(DimIdx::Indices(idxs))
8735}
8736
8737pub fn format_number(n: f64) -> String {
8741 if n.fract() == 0.0 && n.abs() < 1e15 {
8742 format!("{}", n as i64)
8743 } else if n != 0.0 && (n.abs() >= 1e15 || n.abs() < 1e-9) {
8744 trim_sci(&format!("{:.15e}", n))
8745 } else {
8746 let s = format!("{:.10}", n);
8747 s.trim_end_matches('0').trim_end_matches('.').to_string()
8748 }
8749}
8750
8751pub fn format_scalar(n: f64, base: Base, mode: &FormatMode) -> String {
8753 if matches!(mode, FormatMode::Hex) {
8755 return format_decimal(n, mode);
8756 }
8757 match base {
8758 Base::Dec => format_decimal(n, mode),
8759 _ => format_non_dec(n, base),
8760 }
8761}
8762
8763pub fn format_complex(re: f64, im: f64, mode: &FormatMode) -> String {
8769 if im == 0.0 {
8770 return format_decimal(re, mode);
8771 }
8772 let im_abs = im.abs();
8773 let im_str = if im_abs == 1.0 {
8774 String::new()
8775 } else {
8776 format_decimal(im_abs, mode)
8777 };
8778 if re == 0.0 {
8779 if im < 0.0 {
8780 format!("-{}i", im_str)
8781 } else {
8782 format!("{}i", im_str)
8783 }
8784 } else {
8785 let re_str = format_decimal(re, mode);
8786 if im < 0.0 {
8787 format!("{} - {}i", re_str, im_str)
8788 } else {
8789 format!("{} + {}i", re_str, im_str)
8790 }
8791 }
8792}
8793
8794pub fn expr_to_string(e: &Expr) -> String {
8799 match e {
8800 Expr::Number(n) => {
8801 if n.is_nan() {
8802 "nan".to_string()
8803 } else if n.is_infinite() {
8804 if *n > 0.0 {
8805 "inf".to_string()
8806 } else {
8807 "-inf".to_string()
8808 }
8809 } else {
8810 format!("{n}")
8811 }
8812 }
8813 Expr::Var(name) => name.clone(),
8814 Expr::UnaryMinus(e) => format!("-{}", expr_to_string(e)),
8815 Expr::UnaryNot(e) => format!("~{}", expr_to_string(e)),
8816 Expr::BinOp(l, op, r) => {
8817 let op_str = match op {
8818 Op::Add => "+",
8819 Op::Sub => "-",
8820 Op::Mul => "*",
8821 Op::Div => "/",
8822 Op::Pow => "^",
8823 Op::ElemMul => ".*",
8824 Op::ElemDiv => "./",
8825 Op::ElemPow => ".^",
8826 Op::Eq => "==",
8827 Op::NotEq => "~=",
8828 Op::Lt => "<",
8829 Op::Gt => ">",
8830 Op::LtEq => "<=",
8831 Op::GtEq => ">=",
8832 Op::And => "&&",
8833 Op::Or => "||",
8834 Op::ElemAnd => "&",
8835 Op::ElemOr => "|",
8836 Op::LDiv => "\\",
8837 };
8838 format!("{} {op_str} {}", expr_to_string(l), expr_to_string(r))
8839 }
8840 Expr::Call(name, args) => {
8841 let args_str = args
8842 .iter()
8843 .map(expr_to_string)
8844 .collect::<Vec<_>>()
8845 .join(", ");
8846 format!("{name}({args_str})")
8847 }
8848 Expr::Transpose(e) => format!("{}'", expr_to_string(e)),
8849 Expr::PlainTranspose(e) => format!("{}.'", expr_to_string(e)),
8850 Expr::Range(start, step, stop) => {
8851 if let Some(step) = step {
8852 format!(
8853 "{}:{}:{}",
8854 expr_to_string(start),
8855 expr_to_string(step),
8856 expr_to_string(stop)
8857 )
8858 } else {
8859 format!("{}:{}", expr_to_string(start), expr_to_string(stop))
8860 }
8861 }
8862 Expr::StrLiteral(s) => format!("'{s}'"),
8863 Expr::StringObjLiteral(s) => format!("\"{s}\""),
8864 Expr::Lambda { params, body, .. } => {
8865 format!("@({}) {}", params.join(", "), expr_to_string(body))
8866 }
8867 Expr::FuncHandle(name) => format!("@{name}"),
8868 Expr::Matrix(_) => "[...]".to_string(),
8869 Expr::CellLiteral(_) => "{...}".to_string(),
8870 Expr::CellIndex(e, i) => format!("{}{{{}}}", expr_to_string(e), expr_to_string(i)),
8871 Expr::Colon => ":".to_string(),
8872 Expr::NaT => "NaT".to_string(),
8873 Expr::FieldGet(base, field) => format!("{}.{field}", expr_to_string(base)),
8874 Expr::DynFieldGet(base, field_expr) => {
8875 format!("{}.({})", expr_to_string(base), expr_to_string(field_expr))
8876 }
8877 Expr::DotCall(segs, args) => {
8878 let args_str = args
8879 .iter()
8880 .map(expr_to_string)
8881 .collect::<Vec<_>>()
8882 .join(", ");
8883 format!("{}({args_str})", segs.join("."))
8884 }
8885 }
8886}
8887
8888pub fn format_value(v: &Value, base: Base, mode: &FormatMode) -> String {
8890 match v {
8891 Value::Void => String::new(),
8892 Value::Scalar(n) => format_scalar(*n, base, mode),
8893 Value::Matrix(m) => format!("[{}x{} double]", m.nrows(), m.ncols()),
8894 Value::ComplexMatrix(m) => format!("[{}×{} complex]", m.nrows(), m.ncols()),
8895 Value::Complex(re, im) => format_complex(*re, *im, mode),
8896 Value::Str(s) => s.clone(),
8897 Value::StringObj(s) => s.clone(),
8898 Value::Lambda(lf) => lf.1.clone(),
8899 Value::Function(fd) => {
8900 let params_str = fd.params.join(", ");
8901 let out_str = match fd.outputs.len() {
8902 0 => String::new(),
8903 1 => format!("{} = ", fd.outputs[0]),
8904 _ => format!("[{}] = ", fd.outputs.join(", ")),
8905 };
8906 format!("@function {out_str}f({params_str})")
8907 }
8908 Value::Tuple(vals) => {
8909 let parts: Vec<String> = vals.iter().map(|v| format_value(v, base, mode)).collect();
8910 format!("({})", parts.join(", "))
8911 }
8912 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
8913 Value::Struct(_) => "[1×1 struct]".to_string(),
8914 Value::StructArray(arr) => format!("[1×{} struct]", arr.len()),
8915 Value::DateTime(ts) => crate::datetime::format_datetime(*ts),
8916 Value::Duration(s) => crate::datetime::format_duration(*s),
8917 Value::DateTimeArray(v) => format!("[{}×1 datetime]", v.len()),
8918 Value::DurationArray(v) => format!("[{}×1 duration]", v.len()),
8919 Value::Map(m) => format!("[Map with {} entries]", m.len()),
8920 }
8921}
8922
8923pub fn format_value_full(v: &Value, mode: &FormatMode) -> Option<String> {
8926 match v {
8927 Value::Void
8928 | Value::Scalar(_)
8929 | Value::Complex(_, _)
8930 | Value::Str(_)
8931 | Value::StringObj(_)
8932 | Value::Lambda(_)
8933 | Value::Function(_)
8934 | Value::Tuple(_)
8935 | Value::DateTime(_)
8936 | Value::Duration(_) => None,
8937 Value::Matrix(m) => Some(format_matrix(m, mode)),
8938 Value::ComplexMatrix(m) => Some(format_complex_matrix(m, mode)),
8939 Value::Cell(elems) => Some(format_cell(elems, mode)),
8940 Value::Struct(map) => Some(format_struct(map, mode)),
8941 Value::StructArray(arr) => Some(format_struct_array(arr, mode)),
8942 Value::DateTimeArray(v) => Some(format_datetime_array(v)),
8943 Value::DurationArray(v) => Some(format_duration_array(v)),
8944 Value::Map(m) => Some(format_map(m, mode)),
8945 }
8946}
8947
8948fn format_cell(elems: &[Value], mode: &FormatMode) -> String {
8950 if elems.is_empty() {
8951 return " {}".to_string();
8952 }
8953 let mut lines = vec![" {".to_string()];
8954 for (i, val) in elems.iter().enumerate() {
8955 let label = format!(" [1,{}]", i + 1);
8956 match val {
8957 Value::Matrix(_) => {
8958 lines.push(format!("{label}:"));
8959 if let Some(full) = format_value_full(val, mode) {
8960 for line in full.lines() {
8961 lines.push(format!(" {line}"));
8962 }
8963 }
8964 }
8965 Value::Cell(_) => {
8966 lines.push(format!("{label}: {}", format_value(val, Base::Dec, mode)));
8967 }
8968 _ => {
8969 lines.push(format!("{label}: {}", format_value(val, Base::Dec, mode)));
8970 }
8971 }
8972 }
8973 lines.push(" }".to_string());
8974 lines.join("\n")
8975}
8976
8977fn format_struct(map: &IndexMap<String, Value>, mode: &FormatMode) -> String {
8979 let mut lines = vec![
8980 String::new(),
8981 " scalar structure containing the fields:".to_string(),
8982 String::new(),
8983 ];
8984 for (key, val) in map {
8985 let val_str = match val {
8986 Value::Struct(_) => "[1×1 struct]".to_string(),
8987 Value::StructArray(arr) => format!("[1×{} struct]", arr.len()),
8988 Value::Matrix(m) => format!("[{}×{} double]", m.nrows(), m.ncols()),
8989 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
8990 _ => format_value(val, Base::Dec, mode),
8991 };
8992 lines.push(format!(" {key}: {val_str}"));
8993 }
8994 lines.join("\n")
8995}
8996
8997fn format_struct_array(arr: &[IndexMap<String, Value>], mode: &FormatMode) -> String {
8999 let n = arr.len();
9000 let mut lines = vec![
9001 String::new(),
9002 format!(" 1×{n} struct array with fields:"),
9003 String::new(),
9004 ];
9005 if let Some(first) = arr.first() {
9007 for key in first.keys() {
9008 lines.push(format!(" {key}"));
9009 }
9010 }
9011 if n == 1
9013 && let Some(first) = arr.first()
9014 {
9015 lines.clear();
9016 lines.push(String::new());
9017 lines.push(" scalar structure containing the fields:".to_string());
9018 lines.push(String::new());
9019 for (key, val) in first {
9020 let val_str = match val {
9021 Value::Struct(_) => "[1×1 struct]".to_string(),
9022 Value::StructArray(a) => format!("[1×{} struct]", a.len()),
9023 Value::Matrix(m) => format!("[{}×{} double]", m.nrows(), m.ncols()),
9024 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
9025 _ => format_value(val, Base::Dec, mode),
9026 };
9027 lines.push(format!(" {key}: {val_str}"));
9028 }
9029 }
9030 lines.join("\n")
9031}
9032
9033fn format_map(map: &IndexMap<String, Value>, mode: &FormatMode) -> String {
9034 let n = map.len();
9035 let mut lines = vec![
9036 String::new(),
9037 format!(" Map with {n} entries:"),
9038 String::new(),
9039 ];
9040 for (key, val) in map {
9041 let val_str = match val {
9042 Value::Struct(_) => "[1×1 struct]".to_string(),
9043 Value::Matrix(m) => format!("[{}×{} double]", m.nrows(), m.ncols()),
9044 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
9045 _ => format_value(val, Base::Dec, mode),
9046 };
9047 lines.push(format!(" '{key}' → {val_str}"));
9048 }
9049 lines.join("\n")
9050}
9051
9052fn format_datetime_array(v: &[f64]) -> String {
9053 let mut lines = Vec::with_capacity(v.len());
9054 for ts in v {
9055 lines.push(format!(" {}", crate::datetime::format_datetime(*ts)));
9056 }
9057 lines.join("\n")
9058}
9059
9060fn format_duration_array(v: &[f64]) -> String {
9061 let mut lines = Vec::with_capacity(v.len());
9062 for secs in v {
9063 lines.push(format!(" {}", crate::datetime::format_duration(*secs)));
9064 }
9065 lines.join("\n")
9066}
9067
9068fn format_complex_matrix(m: &Array2<Complex<f64>>, mode: &FormatMode) -> String {
9071 if m.nrows() == 0 || m.ncols() == 0 {
9072 return " []".to_string();
9073 }
9074 let ncols = m.ncols();
9075
9076 let parts: Vec<Vec<(String, &'static str, String)>> = m
9080 .rows()
9081 .into_iter()
9082 .map(|row| {
9083 row.iter()
9084 .map(|c| {
9085 let re_str = format_decimal(c.re, mode);
9086 let im_abs = format_decimal(c.im.abs(), mode);
9087 let sign = if c.im < 0.0 { " - " } else { " + " };
9088 (re_str, sign, im_abs)
9089 })
9090 .collect()
9091 })
9092 .collect();
9093
9094 let re_widths: Vec<usize> = (0..ncols)
9096 .map(|c| parts.iter().map(|row| row[c].0.len()).max().unwrap_or(0))
9097 .collect();
9098 let im_widths: Vec<usize> = (0..ncols)
9099 .map(|c| parts.iter().map(|row| row[c].2.len()).max().unwrap_or(0))
9100 .collect();
9101
9102 let mut lines = Vec::new();
9103 for row in &parts {
9104 let mut line = String::from(" ");
9105 for (c, (re_str, sign, im_str)) in row.iter().enumerate() {
9106 if c > 0 {
9107 let prev_im_pad = im_widths[c - 1].saturating_sub(row[c - 1].2.len());
9110 for _ in 0..prev_im_pad {
9111 line.push(' ');
9112 }
9113 line.push_str(" ");
9114 }
9115 let re_pad = re_widths[c].saturating_sub(re_str.len());
9116 for _ in 0..re_pad {
9117 line.push(' ');
9118 }
9119 line.push_str(re_str);
9120 line.push_str(sign);
9121 line.push_str(im_str);
9122 line.push('i');
9123 }
9124 lines.push(line);
9125 }
9126 lines.join("\n")
9127}
9128
9129fn format_matrix(m: &Array2<f64>, mode: &FormatMode) -> String {
9132 if m.nrows() == 0 || m.ncols() == 0 {
9133 return " []".to_string();
9134 }
9135 if matches!(mode, FormatMode::Plus) {
9137 let lines: Vec<String> = m
9138 .rows()
9139 .into_iter()
9140 .map(|row| {
9141 let chars: String = row
9142 .iter()
9143 .map(|&x| {
9144 if x > 0.0 {
9145 '+'
9146 } else if x < 0.0 {
9147 '-'
9148 } else {
9149 '0'
9150 }
9151 })
9152 .collect();
9153 format!(" {}", chars)
9154 })
9155 .collect();
9156 return lines.join("\n");
9157 }
9158 let ncols = m.ncols();
9159 let cells: Vec<Vec<String>> = m
9160 .rows()
9161 .into_iter()
9162 .map(|row| row.iter().map(|&x| format_decimal(x, mode)).collect())
9163 .collect();
9164 let col_widths: Vec<usize> = (0..ncols)
9165 .map(|c| cells.iter().map(|row| row[c].len()).max().unwrap_or(0))
9166 .collect();
9167 let mut lines = Vec::new();
9168 for row in &cells {
9169 let mut line = String::from(" ");
9170 for (c, cell) in row.iter().enumerate() {
9171 if c > 0 {
9172 line.push_str(" ");
9173 }
9174 let pad = col_widths[c].saturating_sub(cell.len());
9175 for _ in 0..pad {
9176 line.push(' ');
9177 }
9178 line.push_str(cell);
9179 }
9180 lines.push(line);
9181 }
9182 lines.join("\n")
9183}
9184
9185pub fn format_non_dec(n: f64, base: Base) -> String {
9188 let i = n.round() as i64;
9189 let u = i.unsigned_abs();
9190 let sign = if i < 0 { "-" } else { "" };
9191 match base {
9192 Base::Hex => format!("{}0x{:X}", sign, u),
9193 Base::Bin => format!("{}0b{:b}", sign, u),
9194 Base::Oct => format!("{}0o{:o}", sign, u),
9195 Base::Dec => format_decimal(n, &FormatMode::default()),
9196 }
9197}
9198
9199fn format_decimal(n: f64, mode: &FormatMode) -> String {
9204 if n.is_nan() {
9205 return "NaN".to_string();
9206 }
9207 if n.is_infinite() {
9208 return if n > 0.0 { "Inf" } else { "-Inf" }.to_string();
9209 }
9210 match mode {
9211 FormatMode::Short | FormatMode::ShortG => fmt_auto_sig(n, 5),
9212 FormatMode::Long | FormatMode::LongG => fmt_auto_sig(n, 15),
9213 FormatMode::ShortE => fmt_sci_dp(n, 4),
9214 FormatMode::LongE => fmt_sci_dp(n, 14),
9215 FormatMode::Bank => format!("{:.2}", n),
9216 FormatMode::Rat => fmt_rat(n),
9217 FormatMode::Hex => fmt_hex_ieee754(n),
9218 FormatMode::Plus => fmt_plus_sign(n),
9219 FormatMode::Custom(prec) => fmt_custom_prec(n, *prec),
9220 }
9221}
9222
9223#[inline]
9225fn is_exact_int(n: f64) -> bool {
9226 n.fract() == 0.0 && n.abs() < 1e15
9227}
9228
9229fn fmt_auto_sig(n: f64, sig: usize) -> String {
9233 if is_exact_int(n) {
9234 return format!("{}", n as i64);
9235 }
9236 let abs_n = n.abs();
9237 let exp = if abs_n == 0.0 {
9238 0i32
9239 } else {
9240 abs_n.log10().floor() as i32
9241 };
9242 if exp >= -3 && exp < sig as i32 {
9243 let dp = (sig as i32 - 1 - exp) as usize;
9244 let s = format!("{:.prec$}", n, prec = dp);
9245 if s.contains('.') {
9247 s.trim_end_matches('0').trim_end_matches('.').to_string()
9248 } else {
9249 s
9250 }
9251 } else {
9252 let s = format!("{:.prec$e}", n, prec = sig - 1);
9253 trim_sci(&s)
9254 }
9255}
9256
9257fn fmt_sci_dp(n: f64, dp: usize) -> String {
9259 let s = format!("{:.prec$e}", n, prec = dp);
9260 trim_sci(&s)
9261}
9262
9263fn fmt_custom_prec(n: f64, prec: usize) -> String {
9265 if is_exact_int(n) {
9266 return format!("{}", n as i64);
9267 }
9268 if n.abs() >= 1e15 || (n != 0.0 && n.abs() < 1e-9) {
9269 let s = format!("{:.prec$e}", n, prec = prec);
9270 trim_sci(&s)
9271 } else {
9272 let s = format!("{:.prec$}", n, prec = prec);
9273 s.trim_end_matches('0').trim_end_matches('.').to_string()
9274 }
9275}
9276
9277fn fmt_rat(n: f64) -> String {
9279 if is_exact_int(n) {
9280 return format!("{}", n as i64);
9281 }
9282 let sign = if n < 0.0 { -1i64 } else { 1i64 };
9283 let x = n.abs();
9284 let (mut h1, mut h2): (i64, i64) = (1, 0);
9285 let (mut k1, mut k2): (i64, i64) = (0, 1);
9286 let mut b = x;
9287 for _ in 0..64 {
9288 let a = b.floor() as i64;
9289 let (nh, nk) = (a * h1 + h2, a * k1 + k2);
9290 if nk > 10_000 {
9291 break;
9292 }
9293 h2 = h1;
9294 h1 = nh;
9295 k2 = k1;
9296 k1 = nk;
9297 let frac = b - a as f64;
9298 if frac < 1e-12 || (h1 as f64 / k1 as f64 - x).abs() < 1e-6 {
9299 break;
9300 }
9301 b = 1.0 / frac;
9302 }
9303 let p = sign * h1;
9304 if k1 == 1 {
9305 format!("{}", p)
9306 } else {
9307 format!("{}/{}", p, k1)
9308 }
9309}
9310
9311fn fmt_hex_ieee754(n: f64) -> String {
9313 format!("{:016X}", n.to_bits())
9314}
9315
9316fn fmt_plus_sign(n: f64) -> String {
9318 if n > 0.0 {
9319 "+".to_string()
9320 } else if n < 0.0 {
9321 "-".to_string()
9322 } else {
9323 " ".to_string()
9324 }
9325}
9326
9327fn trim_sci(s: &str) -> String {
9328 if let Some(e_pos) = s.find('e') {
9329 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
9330 let exp_str = &s[e_pos + 1..];
9331 let (sign, digits) = if let Some(d) = exp_str.strip_prefix('-') {
9332 ("-", d)
9333 } else if let Some(d) = exp_str.strip_prefix('+') {
9334 ("+", d)
9335 } else {
9336 ("+", exp_str)
9337 };
9338 let exp_num: i32 = digits.parse().unwrap_or(0);
9339 format!("{}e{}{:02}", mantissa, sign, exp_num)
9340 } else {
9341 s.to_string()
9342 }
9343}
9344
9345pub fn load_mat_file(path: &str) -> Result<Value, String> {
9351 load_mat_file_impl(path)
9352}
9353
9354#[cfg(feature = "mat")]
9355fn load_mat_file_impl(path: &str) -> Result<Value, String> {
9356 crate::mat::mat_load(path)
9357}
9358
9359#[cfg(not(feature = "mat"))]
9360fn load_mat_file_impl(_path: &str) -> Result<Value, String> {
9361 Err("load: .mat support not available — rebuild with --features mat".to_string())
9362}
9363
9364#[cfg(feature = "regex")]
9367fn regexp_impl(
9368 fname: &str,
9369 s: &str,
9370 pat: &str,
9371 ignore_case: bool,
9372 return_match: bool,
9373) -> Result<Value, String> {
9374 use ndarray::Array2;
9375 let full_pat = if ignore_case {
9376 format!("(?i){pat}")
9377 } else {
9378 pat.to_string()
9379 };
9380 let re = regex::Regex::new(&full_pat).map_err(|e| format!("{fname}: invalid pattern: {e}"))?;
9381 if return_match {
9382 let matches: Vec<Value> = re
9383 .find_iter(s)
9384 .map(|m| Value::Str(m.as_str().to_string()))
9385 .collect();
9386 Ok(Value::Cell(Box::new(matches)))
9387 } else {
9388 match re.find(s) {
9389 Some(m) => Ok(Value::Scalar((s[..m.start()].chars().count() + 1) as f64)),
9390 None => Ok(Value::Matrix(Box::new(Array2::zeros((0, 0))))),
9391 }
9392 }
9393}
9394
9395#[cfg(not(feature = "regex"))]
9396fn regexp_impl(
9397 fname: &str,
9398 _s: &str,
9399 _pat: &str,
9400 _ignore_case: bool,
9401 _return_match: bool,
9402) -> Result<Value, String> {
9403 Err(format!(
9404 "{fname}: not available — rebuild with --features regex"
9405 ))
9406}
9407
9408#[cfg(feature = "regex")]
9409fn regexprep_impl(s: &str, pat: &str, rep: &str) -> Result<Value, String> {
9410 let re = regex::Regex::new(pat).map_err(|e| format!("regexprep: invalid pattern: {e}"))?;
9411 let result = re.replace_all(s, regex::NoExpand(rep));
9412 Ok(Value::Str(result.into_owned()))
9413}
9414
9415#[cfg(not(feature = "regex"))]
9416fn regexprep_impl(_s: &str, _pat: &str, _rep: &str) -> Result<Value, String> {
9417 Err("regexprep: not available — rebuild with --features regex".to_string())
9418}
9419
9420#[cfg(feature = "fft")]
9424fn extract_real_vec(v: &Value, name: &str) -> Result<Vec<f64>, String> {
9425 match v {
9426 Value::Scalar(s) => Ok(vec![*s]),
9427 Value::Matrix(m) if m.nrows() == 1 || m.ncols() == 1 => Ok(m.iter().copied().collect()),
9428 Value::Matrix(m) => Err(format!(
9429 "{name}: input must be a vector (got {}×{} matrix)",
9430 m.nrows(),
9431 m.ncols()
9432 )),
9433 _ => Err(format!("{name}: input must be a real numeric vector")),
9434 }
9435}
9436
9437#[cfg(feature = "fft")]
9439fn complex_pairs_to_complex_matrix(data: Vec<(f64, f64)>) -> Value {
9440 let n = data.len();
9441 if n == 0 {
9442 return Value::ComplexMatrix(Box::new(Array2::zeros((1, 0))));
9443 }
9444 let elems: Vec<Complex<f64>> = data
9445 .into_iter()
9446 .map(|(re, im)| Complex::new(re, im))
9447 .collect();
9448 Value::ComplexMatrix(Box::new(Array2::from_shape_vec((1, n), elems).unwrap()))
9449}
9450
9451#[cfg(feature = "fft")]
9453fn extract_complex_vec(v: &Value, name: &str) -> Result<Vec<(f64, f64)>, String> {
9454 match v {
9455 Value::Scalar(s) => Ok(vec![(*s, 0.0)]),
9456 Value::Matrix(m) => Ok(m.iter().copied().map(|x| (x, 0.0)).collect()),
9457 Value::ComplexMatrix(m) => Ok(m.iter().map(|c| (c.re, c.im)).collect()),
9458 Value::Cell(elems) => elems
9459 .iter()
9460 .enumerate()
9461 .map(|(i, e)| match e {
9462 Value::Complex(re, im) => Ok((*re, *im)),
9463 Value::Scalar(s) => Ok((*s, 0.0)),
9464 _ => Err(format!(
9465 "{name}: cell element {} must be a complex or real number",
9466 i + 1
9467 )),
9468 })
9469 .collect(),
9470 _ => Err(format!(
9471 "{name}: input must be a complex matrix, cell array, or numeric vector"
9472 )),
9473 }
9474}
9475
9476#[cfg(feature = "fft")]
9477fn fft_call(v: &Value, n_opt: Option<usize>) -> Result<Value, String> {
9478 let real = extract_real_vec(v, "fft")?;
9479 let n = n_opt.unwrap_or(real.len());
9480 if n == 0 {
9481 return Err("fft: length must be positive".to_string());
9482 }
9483 let out = crate::fft::fft_forward(&real, n);
9484 Ok(complex_pairs_to_complex_matrix(out))
9485}
9486
9487#[cfg(not(feature = "fft"))]
9488fn fft_call(_v: &Value, _n_opt: Option<usize>) -> Result<Value, String> {
9489 Err("fft: not available — rebuild with --features fft".to_string())
9490}
9491
9492#[cfg(feature = "fft")]
9493fn ifft_call(v: &Value) -> Result<Value, String> {
9494 let complex = extract_complex_vec(v, "ifft")?;
9495 if complex.is_empty() {
9496 return Ok(Value::Matrix(Box::new(ndarray::Array2::zeros((1, 0)))));
9497 }
9498 let out = crate::fft::fft_inverse(&complex);
9499 if out.iter().all(|(_, im)| im.abs() < 1e-12) {
9500 let real: Vec<f64> = out.iter().map(|(re, _)| *re).collect();
9501 let n = real.len();
9502 Ok(Value::Matrix(Box::new(
9503 ndarray::Array2::from_shape_vec((1, n), real).unwrap(),
9504 )))
9505 } else {
9506 Ok(complex_pairs_to_complex_matrix(out))
9507 }
9508}
9509
9510#[cfg(not(feature = "fft"))]
9511fn ifft_call(_v: &Value) -> Result<Value, String> {
9512 Err("ifft: not available — rebuild with --features fft".to_string())
9513}
9514
9515#[cfg(feature = "json")]
9518fn jsondecode_impl(arg: &Value) -> Result<Value, String> {
9519 let s = match arg {
9520 Value::Str(s) | Value::StringObj(s) => s.as_str(),
9521 _ => return Err("jsondecode: argument must be a string".to_string()),
9522 };
9523 let jval: serde_json::Value =
9524 serde_json::from_str(s).map_err(|e| format!("jsondecode: invalid JSON: {e}"))?;
9525 Ok(crate::json::json_to_value(&jval))
9526}
9527
9528#[cfg(not(feature = "json"))]
9529fn jsondecode_impl(_arg: &Value) -> Result<Value, String> {
9530 Err("jsondecode: not available — rebuild with --features json".to_string())
9531}
9532
9533#[cfg(feature = "json")]
9534fn jsonencode_impl(arg: &Value) -> Result<Value, String> {
9535 let jval = crate::json::value_to_json(arg)?;
9536 let s = serde_json::to_string(&jval)
9537 .map_err(|e| format!("jsonencode: serialization error: {e}"))?;
9538 Ok(Value::Str(s))
9539}
9540
9541#[cfg(not(feature = "json"))]
9542fn jsonencode_impl(_arg: &Value) -> Result<Value, String> {
9543 Err("jsonencode: not available — rebuild with --features json".to_string())
9544}
9545
9546fn cpoly_eval(coeffs: &[f64], z: (f64, f64)) -> (f64, f64) {
9552 let mut acc = (0.0_f64, 0.0_f64);
9553 for &c in coeffs {
9554 acc = (acc.0 * z.0 - acc.1 * z.1 + c, acc.0 * z.1 + acc.1 * z.0);
9556 }
9557 acc
9558}
9559
9560fn horner(coeffs: &[f64], x: f64) -> f64 {
9562 coeffs.iter().fold(0.0, |acc, &c| acc * x + c)
9563}
9564
9565fn poly_coeffs(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
9567 match v {
9568 Value::Scalar(s) => Ok(vec![*s]),
9569 Value::Matrix(m) => {
9570 if m.nrows() == 1 {
9571 Ok(m.row(0).iter().copied().collect())
9572 } else if m.ncols() == 1 {
9573 Ok(m.column(0).iter().copied().collect())
9574 } else {
9575 Err(format!(
9576 "{fname}: argument must be a vector, got {}×{}",
9577 m.nrows(),
9578 m.ncols()
9579 ))
9580 }
9581 }
9582 _ => Err(format!("{fname}: argument must be a real numeric vector")),
9583 }
9584}
9585
9586fn poly_conv(a: &[f64], b: &[f64]) -> Vec<f64> {
9588 if a.is_empty() || b.is_empty() {
9589 return vec![];
9590 }
9591 let mut result = vec![0.0_f64; a.len() + b.len() - 1];
9592 for (i, &ai) in a.iter().enumerate() {
9593 for (j, &bj) in b.iter().enumerate() {
9594 result[i + j] += ai * bj;
9595 }
9596 }
9597 result
9598}
9599
9600fn poly_deconv(c: &[f64], b: &[f64]) -> Result<(Vec<f64>, Vec<f64>), String> {
9605 if b.is_empty() || b.iter().all(|&x| x == 0.0) {
9606 return Err("deconv: divisor polynomial must not be zero".to_string());
9607 }
9608 let mc = c.len();
9609 let mb = b.len();
9610 if mb > mc {
9611 return Ok((vec![0.0], c.to_vec()));
9612 }
9613 let q_len = mc - mb + 1;
9614 let mut remainder = c.to_vec();
9615 let mut q = vec![0.0_f64; q_len];
9616 for i in 0..q_len {
9617 let coeff = remainder[i] / b[0];
9618 q[i] = coeff;
9619 for j in 0..mb {
9620 remainder[i + j] -= coeff * b[j];
9621 }
9622 }
9623 let scale = c.iter().map(|v| v.abs()).fold(0.0_f64, f64::max).max(1.0);
9625 for x in &mut remainder {
9626 if x.abs() < 1e-10 * scale {
9627 *x = 0.0;
9628 }
9629 }
9630 Ok((q, remainder))
9631}
9632
9633fn durand_kerner(coeffs: &[f64]) -> Result<Vec<(f64, f64)>, String> {
9639 let n = coeffs.len() - 1; if n == 0 {
9641 return Ok(vec![]);
9642 }
9643 let lc = coeffs[0];
9644 if lc == 0.0 {
9645 return Err("roots: leading coefficient must not be zero".to_string());
9646 }
9647 let monic: Vec<f64> = coeffs.iter().map(|&c| c / lc).collect();
9649
9650 let r = 1.0 + monic[1..].iter().map(|c| c.abs()).fold(0.0_f64, f64::max);
9652
9653 let mut z: Vec<(f64, f64)> = (0..n)
9656 .map(|k| {
9657 let angle = 2.0 * std::f64::consts::PI * (k as f64 + 0.25) / n as f64;
9658 (r * angle.cos(), r * angle.sin())
9659 })
9660 .collect();
9661
9662 const MAX_ITER: usize = 2000;
9663 const EPS: f64 = 1e-12;
9664
9665 for _ in 0..MAX_ITER {
9666 let z_old = z.clone();
9667 let mut max_corr = 0.0_f64;
9668 for i in 0..n {
9669 let (pre, pim) = cpoly_eval(&monic, z_old[i]);
9670 let mut dre = 1.0_f64;
9672 let mut dim = 0.0_f64;
9673 for j in 0..n {
9674 if j == i {
9675 continue;
9676 }
9677 let (dr, di) = (z_old[i].0 - z_old[j].0, z_old[i].1 - z_old[j].1);
9678 (dre, dim) = (dre * dr - dim * di, dre * di + dim * dr);
9679 }
9680 let d2 = dre * dre + dim * dim;
9682 let (cre, cim) = if d2 > 0.0 {
9683 ((pre * dre + pim * dim) / d2, (pim * dre - pre * dim) / d2)
9684 } else {
9685 (pre, pim)
9686 };
9687 let corr_abs = (cre * cre + cim * cim).sqrt();
9688 max_corr = max_corr.max(corr_abs);
9689 z[i] = (z_old[i].0 - cre, z_old[i].1 - cim);
9690 }
9691 if max_corr < EPS {
9692 break;
9693 }
9694 }
9695
9696 z.sort_by(|a, b| {
9698 b.0.partial_cmp(&a.0)
9699 .unwrap_or(std::cmp::Ordering::Equal)
9700 .then(b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal))
9701 });
9702
9703 Ok(z)
9704}
9705
9706fn roots_to_value(roots: &[(f64, f64)]) -> Value {
9711 const IMAG_TOL: f64 = 1e-9;
9712 let all_real = roots.iter().all(|(_, im)| im.abs() < IMAG_TOL);
9713 if all_real {
9714 let data: Vec<f64> = roots.iter().map(|(re, _)| *re).collect();
9715 let n = data.len();
9716 Value::Matrix(Box::new(Array2::from_shape_vec((n, 1), data).unwrap()))
9717 } else {
9718 let vals: Vec<Value> = roots
9719 .iter()
9720 .map(|&(re, im)| {
9721 if im.abs() < IMAG_TOL {
9722 Value::Scalar(re)
9723 } else {
9724 Value::Complex(re, im)
9725 }
9726 })
9727 .collect();
9728 Value::Cell(Box::new(vals))
9729 }
9730}
9731
9732fn characteristic_poly(a: &Array2<f64>) -> Result<Vec<f64>, String> {
9737 let n = a.nrows();
9738 if a.ncols() != n {
9739 return Err("poly: matrix must be square".to_string());
9740 }
9741 if n == 0 {
9742 return Ok(vec![1.0]);
9743 }
9744 let mut coeffs = vec![0.0_f64; n + 1];
9745 coeffs[0] = 1.0;
9746 let mut nk = Array2::<f64>::eye(n); for (k, coeff) in coeffs.iter_mut().enumerate().skip(1) {
9748 let ank = a.dot(&nk); let tr: f64 = (0..n).map(|i| ank[[i, i]]).sum();
9750 let ak = -tr / k as f64;
9751 *coeff = ak;
9752 nk = ank; for i in 0..n {
9754 nk[[i, i]] += ak;
9755 }
9756 }
9757 Ok(coeffs)
9758}
9759
9760fn poly_back_sub(r: &Array2<f64>, b: &[f64]) -> Result<Vec<f64>, String> {
9762 let n = r.nrows();
9763 let mut x = vec![0.0_f64; n];
9764 for i in (0..n).rev() {
9765 let mut s = b[i];
9766 for j in (i + 1)..n {
9767 s -= r[[i, j]] * x[j];
9768 }
9769 if r[[i, i]].abs() < 1e-14 {
9770 return Err(
9771 "polyfit: Vandermonde matrix is rank-deficient; reduce polynomial degree"
9772 .to_string(),
9773 );
9774 }
9775 x[i] = s / r[[i, i]];
9776 }
9777 Ok(x)
9778}
9779
9780fn interp1_at(x: &[f64], y: &[f64], xi: f64, method: &str) -> f64 {
9784 let n = x.len();
9785 if xi < x[0] || xi > x[n - 1] {
9786 return f64::NAN;
9787 }
9788 let lo = x.partition_point(|&xk| xk <= xi).saturating_sub(1);
9790 let lo2 = lo.min(n - 2);
9792 match method {
9793 "nearest" => {
9794 if lo == n - 1 {
9795 return y[n - 1];
9796 }
9797 if (xi - x[lo2]) <= (x[lo2 + 1] - xi) {
9798 y[lo2]
9799 } else {
9800 y[lo2 + 1]
9801 }
9802 }
9803 "previous" => y[lo],
9804 "next" => {
9805 if lo == n - 1 || xi == x[lo] {
9806 y[lo]
9807 } else {
9808 y[lo2 + 1]
9809 }
9810 }
9811 _ => {
9812 if lo == n - 1 {
9814 return y[n - 1];
9815 }
9816 let t = (xi - x[lo2]) / (x[lo2 + 1] - x[lo2]);
9817 y[lo2] + t * (y[lo2 + 1] - y[lo2])
9818 }
9819 }
9820}
9821
9822#[cfg(test)]
9823#[path = "eval_tests.rs"]
9824mod tests;