1use std::cell::{Cell, RefCell};
2use std::collections::{HashMap, HashSet};
3use std::io::Write;
4
5use indexmap::IndexMap;
6use ndarray::Array2;
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}
55
56pub fn set_autoload_hook(f: AutoloadHook) {
58 AUTOLOAD_HOOK.with(|c| c.set(Some(f)));
59}
60
61pub fn autoload_cache_insert(name: String, val: Value) {
63 AUTOLOAD_CACHE.with(|c| c.borrow_mut().insert(name, val));
64}
65
66pub fn resolve_autoloaded(name: &str) -> Option<Value> {
72 let cached = AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned());
73 if cached.is_some() {
74 return cached;
75 }
76 let hook = AUTOLOAD_HOOK.with(|c| c.get());
77 if let Some(f) = hook {
78 f(name);
79 }
80 AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned())
81}
82
83thread_local! {
86 static LAST_ERR: RefCell<String> = const { RefCell::new(String::new()) };
87}
88
89pub fn set_last_err(msg: &str) {
91 LAST_ERR.with(|e| *e.borrow_mut() = msg.to_string());
92}
93
94pub fn get_last_err() -> String {
96 LAST_ERR.with(|e| e.borrow().clone())
97}
98
99thread_local! {
102 static NARGOUT: Cell<usize> = const { Cell::new(1) };
103}
104
105pub fn set_nargout(n: usize) {
111 NARGOUT.with(|c| c.set(n));
112}
113
114fn get_nargout() -> usize {
115 NARGOUT.with(|c| c.get())
116}
117
118thread_local! {
121 static DISPLAY_FMT: RefCell<FormatMode> = const { RefCell::new(FormatMode::Short) };
122 static DISPLAY_BASE: Cell<Base> = const { Cell::new(Base::Dec) };
123 static DISPLAY_COMPACT: Cell<bool> = const { Cell::new(false) };
124}
125
126pub fn set_display_ctx(fmt: &FormatMode, base: Base, compact: bool) {
131 DISPLAY_FMT.with(|f| *f.borrow_mut() = fmt.clone());
132 DISPLAY_BASE.with(|b| b.set(base));
133 DISPLAY_COMPACT.with(|c| c.set(compact));
134}
135
136pub fn get_display_fmt() -> FormatMode {
138 DISPLAY_FMT.with(|f| f.borrow().clone())
139}
140
141pub fn get_display_base() -> Base {
143 DISPLAY_BASE.with(|b| b.get())
144}
145
146pub fn get_display_compact() -> bool {
148 DISPLAY_COMPACT.with(|c| c.get())
149}
150
151thread_local! {
154 static GLOBAL_ENV: RefCell<Env> = RefCell::new(Env::new());
159
160 static GLOBAL_NAMES_STACK: RefCell<Vec<HashSet<String>>> =
165 RefCell::new(vec![HashSet::new()]);
166}
167
168pub fn global_frame_push() {
170 GLOBAL_NAMES_STACK.with(|s| s.borrow_mut().push(HashSet::new()));
171}
172
173pub fn global_frame_pop() {
175 GLOBAL_NAMES_STACK.with(|s| {
176 s.borrow_mut().pop();
177 });
178}
179
180pub fn global_declare(name: &str) {
182 GLOBAL_NAMES_STACK.with(|s| {
183 if let Some(frame) = s.borrow_mut().last_mut() {
184 frame.insert(name.to_string());
185 }
186 });
187}
188
189pub fn is_global(name: &str) -> bool {
191 GLOBAL_NAMES_STACK.with(|s| s.borrow().last().is_some_and(|f| f.contains(name)))
192}
193
194pub fn global_get(name: &str) -> Option<Value> {
196 GLOBAL_ENV.with(|e| e.borrow().get(name).cloned())
197}
198
199pub fn global_set(name: &str, val: Value) {
201 GLOBAL_ENV.with(|e| e.borrow_mut().insert(name.to_string(), val));
202}
203
204pub fn global_init_if_absent(name: &str) {
206 GLOBAL_ENV.with(|e| {
207 e.borrow_mut()
208 .entry(name.to_string())
209 .or_insert(Value::Scalar(0.0));
210 });
211}
212
213pub fn global_refresh_into_env(env: &mut crate::env::Env) {
218 GLOBAL_NAMES_STACK.with(|s| {
219 GLOBAL_ENV.with(|ge| {
220 if let Some(frame) = s.borrow().last() {
221 let store = ge.borrow();
222 for name in frame {
223 if let Some(val) = store.get(name) {
224 env.insert(name.clone(), val.clone());
225 }
226 }
227 }
228 });
229 });
230}
231
232thread_local! {
235 static PERSISTENT_STORE: RefCell<HashMap<String, Value>> =
240 RefCell::new(HashMap::new());
241
242 static FUNC_NAME_STACK: RefCell<Vec<String>> =
247 RefCell::new(vec![String::new()]);
248
249 static PERSISTENT_NAMES_STACK: RefCell<Vec<HashSet<String>>> =
251 RefCell::new(vec![HashSet::new()]);
252}
253
254pub fn persistent_frame_push(func_name: &str) {
256 FUNC_NAME_STACK.with(|s| s.borrow_mut().push(func_name.to_string()));
257 PERSISTENT_NAMES_STACK.with(|s| s.borrow_mut().push(HashSet::new()));
258}
259
260pub fn persistent_frame_pop() -> (String, HashSet<String>) {
262 let func_name = FUNC_NAME_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default());
263 let names = PERSISTENT_NAMES_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default());
264 (func_name, names)
265}
266
267pub fn persistent_declare(name: &str) {
269 PERSISTENT_NAMES_STACK.with(|s| {
270 if let Some(frame) = s.borrow_mut().last_mut() {
271 frame.insert(name.to_string());
272 }
273 });
274}
275
276pub fn persistent_load(func_name: &str, var_name: &str) -> Option<Value> {
278 let key = format!("{func_name}\x00{var_name}");
279 PERSISTENT_STORE.with(|s| s.borrow().get(&key).cloned())
280}
281
282pub fn persistent_save(func_name: &str, var_name: &str, val: Value) {
284 let key = format!("{func_name}\x00{var_name}");
285 PERSISTENT_STORE.with(|s| s.borrow_mut().insert(key, val));
286}
287
288pub fn current_func_name() -> String {
292 FUNC_NAME_STACK.with(|s| s.borrow().last().cloned().unwrap_or_default())
293}
294
295pub fn is_persistent(name: &str) -> bool {
297 PERSISTENT_NAMES_STACK.with(|s| s.borrow().last().is_some_and(|frame| frame.contains(name)))
298}
299
300thread_local! {
303 static RNG: RefCell<SmallRng> = RefCell::new(SmallRng::from_entropy());
307}
308
309pub fn rng_seed(seed: u64) {
311 RNG.with(|r| *r.borrow_mut() = SmallRng::seed_from_u64(seed));
312}
313
314pub fn rng_shuffle() {
316 RNG.with(|r| *r.borrow_mut() = SmallRng::from_entropy());
317}
318
319fn rand_uniform() -> f64 {
321 RNG.with(|r| r.borrow_mut().gen_range(0.0_f64..1.0))
322}
323
324fn rand_normal() -> f64 {
326 let u1 = rand_uniform().max(f64::EPSILON);
327 let u2 = rand_uniform();
328 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
329}
330
331#[derive(Debug, Clone)]
337pub enum Expr {
338 Number(f64),
340 Var(String),
342 UnaryMinus(Box<Expr>),
344 UnaryNot(Box<Expr>),
346 BinOp(Box<Expr>, Op, Box<Expr>),
348 Call(String, Vec<Expr>),
353 Matrix(Vec<Vec<Expr>>),
355 Transpose(Box<Expr>),
357 Range(Box<Expr>, Option<Box<Expr>>, Box<Expr>),
360 Colon,
363 StrLiteral(String),
365 StringObjLiteral(String),
367 Lambda {
372 params: Vec<String>,
374 body: Box<Expr>,
376 source: String,
378 },
379 PlainTranspose(Box<Expr>),
384 CellLiteral(Vec<Expr>),
388 CellIndex(Box<Expr>, Box<Expr>),
393 FuncHandle(String),
398 FieldGet(Box<Expr>, String),
402 DotCall(Vec<String>, Vec<Expr>),
412}
413
414#[derive(Debug, Clone)]
416pub enum Op {
417 Add,
419 Sub,
421 Mul,
423 Div,
425 Pow,
427 ElemMul,
429 ElemDiv,
431 ElemPow,
433 Eq,
436 NotEq,
438 Lt,
440 Gt,
442 LtEq,
444 GtEq,
446 And,
449 Or,
451 ElemAnd,
454 ElemOr,
456 LDiv,
458}
459
460#[derive(Debug, Clone, Copy, PartialEq, Default)]
462pub enum Base {
463 #[default]
465 Dec,
466 Hex,
468 Bin,
470 Oct,
472}
473
474#[derive(Debug, Clone, PartialEq)]
476pub enum FormatMode {
477 Short,
479 Long,
481 ShortE,
483 LongE,
485 ShortG,
487 LongG,
489 Bank,
491 Rat,
493 Hex,
495 Plus,
497 Custom(usize),
499}
500
501impl Default for FormatMode {
502 fn default() -> Self {
503 FormatMode::Custom(10)
504 }
505}
506
507impl FormatMode {
508 pub fn name(&self) -> String {
510 match self {
511 FormatMode::Short => "short".to_string(),
512 FormatMode::Long => "long".to_string(),
513 FormatMode::ShortE => "shortE".to_string(),
514 FormatMode::LongE => "longE".to_string(),
515 FormatMode::ShortG => "shortG".to_string(),
516 FormatMode::LongG => "longG".to_string(),
517 FormatMode::Bank => "bank".to_string(),
518 FormatMode::Rat => "rat".to_string(),
519 FormatMode::Hex => "hex".to_string(),
520 FormatMode::Plus => "+".to_string(),
521 FormatMode::Custom(n) => format!("custom({n})"),
522 }
523 }
524}
525
526pub fn eval(expr: &Expr, env: &Env) -> Result<Value, String> {
529 eval_inner(expr, env, None)
530}
531
532pub fn eval_with_io(expr: &Expr, env: &Env, io: &mut IoContext) -> Result<Value, String> {
535 eval_inner(expr, env, Some(io))
536}
537
538fn eval_inner(expr: &Expr, env: &Env, mut io: Option<&mut IoContext>) -> Result<Value, String> {
539 match expr {
540 Expr::Number(n) => Ok(Value::Scalar(*n)),
541 Expr::Var(name) => env.get(name).cloned().ok_or(()).or_else(|_| {
542 if is_global(name)
544 && let Some(val) = global_get(name)
545 {
546 return Ok(val);
547 }
548 if name == "e" {
550 Ok(Value::Scalar(std::f64::consts::E))
551 } else {
552 let hint = suggest_similar(name, env);
553 match hint {
554 Some(s) => Err(format!("Undefined variable '{name}'; did you mean '{s}'?")),
555 None => Err(format!("Undefined variable: '{name}'")),
556 }
557 }
558 }),
559 Expr::UnaryMinus(e) => match eval_inner(e, env, io)? {
560 Value::Void => Err("Unary minus is not applicable to void".to_string()),
561 Value::Scalar(n) => Ok(Value::Scalar(-n)),
562 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| -x))),
563 Value::Complex(re, im) => Ok(Value::Complex(-re, -im)),
564 Value::Str(s) => match str_to_numeric(&s) {
565 Value::Scalar(n) => Ok(Value::Scalar(-n)),
566 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| -x))),
567 _ => unreachable!(),
568 },
569 Value::StringObj(_) => {
570 Err("Unary minus is not applicable to string objects".to_string())
571 }
572 Value::Lambda(_)
573 | Value::Function { .. }
574 | Value::Tuple(_)
575 | Value::Cell(_)
576 | Value::Struct(_)
577 | Value::StructArray(_) => {
578 Err("Unary minus is not applicable to this type".to_string())
579 }
580 },
581 Expr::UnaryNot(e) => match eval_inner(e, env, io)? {
582 Value::Void => Err("Logical NOT is not applicable to void".to_string()),
583 Value::Scalar(n) => Ok(Value::Scalar(if n == 0.0 { 1.0 } else { 0.0 })),
584 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| if x == 0.0 { 1.0 } else { 0.0 }))),
585 Value::Complex(re, im) => Ok(Value::Scalar(if re == 0.0 && im == 0.0 {
586 1.0
587 } else {
588 0.0
589 })),
590 Value::Str(s) => match str_to_numeric(&s) {
591 Value::Scalar(n) => Ok(Value::Scalar(if n == 0.0 { 1.0 } else { 0.0 })),
592 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| if x == 0.0 { 1.0 } else { 0.0 }))),
593 _ => unreachable!(),
594 },
595 Value::StringObj(_) => {
596 Err("Logical NOT is not applicable to string objects".to_string())
597 }
598 Value::Lambda(_)
599 | Value::Function { .. }
600 | Value::Tuple(_)
601 | Value::Cell(_)
602 | Value::Struct(_)
603 | Value::StructArray(_) => {
604 Err("Logical NOT is not applicable to this type".to_string())
605 }
606 },
607 Expr::BinOp(left, op, right) => {
608 let l = eval_inner(left, env, io.as_deref_mut())?;
609 let r = eval_inner(right, env, io)?;
610 eval_binop(l, op, r)
611 }
612 Expr::Call(name, args) => {
613 if name == "try" && args.len() == 2 {
616 return match eval_inner(&args[0], env, io.as_deref_mut()) {
617 Ok(v) => Ok(v),
618 Err(msg) => {
619 set_last_err(&msg);
620 eval_inner(&args[1], env, io.as_deref_mut())
621 }
622 };
623 }
624
625 if let Some(val) = env.get(name).cloned() {
629 match &val {
630 Value::Lambda(f) => {
631 let mut evaled = Vec::with_capacity(args.len().max(1));
634 for a in args {
635 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
636 }
637 if evaled.is_empty() {
638 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
639 }
640 let f = f.clone();
641 return f.0(&evaled, io);
642 }
643 Value::Function { .. } => {
644 let mut evaled = Vec::with_capacity(args.len());
648 for a in args {
649 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
650 }
651 return match io.as_deref_mut() {
652 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
653 Some(hook) => hook(name, &val, &evaled, env, io_ref),
654 None => Err(format!(
655 "'{name}': user function execution not initialized \
656 (call exec::init() first)"
657 )),
658 }),
659 None => {
660 let mut tmp_io = IoContext::new();
663 FN_CALL_HOOK.with(|c| match c.get() {
664 Some(hook) => hook(name, &val, &evaled, env, &mut tmp_io),
665 None => Err(format!(
666 "'{name}': user function execution not initialized"
667 )),
668 })
669 }
670 };
671 }
672 _ => return eval_index(&val, args, env),
673 }
674 }
675 let cached = AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned());
680 let autoloaded_val = cached.or_else(|| {
681 let loaded = AUTOLOAD_HOOK
682 .with(|c| c.get())
683 .is_some_and(|hook| hook(name));
684 if loaded {
685 AUTOLOAD_CACHE.with(|c| c.borrow().get(name).cloned())
686 } else {
687 None
688 }
689 });
690 if let Some(val) = autoloaded_val {
691 let mut evaled = Vec::with_capacity(args.len());
692 for a in args {
693 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
694 }
695 return match io.as_deref_mut() {
696 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
697 Some(hook) => hook(name, &val, &evaled, env, io_ref),
698 None => Err(format!("'{name}': exec::init() not called")),
699 }),
700 None => {
701 let mut tmp_io = IoContext::new();
702 FN_CALL_HOOK.with(|c| match c.get() {
703 Some(hook) => hook(name, &val, &evaled, env, &mut tmp_io),
704 None => Err(format!("'{name}': exec::init() not called")),
705 })
706 }
707 };
708 }
709
710 let mut evaled = Vec::with_capacity(args.len().max(1));
712 for a in args {
713 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
714 }
715 let no_ans_inject = matches!(
718 name.as_str(),
719 "struct"
720 | "fieldnames"
721 | "isfield"
722 | "rmfield"
723 | "isstruct"
724 | "cell"
725 | "iscell"
726 | "isempty"
727 | "cellfun"
728 | "error"
729 | "warning"
730 | "lasterr"
731 | "pcall"
732 | "rand"
733 | "randn"
734 | "rng"
735 );
736 if evaled.is_empty() && !no_ans_inject {
737 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
738 }
739 call_builtin(name, &evaled, env, io)
740 }
741
742 Expr::Lambda {
743 params,
744 body,
745 source,
746 } => {
747 let captured_env = env.clone();
750 let captured_params = params.clone();
751 let captured_body = *body.clone();
752 let src = source.clone();
753 let lambda = LambdaFn(
754 std::rc::Rc::new(move |args: &[Value], io: Option<&mut IoContext>| {
755 let effective = if args.len() > captured_params.len() {
757 if args.len() > captured_params.len() + 1 {
758 return Err(format!(
759 "Lambda: too many arguments (expected at most {}, got {})",
760 captured_params.len(),
761 args.len()
762 ));
763 }
764 &args[..captured_params.len()]
765 } else {
766 args
767 };
768 let mut local_env = captured_env.clone();
769 for (p, a) in captured_params.iter().zip(effective.iter()) {
770 local_env.insert(p.clone(), a.clone());
771 }
772 local_env.insert("nargin".to_string(), Value::Scalar(effective.len() as f64));
773 eval_inner(&captured_body, &local_env, io)
774 }),
775 src,
776 );
777 Ok(Value::Lambda(lambda))
778 }
779 Expr::CellLiteral(elems) => {
780 let mut vals = Vec::with_capacity(elems.len());
781 for e in elems {
782 vals.push(eval_inner(e, env, io.as_deref_mut())?);
783 }
784 Ok(Value::Cell(vals))
785 }
786 Expr::CellIndex(cell_expr, idx_expr) => {
787 let cell = eval_inner(cell_expr, env, io.as_deref_mut())?;
788 let idx = eval_inner(idx_expr, env, io)?;
789 match (cell, idx) {
790 (Value::Cell(v), Value::Scalar(i)) => {
791 let i = i as isize;
792 if i < 1 || i as usize > v.len() {
793 Err(format!("Cell index {} out of range (1..{})", i, v.len()))
794 } else {
795 Ok(v[(i - 1) as usize].clone())
796 }
797 }
798 (Value::Cell(_), _) => Err("Cell index must be a scalar integer".to_string()),
799 _ => Err("Brace indexing '{}' is only valid on cell arrays".to_string()),
800 }
801 }
802 Expr::FieldGet(base_expr, field) => {
803 let base_val = eval_inner(base_expr, env, io)?;
804 match base_val {
805 Value::Struct(map) => map
806 .get(field)
807 .cloned()
808 .ok_or_else(|| format!("No field '{field}' in struct")),
809 Value::StructArray(arr) => {
811 let mut values: Vec<Value> = Vec::with_capacity(arr.len());
812 for (idx, elem) in arr.iter().enumerate() {
813 let v = elem.get(field).cloned().ok_or_else(|| {
814 format!("No field '{field}' in struct array element {}", idx + 1)
815 })?;
816 values.push(v);
817 }
818 let all_scalar = values.iter().all(|v| matches!(v, Value::Scalar(_)));
820 if all_scalar {
821 let nums: Vec<f64> = values
822 .into_iter()
823 .map(|v| {
824 if let Value::Scalar(n) = v {
825 n
826 } else {
827 unreachable!()
828 }
829 })
830 .collect();
831 let n = nums.len();
832 Ok(Value::Matrix(Array2::from_shape_vec((1, n), nums).unwrap()))
833 } else {
834 Ok(Value::Cell(values))
835 }
836 }
837 _ => Err(format!(
838 "Cannot access field '{field}' on a non-struct value"
839 )),
840 }
841 }
842 Expr::DotCall(segs, args) => {
843 let qualified = segs.join(".");
844 if let Some(head_val) = env.get(&segs[0]).cloned() {
846 let mut val = head_val;
847 for field in &segs[1..] {
848 val = match val {
849 Value::Struct(ref map) => map
850 .get(field)
851 .cloned()
852 .ok_or_else(|| format!("No field '{field}' in struct"))?,
853 _ => {
854 return Err(format!(
855 "Cannot access field '{field}' on a non-struct value"
856 ));
857 }
858 };
859 }
860 let mut evaled = Vec::with_capacity(args.len());
861 for a in args {
862 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
863 }
864 return match val {
865 Value::Lambda(f) => {
866 if evaled.is_empty() {
867 evaled.push(env.get("ans").cloned().unwrap_or(Value::Scalar(0.0)));
868 }
869 f.0(&evaled, io)
870 }
871 Value::Function { .. } => match io.as_deref_mut() {
872 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
873 Some(hook) => hook(&qualified, &val, &evaled, env, io_ref),
874 None => Err(format!("'{qualified}': exec::init() not called")),
875 }),
876 None => {
877 let mut tmp_io = IoContext::new();
878 FN_CALL_HOOK.with(|c| match c.get() {
879 Some(hook) => hook(&qualified, &val, &evaled, env, &mut tmp_io),
880 None => Err(format!("'{qualified}': exec::init() not called")),
881 })
882 }
883 },
884 _ => Err(format!("'{qualified}': not a callable")),
885 };
886 }
887 let cached = AUTOLOAD_CACHE.with(|c| c.borrow().get(&qualified).cloned());
889 let autoloaded_val = cached.or_else(|| {
890 let loaded = AUTOLOAD_HOOK
891 .with(|c| c.get())
892 .is_some_and(|hook| hook(&qualified));
893 if loaded {
894 AUTOLOAD_CACHE.with(|c| c.borrow().get(&qualified).cloned())
895 } else {
896 None
897 }
898 });
899 if let Some(val) = autoloaded_val {
900 let mut evaled = Vec::with_capacity(args.len());
901 for a in args {
902 evaled.push(eval_inner(a, env, io.as_deref_mut())?);
903 }
904 return match io.as_deref_mut() {
905 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
906 Some(hook) => hook(&qualified, &val, &evaled, env, io_ref),
907 None => Err(format!("'{qualified}': exec::init() not called")),
908 }),
909 None => {
910 let mut tmp_io = IoContext::new();
911 FN_CALL_HOOK.with(|c| match c.get() {
912 Some(hook) => hook(&qualified, &val, &evaled, env, &mut tmp_io),
913 None => Err(format!("'{qualified}': exec::init() not called")),
914 })
915 }
916 };
917 }
918 Err(format!("Unknown package function: '{qualified}'"))
919 }
920 Expr::FuncHandle(name) => {
921 let name = name.clone();
922 let captured_env = env.clone();
923 let src = format!("@{name}");
924 let lambda = LambdaFn(
925 std::rc::Rc::new(move |args: &[Value], io: Option<&mut IoContext>| {
926 if let Some(f) = captured_env.get(&name) {
928 let f = f.clone();
929 call_function_value(&f, args, io)
930 } else {
931 call_builtin(&name, args, &captured_env, io)
932 }
933 }),
934 src,
935 );
936 Ok(Value::Lambda(lambda))
937 }
938 Expr::PlainTranspose(e) => match eval_inner(e, env, io)? {
939 Value::Void => Err("Transpose is not applicable to void".to_string()),
940 Value::Scalar(n) => Ok(Value::Scalar(n)),
941 Value::Matrix(m) => Ok(Value::Matrix(m.t().to_owned())),
942 Value::Complex(re, im) => Ok(Value::Complex(re, im)),
944 Value::Str(s) => Ok(Value::Str(s)),
945 Value::StringObj(s) => Ok(Value::StringObj(s)),
946 Value::Lambda(_)
947 | Value::Function { .. }
948 | Value::Tuple(_)
949 | Value::Cell(_)
950 | Value::Struct(_)
951 | Value::StructArray(_) => Err("Transpose is not applicable to this type".to_string()),
952 },
953 Expr::Colon => Err("':' is only valid inside index expressions".to_string()),
954 Expr::Matrix(rows) => {
955 if rows.is_empty() {
956 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
957 }
958 let mut row_blocks: Vec<Array2<f64>> = Vec::with_capacity(rows.len());
962 for row in rows {
963 if row.is_empty() {
964 continue;
965 }
966 let mut elem_mats: Vec<Array2<f64>> = Vec::with_capacity(row.len());
967 for elem_expr in row {
968 match eval_inner(elem_expr, env, io.as_deref_mut())? {
969 Value::Scalar(n) => elem_mats.push(Array2::from_elem((1, 1), n)),
970 Value::Matrix(m) => elem_mats.push(m),
971 Value::Void => {
972 return Err("Void value cannot be used in matrix literal".to_string());
973 }
974 Value::Complex(_, _) => {
975 return Err(
976 "Complex elements in matrix literals are not supported yet"
977 .to_string(),
978 );
979 }
980 Value::Str(_) | Value::StringObj(_) => {
981 return Err(
982 "String elements in matrix literals are not supported".to_string()
983 );
984 }
985 Value::Lambda(_)
986 | Value::Function { .. }
987 | Value::Tuple(_)
988 | Value::Cell(_)
989 | Value::Struct(_)
990 | Value::StructArray(_) => {
991 return Err("Struct/function values cannot be used in matrix literals"
992 .to_string());
993 }
994 }
995 }
996 let nrows = elem_mats[0].nrows();
998 for (i, m) in elem_mats.iter().enumerate().skip(1) {
999 if m.nrows() != nrows {
1000 return Err(format!(
1001 "Matrix row height mismatch: expected {} rows, element {} has {} rows",
1002 nrows,
1003 i + 1,
1004 m.nrows()
1005 ));
1006 }
1007 }
1008 let ncols: usize = elem_mats.iter().map(|m| m.ncols()).sum();
1010 let mut flat: Vec<f64> = Vec::with_capacity(nrows * ncols);
1011 for r in 0..nrows {
1012 for m in &elem_mats {
1013 flat.extend(m.row(r).iter().copied());
1014 }
1015 }
1016 row_blocks.push(
1017 Array2::from_shape_vec((nrows, ncols), flat)
1018 .map_err(|e| format!("Matrix shape error: {e}"))?,
1019 );
1020 }
1021 if row_blocks.is_empty() {
1022 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
1023 }
1024 let ncols = row_blocks[0].ncols();
1025 if ncols == 0 {
1026 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1027 return Ok(Value::Matrix(Array2::zeros((total_rows, 0))));
1028 }
1029 for (i, blk) in row_blocks.iter().enumerate().skip(1) {
1031 if blk.ncols() != ncols {
1032 return Err(format!(
1033 "Matrix column count mismatch: expected {} columns, row {} has {} columns",
1034 ncols,
1035 i + 1,
1036 blk.ncols()
1037 ));
1038 }
1039 }
1040 let total_rows: usize = row_blocks.iter().map(|b| b.nrows()).sum();
1042 let mut flat: Vec<f64> = Vec::with_capacity(total_rows * ncols);
1043 for blk in &row_blocks {
1044 flat.extend(blk.iter().copied());
1045 }
1046 let m = Array2::from_shape_vec((total_rows, ncols), flat)
1047 .map_err(|e| format!("Matrix shape error: {e}"))?;
1048 Ok(Value::Matrix(m))
1049 }
1050 Expr::Transpose(e) => match eval_inner(e, env, io)? {
1051 Value::Void => Err("Transpose is not applicable to void".to_string()),
1052 Value::Scalar(n) => Ok(Value::Scalar(n)),
1053 Value::Matrix(m) => Ok(Value::Matrix(m.t().to_owned())),
1054 Value::Complex(re, im) => Ok(Value::Complex(re, -im)),
1055 Value::Str(s) => Ok(Value::Str(s)),
1057 Value::StringObj(s) => Ok(Value::StringObj(s)),
1058 Value::Lambda(_)
1059 | Value::Function { .. }
1060 | Value::Tuple(_)
1061 | Value::Cell(_)
1062 | Value::Struct(_)
1063 | Value::StructArray(_) => Err("Transpose is not applicable to this type".to_string()),
1064 },
1065 Expr::StrLiteral(s) => Ok(Value::Str(s.clone())),
1066 Expr::StringObjLiteral(s) => Ok(Value::StringObj(s.clone())),
1067 Expr::Range(start_expr, step_expr, stop_expr) => {
1068 let start = match eval_inner(start_expr, env, io.as_deref_mut())? {
1069 Value::Scalar(n) => n,
1070 Value::Void
1071 | Value::Matrix(_)
1072 | Value::Complex(_, _)
1073 | Value::Str(_)
1074 | Value::StringObj(_)
1075 | Value::Lambda(_)
1076 | Value::Function { .. }
1077 | Value::Tuple(_)
1078 | Value::Cell(_)
1079 | Value::Struct(_)
1080 | Value::StructArray(_) => {
1081 return Err("Range bounds must be real scalars".to_string());
1082 }
1083 };
1084 let stop = match eval_inner(stop_expr, env, io.as_deref_mut())? {
1085 Value::Scalar(n) => n,
1086 Value::Void
1087 | Value::Matrix(_)
1088 | Value::Complex(_, _)
1089 | Value::Str(_)
1090 | Value::StringObj(_)
1091 | Value::Lambda(_)
1092 | Value::Function { .. }
1093 | Value::Tuple(_)
1094 | Value::Cell(_)
1095 | Value::Struct(_)
1096 | Value::StructArray(_) => {
1097 return Err("Range bounds must be real scalars".to_string());
1098 }
1099 };
1100 let step = match step_expr {
1101 None => 1.0,
1102 Some(s) => match eval_inner(s, env, io)? {
1103 Value::Scalar(n) => n,
1104 Value::Void
1105 | Value::Matrix(_)
1106 | Value::Complex(_, _)
1107 | Value::Str(_)
1108 | Value::StringObj(_)
1109 | Value::Lambda(_)
1110 | Value::Function { .. }
1111 | Value::Tuple(_)
1112 | Value::Cell(_)
1113 | Value::Struct(_)
1114 | Value::StructArray(_) => {
1115 return Err("Range step must be a real scalar".to_string());
1116 }
1117 },
1118 };
1119 if step == 0.0 {
1120 return Err("Range step cannot be zero".to_string());
1121 }
1122 let n_float = (stop - start) / step;
1123 if n_float < -1e-10 {
1124 return Ok(Value::Matrix(Array2::zeros((1, 0))));
1126 }
1127 let n = (n_float + 1e-10).floor() as usize + 1;
1128 let vals: Vec<f64> = (0..n).map(|i| start + i as f64 * step).collect();
1129 let m =
1130 Array2::from_shape_vec((1, n), vals).map_err(|e| format!("Range error: {e}"))?;
1131 Ok(Value::Matrix(m))
1132 }
1133 }
1134}
1135
1136fn eval_binop(l: Value, op: &Op, r: Value) -> Result<Value, String> {
1137 match (l, r) {
1138 (Value::Void, _) | (_, Value::Void) => {
1139 Err("Cannot apply operator to void value".to_string())
1140 }
1141 (Value::StringObj(a), Value::StringObj(b)) => match op {
1143 Op::Add => Ok(Value::StringObj(a + &b)),
1144 Op::Eq => Ok(Value::Scalar(bool_to_f64(a == b))),
1145 Op::NotEq => Ok(Value::Scalar(bool_to_f64(a != b))),
1146 _ => Err("Operator not supported on string objects".to_string()),
1147 },
1148 (Value::Str(s), r) => eval_binop(str_to_numeric(&s), op, r),
1150 (l, Value::Str(s)) => eval_binop(l, op, str_to_numeric(&s)),
1151 (Value::StringObj(_), _) | (_, Value::StringObj(_)) => {
1153 Err("String object cannot be combined with non-string values".to_string())
1154 }
1155 (Value::Lambda(_), _)
1157 | (_, Value::Lambda(_))
1158 | (Value::Function { .. }, _)
1159 | (_, Value::Function { .. })
1160 | (Value::Tuple(_), _)
1161 | (_, Value::Tuple(_))
1162 | (Value::Cell(_), _)
1163 | (_, Value::Cell(_))
1164 | (Value::Struct(_), _)
1165 | (_, Value::Struct(_))
1166 | (Value::StructArray(_), _)
1167 | (_, Value::StructArray(_)) => Err("Cannot apply operator to a struct value".to_string()),
1168 (Value::Complex(re1, im1), Value::Complex(re2, im2)) => {
1170 complex_binop(re1, im1, op, re2, im2)
1171 }
1172 (Value::Complex(re, im), Value::Scalar(s)) => complex_binop(re, im, op, s, 0.0),
1173 (Value::Scalar(s), Value::Complex(re, im)) => complex_binop(s, 0.0, op, re, im),
1174 (Value::Complex(_, _), Value::Matrix(_)) | (Value::Matrix(_), Value::Complex(_, _)) => {
1175 Err("Operations between complex scalars and matrices are not supported".to_string())
1176 }
1177 (Value::Scalar(lv), Value::Scalar(rv)) => {
1178 let result = match op {
1179 Op::Add => lv + rv,
1180 Op::Sub => lv - rv,
1181 Op::Mul | Op::ElemMul => lv * rv,
1182 Op::Div | Op::ElemDiv => {
1183 if rv == 0.0 {
1184 return Err("Division by zero".to_string());
1185 }
1186 lv / rv
1187 }
1188 Op::LDiv => {
1189 if lv == 0.0 {
1190 return Err("Left division by zero (a \\ b requires a ≠ 0)".to_string());
1191 }
1192 rv / lv
1193 }
1194 Op::Pow | Op::ElemPow => lv.powf(rv),
1195 Op::Eq => bool_to_f64(lv == rv),
1196 Op::NotEq => bool_to_f64(lv != rv),
1197 Op::Lt => bool_to_f64(lv < rv),
1198 Op::Gt => bool_to_f64(lv > rv),
1199 Op::LtEq => bool_to_f64(lv <= rv),
1200 Op::GtEq => bool_to_f64(lv >= rv),
1201 Op::And | Op::ElemAnd => bool_to_f64(lv != 0.0 && rv != 0.0),
1202 Op::Or | Op::ElemOr => bool_to_f64(lv != 0.0 || rv != 0.0),
1203 };
1204 Ok(Value::Scalar(result))
1205 }
1206 (Value::Matrix(lm), Value::Matrix(rm)) => match op {
1207 Op::Add => {
1208 check_same_shape(&lm, &rm)?;
1209 Ok(Value::Matrix(&lm + &rm))
1210 }
1211 Op::Sub => {
1212 check_same_shape(&lm, &rm)?;
1213 Ok(Value::Matrix(&lm - &rm))
1214 }
1215 Op::Mul => {
1216 if lm.ncols() != rm.nrows() {
1217 return Err(format!(
1218 "Inner dimensions must agree: {}x{} * {}x{}",
1219 lm.nrows(),
1220 lm.ncols(),
1221 rm.nrows(),
1222 rm.ncols()
1223 ));
1224 }
1225 Ok(Value::Matrix(lm.dot(&rm)))
1226 }
1227 Op::ElemMul => {
1228 check_same_shape(&lm, &rm)?;
1229 Ok(Value::Matrix(&lm * &rm))
1230 }
1231 Op::ElemDiv => {
1232 check_same_shape(&lm, &rm)?;
1233 Ok(Value::Matrix(&lm / &rm))
1234 }
1235 Op::ElemPow => {
1236 check_same_shape(&lm, &rm)?;
1237 Ok(Value::Matrix(
1238 ndarray::Zip::from(&lm)
1239 .and(&rm)
1240 .map_collect(|a, b| a.powf(*b)),
1241 ))
1242 }
1243 Op::Eq | Op::NotEq | Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1244 check_same_shape(&lm, &rm)?;
1245 Ok(Value::Matrix(
1246 ndarray::Zip::from(&lm)
1247 .and(&rm)
1248 .map_collect(|a, b| bool_to_f64(cmp_op(op, *a, *b))),
1249 ))
1250 }
1251 Op::And | Op::Or | Op::ElemAnd | Op::ElemOr => {
1252 check_same_shape(&lm, &rm)?;
1253 Ok(Value::Matrix(
1254 ndarray::Zip::from(&lm)
1255 .and(&rm)
1256 .map_collect(|a, b| bool_to_f64(cmp_op(op, *a, *b))),
1257 ))
1258 }
1259 Op::Div => Err("Matrix / Matrix: use inv(B)*A or A*inv(B)".to_string()),
1260 Op::LDiv => Ok(Value::Matrix(solve_linear(&lm, &rm)?)),
1261 Op::Pow => Err("Matrix ^ Matrix: not supported".to_string()),
1262 },
1263 (Value::Scalar(s), Value::Matrix(m)) => match op {
1264 Op::Add => Ok(Value::Matrix(s + &m)),
1265 Op::Sub => Ok(Value::Matrix(m.mapv(|x| s - x))),
1266 Op::Mul | Op::ElemMul => Ok(Value::Matrix(s * &m)),
1267 Op::Div => Err("Scalar / Matrix: not supported".to_string()),
1268 Op::ElemDiv => Err("Scalar ./ Matrix: not supported".to_string()),
1269 Op::LDiv => {
1270 if s == 0.0 {
1271 return Err("Left division by zero (a \\ B requires a ≠ 0)".to_string());
1272 }
1273 Ok(Value::Matrix(m.mapv(|x| x / s)))
1274 }
1275 Op::Pow | Op::ElemPow => Ok(Value::Matrix(m.mapv(|x| s.powf(x)))),
1276 Op::Eq
1277 | Op::NotEq
1278 | Op::Lt
1279 | Op::Gt
1280 | Op::LtEq
1281 | Op::GtEq
1282 | Op::And
1283 | Op::Or
1284 | Op::ElemAnd
1285 | Op::ElemOr => Ok(Value::Matrix(m.mapv(|x| bool_to_f64(cmp_op(op, s, x))))),
1286 },
1287 (Value::Matrix(m), Value::Scalar(s)) => match op {
1288 Op::Add => Ok(Value::Matrix(&m + s)),
1289 Op::Sub => Ok(Value::Matrix(&m - s)),
1290 Op::Mul | Op::ElemMul => Ok(Value::Matrix(&m * s)),
1291 Op::Div | Op::ElemDiv => Ok(Value::Matrix(m.mapv(|x| x / s))),
1292 Op::LDiv => {
1293 let b = Array2::from_elem((m.nrows(), 1), s);
1294 Ok(Value::Matrix(solve_linear(&m, &b)?))
1295 }
1296 Op::Pow | Op::ElemPow => Ok(Value::Matrix(m.mapv(|x| x.powf(s)))),
1297 Op::Eq
1298 | Op::NotEq
1299 | Op::Lt
1300 | Op::Gt
1301 | Op::LtEq
1302 | Op::GtEq
1303 | Op::And
1304 | Op::Or
1305 | Op::ElemAnd
1306 | Op::ElemOr => Ok(Value::Matrix(m.mapv(|x| bool_to_f64(cmp_op(op, x, s))))),
1307 },
1308 }
1309}
1310
1311#[inline]
1312fn bool_to_f64(b: bool) -> f64 {
1313 if b { 1.0 } else { 0.0 }
1314}
1315
1316fn cmp_op(op: &Op, a: f64, b: f64) -> bool {
1318 match op {
1319 Op::Eq => a == b,
1320 Op::NotEq => a != b,
1321 Op::Lt => a < b,
1322 Op::Gt => a > b,
1323 Op::LtEq => a <= b,
1324 Op::GtEq => a >= b,
1325 Op::And | Op::ElemAnd => a != 0.0 && b != 0.0,
1326 Op::Or | Op::ElemOr => a != 0.0 || b != 0.0,
1327 _ => unreachable!(),
1328 }
1329}
1330
1331fn complex_binop(re1: f64, im1: f64, op: &Op, re2: f64, im2: f64) -> Result<Value, String> {
1333 match op {
1334 Op::Add => Ok(make_complex(re1 + re2, im1 + im2)),
1335 Op::Sub => Ok(make_complex(re1 - re2, im1 - im2)),
1336 Op::Mul | Op::ElemMul => {
1337 Ok(make_complex(re1 * re2 - im1 * im2, re1 * im2 + im1 * re2))
1339 }
1340 Op::Div | Op::ElemDiv => {
1341 let denom = re2 * re2 + im2 * im2;
1343 if denom == 0.0 {
1344 return Err("Division by zero (complex)".to_string());
1345 }
1346 Ok(make_complex(
1347 (re1 * re2 + im1 * im2) / denom,
1348 (im1 * re2 - re1 * im2) / denom,
1349 ))
1350 }
1351 Op::Pow | Op::ElemPow => {
1352 let r1 = (re1 * re1 + im1 * im1).sqrt();
1353 if r1 == 0.0 {
1354 if re2 > 0.0 {
1355 return Ok(Value::Scalar(0.0));
1356 }
1357 return Ok(Value::Complex(f64::NAN, f64::NAN));
1358 }
1359 if im2 == 0.0 && re2.fract() == 0.0 && re2.abs() < 1_000_000.0 {
1362 let n = re2 as i64;
1363 if n == 0 {
1364 return Ok(Value::Scalar(1.0));
1365 }
1366 let abs_n = n.unsigned_abs();
1368 let (mut rr, mut ri) = (1.0_f64, 0.0_f64);
1369 let (mut br, mut bi) = (re1, im1);
1370 let mut exp = abs_n;
1371 while exp > 0 {
1372 if exp & 1 == 1 {
1373 let nr = rr * br - ri * bi;
1374 let ni = rr * bi + ri * br;
1375 rr = nr;
1376 ri = ni;
1377 }
1378 let nr = br * br - bi * bi;
1379 let ni = 2.0 * br * bi;
1380 br = nr;
1381 bi = ni;
1382 exp >>= 1;
1383 }
1384 if n < 0 {
1385 let denom = rr * rr + ri * ri;
1387 return Ok(make_complex(rr / denom, -ri / denom));
1388 }
1389 return Ok(make_complex(rr, ri));
1390 }
1391 let theta1 = im1.atan2(re1);
1393 let ln_r1 = r1.ln();
1394 let exp_re = re2 * ln_r1 - im2 * theta1;
1395 let exp_im = im2 * ln_r1 + re2 * theta1;
1396 let mag = exp_re.exp();
1397 Ok(make_complex(mag * exp_im.cos(), mag * exp_im.sin()))
1398 }
1399 Op::Eq => Ok(Value::Scalar(bool_to_f64(re1 == re2 && im1 == im2))),
1400 Op::NotEq => Ok(Value::Scalar(bool_to_f64(re1 != re2 || im1 != im2))),
1401 Op::Lt | Op::Gt | Op::LtEq | Op::GtEq => {
1402 Err("Ordering is not defined for complex numbers".to_string())
1403 }
1404 Op::And | Op::ElemAnd => Ok(Value::Scalar(bool_to_f64(
1405 (re1 != 0.0 || im1 != 0.0) && (re2 != 0.0 || im2 != 0.0),
1406 ))),
1407 Op::Or | Op::ElemOr => Ok(Value::Scalar(bool_to_f64(
1408 re1 != 0.0 || im1 != 0.0 || re2 != 0.0 || im2 != 0.0,
1409 ))),
1410 Op::LDiv => Err("Left division (\\) is not supported for complex numbers".to_string()),
1411 }
1412}
1413
1414#[inline]
1416fn make_complex(re: f64, im: f64) -> Value {
1417 if im == 0.0 {
1418 Value::Scalar(re)
1419 } else {
1420 Value::Complex(re, im)
1421 }
1422}
1423
1424fn str_to_numeric(s: &str) -> Value {
1427 let codes: Vec<f64> = s.chars().map(|c| c as u32 as f64).collect();
1428 match codes.len() {
1429 0 => Value::Matrix(Array2::zeros((1, 0))),
1430 1 => Value::Scalar(codes[0]),
1431 n => Value::Matrix(Array2::from_shape_vec((1, n), codes).unwrap()),
1432 }
1433}
1434
1435fn string_arg<'a>(v: &'a Value, fname: &str, pos: usize) -> Result<&'a str, String> {
1437 match v {
1438 Value::Str(s) | Value::StringObj(s) => Ok(s.as_str()),
1439 _ => Err(format!(
1440 "Function '{fname}' argument {pos} must be a string"
1441 )),
1442 }
1443}
1444
1445fn check_same_shape(lm: &Array2<f64>, rm: &Array2<f64>) -> Result<(), String> {
1446 if lm.shape() != rm.shape() {
1447 return Err(format!(
1448 "Matrix size mismatch: {}x{} vs {}x{}",
1449 lm.nrows(),
1450 lm.ncols(),
1451 rm.nrows(),
1452 rm.ncols()
1453 ));
1454 }
1455 Ok(())
1456}
1457
1458fn scalar_arg(v: &Value, fname: &str, pos: usize) -> Result<f64, String> {
1459 match v {
1460 Value::Void => Err(format!(
1461 "Function '{fname}' argument {pos} must be a scalar, got void"
1462 )),
1463 Value::Scalar(n) => Ok(*n),
1464 Value::Complex(re, im) if *im == 0.0 => Ok(*re),
1465 Value::Complex(_, _) => Err(format!(
1466 "Function '{fname}' argument {pos} must be real, got a complex number"
1467 )),
1468 Value::Matrix(_) => Err(format!(
1469 "Function '{fname}' argument {pos} must be a scalar, got a matrix"
1470 )),
1471 Value::Str(s) if s.chars().count() == 1 => Ok(s.chars().next().unwrap() as u32 as f64),
1472 Value::Str(_) | Value::StringObj(_) => Err(format!(
1473 "Function '{fname}' argument {pos} must be a scalar, got a string"
1474 )),
1475 Value::Lambda(_)
1476 | Value::Function { .. }
1477 | Value::Tuple(_)
1478 | Value::Cell(_)
1479 | Value::Struct(_)
1480 | Value::StructArray(_) => Err(format!(
1481 "Function '{fname}' argument {pos} must be a scalar, got a non-numeric value"
1482 )),
1483 }
1484}
1485
1486fn randi_range(v: &Value) -> Result<(i64, i64), String> {
1491 match v {
1492 Value::Scalar(n) => {
1493 let hi = *n as i64;
1494 if hi < 1 {
1495 return Err("randi: max must be a positive integer".to_string());
1496 }
1497 Ok((1, hi))
1498 }
1499 Value::Matrix(m) if m.len() == 2 => {
1500 let vals: Vec<f64> = m.iter().copied().collect();
1501 let lo = vals[0] as i64;
1502 let hi = vals[1] as i64;
1503 if lo > hi {
1504 return Err("randi: [min, max] range is empty".to_string());
1505 }
1506 Ok((lo, hi))
1507 }
1508 _ => Err("randi: first argument must be a scalar max or a [min, max] vector".to_string()),
1509 }
1510}
1511
1512fn numeric_vec(v: &Value, fname: &str) -> Result<Vec<f64>, String> {
1516 match v {
1517 Value::Scalar(n) => Ok(vec![*n]),
1518 Value::Matrix(m) => Ok(m.iter().copied().collect()),
1519 _ => Err(format!("{fname}: argument must be numeric")),
1520 }
1521}
1522
1523fn stat_var_vec(vals: &[f64], population: bool) -> f64 {
1526 let n = vals.len();
1527 if n == 0 {
1528 return f64::NAN;
1529 }
1530 if n == 1 {
1531 return 0.0;
1532 }
1533 let mean = vals.iter().sum::<f64>() / n as f64;
1534 let ss: f64 = vals.iter().map(|&x| (x - mean).powi(2)).sum();
1535 let denom = if population { n as f64 } else { (n - 1) as f64 };
1536 ss / denom
1537}
1538
1539fn apply_stat<F>(v: &Value, mut f: F, fname: &str) -> Result<Value, String>
1545where
1546 F: FnMut(&[f64]) -> f64,
1547{
1548 match v {
1549 Value::Scalar(n) => Ok(Value::Scalar(f(&[*n]))),
1550 Value::Matrix(m) => {
1551 if m.nrows() == 1 || m.ncols() == 1 {
1552 let vals: Vec<f64> = m.iter().copied().collect();
1553 Ok(Value::Scalar(f(&vals)))
1554 } else {
1555 let ncols = m.ncols();
1556 let result: Vec<f64> = (0..ncols)
1557 .map(|c| {
1558 let col: Vec<f64> = m.column(c).iter().copied().collect();
1559 f(&col)
1560 })
1561 .collect();
1562 Ok(Value::Matrix(
1563 Array2::from_shape_vec((1, ncols), result).unwrap(),
1564 ))
1565 }
1566 }
1567 _ => Err(format!("{fname}: argument must be numeric")),
1568 }
1569}
1570
1571fn percentile_sorted(sorted: &[f64], p: f64) -> f64 {
1573 let n = sorted.len();
1574 if n == 0 {
1575 return f64::NAN;
1576 }
1577 if n == 1 {
1578 return sorted[0];
1579 }
1580 let p = p.clamp(0.0, 100.0);
1581 let idx = p / 100.0 * (n - 1) as f64;
1582 let lo = idx.floor() as usize;
1583 let hi = idx.ceil() as usize;
1584 let frac = idx - lo as f64;
1585 sorted[lo] * (1.0 - frac) + sorted[hi] * frac
1586}
1587
1588fn apply_elem<F: Fn(f64) -> f64>(v: &Value, f: F) -> Result<Value, String> {
1589 match v {
1590 Value::Void => Err("Element-wise function not applicable to void".to_string()),
1591 Value::Scalar(n) => Ok(Value::Scalar(f(*n))),
1592 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(f))),
1593 Value::Complex(re, im) if *im == 0.0 => Ok(Value::Scalar(f(*re))),
1594 Value::Complex(_, _) => {
1595 Err("Element-wise real function not applicable to complex values".to_string())
1596 }
1597 Value::Str(_) | Value::StringObj(_) => {
1598 Err("Element-wise function not applicable to strings".to_string())
1599 }
1600 Value::Lambda(_)
1601 | Value::Function { .. }
1602 | Value::Tuple(_)
1603 | Value::Cell(_)
1604 | Value::Struct(_)
1605 | Value::StructArray(_) => {
1606 Err("Element-wise function not applicable to this type".to_string())
1607 }
1608 }
1609}
1610
1611fn apply_reduction<F>(v: &Value, f: F) -> Result<Value, String>
1617where
1618 F: Fn(&[f64]) -> f64,
1619{
1620 match v {
1621 Value::Void => Err("Reduction not applicable to void".to_string()),
1622 Value::Scalar(n) => Ok(Value::Scalar(f(&[*n]))),
1623 Value::Complex(_, _) => Err("Reduction not applicable to complex values".to_string()),
1624 Value::Str(_) | Value::StringObj(_) => {
1625 Err("Reduction not applicable to strings".to_string())
1626 }
1627 Value::Lambda(_)
1628 | Value::Function { .. }
1629 | Value::Tuple(_)
1630 | Value::Cell(_)
1631 | Value::Struct(_)
1632 | Value::StructArray(_) => Err("Reduction not applicable to this type".to_string()),
1633 Value::Matrix(m) => {
1634 if m.nrows() == 1 || m.ncols() == 1 {
1635 let vals: Vec<f64> = m.iter().copied().collect();
1636 Ok(Value::Scalar(f(&vals)))
1637 } else {
1638 let ncols = m.ncols();
1639 let result: Vec<f64> = (0..ncols)
1640 .map(|c| {
1641 let col: Vec<f64> = m.column(c).iter().copied().collect();
1642 f(&col)
1643 })
1644 .collect();
1645 Ok(Value::Matrix(
1646 Array2::from_shape_vec((1, ncols), result).unwrap(),
1647 ))
1648 }
1649 }
1650 }
1651}
1652
1653fn apply_cumulative<F>(v: &Value, combine: F) -> Result<Value, String>
1657where
1658 F: Fn(f64, f64) -> f64,
1659{
1660 match v {
1661 Value::Void => Err("Cumulative reduction not applicable to void".to_string()),
1662 Value::Scalar(n) => Ok(Value::Scalar(*n)),
1663 Value::Complex(_, _) => {
1664 Err("Cumulative reduction not applicable to complex values".to_string())
1665 }
1666 Value::Str(_) | Value::StringObj(_) => {
1667 Err("Cumulative reduction not applicable to strings".to_string())
1668 }
1669 Value::Lambda(_)
1670 | Value::Function { .. }
1671 | Value::Tuple(_)
1672 | Value::Cell(_)
1673 | Value::Struct(_)
1674 | Value::StructArray(_) => {
1675 Err("Cumulative reduction not applicable to this type".to_string())
1676 }
1677 Value::Matrix(m) => {
1678 let initial = combine(0.0, 0.0); let identity = if (combine(1.0, 1.0) - 1.0).abs() < 1e-15 && initial == 0.0 {
1682 1.0 } else {
1684 0.0 };
1686 let (nrows, ncols) = (m.nrows(), m.ncols());
1687 let mut result = m.clone();
1688 if nrows == 1 || ncols == 1 {
1689 let mut acc = identity;
1691 for v in result.iter_mut() {
1692 acc = combine(acc, *v);
1693 *v = acc;
1694 }
1695 } else {
1696 for c in 0..ncols {
1698 let mut acc = identity;
1699 for r in 0..nrows {
1700 acc = combine(acc, result[[r, c]]);
1701 result[[r, c]] = acc;
1702 }
1703 }
1704 }
1705 Ok(Value::Matrix(result))
1706 }
1707 }
1708}
1709
1710fn find_nonzero(v: &Value, max_k: usize) -> Result<Value, String> {
1712 match v {
1713 Value::Void => Err("find: not applicable to void".to_string()),
1714 Value::Str(_) | Value::StringObj(_) => Err("find: not applicable to strings".to_string()),
1715 Value::Lambda(_)
1716 | Value::Function { .. }
1717 | Value::Tuple(_)
1718 | Value::Cell(_)
1719 | Value::Struct(_)
1720 | Value::StructArray(_) => Err("find: not applicable to this type".to_string()),
1721 Value::Complex(re, im) => {
1722 if (*re != 0.0 || *im != 0.0) && max_k >= 1 {
1723 Ok(Value::Matrix(
1724 Array2::from_shape_vec((1, 1), vec![1.0]).unwrap(),
1725 ))
1726 } else {
1727 Ok(Value::Matrix(Array2::zeros((1, 0))))
1728 }
1729 }
1730 Value::Scalar(n) => {
1731 if *n != 0.0 && max_k >= 1 {
1732 Ok(Value::Matrix(
1733 Array2::from_shape_vec((1, 1), vec![1.0]).unwrap(),
1734 ))
1735 } else {
1736 Ok(Value::Matrix(Array2::zeros((1, 0))))
1737 }
1738 }
1739 Value::Matrix(m) => {
1740 let nrows = m.nrows();
1741 let total = m.len();
1742 let mut idxs: Vec<f64> = Vec::new();
1743 for i in 0..total {
1744 if idxs.len() >= max_k {
1745 break;
1746 }
1747 let row = i % nrows;
1748 let col = i / nrows;
1749 if m[[row, col]] != 0.0 {
1750 idxs.push((i + 1) as f64);
1751 }
1752 }
1753 let n = idxs.len();
1754 if n == 0 {
1755 Ok(Value::Matrix(Array2::zeros((1, 0))))
1756 } else {
1757 Ok(Value::Matrix(Array2::from_shape_vec((1, n), idxs).unwrap()))
1758 }
1759 }
1760 }
1761}
1762
1763pub fn format_printf(fmt: &str, args: &[Value]) -> Result<String, String> {
1777 let mut result = String::new();
1778 let mut arg_idx = 0;
1779
1780 loop {
1781 let consumed_before = arg_idx;
1782 let mut chars = fmt.chars().peekable();
1783
1784 while let Some(c) = chars.next() {
1785 if c == '\\' {
1786 match chars.next() {
1787 Some('n') => result.push('\n'),
1788 Some('t') => result.push('\t'),
1789 Some('\\') => result.push('\\'),
1790 Some('\'') => result.push('\''),
1791 Some('"') => result.push('"'),
1792 Some(other) => {
1793 result.push('\\');
1794 result.push(other);
1795 }
1796 None => result.push('\\'),
1797 }
1798 continue;
1799 }
1800
1801 if c != '%' {
1802 result.push(c);
1803 continue;
1804 }
1805
1806 if chars.peek() == Some(&'%') {
1808 chars.next();
1809 result.push('%');
1810 continue;
1811 }
1812
1813 let mut flag_minus = false;
1815 let mut flag_plus = false;
1816 let mut flag_zero = false;
1817 let mut flag_space = false;
1818 loop {
1819 match chars.peek() {
1820 Some('-') => {
1821 flag_minus = true;
1822 chars.next();
1823 }
1824 Some('+') => {
1825 flag_plus = true;
1826 chars.next();
1827 }
1828 Some('0') => {
1829 flag_zero = true;
1830 chars.next();
1831 }
1832 Some(' ') => {
1833 flag_space = true;
1834 chars.next();
1835 }
1836 _ => break,
1837 }
1838 }
1839
1840 let mut width_str = String::new();
1842 while let Some(&d) = chars.peek() {
1843 if d.is_ascii_digit() {
1844 width_str.push(d);
1845 chars.next();
1846 } else {
1847 break;
1848 }
1849 }
1850 let width: usize = width_str.parse().unwrap_or(0);
1851
1852 let mut precision: Option<usize> = None;
1854 if chars.peek() == Some(&'.') {
1855 chars.next();
1856 let mut p = String::new();
1857 while let Some(&d) = chars.peek() {
1858 if d.is_ascii_digit() {
1859 p.push(d);
1860 chars.next();
1861 } else {
1862 break;
1863 }
1864 }
1865 precision = Some(p.parse().unwrap_or(0));
1866 }
1867
1868 let spec = match chars.next() {
1870 Some(s) => s,
1871 None => {
1872 return Err("fprintf: incomplete format specifier at end of string".to_string());
1873 }
1874 };
1875
1876 if arg_idx >= args.len() {
1878 continue;
1879 }
1880
1881 let arg = &args[arg_idx];
1882 arg_idx += 1;
1883
1884 let formatted = match spec {
1885 'd' | 'i' => {
1886 let n = printf_scalar(arg, spec)?;
1887 let i = n.trunc() as i64;
1888 let s = printf_sign_str(i >= 0, flag_plus, flag_space, format!("{}", i.abs()));
1889 printf_pad(s, width, flag_minus, flag_zero)
1890 }
1891 'f' => {
1892 let n = printf_scalar(arg, spec)?;
1893 let prec = precision.unwrap_or(6);
1894 let s = printf_sign_str(
1895 n >= 0.0,
1896 flag_plus,
1897 flag_space,
1898 format!("{:.prec$}", n.abs(), prec = prec),
1899 );
1900 printf_pad(s, width, flag_minus, flag_zero)
1901 }
1902 'e' | 'E' => {
1903 let n = printf_scalar(arg, spec)?;
1904 let prec = precision.unwrap_or(6);
1905 let s = printf_format_sci(n, prec, flag_plus, flag_space, spec == 'E');
1906 printf_pad(s, width, flag_minus, flag_zero)
1907 }
1908 'g' | 'G' => {
1909 let n = printf_scalar(arg, spec)?;
1910 let prec = precision.unwrap_or(6).max(1);
1911 let s = printf_format_g(n, prec, flag_plus, flag_space, spec == 'G');
1912 printf_pad(s, width, flag_minus, flag_zero)
1913 }
1914 's' => {
1915 let s = printf_string(arg)?;
1916 let s = if let Some(max_len) = precision {
1917 s.chars().take(max_len).collect::<String>()
1918 } else {
1919 s
1920 };
1921 printf_pad(s, width, flag_minus, false)
1922 }
1923 other => return Err(format!("fprintf: unknown format specifier '%{other}'")),
1924 };
1925
1926 result.push_str(&formatted);
1927 }
1928
1929 if arg_idx >= args.len() || arg_idx == consumed_before {
1931 break;
1932 }
1933 }
1934
1935 Ok(result)
1936}
1937
1938fn printf_scalar(v: &Value, spec: char) -> Result<f64, String> {
1940 match v {
1941 Value::Scalar(n) => Ok(*n),
1942 Value::Complex(re, im) if *im == 0.0 => Ok(*re),
1943 Value::Str(s) if s.chars().count() == 1 => Ok(s.chars().next().unwrap() as u32 as f64),
1944 _ => Err(format!(
1945 "fprintf: expected numeric argument for '%{spec}', got {:?}",
1946 std::mem::discriminant(v)
1947 )),
1948 }
1949}
1950
1951fn printf_string(v: &Value) -> Result<String, String> {
1953 match v {
1954 Value::Str(s) | Value::StringObj(s) => Ok(s.clone()),
1955 Value::Scalar(n) => Ok(format_number(*n)),
1956 Value::Complex(re, im) => Ok(format_complex(*re, *im, &FormatMode::Custom(6))),
1957 Value::Void => Err("fprintf: cannot format void as string".to_string()),
1958 Value::Matrix(_) => Err("fprintf: cannot format matrix as string".to_string()),
1959 Value::Lambda(_)
1960 | Value::Function { .. }
1961 | Value::Tuple(_)
1962 | Value::Cell(_)
1963 | Value::Struct(_)
1964 | Value::StructArray(_) => Err("fprintf: cannot format this type as string".to_string()),
1965 }
1966}
1967
1968fn printf_sign_str(positive: bool, flag_plus: bool, flag_space: bool, digits: String) -> String {
1970 if positive {
1971 if flag_plus {
1972 format!("+{digits}")
1973 } else if flag_space {
1974 format!(" {digits}")
1975 } else {
1976 digits
1977 }
1978 } else {
1979 format!("-{digits}")
1980 }
1981}
1982
1983fn printf_pad(s: String, width: usize, left_align: bool, zero_pad: bool) -> String {
1985 if s.len() >= width {
1986 return s;
1987 }
1988 let pad_len = width - s.len();
1989 if left_align {
1990 format!("{s}{}", " ".repeat(pad_len))
1991 } else if zero_pad {
1992 let (prefix, rest) = if s.starts_with(['+', '-', ' ']) {
1994 s.split_at(1)
1995 } else {
1996 ("", s.as_str())
1997 };
1998 format!("{prefix}{}{rest}", "0".repeat(pad_len))
1999 } else {
2000 format!("{}{s}", " ".repeat(pad_len))
2001 }
2002}
2003
2004fn printf_format_sci(
2007 n: f64,
2008 prec: usize,
2009 flag_plus: bool,
2010 flag_space: bool,
2011 upper: bool,
2012) -> String {
2013 if n == 0.0 {
2014 let zeros = "0".repeat(prec);
2015 let sep = if prec > 0 {
2016 format!(".{zeros}")
2017 } else {
2018 String::new()
2019 };
2020 let e_char = if upper { 'E' } else { 'e' };
2021 let sign = if flag_plus {
2022 "+"
2023 } else if flag_space {
2024 " "
2025 } else {
2026 ""
2027 };
2028 return format!("{sign}0{sep}{e_char}+00");
2029 }
2030
2031 let neg = n < 0.0;
2032 let abs_n = n.abs();
2033 let exp = abs_n.log10().floor() as i32;
2034 let mantissa = abs_n / 10f64.powi(exp);
2035 let man_str = format!("{:.prec$}", mantissa, prec = prec);
2036
2037 let e_char = if upper { 'E' } else { 'e' };
2038 let exp_sign = if exp >= 0 { '+' } else { '-' };
2039 let exp_abs = exp.unsigned_abs();
2040 let exp_str = if exp_abs < 10 {
2041 format!("{e_char}{exp_sign}0{exp_abs}")
2042 } else {
2043 format!("{e_char}{exp_sign}{exp_abs}")
2044 };
2045
2046 let sign_str = if neg {
2047 "-"
2048 } else if flag_plus {
2049 "+"
2050 } else if flag_space {
2051 " "
2052 } else {
2053 ""
2054 };
2055 format!("{sign_str}{man_str}{exp_str}")
2056}
2057
2058fn printf_format_g(n: f64, prec: usize, flag_plus: bool, flag_space: bool, upper: bool) -> String {
2061 if n == 0.0 {
2062 let sign = if flag_plus {
2063 "+"
2064 } else if flag_space {
2065 " "
2066 } else {
2067 ""
2068 };
2069 return format!("{sign}0");
2070 }
2071 let abs_n = n.abs();
2072 let exp = abs_n.log10().floor() as i32;
2073 if exp < -4 || exp >= prec as i32 {
2074 let s = printf_format_sci(n, prec.saturating_sub(1), flag_plus, flag_space, upper);
2075 trim_g_sci(s, upper)
2076 } else {
2077 let decimal_places = (prec as i32 - 1 - exp).max(0) as usize;
2078 let neg = n < 0.0;
2079 let s = format!("{:.prec$}", abs_n, prec = decimal_places);
2080 let s = if s.contains('.') {
2081 s.trim_end_matches('0').trim_end_matches('.').to_string()
2082 } else {
2083 s
2084 };
2085 let sign = if neg {
2086 "-"
2087 } else if flag_plus {
2088 "+"
2089 } else if flag_space {
2090 " "
2091 } else {
2092 ""
2093 };
2094 format!("{sign}{s}")
2095 }
2096}
2097
2098fn trim_g_sci(s: String, upper: bool) -> String {
2100 let e_char = if upper { 'E' } else { 'e' };
2101 if let Some(e_pos) = s.find(e_char) {
2102 let mantissa = &s[..e_pos];
2103 let exp_part = &s[e_pos..];
2104 let trimmed = if mantissa.contains('.') {
2105 mantissa.trim_end_matches('0').trim_end_matches('.')
2106 } else {
2107 mantissa
2108 };
2109 format!("{trimmed}{exp_part}")
2110 } else {
2111 s
2112 }
2113}
2114
2115fn call_function_value(
2120 f: &Value,
2121 args: &[Value],
2122 io: Option<&mut IoContext>,
2123) -> Result<Value, String> {
2124 match f {
2125 Value::Lambda(lf) => {
2126 let lf = lf.clone();
2127 lf.0(args, io)
2128 }
2129 Value::Function { .. } => {
2130 let empty_env = Env::new();
2134 match io {
2135 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
2136 Some(hook) => hook("<anonymous>", f, args, &empty_env, io_ref),
2137 None => Err("User function execution not initialized".to_string()),
2138 }),
2139 None => {
2140 let mut tmp_io = IoContext::new();
2141 FN_CALL_HOOK.with(|c| match c.get() {
2142 Some(hook) => hook("<anonymous>", f, args, &empty_env, &mut tmp_io),
2143 None => Err("User function execution not initialized".to_string()),
2144 })
2145 }
2146 }
2147 }
2148 _ => Err("cellfun/arrayfun: first argument must be a function or lambda (@fn)".to_string()),
2149 }
2150}
2151
2152pub fn builtin_names() -> &'static [&'static str] {
2156 &[
2157 "abs",
2158 "acos",
2159 "all",
2160 "angle",
2161 "any",
2162 "arrayfun",
2163 "asin",
2164 "assert",
2165 "atan",
2166 "atan2",
2167 "bitand",
2168 "bitnot",
2169 "bitor",
2170 "bitshift",
2171 "bitxor",
2172 "ceil",
2173 "cell",
2174 "cellfun",
2175 "chol",
2176 "complex",
2177 "cond",
2178 "conj",
2179 "cos",
2180 "cov",
2181 "cumprod",
2182 "cumsum",
2183 "det",
2184 "diag",
2185 "disp",
2186 "dlmread",
2187 "dlmwrite",
2188 "eig",
2189 "erf",
2190 "erfc",
2191 "exist",
2192 "exp",
2193 "eye",
2194 "fclose",
2195 "fgetl",
2196 "fgets",
2197 "fieldnames",
2198 "find",
2199 "fliplr",
2200 "flipud",
2201 "floor",
2202 "fopen",
2203 "fprintf",
2204 "genpath",
2205 "histc",
2206 "hypot",
2207 "imag",
2208 "int2str",
2209 "inv",
2210 "iqr",
2211 "iscell",
2212 "ischar",
2213 "isempty",
2214 "isfield",
2215 "isfile",
2216 "isfinite",
2217 "isfolder",
2218 "isinf",
2219 "isnan",
2220 "isreal",
2221 "isstring",
2222 "isstruct",
2223 "jsonencode",
2224 "jsondecode",
2225 "kurtosis",
2226 "lasterr",
2227 "length",
2228 "linspace",
2229 "load",
2230 "log",
2231 "log10",
2232 "log2",
2233 "lower",
2234 "lu",
2235 "mat2str",
2236 "max",
2237 "mean",
2238 "median",
2239 "min",
2240 "mod",
2241 "mode",
2242 "nan",
2243 "norm",
2244 "normcdf",
2245 "normpdf",
2246 "not",
2247 "null",
2248 "num2str",
2249 "numel",
2250 "ones",
2251 "orth",
2252 "pinv",
2253 "prctile",
2254 "prod",
2255 "qr",
2256 "rand",
2257 "randi",
2258 "randn",
2259 "rank",
2260 "readmatrix",
2261 "readtable",
2262 "real",
2263 "rem",
2264 "reshape",
2265 "rmfield",
2266 "rng",
2267 "round",
2268 "sign",
2269 "sin",
2270 "size",
2271 "skewness",
2272 "sort",
2273 "sprintf",
2274 "sqrt",
2275 "std",
2276 "str2double",
2277 "str2num",
2278 "strcmp",
2279 "strcmpi",
2280 "strrep",
2281 "strsplit",
2282 "strtrim",
2283 "sum",
2284 "svd",
2285 "tan",
2286 "trace",
2287 "unique",
2288 "upper",
2289 "var",
2290 "writetable",
2291 "xor",
2292 "zeros",
2293 "zscore",
2294 ]
2295}
2296
2297fn levenshtein(a: &str, b: &str) -> usize {
2299 let a: Vec<char> = a.chars().collect();
2300 let b: Vec<char> = b.chars().collect();
2301 let (m, n) = (a.len(), b.len());
2302 let mut row: Vec<usize> = (0..=n).collect();
2303 for i in 1..=m {
2304 let mut prev = row[0];
2305 row[0] = i;
2306 for j in 1..=n {
2307 let next = if a[i - 1] == b[j - 1] {
2308 prev
2309 } else {
2310 1 + prev.min(row[j]).min(row[j - 1])
2311 };
2312 prev = row[j];
2313 row[j] = next;
2314 }
2315 }
2316 row[n]
2317}
2318
2319fn suggest_similar(name: &str, env: &Env) -> Option<String> {
2321 const MAX_DIST: usize = 2;
2322 let mut best: Option<(String, usize)> = None;
2323 let mut update = |candidate: &str| {
2324 let d = levenshtein(name, candidate);
2325 if d <= MAX_DIST && best.as_ref().is_none_or(|(_, bd)| d < *bd) {
2326 best = Some((candidate.to_string(), d));
2327 }
2328 };
2329 for key in env.keys() {
2330 update(key);
2331 }
2332 for &bname in builtin_names() {
2333 update(bname);
2334 }
2335 best.map(|(s, _)| s)
2336}
2337
2338fn assert_values_equal(a: &Value, b: &Value, tol: Option<f64>) -> Result<Value, String> {
2340 match (a, b) {
2341 (Value::Scalar(x), Value::Scalar(y)) => {
2342 let ok = match tol {
2343 None => x == y,
2344 Some(t) => (x - y).abs() <= t,
2345 };
2346 if ok {
2347 Ok(Value::Void)
2348 } else if let Some(t) = tol {
2349 Err(format!(
2350 "assert: |{x} - {y}| = {} exceeds tolerance {t}",
2351 (x - y).abs()
2352 ))
2353 } else {
2354 Err(format!("assert: {x} ~= {y}"))
2355 }
2356 }
2357 (Value::Matrix(ma), Value::Matrix(mb)) => {
2358 if ma.shape() != mb.shape() {
2359 return Err(format!(
2360 "assert: size mismatch [{}×{}] vs [{}×{}]",
2361 ma.nrows(),
2362 ma.ncols(),
2363 mb.nrows(),
2364 mb.ncols()
2365 ));
2366 }
2367 for (x, y) in ma.iter().zip(mb.iter()) {
2368 let ok = match tol {
2369 None => x == y,
2370 Some(t) => (x - y).abs() <= t,
2371 };
2372 if !ok {
2373 if let Some(t) = tol {
2374 return Err(format!(
2375 "assert: difference {} exceeds tolerance {t}",
2376 (x - y).abs()
2377 ));
2378 } else {
2379 return Err(format!("assert: {x} ~= {y}"));
2380 }
2381 }
2382 }
2383 Ok(Value::Void)
2384 }
2385 _ => {
2386 if tol.is_some() {
2387 return Err("assert: tolerance requires numeric arguments".to_string());
2388 }
2389 if a == b {
2390 Ok(Value::Void)
2391 } else {
2392 Err("assert: values not equal".to_string())
2393 }
2394 }
2395 }
2396}
2397
2398fn call_builtin(
2399 name: &str,
2400 args: &[Value],
2401 env: &Env,
2402 mut io: Option<&mut IoContext>,
2403) -> Result<Value, String> {
2404 match (name, args.len()) {
2405 ("sqrt", 1) => apply_elem(&args[0], |x| x.sqrt()),
2407 ("floor", 1) => apply_elem(&args[0], |x| x.floor()),
2408 ("ceil", 1) => apply_elem(&args[0], |x| x.ceil()),
2409 ("round", 1) => apply_elem(&args[0], |x| x.round()),
2410 ("sign", 1) => apply_elem(&args[0], |x| x.signum()),
2411 ("log", 1) => apply_elem(&args[0], |x| x.ln()),
2412 ("log2", 1) => apply_elem(&args[0], |x| x.log2()),
2413 ("log10", 1) => apply_elem(&args[0], |x| x.log10()),
2414 ("exp", 1) => apply_elem(&args[0], |x| x.exp()),
2415 ("sin", 1) => apply_elem(&args[0], |x| x.sin()),
2416 ("cos", 1) => apply_elem(&args[0], |x| x.cos()),
2417 ("tan", 1) => apply_elem(&args[0], |x| x.tan()),
2418 ("asin", 1) => apply_elem(&args[0], |x| x.asin()),
2419 ("acos", 1) => apply_elem(&args[0], |x| x.acos()),
2420 ("atan", 1) => apply_elem(&args[0], |x| x.atan()),
2421 ("erf", 1) => apply_elem(&args[0], libm::erf),
2423 ("erfc", 1) => apply_elem(&args[0], libm::erfc),
2424 ("normcdf", 1) => apply_elem(&args[0], |x| {
2425 0.5 * (1.0 + libm::erf(x / std::f64::consts::SQRT_2))
2426 }),
2427 ("normcdf", 3) => {
2428 let mu = scalar_arg(&args[1], name, 2)?;
2429 let s = scalar_arg(&args[2], name, 3)?;
2430 if s <= 0.0 {
2431 return Err("normcdf: sigma must be positive".to_string());
2432 }
2433 apply_elem(&args[0], move |x| {
2434 0.5 * (1.0 + libm::erf((x - mu) / (s * std::f64::consts::SQRT_2)))
2435 })
2436 }
2437 ("normpdf", 1) => apply_elem(&args[0], |x| {
2438 (-0.5 * x * x).exp() / (2.0 * std::f64::consts::PI).sqrt()
2439 }),
2440 ("normpdf", 3) => {
2441 let mu = scalar_arg(&args[1], name, 2)?;
2442 let s = scalar_arg(&args[2], name, 3)?;
2443 if s <= 0.0 {
2444 return Err("normpdf: sigma must be positive".to_string());
2445 }
2446 apply_elem(&args[0], move |x| {
2447 let z = (x - mu) / s;
2448 (-0.5 * z * z).exp() / (s * (2.0 * std::f64::consts::PI).sqrt())
2449 })
2450 }
2451 ("atan2", 2) => Ok(Value::Scalar(
2453 scalar_arg(&args[0], name, 1)?.atan2(scalar_arg(&args[1], name, 2)?),
2454 )),
2455 ("mod", 2) => {
2456 let a = scalar_arg(&args[0], name, 1)?;
2457 let b = scalar_arg(&args[1], name, 2)?;
2458 Ok(Value::Scalar(a - b * (a / b).floor()))
2459 }
2460 ("rem", 2) => {
2461 let a = scalar_arg(&args[0], name, 1)?;
2462 let b = scalar_arg(&args[1], name, 2)?;
2463 Ok(Value::Scalar(a - b * (a / b).trunc()))
2464 }
2465 ("max", 2) => Ok(Value::Scalar(
2466 scalar_arg(&args[0], name, 1)?.max(scalar_arg(&args[1], name, 2)?),
2467 )),
2468 ("min", 2) => Ok(Value::Scalar(
2469 scalar_arg(&args[0], name, 1)?.min(scalar_arg(&args[1], name, 2)?),
2470 )),
2471 ("hypot", 2) => Ok(Value::Scalar(
2472 scalar_arg(&args[0], name, 1)?.hypot(scalar_arg(&args[1], name, 2)?),
2473 )),
2474 ("log", 2) => Ok(Value::Scalar(
2475 scalar_arg(&args[0], name, 1)?.log(scalar_arg(&args[1], name, 2)?),
2476 )),
2477 ("zeros", 1) => {
2479 let n = scalar_arg(&args[0], name, 1)? as usize;
2480 Ok(Value::Matrix(Array2::zeros((n, n))))
2481 }
2482 ("zeros", 2) => {
2483 let r = scalar_arg(&args[0], name, 1)? as usize;
2484 let c = scalar_arg(&args[1], name, 2)? as usize;
2485 Ok(Value::Matrix(Array2::zeros((r, c))))
2486 }
2487 ("ones", 1) => {
2488 let n = scalar_arg(&args[0], name, 1)? as usize;
2489 Ok(Value::Matrix(Array2::ones((n, n))))
2490 }
2491 ("ones", 2) => {
2492 let r = scalar_arg(&args[0], name, 1)? as usize;
2493 let c = scalar_arg(&args[1], name, 2)? as usize;
2494 Ok(Value::Matrix(Array2::ones((r, c))))
2495 }
2496 ("eye", 1) => {
2497 let n = scalar_arg(&args[0], name, 1)? as usize;
2498 let mut m = Array2::<f64>::zeros((n, n));
2499 for i in 0..n {
2500 m[[i, i]] = 1.0;
2501 }
2502 Ok(Value::Matrix(m))
2503 }
2504 ("size", 1) => match &args[0] {
2506 Value::Void => Err("size: not applicable to void".to_string()),
2507 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Matrix(
2508 Array2::from_shape_vec((1, 2), vec![1.0, 1.0]).unwrap(),
2509 )),
2510 Value::Matrix(m) => Ok(Value::Matrix(
2511 Array2::from_shape_vec((1, 2), vec![m.nrows() as f64, m.ncols() as f64]).unwrap(),
2512 )),
2513 Value::Str(s) => Ok(Value::Matrix(
2514 Array2::from_shape_vec((1, 2), vec![1.0, s.chars().count() as f64]).unwrap(),
2515 )),
2516 Value::StringObj(_) => Ok(Value::Matrix(
2517 Array2::from_shape_vec((1, 2), vec![1.0, 1.0]).unwrap(),
2518 )),
2519 Value::Cell(v) => Ok(Value::Matrix(
2520 Array2::from_shape_vec((1, 2), vec![1.0, v.len() as f64]).unwrap(),
2521 )),
2522 Value::StructArray(arr) => Ok(Value::Matrix(
2523 Array2::from_shape_vec((1, 2), vec![1.0, arr.len() as f64]).unwrap(),
2524 )),
2525 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
2526 Err("size: not applicable to function values".to_string())
2527 }
2528 },
2529 ("size", 2) => {
2530 let dim = scalar_arg(&args[1], name, 2)? as usize;
2531 match &args[0] {
2532 Value::Void => Err("size: not applicable to void".to_string()),
2533 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => {
2534 Ok(Value::Scalar(1.0))
2535 }
2536 Value::Matrix(m) => match dim {
2537 1 => Ok(Value::Scalar(m.nrows() as f64)),
2538 2 => Ok(Value::Scalar(m.ncols() as f64)),
2539 _ => Err(format!("size: invalid dimension {dim}, must be 1 or 2")),
2540 },
2541 Value::Str(s) => match dim {
2542 1 => Ok(Value::Scalar(1.0)),
2543 2 => Ok(Value::Scalar(s.chars().count() as f64)),
2544 _ => Err(format!("size: invalid dimension {dim}")),
2545 },
2546 Value::StringObj(_) => Ok(Value::Scalar(1.0)),
2547 Value::Cell(v) => match dim {
2548 1 => Ok(Value::Scalar(1.0)),
2549 2 => Ok(Value::Scalar(v.len() as f64)),
2550 _ => Err(format!("size: invalid dimension {dim}")),
2551 },
2552 Value::StructArray(arr) => match dim {
2553 1 => Ok(Value::Scalar(1.0)),
2554 2 => Ok(Value::Scalar(arr.len() as f64)),
2555 _ => Err(format!("size: invalid dimension {dim}")),
2556 },
2557 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
2558 Err("size: not applicable to function values".to_string())
2559 }
2560 }
2561 }
2562 ("length", 1) => match &args[0] {
2563 Value::Void => Err("length: not applicable to void".to_string()),
2564 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Scalar(1.0)),
2565 Value::Matrix(m) => Ok(Value::Scalar(m.nrows().max(m.ncols()) as f64)),
2566 Value::Str(s) => Ok(Value::Scalar(s.chars().count() as f64)),
2567 Value::StringObj(_) => Ok(Value::Scalar(1.0)),
2568 Value::Cell(v) => Ok(Value::Scalar(v.len() as f64)),
2569 Value::StructArray(arr) => Ok(Value::Scalar(arr.len() as f64)),
2570 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
2571 Err("length: not applicable to function values".to_string())
2572 }
2573 },
2574 ("numel", 1) => match &args[0] {
2575 Value::Void => Err("numel: not applicable to void".to_string()),
2576 Value::Scalar(_) | Value::Complex(_, _) | Value::Struct(_) => Ok(Value::Scalar(1.0)),
2577 Value::Matrix(m) => Ok(Value::Scalar(m.len() as f64)),
2578 Value::Str(s) => Ok(Value::Scalar(s.chars().count() as f64)),
2579 Value::StringObj(_) => Ok(Value::Scalar(1.0)),
2580 Value::Cell(v) => Ok(Value::Scalar(v.len() as f64)),
2581 Value::StructArray(arr) => Ok(Value::Scalar(arr.len() as f64)),
2582 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
2583 Err("numel: not applicable to function values".to_string())
2584 }
2585 },
2586 ("trace", 1) => match &args[0] {
2587 Value::Void => Err("trace: not applicable to void".to_string()),
2588 Value::Scalar(n) => Ok(Value::Scalar(*n)),
2589 Value::Complex(re, _) => Ok(Value::Scalar(*re)),
2590 Value::Matrix(m) => {
2591 let n = m.nrows().min(m.ncols());
2592 Ok(Value::Scalar((0..n).map(|i| m[[i, i]]).sum()))
2593 }
2594 Value::Str(_)
2595 | Value::StringObj(_)
2596 | Value::Lambda(_)
2597 | Value::Function { .. }
2598 | Value::Tuple(_)
2599 | Value::Cell(_)
2600 | Value::Struct(_)
2601 | Value::StructArray(_) => {
2602 Err("trace: not applicable to non-numeric values".to_string())
2603 }
2604 },
2605 ("det", 1) => match &args[0] {
2606 Value::Void => Err("det: not applicable to void".to_string()),
2607 Value::Scalar(n) => Ok(Value::Scalar(*n)),
2608 Value::Complex(_, _) => Err("det: not applicable to complex scalars".to_string()),
2609 Value::Matrix(m) => Ok(Value::Scalar(det_matrix(m)?)),
2610 Value::Str(_)
2611 | Value::StringObj(_)
2612 | Value::Lambda(_)
2613 | Value::Function { .. }
2614 | Value::Tuple(_)
2615 | Value::Cell(_)
2616 | Value::Struct(_)
2617 | Value::StructArray(_) => Err("det: not applicable to non-numeric values".to_string()),
2618 },
2619 ("inv", 1) => match &args[0] {
2620 Value::Void => Err("inv: not applicable to void".to_string()),
2621 Value::Scalar(n) => {
2622 if *n == 0.0 {
2623 Err("inv: singular (zero scalar)".to_string())
2624 } else {
2625 Ok(Value::Scalar(1.0 / n))
2626 }
2627 }
2628 Value::Complex(re, im) => {
2629 let denom = re * re + im * im;
2631 if denom == 0.0 {
2632 Err("inv: singular (zero complex)".to_string())
2633 } else {
2634 Ok(make_complex(re / denom, -im / denom))
2635 }
2636 }
2637 Value::Matrix(m) => Ok(Value::Matrix(inv_matrix(m)?)),
2638 Value::Str(_)
2639 | Value::StringObj(_)
2640 | Value::Lambda(_)
2641 | Value::Function { .. }
2642 | Value::Tuple(_)
2643 | Value::Cell(_)
2644 | Value::Struct(_)
2645 | Value::StructArray(_) => Err("inv: not applicable to non-numeric values".to_string()),
2646 },
2647 ("linspace", 3) => {
2649 let a = scalar_arg(&args[0], name, 1)?;
2650 let b = scalar_arg(&args[1], name, 2)?;
2651 let n = scalar_arg(&args[2], name, 3)? as usize;
2652 if n == 0 {
2653 return Ok(Value::Matrix(Array2::zeros((1, 0))));
2654 }
2655 if n == 1 {
2656 return Ok(Value::Matrix(
2657 Array2::from_shape_vec((1, 1), vec![b]).unwrap(),
2658 ));
2659 }
2660 let vals: Vec<f64> = (0..n)
2661 .map(|i| a + (b - a) * i as f64 / (n - 1) as f64)
2662 .collect();
2663 Ok(Value::Matrix(Array2::from_shape_vec((1, n), vals).unwrap()))
2664 }
2665 ("bitand", 2) => {
2669 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
2670 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
2671 Ok(Value::Scalar((a & b) as f64))
2672 }
2673 ("bitor", 2) => {
2674 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
2675 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
2676 Ok(Value::Scalar((a | b) as f64))
2677 }
2678 ("bitxor", 2) => {
2679 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
2680 let b = to_bits(scalar_arg(&args[1], name, 2)?, name, 2)?;
2681 Ok(Value::Scalar((a ^ b) as f64))
2682 }
2683 ("bitshift", 2) => {
2686 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
2687 let n = scalar_arg(&args[1], name, 2)?;
2688 if n.fract() != 0.0 {
2689 return Err("bitshift: shift amount must be an integer".to_string());
2690 }
2691 let n = n as i64;
2692 let result: u64 = if n >= 64 || n <= -64 {
2693 0
2694 } else if n >= 0 {
2695 a.wrapping_shl(n as u32)
2696 } else {
2697 a.wrapping_shr((-n) as u32)
2698 };
2699 Ok(Value::Scalar(result as f64))
2700 }
2701 ("bitnot", 1) => {
2704 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
2705 let mask: u64 = 0xFFFF_FFFF;
2706 Ok(Value::Scalar(((a ^ mask) & mask) as f64))
2707 }
2708 ("bitnot", 2) => {
2709 let a = to_bits(scalar_arg(&args[0], name, 1)?, name, 1)?;
2710 let bits = scalar_arg(&args[1], name, 2)?;
2711 if bits.fract() != 0.0 || !(1.0..=53.0).contains(&bits) {
2712 return Err(format!(
2713 "bitnot: bit-width must be an integer in [1, 53], got {bits}"
2714 ));
2715 }
2716 let mask: u64 = (1u64 << bits as u32) - 1;
2717 Ok(Value::Scalar(((a ^ mask) & mask) as f64))
2718 }
2719 ("isnan", 1) => apply_elem(&args[0], |x| if x.is_nan() { 1.0 } else { 0.0 }),
2721 ("isinf", 1) => apply_elem(&args[0], |x| if x.is_infinite() { 1.0 } else { 0.0 }),
2722 ("isfinite", 1) => apply_elem(&args[0], |x| if x.is_finite() { 1.0 } else { 0.0 }),
2723 ("nan", 1) => {
2725 let n = scalar_arg(&args[0], name, 1)? as usize;
2726 Ok(Value::Matrix(Array2::from_elem((n, n), f64::NAN)))
2727 }
2728 ("nan", 2) => {
2729 let r = scalar_arg(&args[0], name, 1)? as usize;
2730 let c = scalar_arg(&args[1], name, 2)? as usize;
2731 Ok(Value::Matrix(Array2::from_elem((r, c), f64::NAN)))
2732 }
2733 ("rand", 0) => Ok(Value::Scalar(rand_uniform())),
2735 ("rand", 1) => {
2736 let n = scalar_arg(&args[0], name, 1)? as usize;
2737 let data: Vec<f64> = (0..n * n).map(|_| rand_uniform()).collect();
2738 Ok(Value::Matrix(Array2::from_shape_vec((n, n), data).unwrap()))
2739 }
2740 ("rand", 2) => {
2741 let r = scalar_arg(&args[0], name, 1)? as usize;
2742 let c = scalar_arg(&args[1], name, 2)? as usize;
2743 let data: Vec<f64> = (0..r * c).map(|_| rand_uniform()).collect();
2744 Ok(Value::Matrix(Array2::from_shape_vec((r, c), data).unwrap()))
2745 }
2746 ("randn", 0) => Ok(Value::Scalar(rand_normal())),
2747 ("randn", 1) => {
2748 let n = scalar_arg(&args[0], name, 1)? as usize;
2749 let data: Vec<f64> = (0..n * n).map(|_| rand_normal()).collect();
2750 Ok(Value::Matrix(Array2::from_shape_vec((n, n), data).unwrap()))
2751 }
2752 ("randn", 2) => {
2753 let r = scalar_arg(&args[0], name, 1)? as usize;
2754 let c = scalar_arg(&args[1], name, 2)? as usize;
2755 let data: Vec<f64> = (0..r * c).map(|_| rand_normal()).collect();
2756 Ok(Value::Matrix(Array2::from_shape_vec((r, c), data).unwrap()))
2757 }
2758 ("randi", 1) => {
2759 let (lo, hi) = randi_range(&args[0])?;
2760 let v = RNG.with(|r| r.borrow_mut().gen_range(lo..=hi)) as f64;
2761 Ok(Value::Scalar(v))
2762 }
2763 ("randi", 2) => {
2764 let (lo, hi) = randi_range(&args[0])?;
2765 let n = scalar_arg(&args[1], name, 2)? as usize;
2766 let data: Vec<f64> = (0..n * n)
2767 .map(|_| RNG.with(|r| r.borrow_mut().gen_range(lo..=hi)) as f64)
2768 .collect();
2769 Ok(Value::Matrix(Array2::from_shape_vec((n, n), data).unwrap()))
2770 }
2771 ("randi", 3) => {
2772 let (lo, hi) = randi_range(&args[0])?;
2773 let r = scalar_arg(&args[1], name, 2)? as usize;
2774 let c = scalar_arg(&args[2], name, 3)? as usize;
2775 let data: Vec<f64> = (0..r * c)
2776 .map(|_| RNG.with(|rng| rng.borrow_mut().gen_range(lo..=hi)) as f64)
2777 .collect();
2778 Ok(Value::Matrix(Array2::from_shape_vec((r, c), data).unwrap()))
2779 }
2780 ("rng", 1) => match &args[0] {
2781 Value::Scalar(n) => {
2782 rng_seed(*n as u64);
2783 Ok(Value::Void)
2784 }
2785 Value::Str(s) | Value::StringObj(s) if s == "shuffle" => {
2786 rng_shuffle();
2787 Ok(Value::Void)
2788 }
2789 _ => Err("rng: argument must be a numeric seed or 'shuffle'".to_string()),
2790 },
2791 ("sum", 1) => apply_reduction(&args[0], |v| v.iter().copied().sum()),
2795 ("prod", 1) => apply_reduction(&args[0], |v| v.iter().copied().product()),
2796 ("any", 1) => apply_reduction(&args[0], |v| {
2797 if v.iter().any(|&x| x != 0.0) {
2798 1.0
2799 } else {
2800 0.0
2801 }
2802 }),
2803 ("all", 1) => apply_reduction(&args[0], |v| {
2804 if v.iter().all(|&x| x != 0.0) {
2805 1.0
2806 } else {
2807 0.0
2808 }
2809 }),
2810 ("mean", 1) => apply_reduction(&args[0], |v| {
2811 if v.is_empty() {
2812 f64::NAN
2813 } else {
2814 v.iter().copied().sum::<f64>() / v.len() as f64
2815 }
2816 }),
2817 ("min", 1) => apply_reduction(&args[0], |v| {
2820 v.iter().copied().fold(f64::INFINITY, f64::min)
2821 }),
2822 ("max", 1) => apply_reduction(&args[0], |v| {
2823 v.iter().copied().fold(f64::NEG_INFINITY, f64::max)
2824 }),
2825 ("norm", 1) => match &args[0] {
2827 Value::Void => Err("norm: not applicable to void".to_string()),
2828 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
2829 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt())),
2830 Value::Matrix(m) => {
2831 if m.nrows() <= 1 || m.ncols() <= 1 {
2832 Ok(Value::Scalar(m.iter().map(|x| x * x).sum::<f64>().sqrt()))
2834 } else {
2835 let (_, s, _) = svd_compute(m)?;
2837 Ok(Value::Scalar(s.first().copied().unwrap_or(0.0)))
2838 }
2839 }
2840 Value::Str(_)
2841 | Value::StringObj(_)
2842 | Value::Lambda(_)
2843 | Value::Function { .. }
2844 | Value::Tuple(_)
2845 | Value::Cell(_)
2846 | Value::Struct(_)
2847 | Value::StructArray(_) => {
2848 Err("norm: not applicable to non-numeric values".to_string())
2849 }
2850 },
2851 ("norm", 2) => match &args[1] {
2852 Value::Str(s) | Value::StringObj(s) => match s.as_str() {
2853 "fro" => match &args[0] {
2854 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
2855 Value::Matrix(m) => {
2856 Ok(Value::Scalar(m.iter().map(|x| x * x).sum::<f64>().sqrt()))
2857 }
2858 _ => Err("norm: first argument must be numeric".to_string()),
2859 },
2860 other => Err(format!("norm: unknown norm type '{other}'")),
2861 },
2862 _ => {
2863 let p = scalar_arg(&args[1], name, 2)?;
2864 match &args[0] {
2865 Value::Void => Err("norm: not applicable to void".to_string()),
2866 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
2867 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt().powf(p))),
2868 Value::Matrix(m) => {
2869 if m.nrows() > 1 && m.ncols() > 1 {
2870 if (p - 2.0).abs() < 1e-15 {
2872 let (_, s, _) = svd_compute(m)?;
2873 return Ok(Value::Scalar(s.first().copied().unwrap_or(0.0)));
2874 } else if (p - 1.0).abs() < 1e-15 {
2875 let v = (0..m.ncols())
2877 .map(|j| m.column(j).iter().map(|&x| x.abs()).sum::<f64>())
2878 .fold(0.0_f64, f64::max);
2879 return Ok(Value::Scalar(v));
2880 } else if p == f64::INFINITY {
2881 let v = (0..m.nrows())
2883 .map(|i| m.row(i).iter().map(|&x| x.abs()).sum::<f64>())
2884 .fold(0.0_f64, f64::max);
2885 return Ok(Value::Scalar(v));
2886 }
2887 }
2888 if p == f64::INFINITY {
2890 Ok(Value::Scalar(
2891 m.iter().copied().fold(0.0_f64, |acc, x| acc.max(x.abs())),
2892 ))
2893 } else {
2894 Ok(Value::Scalar(
2895 m.iter().map(|x| x.abs().powf(p)).sum::<f64>().powf(1.0 / p),
2896 ))
2897 }
2898 }
2899 Value::Str(_)
2900 | Value::StringObj(_)
2901 | Value::Lambda(_)
2902 | Value::Function { .. }
2903 | Value::Tuple(_)
2904 | Value::Cell(_)
2905 | Value::Struct(_)
2906 | Value::StructArray(_) => {
2907 Err("norm: not applicable to non-numeric values".to_string())
2908 }
2909 }
2910 }
2911 },
2912 ("cumsum", 1) => apply_cumulative(&args[0], |acc, x| acc + x),
2914 ("cumprod", 1) => apply_cumulative(&args[0], |acc, x| acc * x),
2915 ("sort", 1) => match &args[0] {
2917 Value::Void => Err("sort: not applicable to void".to_string()),
2918 Value::Scalar(n) => Ok(Value::Scalar(*n)),
2919 Value::Complex(_, _) => Err("sort: not applicable to complex values".to_string()),
2920 Value::Str(_)
2921 | Value::StringObj(_)
2922 | Value::Lambda(_)
2923 | Value::Function { .. }
2924 | Value::Tuple(_)
2925 | Value::Cell(_)
2926 | Value::Struct(_)
2927 | Value::StructArray(_) => {
2928 Err("sort: not applicable to non-numeric values".to_string())
2929 }
2930 Value::Matrix(m) => {
2931 if m.nrows() > 1 && m.ncols() > 1 {
2932 return Err("sort: input must be a vector".to_string());
2933 }
2934 let mut vals: Vec<f64> = m.iter().copied().collect();
2935 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
2936 Ok(Value::Matrix(
2937 Array2::from_shape_vec(m.raw_dim(), vals).unwrap(),
2938 ))
2939 }
2940 },
2941 ("reshape", 3) => {
2943 let r = scalar_arg(&args[1], name, 2)? as usize;
2944 let c = scalar_arg(&args[2], name, 3)? as usize;
2945 match &args[0] {
2946 Value::Void => Err("reshape: not applicable to void".to_string()),
2947 Value::Scalar(n) => {
2948 if r * c != 1 {
2949 return Err(format!("reshape: cannot reshape 1 element into {r}x{c}"));
2950 }
2951 Ok(Value::Matrix(
2952 Array2::from_shape_vec((1, 1), vec![*n]).unwrap(),
2953 ))
2954 }
2955 Value::Complex(_, _) => {
2956 Err("reshape: not applicable to complex values".to_string())
2957 }
2958 Value::Str(_)
2959 | Value::StringObj(_)
2960 | Value::Lambda(_)
2961 | Value::Function { .. }
2962 | Value::Tuple(_)
2963 | Value::Cell(_)
2964 | Value::Struct(_)
2965 | Value::StructArray(_) => {
2966 Err("reshape: not applicable to non-numeric values".to_string())
2967 }
2968 Value::Matrix(m) => {
2969 let total = m.len();
2970 if r * c != total {
2971 return Err(format!(
2972 "reshape: cannot reshape {total} elements into {r}x{c}"
2973 ));
2974 }
2975 let flat: Vec<f64> = (0..m.ncols())
2977 .flat_map(|col| (0..m.nrows()).map(move |row| m[[row, col]]))
2978 .collect();
2979 let mut result = Array2::<f64>::zeros((r, c));
2980 for (i, &v) in flat.iter().enumerate() {
2981 result[[i % r, i / r]] = v;
2982 }
2983 Ok(Value::Matrix(result))
2984 }
2985 }
2986 }
2987 ("fliplr", 1) => match &args[0] {
2989 Value::Void => Err(format!("{name}: not applicable to void")),
2990 Value::Scalar(n) => Ok(Value::Scalar(*n)),
2991 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
2992 Value::Str(_)
2993 | Value::StringObj(_)
2994 | Value::Lambda(_)
2995 | Value::Function { .. }
2996 | Value::Tuple(_)
2997 | Value::Cell(_)
2998 | Value::Struct(_)
2999 | Value::StructArray(_) => Err(format!("{name}: not applicable to non-numeric values")),
3000 Value::Matrix(m) => {
3001 let (nrows, ncols) = (m.nrows(), m.ncols());
3002 let mut result = m.clone();
3003 for r in 0..nrows {
3004 for c in 0..ncols / 2 {
3005 let tmp = result[[r, c]];
3006 result[[r, c]] = result[[r, ncols - 1 - c]];
3007 result[[r, ncols - 1 - c]] = tmp;
3008 }
3009 }
3010 Ok(Value::Matrix(result))
3011 }
3012 },
3013 ("flipud", 1) => match &args[0] {
3014 Value::Void => Err(format!("{name}: not applicable to void")),
3015 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3016 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
3017 Value::Str(_)
3018 | Value::StringObj(_)
3019 | Value::Lambda(_)
3020 | Value::Function { .. }
3021 | Value::Tuple(_)
3022 | Value::Cell(_)
3023 | Value::Struct(_)
3024 | Value::StructArray(_) => Err(format!("{name}: not applicable to non-numeric values")),
3025 Value::Matrix(m) => {
3026 let (nrows, ncols) = (m.nrows(), m.ncols());
3027 let mut result = m.clone();
3028 for c in 0..ncols {
3029 for r in 0..nrows / 2 {
3030 let tmp = result[[r, c]];
3031 result[[r, c]] = result[[nrows - 1 - r, c]];
3032 result[[nrows - 1 - r, c]] = tmp;
3033 }
3034 }
3035 Ok(Value::Matrix(result))
3036 }
3037 },
3038 ("find", 1) => find_nonzero(&args[0], usize::MAX),
3040 ("find", 2) => {
3041 let k = scalar_arg(&args[1], name, 2)?;
3042 if k < 0.0 {
3043 return Err("find: k must be non-negative".to_string());
3044 }
3045 find_nonzero(&args[0], k as usize)
3046 }
3047 ("unique", 1) => match &args[0] {
3049 Value::Void => Err("unique: not applicable to void".to_string()),
3050 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3051 Value::Matrix(m) => {
3052 let mut vals: Vec<f64> = m.iter().copied().collect();
3053 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3054 let mut unique: Vec<f64> = Vec::new();
3055 for v in vals {
3056 if unique.last().is_none_or(|&last| last != v) {
3057 unique.push(v);
3058 }
3059 }
3060 let n = unique.len();
3061 Ok(Value::Matrix(
3062 Array2::from_shape_vec((1, n), unique).unwrap(),
3063 ))
3064 }
3065 Value::Complex(_, _) => Err("unique: not applicable to complex values".to_string()),
3066 Value::Str(_)
3067 | Value::StringObj(_)
3068 | Value::Lambda(_)
3069 | Value::Function { .. }
3070 | Value::Tuple(_)
3071 | Value::Cell(_)
3072 | Value::Struct(_)
3073 | Value::StructArray(_) => {
3074 Err("unique: not applicable to non-numeric values".to_string())
3075 }
3076 },
3077 ("std", 1) => apply_stat(&args[0], |s| stat_var_vec(s, false).sqrt(), "std"),
3079 ("std", 2) => {
3080 let w = scalar_arg(&args[1], name, 2)?;
3081 let population = w != 0.0;
3082 apply_stat(&args[0], |s| stat_var_vec(s, population).sqrt(), "std")
3083 }
3084 ("var", 1) => apply_stat(&args[0], |s| stat_var_vec(s, false), "var"),
3085 ("var", 2) => {
3086 let w = scalar_arg(&args[1], name, 2)?;
3087 let population = w != 0.0;
3088 apply_stat(&args[0], |s| stat_var_vec(s, population), "var")
3089 }
3090 ("cov", 1) => match &args[0] {
3091 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
3092 Value::Matrix(m) => {
3093 if m.nrows() == 1 || m.ncols() == 1 {
3094 let vals: Vec<f64> = m.iter().copied().collect();
3095 Ok(Value::Scalar(stat_var_vec(&vals, false)))
3096 } else {
3097 let (nobs, nvars) = (m.nrows(), m.ncols());
3098 if nobs < 2 {
3099 return Err("cov: need at least 2 observations".to_string());
3100 }
3101 let mut centered = m.clone();
3102 for c in 0..nvars {
3103 let col_mean: f64 = m.column(c).iter().sum::<f64>() / nobs as f64;
3104 for r in 0..nobs {
3105 centered[[r, c]] -= col_mean;
3106 }
3107 }
3108 let denom = (nobs - 1) as f64;
3109 let mut cov_mat = Array2::<f64>::zeros((nvars, nvars));
3110 for i in 0..nvars {
3111 for j in 0..nvars {
3112 let dot: f64 =
3113 (0..nobs).map(|r| centered[[r, i]] * centered[[r, j]]).sum();
3114 cov_mat[[i, j]] = dot / denom;
3115 }
3116 }
3117 Ok(Value::Matrix(cov_mat))
3118 }
3119 }
3120 _ => Err("cov: argument must be numeric".to_string()),
3121 },
3122 ("median", 1) => apply_stat(
3123 &args[0],
3124 |s| {
3125 if s.is_empty() {
3126 return f64::NAN;
3127 }
3128 let mut v = s.to_vec();
3129 v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3130 let n = v.len();
3131 if n % 2 == 0 {
3132 (v[n / 2 - 1] + v[n / 2]) / 2.0
3133 } else {
3134 v[n / 2]
3135 }
3136 },
3137 "median",
3138 ),
3139 ("mode", 1) => apply_stat(
3140 &args[0],
3141 |s| {
3142 if s.is_empty() {
3143 return f64::NAN;
3144 }
3145 let mut counts: std::collections::HashMap<u64, usize> =
3146 std::collections::HashMap::new();
3147 for &x in s {
3148 *counts.entry(x.to_bits()).or_insert(0) += 1;
3149 }
3150 let max_count = counts.values().copied().max().unwrap_or(0);
3151 let mut candidates: Vec<f64> = counts
3152 .iter()
3153 .filter(|&(_, &c)| c == max_count)
3154 .map(|(&bits, _)| f64::from_bits(bits))
3155 .collect();
3156 candidates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3157 candidates[0]
3158 },
3159 "mode",
3160 ),
3161 ("skewness", 1) => apply_stat(
3162 &args[0],
3163 |s| {
3164 let n = s.len();
3165 if n == 0 {
3166 return f64::NAN;
3167 }
3168 if n == 1 {
3169 return 0.0;
3170 }
3171 let mean = s.iter().sum::<f64>() / n as f64;
3172 let m2 = s.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
3173 if m2 == 0.0 {
3174 return 0.0;
3175 }
3176 let m3 = s.iter().map(|&x| (x - mean).powi(3)).sum::<f64>() / n as f64;
3177 m3 / m2.powf(1.5)
3178 },
3179 "skewness",
3180 ),
3181 ("kurtosis", 1) => apply_stat(
3182 &args[0],
3183 |s| {
3184 let n = s.len();
3185 if n < 2 {
3186 return f64::NAN;
3187 }
3188 let mean = s.iter().sum::<f64>() / n as f64;
3189 let m2 = s.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
3190 if m2 == 0.0 {
3191 return f64::NAN;
3192 }
3193 let m4 = s.iter().map(|&x| (x - mean).powi(4)).sum::<f64>() / n as f64;
3194 m4 / m2.powi(2)
3195 },
3196 "kurtosis",
3197 ),
3198 ("hist", n) if n == 1 || n == 2 => {
3199 let n_bins = if args.len() == 2 {
3200 scalar_arg(&args[1], name, 2)? as usize
3201 } else {
3202 10
3203 };
3204 if n_bins == 0 {
3205 return Err("hist: number of bins must be positive".to_string());
3206 }
3207 let vals = numeric_vec(&args[0], name)?;
3208 if vals.is_empty() {
3209 return Ok(Value::Void);
3210 }
3211 let min_v = vals.iter().cloned().fold(f64::INFINITY, f64::min);
3212 let max_v = vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
3213 let range = if max_v > min_v { max_v - min_v } else { 1.0 };
3214 let mut counts = vec![0usize; n_bins];
3215 for &v in &vals {
3216 let b = ((v - min_v) / range * n_bins as f64) as usize;
3217 counts[b.min(n_bins - 1)] += 1;
3218 }
3219 let bar_cols: usize = std::env::var("COLUMNS")
3220 .ok()
3221 .and_then(|s| s.parse::<usize>().ok())
3222 .unwrap_or(80)
3223 .saturating_sub(26)
3224 .max(10);
3225 let max_count = *counts.iter().max().unwrap_or(&1).max(&1);
3226 let mut output = String::new();
3227 for (i, &c) in counts.iter().enumerate() {
3228 let lo = min_v + range * (i as f64 / n_bins as f64);
3229 let hi = min_v + range * ((i + 1) as f64 / n_bins as f64);
3230 let bar_len = c * bar_cols / max_count;
3231 output.push_str(&format!(
3232 "{lo:8.4} {hi:8.4} |{bar:<bar_cols$}| {c}\n",
3233 bar = "#".repeat(bar_len),
3234 ));
3235 }
3236 match io.as_deref_mut() {
3237 Some(ctx) => ctx.write_to_fd(1, &output)?,
3238 None => {
3239 print!("{output}");
3240 std::io::stdout().flush().ok();
3241 }
3242 }
3243 Ok(Value::Void)
3244 }
3245 ("histc", 2) => {
3246 let vals = numeric_vec(&args[0], name)?;
3247 let edges = numeric_vec(&args[1], name)?;
3248 if edges.is_empty() {
3249 return Err("histc: edges must not be empty".to_string());
3250 }
3251 let n_edges = edges.len();
3252 let mut counts = vec![0.0f64; n_edges];
3253 for &v in &vals {
3254 let last = n_edges - 1;
3256 if v == edges[last] {
3257 counts[last] += 1.0;
3258 } else {
3259 for i in 0..last {
3260 if v >= edges[i] && v < edges[i + 1] {
3261 counts[i] += 1.0;
3262 break;
3263 }
3264 }
3265 }
3266 }
3267 Ok(Value::Matrix(
3268 Array2::from_shape_vec((1, n_edges), counts).unwrap(),
3269 ))
3270 }
3271 ("prctile", 2) => {
3273 let p_vals = numeric_vec(&args[1], name)?;
3274 let n_p = p_vals.len();
3275
3276 let compute_col = |vals: &[f64]| -> Vec<f64> {
3278 let mut s = vals.to_vec();
3279 s.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3280 p_vals.iter().map(|&p| percentile_sorted(&s, p)).collect()
3281 };
3282
3283 match &args[0] {
3284 Value::Scalar(n) => {
3285 let pr = compute_col(&[*n]);
3286 if n_p == 1 {
3287 Ok(Value::Scalar(pr[0]))
3288 } else {
3289 Ok(Value::Matrix(Array2::from_shape_vec((1, n_p), pr).unwrap()))
3290 }
3291 }
3292 Value::Matrix(m) if m.nrows() == 1 || m.ncols() == 1 => {
3293 let vals: Vec<f64> = m.iter().copied().collect();
3294 let pr = compute_col(&vals);
3295 if n_p == 1 {
3296 Ok(Value::Scalar(pr[0]))
3297 } else {
3298 Ok(Value::Matrix(Array2::from_shape_vec((1, n_p), pr).unwrap()))
3299 }
3300 }
3301 Value::Matrix(m) => {
3302 let ncols = m.ncols();
3304 let mut result = Array2::<f64>::zeros((n_p, ncols));
3305 for j in 0..ncols {
3306 let col: Vec<f64> = m.column(j).iter().copied().collect();
3307 let pr = compute_col(&col);
3308 for (i, &v) in pr.iter().enumerate() {
3309 result[[i, j]] = v;
3310 }
3311 }
3312 if n_p == 1 {
3313 let row: Vec<f64> = result.row(0).iter().copied().collect();
3314 Ok(Value::Matrix(
3315 Array2::from_shape_vec((1, ncols), row).unwrap(),
3316 ))
3317 } else {
3318 Ok(Value::Matrix(result))
3319 }
3320 }
3321 _ => Err("prctile: first argument must be numeric".to_string()),
3322 }
3323 }
3324 ("iqr", 1) => apply_stat(
3325 &args[0],
3326 |s| {
3327 let mut sorted = s.to_vec();
3328 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
3329 percentile_sorted(&sorted, 75.0) - percentile_sorted(&sorted, 25.0)
3330 },
3331 "iqr",
3332 ),
3333 ("zscore", 1) => match &args[0] {
3334 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
3335 Value::Matrix(m) => {
3336 if m.nrows() == 1 || m.ncols() == 1 {
3337 let vals: Vec<f64> = m.iter().copied().collect();
3338 let n = vals.len() as f64;
3339 let mean = vals.iter().sum::<f64>() / n;
3340 let s = stat_var_vec(&vals, false).sqrt();
3341 let result: Vec<f64> = vals
3342 .iter()
3343 .map(|&x| if s == 0.0 { 0.0 } else { (x - mean) / s })
3344 .collect();
3345 Ok(Value::Matrix(
3346 Array2::from_shape_vec(m.raw_dim(), result).unwrap(),
3347 ))
3348 } else {
3349 let (nrows, ncols) = (m.nrows(), m.ncols());
3350 let mut result = m.clone();
3351 for j in 0..ncols {
3352 let col: Vec<f64> = m.column(j).iter().copied().collect();
3353 let mean = col.iter().sum::<f64>() / col.len() as f64;
3354 let s = stat_var_vec(&col, false).sqrt();
3355 for i in 0..nrows {
3356 result[[i, j]] = if s == 0.0 {
3357 0.0
3358 } else {
3359 (m[[i, j]] - mean) / s
3360 };
3361 }
3362 }
3363 Ok(Value::Matrix(result))
3364 }
3365 }
3366 _ => Err("zscore: argument must be numeric".to_string()),
3367 },
3368 ("diag", 1) => match &args[0] {
3370 Value::Scalar(n) => Ok(Value::Matrix(Array2::from_elem((1, 1), *n))),
3371 Value::Matrix(m) => {
3372 let (rows, cols) = (m.nrows(), m.ncols());
3373 if rows == 1 || cols == 1 {
3374 let v: Vec<f64> = m.iter().copied().collect();
3376 let n = v.len();
3377 let mut result = Array2::<f64>::zeros((n, n));
3378 for (i, &val) in v.iter().enumerate() {
3379 result[[i, i]] = val;
3380 }
3381 Ok(Value::Matrix(result))
3382 } else {
3383 let n = rows.min(cols);
3385 let d: Vec<f64> = (0..n).map(|i| m[[i, i]]).collect();
3386 Ok(Value::Matrix(Array2::from_shape_vec((n, 1), d).unwrap()))
3387 }
3388 }
3389 Value::Void => Err("diag: not applicable to void".to_string()),
3390 Value::Complex(_, _) => Err("diag: not applicable to complex values".to_string()),
3391 Value::Str(_)
3392 | Value::StringObj(_)
3393 | Value::Lambda(_)
3394 | Value::Function { .. }
3395 | Value::Tuple(_)
3396 | Value::Cell(_)
3397 | Value::Struct(_)
3398 | Value::StructArray(_) => {
3399 Err("diag: not applicable to non-numeric values".to_string())
3400 }
3401 },
3402
3403 ("real", 1) => match &args[0] {
3406 Value::Void => Err("real: not applicable to void".to_string()),
3407 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3408 Value::Complex(re, _) => Ok(Value::Scalar(*re)),
3409 Value::Matrix(_) => Err("real: not applicable to matrices".to_string()),
3410 Value::Str(_)
3411 | Value::StringObj(_)
3412 | Value::Lambda(_)
3413 | Value::Function { .. }
3414 | Value::Tuple(_)
3415 | Value::Cell(_)
3416 | Value::Struct(_)
3417 | Value::StructArray(_) => {
3418 Err("real: not applicable to non-numeric values".to_string())
3419 }
3420 },
3421 ("imag", 1) => match &args[0] {
3423 Value::Void => Err("imag: not applicable to void".to_string()),
3424 Value::Scalar(_) => Ok(Value::Scalar(0.0)),
3425 Value::Complex(_, im) => Ok(Value::Scalar(*im)),
3426 Value::Matrix(_) => Err("imag: not applicable to matrices".to_string()),
3427 Value::Str(_)
3428 | Value::StringObj(_)
3429 | Value::Lambda(_)
3430 | Value::Function { .. }
3431 | Value::Tuple(_)
3432 | Value::Cell(_)
3433 | Value::Struct(_)
3434 | Value::StructArray(_) => {
3435 Err("imag: not applicable to non-numeric values".to_string())
3436 }
3437 },
3438 ("abs", 1) => match &args[0] {
3440 Value::Void => Err("abs: not applicable to void".to_string()),
3441 Value::Scalar(n) => Ok(Value::Scalar(n.abs())),
3442 Value::Complex(re, im) => Ok(Value::Scalar((re * re + im * im).sqrt())),
3443 Value::Matrix(m) => Ok(Value::Matrix(m.mapv(|x| x.abs()))),
3444 Value::Str(_)
3445 | Value::StringObj(_)
3446 | Value::Lambda(_)
3447 | Value::Function { .. }
3448 | Value::Tuple(_)
3449 | Value::Cell(_)
3450 | Value::Struct(_)
3451 | Value::StructArray(_) => Err("abs: not applicable to non-numeric values".to_string()),
3452 },
3453 ("angle", 1) => match &args[0] {
3455 Value::Void => Err("angle: not applicable to void".to_string()),
3456 Value::Scalar(n) => Ok(Value::Scalar(if *n >= 0.0 {
3457 0.0
3458 } else {
3459 std::f64::consts::PI
3460 })),
3461 Value::Complex(re, im) => Ok(Value::Scalar(im.atan2(*re))),
3462 Value::Matrix(_) => Err("angle: not applicable to matrices".to_string()),
3463 Value::Str(_)
3464 | Value::StringObj(_)
3465 | Value::Lambda(_)
3466 | Value::Function { .. }
3467 | Value::Tuple(_)
3468 | Value::Cell(_)
3469 | Value::Struct(_)
3470 | Value::StructArray(_) => {
3471 Err("angle: not applicable to non-numeric values".to_string())
3472 }
3473 },
3474 ("conj", 1) => match &args[0] {
3476 Value::Void => Err("conj: not applicable to void".to_string()),
3477 Value::Scalar(n) => Ok(Value::Scalar(*n)),
3478 Value::Complex(re, im) => Ok(make_complex(*re, -*im)),
3479 Value::Matrix(m) => Ok(Value::Matrix(m.clone())),
3480 Value::Str(_)
3481 | Value::StringObj(_)
3482 | Value::Lambda(_)
3483 | Value::Function { .. }
3484 | Value::Tuple(_)
3485 | Value::Cell(_)
3486 | Value::Struct(_)
3487 | Value::StructArray(_) => {
3488 Err("conj: not applicable to non-numeric values".to_string())
3489 }
3490 },
3491 ("complex", 2) => {
3493 let re = scalar_arg(&args[0], name, 1)?;
3494 let im = scalar_arg(&args[1], name, 2)?;
3495 Ok(make_complex(re, im))
3496 }
3497 ("isreal", 1) => match &args[0] {
3499 Value::Void => Ok(Value::Scalar(0.0)),
3500 Value::Scalar(_) => Ok(Value::Scalar(1.0)),
3501 Value::Complex(_, im) => Ok(Value::Scalar(if *im == 0.0 { 1.0 } else { 0.0 })),
3502 Value::Matrix(_) => Ok(Value::Scalar(1.0)),
3503 Value::Str(_) | Value::StringObj(_) => Ok(Value::Scalar(0.0)),
3505 Value::Lambda(_)
3506 | Value::Function { .. }
3507 | Value::Tuple(_)
3508 | Value::Cell(_)
3509 | Value::Struct(_)
3510 | Value::StructArray(_) => Ok(Value::Scalar(0.0)),
3511 },
3512 ("num2str", 1) => match &args[0] {
3515 Value::Void => Err("num2str: not applicable to void".to_string()),
3516 Value::Str(s) => Ok(Value::Str(s.clone())),
3517 Value::StringObj(s) => Ok(Value::Str(s.clone())),
3518 Value::Scalar(n) => Ok(Value::Str(fmt_auto_sig(*n, 5))),
3519 Value::Complex(re, im) => Ok(Value::Str(format_complex(*re, *im, &FormatMode::Short))),
3520 Value::Matrix(m) => {
3521 let s = m
3522 .iter()
3523 .map(|x| fmt_auto_sig(*x, 5))
3524 .collect::<Vec<_>>()
3525 .join(" ");
3526 Ok(Value::Str(s))
3527 }
3528 Value::Lambda(_)
3529 | Value::Function { .. }
3530 | Value::Tuple(_)
3531 | Value::Cell(_)
3532 | Value::Struct(_)
3533 | Value::StructArray(_) => Err("num2str: not applicable to this type".to_string()),
3534 },
3535 ("num2str", 2) => {
3537 let n = scalar_arg(&args[1], name, 2)? as usize;
3538 match &args[0] {
3539 Value::Void => Err("num2str: not applicable to void".to_string()),
3540 Value::Str(s) => Ok(Value::Str(s.clone())),
3541 Value::StringObj(s) => Ok(Value::Str(s.clone())),
3542 Value::Scalar(v) => Ok(Value::Str(fmt_auto_sig(*v, n))),
3543 Value::Complex(re, im) => {
3544 Ok(Value::Str(format_complex(*re, *im, &FormatMode::Custom(n))))
3545 }
3546 Value::Matrix(m) => {
3547 let s = m
3548 .iter()
3549 .map(|x| fmt_auto_sig(*x, n))
3550 .collect::<Vec<_>>()
3551 .join(" ");
3552 Ok(Value::Str(s))
3553 }
3554 Value::Lambda(_)
3555 | Value::Function { .. }
3556 | Value::Tuple(_)
3557 | Value::Cell(_)
3558 | Value::Struct(_)
3559 | Value::StructArray(_) => Err("num2str: not applicable to this type".to_string()),
3560 }
3561 }
3562 ("str2double", 1) => {
3564 let s = string_arg(&args[0], name, 1)?;
3565 match s.trim().parse::<f64>() {
3566 Ok(n) => Ok(Value::Scalar(n)),
3567 Err(_) => Ok(Value::Scalar(f64::NAN)),
3568 }
3569 }
3570 ("str2num", 1) => {
3572 let s = string_arg(&args[0], name, 1)?;
3573 s.trim()
3574 .parse::<f64>()
3575 .map(Value::Scalar)
3576 .map_err(|_| format!("str2num: cannot convert '{}' to number", s.trim()))
3577 }
3578 ("strcat", n) if n >= 2 => {
3580 let mut result = String::new();
3581 let mut any_obj = false;
3582 for (i, arg) in args.iter().enumerate() {
3583 match arg {
3584 Value::Str(s) => result.push_str(s.trim_end()),
3585 Value::StringObj(s) => {
3586 result.push_str(s);
3587 any_obj = true;
3588 }
3589 _ => return Err(format!("strcat: argument {} must be a string", i + 1)),
3590 }
3591 }
3592 if any_obj {
3593 Ok(Value::StringObj(result))
3594 } else {
3595 Ok(Value::Str(result))
3596 }
3597 }
3598 ("ischar", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::Str(_)) {
3600 1.0
3601 } else {
3602 0.0
3603 })),
3604 ("isstring", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::StringObj(_)) {
3606 1.0
3607 } else {
3608 0.0
3609 })),
3610 ("struct", _) => {
3613 if !args.len().is_multiple_of(2) {
3614 return Err(
3615 "struct: requires an even number of arguments (name, value, ...)".to_string(),
3616 );
3617 }
3618 let mut map = IndexMap::new();
3619 for pair in args.chunks(2) {
3620 let key = match &pair[0] {
3621 Value::Str(s) | Value::StringObj(s) => s.clone(),
3622 _ => return Err("struct: field names must be strings".to_string()),
3623 };
3624 map.insert(key, pair[1].clone());
3625 }
3626 Ok(Value::Struct(map))
3627 }
3628 ("fieldnames", 1) => match &args[0] {
3630 Value::Struct(map) => {
3631 let names: Vec<Value> = map.keys().map(|k| Value::Str(k.clone())).collect();
3632 Ok(Value::Cell(names))
3633 }
3634 Value::StructArray(arr) => {
3635 let names: Vec<Value> = arr
3637 .first()
3638 .map(|m| m.keys().map(|k| Value::Str(k.clone())).collect())
3639 .unwrap_or_default();
3640 Ok(Value::Cell(names))
3641 }
3642 _ => Err("fieldnames: argument must be a struct".to_string()),
3643 },
3644 ("isfield", 2) => {
3646 let field = match &args[1] {
3647 Value::Str(s) | Value::StringObj(s) => s.clone(),
3648 _ => return Err("isfield: second argument must be a string".to_string()),
3649 };
3650 Ok(Value::Scalar(match &args[0] {
3651 Value::Struct(map) if map.contains_key(&field) => 1.0,
3652 Value::StructArray(arr) if arr.first().is_some_and(|m| m.contains_key(&field)) => {
3653 1.0
3654 }
3655 _ => 0.0,
3656 }))
3657 }
3658 ("rmfield", 2) => {
3660 let field = match &args[1] {
3661 Value::Str(s) | Value::StringObj(s) => s.clone(),
3662 _ => return Err("rmfield: second argument must be a string".to_string()),
3663 };
3664 match &args[0] {
3665 Value::Struct(map) => {
3666 if !map.contains_key(&field) {
3667 return Err(format!("rmfield: field '{field}' does not exist"));
3668 }
3669 let mut updated = map.clone();
3670 updated.shift_remove(&field);
3671 Ok(Value::Struct(updated))
3672 }
3673 Value::StructArray(arr) => {
3674 let updated: Result<Vec<_>, _> = arr
3675 .iter()
3676 .map(|m| {
3677 if !m.contains_key(&field) {
3678 return Err(format!("rmfield: field '{field}' does not exist"));
3679 }
3680 let mut m2 = m.clone();
3681 m2.shift_remove(&field);
3682 Ok(m2)
3683 })
3684 .collect();
3685 Ok(Value::StructArray(updated?))
3686 }
3687 _ => Err("rmfield: first argument must be a struct".to_string()),
3688 }
3689 }
3690 ("isstruct", 1) => Ok(Value::Scalar(
3692 if matches!(&args[0], Value::Struct(_) | Value::StructArray(_)) {
3693 1.0
3694 } else {
3695 0.0
3696 },
3697 )),
3698 ("isempty", 1) => {
3702 let empty = match &args[0] {
3703 Value::Matrix(m) => m.is_empty(),
3704 Value::Str(s) | Value::StringObj(s) => s.is_empty(),
3705 Value::Cell(v) => v.is_empty(),
3706 Value::Void => true,
3707 _ => false,
3708 };
3709 Ok(Value::Scalar(if empty { 1.0 } else { 0.0 }))
3710 }
3711 ("iscell", 1) => Ok(Value::Scalar(if matches!(&args[0], Value::Cell(_)) {
3713 1.0
3714 } else {
3715 0.0
3716 })),
3717 ("cell", 1) => {
3719 let n = scalar_arg(&args[0], name, 1)? as usize;
3720 Ok(Value::Cell(vec![Value::Scalar(0.0); n]))
3721 }
3722 ("cell", 2) => {
3724 let m = scalar_arg(&args[0], name, 1)? as usize;
3725 let n = scalar_arg(&args[1], name, 2)? as usize;
3726 Ok(Value::Cell(vec![Value::Scalar(0.0); m * n]))
3727 }
3728 ("cellfun", 2) => {
3731 let f = args[0].clone();
3732 match &args[1] {
3733 Value::Cell(elems) => {
3734 let elems = elems.clone();
3735 let mut results = Vec::with_capacity(elems.len());
3736 for elem in &elems {
3737 let result =
3738 call_function_value(&f, std::slice::from_ref(elem), io.as_deref_mut())?;
3739 results.push(result);
3740 }
3741 let all_scalar = results.iter().all(|v| matches!(v, Value::Scalar(_)));
3743 if all_scalar {
3744 let vals: Vec<f64> = results
3745 .iter()
3746 .map(|v| {
3747 if let Value::Scalar(n) = v {
3748 *n
3749 } else {
3750 unreachable!()
3751 }
3752 })
3753 .collect();
3754 let n = vals.len();
3755 if n == 0 {
3756 Ok(Value::Matrix(Array2::zeros((1, 0))))
3757 } else {
3758 Ok(Value::Matrix(Array2::from_shape_vec((1, n), vals).unwrap()))
3759 }
3760 } else {
3761 Ok(Value::Cell(results))
3762 }
3763 }
3764 _ => Err("cellfun: second argument must be a cell array".to_string()),
3765 }
3766 }
3767 ("arrayfun", 2) => {
3770 let f = args[0].clone();
3771 match &args[1] {
3772 Value::Matrix(m) => {
3773 let m = m.clone();
3774 let mut flat = Vec::with_capacity(m.len());
3775 for col in 0..m.ncols() {
3777 for row in 0..m.nrows() {
3778 let elem = Value::Scalar(m[[row, col]]);
3779 let result = call_function_value(&f, &[elem], io.as_deref_mut())?;
3780 match result {
3781 Value::Scalar(n) => flat.push(n),
3782 _ => {
3783 return Err(
3784 "arrayfun: function must return a scalar".to_string()
3785 );
3786 }
3787 }
3788 }
3789 }
3790 Ok(Value::Matrix(
3791 Array2::from_shape_vec((m.nrows(), m.ncols()), flat).unwrap(),
3792 ))
3793 }
3794 Value::Scalar(n) => {
3795 let elem = Value::Scalar(*n);
3796 let result = call_function_value(&f, &[elem], io.as_deref_mut())?;
3797 Ok(result)
3798 }
3799 _ => {
3800 Err("arrayfun: second argument must be a numeric matrix or scalar".to_string())
3801 }
3802 }
3803 }
3804 ("lower", 1) => match &args[0] {
3806 Value::Str(s) => Ok(Value::Str(s.to_lowercase())),
3807 Value::StringObj(s) => Ok(Value::StringObj(s.to_lowercase())),
3808 _ => Err("lower: argument must be a string".to_string()),
3809 },
3810 ("upper", 1) => match &args[0] {
3812 Value::Str(s) => Ok(Value::Str(s.to_uppercase())),
3813 Value::StringObj(s) => Ok(Value::StringObj(s.to_uppercase())),
3814 _ => Err("upper: argument must be a string".to_string()),
3815 },
3816 ("strtrim", 1) => match &args[0] {
3818 Value::Str(s) => Ok(Value::Str(s.trim().to_string())),
3819 Value::StringObj(s) => Ok(Value::StringObj(s.trim().to_string())),
3820 _ => Err("strtrim: argument must be a string".to_string()),
3821 },
3822 ("strrep", 3) => {
3824 let s = string_arg(&args[0], name, 1)?.to_string();
3825 let old = string_arg(&args[1], name, 2)?;
3826 let new = string_arg(&args[2], name, 3)?;
3827 let result = s.replace(old, new);
3828 match &args[0] {
3829 Value::StringObj(_) => Ok(Value::StringObj(result)),
3830 _ => Ok(Value::Str(result)),
3831 }
3832 }
3833 ("strcmp", 2) => {
3835 let a = string_arg(&args[0], name, 1)?;
3836 let b = string_arg(&args[1], name, 2)?;
3837 Ok(Value::Scalar(bool_to_f64(a == b)))
3838 }
3839 ("strcmpi", 2) => {
3841 let a = string_arg(&args[0], name, 1)?.to_lowercase();
3842 let b = string_arg(&args[1], name, 2)?.to_lowercase();
3843 Ok(Value::Scalar(bool_to_f64(a == b)))
3844 }
3845 ("disp", 1) => {
3847 use std::io::Write;
3848 let mode = get_display_fmt();
3849 let output = match &args[0] {
3850 Value::Str(s) | Value::StringObj(s) => format!("{s}\n"),
3851 v => match format_value_full(v, &mode) {
3852 Some(block) => format!("{block}\n\n"),
3853 None => format!("{}\n", format_value(v, get_display_base(), &mode)),
3854 },
3855 };
3856 match io {
3857 Some(ctx) => ctx.write_to_fd(1, &output)?,
3858 None => {
3859 print!("{output}");
3860 std::io::stdout().flush().ok();
3861 }
3862 }
3863 Ok(Value::Void)
3864 }
3865 ("sprintf", n) if n >= 1 => {
3867 let fmt = string_arg(&args[0], name, 1)?.to_string();
3868 let result = format_printf(&fmt, &args[1..])?;
3869 Ok(Value::Str(result))
3870 }
3871 ("fprintf", n) if n >= 1 => {
3873 let (fd, fmt_idx) = match &args[0] {
3875 Value::Scalar(n) => (*n as i32, 1),
3876 _ => (1, 0),
3877 };
3878 if fmt_idx >= args.len() {
3879 return Err("fprintf: missing format string".to_string());
3880 }
3881 let fmt = string_arg(&args[fmt_idx], name, fmt_idx + 1)?.to_string();
3882 let output = format_printf(&fmt, &args[fmt_idx + 1..])?;
3883 match io {
3884 Some(ctx) => ctx.write_to_fd(fd, &output)?,
3885 None => {
3886 if fd == 1 {
3888 use std::io::Write;
3889 print!("{output}");
3890 std::io::stdout().flush().ok();
3891 } else {
3892 return Err("fprintf: file I/O not available in this context".to_string());
3893 }
3894 }
3895 }
3896 Ok(Value::Void)
3897 }
3898 ("fopen", 2) => {
3900 let path = string_arg(&args[0], name, 1)?;
3901 let mode = string_arg(&args[1], name, 2)?;
3902 match io {
3903 Some(ctx) => Ok(Value::Scalar(ctx.fopen(path, mode) as f64)),
3904 None => Err("fopen: file I/O not available in this context".to_string()),
3905 }
3906 }
3907 ("fclose", 1) => match &args[0] {
3909 Value::Str(s) if s == "all" => {
3910 if let Some(ctx) = io {
3911 ctx.fclose_all();
3912 }
3913 Ok(Value::Scalar(0.0))
3914 }
3915 _ => {
3916 let fd = scalar_arg(&args[0], name, 1)? as i32;
3917 match io {
3918 Some(ctx) => Ok(Value::Scalar(ctx.fclose(fd) as f64)),
3919 None => Err("fclose: file I/O not available in this context".to_string()),
3920 }
3921 }
3922 },
3923 ("fgetl", 1) => {
3925 let fd = scalar_arg(&args[0], name, 1)? as i32;
3926 match io {
3927 Some(ctx) => match ctx.fgetl(fd) {
3928 Some(line) => Ok(Value::Str(line)),
3929 None => Ok(Value::Scalar(-1.0)),
3930 },
3931 None => Err("fgetl: file I/O not available in this context".to_string()),
3932 }
3933 }
3934 ("fgets", 1) => {
3936 let fd = scalar_arg(&args[0], name, 1)? as i32;
3937 match io {
3938 Some(ctx) => match ctx.fgets(fd) {
3939 Some(line) => Ok(Value::Str(line)),
3940 None => Ok(Value::Scalar(-1.0)),
3941 },
3942 None => Err("fgets: file I/O not available in this context".to_string()),
3943 }
3944 }
3945 ("isfile", 1) => {
3947 let path = string_arg(&args[0], name, 1)?;
3948 let is_file = std::fs::metadata(path)
3949 .map(|m| m.is_file())
3950 .unwrap_or(false);
3951 Ok(Value::Scalar(bool_to_f64(is_file)))
3952 }
3953 ("isfolder", 1) => {
3955 let path = string_arg(&args[0], name, 1)?;
3956 let is_dir = std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false);
3957 Ok(Value::Scalar(bool_to_f64(is_dir)))
3958 }
3959 ("genpath", 1) => {
3961 let root = string_arg(&args[0], name, 1)?;
3962 let sep = if cfg!(windows) { ';' } else { ':' };
3963 let mut dirs: Vec<String> = Vec::new();
3964 let mut stack = vec![std::path::PathBuf::from(root)];
3965 while let Some(dir) = stack.pop() {
3966 if !dir.is_dir() {
3967 continue;
3968 }
3969 dirs.push(dir.to_string_lossy().into_owned());
3970 if let Ok(entries) = std::fs::read_dir(&dir) {
3971 let mut children: Vec<std::path::PathBuf> = entries
3972 .filter_map(|e| e.ok())
3973 .map(|e| e.path())
3974 .filter(|p| p.is_dir())
3975 .collect();
3976 children.sort();
3977 children.reverse();
3978 stack.extend(children);
3979 }
3980 }
3981 Ok(Value::Str(dirs.join(&sep.to_string())))
3982 }
3983 ("pwd", _) => {
3985 let cwd = std::env::current_dir()
3986 .map(|p| p.to_string_lossy().into_owned())
3987 .unwrap_or_default();
3988 Ok(Value::Str(cwd))
3989 }
3990 ("exist", 1) => {
3992 let name_arg = string_arg(&args[0], name, 1)?;
3993 if env.contains_key(name_arg) {
3994 Ok(Value::Scalar(1.0))
3995 } else if std::path::Path::new(name_arg).is_file() {
3996 Ok(Value::Scalar(2.0))
3997 } else {
3998 Ok(Value::Scalar(0.0))
3999 }
4000 }
4001 ("exist", 2) => {
4003 let name_arg = string_arg(&args[0], name, 1)?;
4004 let kind = string_arg(&args[1], name, 2)?;
4005 match kind {
4006 "var" => Ok(Value::Scalar(if env.contains_key(name_arg) {
4007 1.0
4008 } else {
4009 0.0
4010 })),
4011 "file" => Ok(Value::Scalar(if std::path::Path::new(name_arg).is_file() {
4012 2.0
4013 } else {
4014 0.0
4015 })),
4016 other => Err(format!(
4017 "exist: unknown type '{other}', expected 'var' or 'file'"
4018 )),
4019 }
4020 }
4021 ("dlmread", 1) => {
4023 let path = string_arg(&args[0], name, 1)?.to_string();
4024 dlmread_impl(&path, None)
4025 }
4026 ("dlmread", 2) => {
4027 let path = string_arg(&args[0], name, 1)?.to_string();
4028 let delim = interpret_delim(string_arg(&args[1], name, 2)?);
4029 dlmread_impl(&path, Some(delim))
4030 }
4031 ("dlmwrite", 2) => {
4033 let path = string_arg(&args[0], name, 1)?.to_string();
4034 dlmwrite_impl(&path, &args[1], None)
4035 }
4036 ("dlmwrite", 3) => {
4037 let path = string_arg(&args[0], name, 1)?.to_string();
4038 let delim = interpret_delim(string_arg(&args[2], name, 3)?);
4039 dlmwrite_impl(&path, &args[1], Some(delim))
4040 }
4041 ("readmatrix", n) if n == 1 || n == 3 => {
4043 let path = string_arg(&args[0], name, 1)?.to_string();
4044 let delim = parse_delimiter_opt(name, args, 1)?;
4045 readmatrix_impl(&path, delim)
4046 }
4047 ("readtable", n) if n == 1 || n == 3 => {
4049 let path = string_arg(&args[0], name, 1)?.to_string();
4050 let delim = parse_delimiter_opt(name, args, 1)?;
4051 readtable_impl(&path, delim)
4052 }
4053 ("writetable", n) if n == 2 || n == 4 => {
4055 let path = string_arg(&args[1], name, 2)?.to_string();
4056 let delim = parse_delimiter_opt(name, args, 2)?;
4057 writetable_impl(&args[0], &path, delim)
4058 }
4059 ("xor", 2) => {
4061 let a = &args[0];
4062 let b = &args[1];
4063 match (a, b) {
4064 (Value::Scalar(x), Value::Scalar(y)) => {
4065 Ok(Value::Scalar(bool_to_f64((*x != 0.0) ^ (*y != 0.0))))
4066 }
4067 (Value::Matrix(mx), Value::Matrix(my)) => {
4068 if mx.shape() != my.shape() {
4069 return Err("xor: matrices must have the same dimensions".to_string());
4070 }
4071 Ok(Value::Matrix(ndarray::Zip::from(mx).and(my).map_collect(
4072 |a, b| bool_to_f64((*a != 0.0) ^ (*b != 0.0)),
4073 )))
4074 }
4075 (Value::Scalar(s), Value::Matrix(m)) => {
4076 let sv = *s != 0.0;
4077 Ok(Value::Matrix(m.mapv(|x| bool_to_f64(sv ^ (x != 0.0)))))
4078 }
4079 (Value::Matrix(m), Value::Scalar(s)) => {
4080 let sv = *s != 0.0;
4081 Ok(Value::Matrix(m.mapv(|x| bool_to_f64((x != 0.0) ^ sv))))
4082 }
4083 _ => Err("xor: arguments must be numeric".to_string()),
4084 }
4085 }
4086 ("not", 1) => apply_elem(&args[0], |x| if x == 0.0 { 1.0 } else { 0.0 }),
4088 ("int2str", 1) => match &args[0] {
4090 Value::Scalar(n) => Ok(Value::Str(format!("{}", n.round() as i64))),
4091 Value::Matrix(m) => {
4092 let parts: Vec<String> =
4093 m.iter().map(|x| format!("{}", x.round() as i64)).collect();
4094 Ok(Value::Str(parts.join(" ")))
4095 }
4096 _ => Err("int2str: argument must be numeric".to_string()),
4097 },
4098 ("mat2str", 1) => match &args[0] {
4100 Value::Scalar(n) => Ok(Value::Str(format!("{n}"))),
4101 Value::Matrix(m) => {
4102 if m.nrows() == 0 || m.ncols() == 0 {
4103 return Ok(Value::Str("[]".to_string()));
4104 }
4105 let mut s = String::from("[");
4106 for (r, row) in m.rows().into_iter().enumerate() {
4107 if r > 0 {
4108 s.push(';');
4109 }
4110 for (c, val) in row.iter().enumerate() {
4111 if c > 0 {
4112 s.push(' ');
4113 }
4114 s.push_str(&format!("{val}"));
4115 }
4116 }
4117 s.push(']');
4118 Ok(Value::Str(s))
4119 }
4120 _ => Err("mat2str: argument must be numeric".to_string()),
4121 },
4122 ("strsplit", 2) => {
4124 let s = string_arg(&args[0], name, 1)?.to_string();
4125 let delim = string_arg(&args[1], name, 2)?.to_string();
4126 let parts: Vec<Value> = s
4127 .split(delim.as_str())
4128 .map(|p| Value::Str(p.to_string()))
4129 .collect();
4130 Ok(Value::Cell(parts))
4131 }
4132 ("strsplit", 1) => {
4134 let s = string_arg(&args[0], name, 1)?.to_string();
4135 let parts: Vec<Value> = s
4136 .split_whitespace()
4137 .map(|p| Value::Str(p.to_string()))
4138 .collect();
4139 Ok(Value::Cell(parts))
4140 }
4141 ("error", _) if !args.is_empty() => {
4143 let fmt_str = match &args[0] {
4144 Value::Str(s) | Value::StringObj(s) => s.clone(),
4145 _ => return Err("error: first argument must be a format string".to_string()),
4146 };
4147 let msg = format_printf(&fmt_str, &args[1..])?;
4148 Err(msg)
4149 }
4150 ("warning", _) if !args.is_empty() => {
4152 let fmt_str = match &args[0] {
4153 Value::Str(s) | Value::StringObj(s) => s.clone(),
4154 _ => return Err("warning: first argument must be a format string".to_string()),
4155 };
4156 let msg = format_printf(&fmt_str, &args[1..])?;
4157 eprintln!("warning: {msg}");
4158 Ok(Value::Void)
4159 }
4160 ("lasterr", 0) => Ok(Value::Str(get_last_err())),
4162 ("lasterr", 1) => {
4163 let prev = get_last_err();
4164 let new_msg = match &args[0] {
4165 Value::Str(s) | Value::StringObj(s) => s.clone(),
4166 _ => return Err("lasterr: argument must be a string".to_string()),
4167 };
4168 set_last_err(&new_msg);
4169 Ok(Value::Str(prev))
4170 }
4171 ("pcall", _) if !args.is_empty() => {
4173 let callable = args[0].clone();
4174 let call_args = &args[1..];
4175 let result = match &callable {
4176 Value::Lambda(f) => {
4177 let f = f.clone();
4178 f.0(call_args, io)
4179 }
4180 Value::Function { .. } => match io {
4181 Some(io_ref) => FN_CALL_HOOK.with(|c| match c.get() {
4182 Some(hook) => hook("<pcall>", &callable, call_args, env, io_ref),
4183 None => Err("pcall: function execution not initialized".to_string()),
4184 }),
4185 None => {
4186 let mut tmp_io = IoContext::new();
4187 FN_CALL_HOOK.with(|c| match c.get() {
4188 Some(hook) => hook("<pcall>", &callable, call_args, env, &mut tmp_io),
4189 None => Err("pcall: function execution not initialized".to_string()),
4190 })
4191 }
4192 },
4193 _ => {
4194 return Err(
4195 "pcall: first argument must be a function handle (@func)".to_string()
4196 );
4197 }
4198 };
4199 match result {
4200 Ok(v) => Ok(Value::Tuple(vec![Value::Scalar(1.0), v])),
4201 Err(msg) => {
4202 set_last_err(&msg);
4203 Ok(Value::Tuple(vec![Value::Scalar(0.0), Value::Str(msg)]))
4204 }
4205 }
4206 }
4207 ("eig", 1) => match &args[0] {
4211 Value::Scalar(n) => {
4212 if get_nargout() <= 1 {
4213 Ok(Value::Matrix(
4214 Array2::from_shape_vec((1, 1), vec![*n]).unwrap(),
4215 ))
4216 } else {
4217 Ok(Value::Tuple(vec![
4218 Value::Matrix(Array2::eye(1)),
4219 Value::Matrix(Array2::from_elem((1, 1), *n)),
4220 ]))
4221 }
4222 }
4223 Value::Matrix(m) => {
4224 let (evals, evecs) = eig_compute(m)?;
4225 let nn = evals.len();
4226 if get_nargout() <= 1 {
4227 let col: Vec<f64> = evals;
4228 Ok(Value::Matrix(Array2::from_shape_vec((nn, 1), col).unwrap()))
4229 } else {
4230 let mut d = Array2::<f64>::zeros((nn, nn));
4231 for (i, &e) in evals.iter().enumerate() {
4232 d[[i, i]] = e;
4233 }
4234 Ok(Value::Tuple(vec![Value::Matrix(evecs), Value::Matrix(d)]))
4235 }
4236 }
4237 _ => Err("eig: argument must be a real numeric matrix".to_string()),
4238 },
4239
4240 ("svd", 1) => match &args[0] {
4243 Value::Scalar(n) => {
4244 let sv = n.abs();
4245 if get_nargout() <= 1 {
4246 Ok(Value::Matrix(
4247 Array2::from_shape_vec((1, 1), vec![sv]).unwrap(),
4248 ))
4249 } else {
4250 Ok(Value::Tuple(vec![
4251 Value::Matrix(Array2::eye(1)),
4252 Value::Matrix(Array2::from_elem((1, 1), sv)),
4253 Value::Matrix(Array2::eye(1)),
4254 ]))
4255 }
4256 }
4257 Value::Matrix(m) => {
4258 let mm = m.nrows();
4259 let nn = m.ncols();
4260 let (u_c, s_v, v_c) = svd_compute(m)?;
4261 let k = s_v.len();
4262 if get_nargout() <= 1 {
4263 let col: Vec<f64> = s_v;
4264 Ok(Value::Matrix(Array2::from_shape_vec((k, 1), col).unwrap()))
4265 } else {
4266 let u_full = complete_orthonormal_basis(&u_c);
4268 let mut s_mat = Array2::<f64>::zeros((mm, nn));
4269 for (i, &sv) in s_v.iter().enumerate() {
4270 s_mat[[i, i]] = sv;
4271 }
4272 Ok(Value::Tuple(vec![
4273 Value::Matrix(u_full),
4274 Value::Matrix(s_mat),
4275 Value::Matrix(v_c),
4276 ]))
4277 }
4278 }
4279 _ => Err("svd: argument must be a real numeric matrix".to_string()),
4280 },
4281 ("svd", 2) => match (&args[0], &args[1]) {
4282 (Value::Matrix(m), Value::Str(opt) | Value::StringObj(opt)) if opt == "econ" => {
4283 let (u_c, s_v, v_c) = svd_compute(m)?;
4284 let k = s_v.len();
4285 let mut s_mat = Array2::<f64>::zeros((k, k));
4286 for (i, &sv) in s_v.iter().enumerate() {
4287 s_mat[[i, i]] = sv;
4288 }
4289 Ok(Value::Tuple(vec![
4290 Value::Matrix(u_c),
4291 Value::Matrix(s_mat),
4292 Value::Matrix(v_c),
4293 ]))
4294 }
4295 _ => Err("svd: expected svd(A, 'econ')".to_string()),
4296 },
4297
4298 ("lu", 1) => match &args[0] {
4300 Value::Scalar(n) => {
4301 if get_nargout() <= 1 {
4302 Ok(Value::Scalar(*n))
4303 } else {
4304 Ok(Value::Tuple(vec![
4305 Value::Matrix(Array2::eye(1)),
4306 Value::Matrix(Array2::from_elem((1, 1), *n)),
4307 Value::Matrix(Array2::eye(1)),
4308 ]))
4309 }
4310 }
4311 Value::Matrix(m) => {
4312 let (l, u, p) = lu_decompose(m)?;
4313 if get_nargout() <= 1 {
4314 Ok(Value::Matrix(u))
4315 } else {
4316 Ok(Value::Tuple(vec![
4317 Value::Matrix(l),
4318 Value::Matrix(u),
4319 Value::Matrix(p),
4320 ]))
4321 }
4322 }
4323 _ => Err("lu: argument must be a real numeric matrix".to_string()),
4324 },
4325
4326 ("qr", 1) => match &args[0] {
4328 Value::Scalar(n) => {
4329 if get_nargout() <= 1 {
4330 Ok(Value::Scalar(*n))
4331 } else {
4332 Ok(Value::Tuple(vec![
4333 Value::Matrix(Array2::from_elem(
4334 (1, 1),
4335 if *n >= 0.0 { 1.0 } else { -1.0 },
4336 )),
4337 Value::Matrix(Array2::from_elem((1, 1), n.abs())),
4338 ]))
4339 }
4340 }
4341 Value::Matrix(m) => {
4342 let (q, r) = qr_decompose(m)?;
4343 if get_nargout() <= 1 {
4344 Ok(Value::Matrix(r))
4345 } else {
4346 Ok(Value::Tuple(vec![Value::Matrix(q), Value::Matrix(r)]))
4347 }
4348 }
4349 _ => Err("qr: argument must be a real numeric matrix".to_string()),
4350 },
4351
4352 ("chol", 1) => match &args[0] {
4354 Value::Scalar(n) => {
4355 if *n < 0.0 {
4356 Err("chol: value is not positive definite".to_string())
4357 } else {
4358 Ok(Value::Scalar(n.sqrt()))
4359 }
4360 }
4361 Value::Matrix(m) => Ok(Value::Matrix(chol_decompose(m)?)),
4362 _ => Err("chol: argument must be a real numeric matrix".to_string()),
4363 },
4364
4365 ("rank", 1) => match &args[0] {
4367 Value::Scalar(x) => Ok(Value::Scalar(if x.abs() > 1e-15 { 1.0 } else { 0.0 })),
4368 Value::Matrix(m) => {
4369 let (_, s_v, _) = svd_compute(m)?;
4370 let tol = (m.nrows().max(m.ncols())) as f64
4371 * s_v.first().copied().unwrap_or(0.0)
4372 * f64::EPSILON
4373 * 2.0;
4374 let r = s_v.iter().filter(|&&s| s > tol).count();
4375 Ok(Value::Scalar(r as f64))
4376 }
4377 _ => Err("rank: argument must be a real numeric matrix".to_string()),
4378 },
4379
4380 ("null", 1) => match &args[0] {
4382 Value::Scalar(_) => Ok(Value::Matrix(Array2::zeros((1, 0)))),
4383 Value::Matrix(m) => {
4384 let nn = m.ncols();
4385 let (_, s_v, v_c) = svd_compute(m)?;
4386 let tol = (m.nrows().max(nn)) as f64
4387 * s_v.first().copied().unwrap_or(0.0)
4388 * f64::EPSILON
4389 * 2.0;
4390 let r = s_v.iter().filter(|&&s| s > tol).count();
4391 let null_k = nn.saturating_sub(r);
4392 if null_k == 0 {
4393 return Ok(Value::Matrix(Array2::zeros((nn, 0))));
4394 }
4395 let mut result = Array2::<f64>::zeros((nn, null_k));
4396 for j in 0..null_k {
4397 let col_idx = r + j;
4398 if col_idx < v_c.ncols() {
4399 for i in 0..nn {
4400 result[[i, j]] = v_c[[i, col_idx]];
4401 }
4402 }
4403 }
4404 Ok(Value::Matrix(result))
4405 }
4406 _ => Err("null: argument must be a real numeric matrix".to_string()),
4407 },
4408
4409 ("orth", 1) => match &args[0] {
4411 Value::Scalar(x) => {
4412 if x.abs() > 1e-15 {
4413 Ok(Value::Matrix(Array2::from_elem((1, 1), 1.0)))
4414 } else {
4415 Ok(Value::Matrix(Array2::zeros((1, 0))))
4416 }
4417 }
4418 Value::Matrix(m) => {
4419 let mm = m.nrows();
4420 let (u_c, s_v, _) = svd_compute(m)?;
4421 let tol = (mm.max(m.ncols())) as f64
4422 * s_v.first().copied().unwrap_or(0.0)
4423 * f64::EPSILON
4424 * 2.0;
4425 let r = s_v.iter().filter(|&&s| s > tol).count();
4426 if r == 0 {
4427 return Ok(Value::Matrix(Array2::zeros((mm, 0))));
4428 }
4429 let mut result = Array2::<f64>::zeros((mm, r));
4430 for j in 0..r {
4431 if j < u_c.ncols() {
4432 for i in 0..mm {
4433 result[[i, j]] = u_c[[i, j]];
4434 }
4435 }
4436 }
4437 Ok(Value::Matrix(result))
4438 }
4439 _ => Err("orth: argument must be a real numeric matrix".to_string()),
4440 },
4441
4442 ("cond", 1) => match &args[0] {
4444 Value::Scalar(x) => {
4445 if x.abs() < 1e-15 {
4446 Ok(Value::Scalar(f64::INFINITY))
4447 } else {
4448 Ok(Value::Scalar(1.0))
4449 }
4450 }
4451 Value::Matrix(m) => {
4452 let (_, s_v, _) = svd_compute(m)?;
4453 if s_v.is_empty() {
4454 return Ok(Value::Scalar(1.0));
4455 }
4456 let s_max = s_v[0];
4457 let s_min = *s_v.last().unwrap();
4458 Ok(Value::Scalar(if s_min < 1e-15 {
4459 f64::INFINITY
4460 } else {
4461 s_max / s_min
4462 }))
4463 }
4464 _ => Err("cond: argument must be a real numeric matrix".to_string()),
4465 },
4466
4467 ("pinv", 1) => match &args[0] {
4469 Value::Scalar(x) => Ok(Value::Scalar(if x.abs() < 1e-15 { 0.0 } else { 1.0 / x })),
4470 Value::Matrix(m) => {
4471 let mm = m.nrows();
4472 let nn = m.ncols();
4473 let (u_c, s_v, v_c) = svd_compute(m)?;
4474 let k = s_v.len();
4475 let tol =
4476 (mm.max(nn)) as f64 * s_v.first().copied().unwrap_or(0.0) * f64::EPSILON * 2.0;
4477 let mut result = Array2::<f64>::zeros((nn, mm));
4479 for j in 0..k {
4480 if s_v[j] > tol {
4481 let inv_s = 1.0 / s_v[j];
4482 for r in 0..nn {
4483 for c in 0..mm {
4484 result[[r, c]] += v_c[[r, j]] * inv_s * u_c[[c, j]];
4485 }
4486 }
4487 }
4488 }
4489 Ok(Value::Matrix(result))
4490 }
4491 _ => Err("pinv: argument must be a real numeric matrix".to_string()),
4492 },
4493
4494 ("jsondecode", 1) => jsondecode_impl(&args[0]),
4496 ("jsonencode", 1) => jsonencode_impl(&args[0]),
4497
4498 ("load", 1) => {
4500 let path = match &args[0] {
4501 Value::Str(s) | Value::StringObj(s) => s.clone(),
4502 _ => return Err("load: argument must be a string path".to_string()),
4503 };
4504 if !path.ends_with(".mat") {
4505 return Err("load: use bare 'load path' syntax for non-.mat files".to_string());
4506 }
4507 load_mat_file(&path)
4508 }
4509
4510 ("assert", 1) => {
4512 let truthy = match &args[0] {
4513 Value::Scalar(n) => *n != 0.0 && !n.is_nan(),
4514 Value::Matrix(m) => m.iter().all(|&x| x != 0.0 && !x.is_nan()),
4515 Value::Complex(re, im) => *re != 0.0 || *im != 0.0,
4516 Value::Str(s) | Value::StringObj(s) => !s.is_empty(),
4517 _ => false,
4518 };
4519 if truthy {
4520 Ok(Value::Void)
4521 } else {
4522 Err("assert: condition is false".to_string())
4523 }
4524 }
4525
4526 ("assert", 2) => assert_values_equal(&args[0], &args[1], None),
4528
4529 ("assert", 3) => {
4531 let tol = match &args[2] {
4532 Value::Scalar(t) => *t,
4533 _ => return Err("assert: tolerance must be a scalar".to_string()),
4534 };
4535 assert_values_equal(&args[0], &args[1], Some(tol))
4536 }
4537
4538 _ => {
4539 let hint = suggest_similar(name, env);
4540 match hint {
4541 Some(s) => Err(format!("Unknown function '{name}'; did you mean '{s}'?")),
4542 None => Err(format!("Unknown function: '{name}'")),
4543 }
4544 }
4545 }
4546}
4547
4548fn interpret_delim(s: &str) -> String {
4551 match s {
4552 r"\t" => "\t".to_string(),
4553 r"\n" => "\n".to_string(),
4554 other => other.to_string(),
4555 }
4556}
4557
4558fn delim_consistent(lines: &[&str], delim: char) -> bool {
4560 let counts: Vec<usize> = lines.iter().map(|l| l.split(delim).count()).collect();
4561 counts.iter().all(|&c| c > 1) && counts.windows(2).all(|w| w[0] == w[1])
4562}
4563
4564fn dlmread_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
4566 let content =
4567 std::fs::read_to_string(path).map_err(|e| format!("dlmread: cannot read '{path}': {e}"))?;
4568
4569 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
4570
4571 if lines.is_empty() {
4572 return Ok(Value::Matrix(Array2::zeros((0, 0))));
4573 }
4574
4575 let delim: Option<String> = match explicit_delim {
4577 Some(d) => Some(d),
4578 None => {
4579 if delim_consistent(&lines, ',') {
4580 Some(",".to_string())
4581 } else if delim_consistent(&lines, '\t') {
4582 Some("\t".to_string())
4583 } else {
4584 None }
4586 }
4587 };
4588
4589 let mut rows: Vec<Vec<f64>> = Vec::new();
4590 for (line_num, line) in lines.iter().enumerate() {
4591 let fields: Vec<&str> = match &delim {
4592 Some(d) => line.split(d.as_str()).collect(),
4593 None => line.split_whitespace().collect(),
4594 };
4595 let mut row_vals: Vec<f64> = Vec::with_capacity(fields.len());
4596 for field in &fields {
4597 let trimmed = field.trim();
4598 if trimmed.is_empty() {
4599 row_vals.push(0.0);
4600 } else {
4601 row_vals.push(trimmed.parse::<f64>().map_err(|_| {
4602 format!(
4603 "dlmread: non-numeric value '{trimmed}' on line {}",
4604 line_num + 1
4605 )
4606 })?);
4607 }
4608 }
4609 if !row_vals.is_empty() {
4610 rows.push(row_vals);
4611 }
4612 }
4613
4614 if rows.is_empty() {
4615 return Ok(Value::Matrix(Array2::zeros((0, 0))));
4616 }
4617
4618 let ncols = rows[0].len();
4619 for (i, row) in rows.iter().enumerate() {
4620 if row.len() != ncols {
4621 return Err(format!(
4622 "dlmread: row {} has {} fields, expected {ncols}",
4623 i + 1,
4624 row.len()
4625 ));
4626 }
4627 }
4628
4629 let nrows = rows.len();
4630 let flat: Vec<f64> = rows.into_iter().flatten().collect();
4631 Array2::from_shape_vec((nrows, ncols), flat)
4632 .map_err(|e| format!("dlmread: shape error: {e}"))
4633 .map(Value::Matrix)
4634}
4635
4636fn fmt_dlm_number(n: f64) -> String {
4639 if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
4640 format!("{}", n as i64)
4641 } else {
4642 format!("{n}")
4643 }
4644}
4645
4646fn dlmwrite_impl(path: &str, val: &Value, explicit_delim: Option<String>) -> Result<Value, String> {
4648 let delim = explicit_delim.unwrap_or_else(|| ",".to_string());
4649
4650 let content = match val {
4651 Value::Scalar(n) => format!("{}\n", fmt_dlm_number(*n)),
4652 Value::Matrix(m) => {
4653 let mut out = String::new();
4654 for row in m.rows() {
4655 let parts: Vec<String> = row.iter().map(|n| fmt_dlm_number(*n)).collect();
4656 out.push_str(&parts.join(&delim));
4657 out.push('\n');
4658 }
4659 out
4660 }
4661 _ => {
4662 return Err("dlmwrite: second argument must be a numeric scalar or matrix".to_string());
4663 }
4664 };
4665
4666 std::fs::write(path, content).map_err(|e| format!("dlmwrite: cannot write '{path}': {e}"))?;
4667 Ok(Value::Void)
4668}
4669
4670fn auto_detect_delim(lines: &[&str]) -> Option<String> {
4676 let comma_counts: Vec<usize> = lines.iter().map(|l| split_csv_row(l, ",").len()).collect();
4678 if comma_counts.iter().all(|&c| c > 1) && comma_counts.windows(2).all(|w| w[0] == w[1]) {
4679 return Some(",".to_string());
4680 }
4681 if delim_consistent(lines, '\t') {
4682 Some("\t".to_string())
4683 } else {
4684 None
4685 }
4686}
4687
4688fn split_csv_row(line: &str, delim: &str) -> Vec<String> {
4692 if delim.chars().count() != 1 {
4693 return line.split(delim).map(str::to_string).collect();
4694 }
4695 let delim_char = delim.chars().next().unwrap();
4696 let chars: Vec<char> = line.chars().collect();
4697 let mut fields: Vec<String> = Vec::new();
4698 let mut field = String::new();
4699 let mut i = 0;
4700 let mut in_quotes = false;
4701 while i < chars.len() {
4702 let c = chars[i];
4703 if in_quotes {
4704 if c == '"' && i + 1 < chars.len() && chars[i + 1] == '"' {
4705 field.push('"');
4706 i += 2;
4707 continue;
4708 } else if c == '"' {
4709 in_quotes = false;
4710 } else {
4711 field.push(c);
4712 }
4713 } else if c == '"' {
4714 in_quotes = true;
4715 } else if c == delim_char {
4716 fields.push(std::mem::take(&mut field));
4717 } else {
4718 field.push(c);
4719 }
4720 i += 1;
4721 }
4722 fields.push(field);
4723 fields
4724}
4725
4726fn split_csv_row_opt(line: &str, delim: &Option<String>) -> Vec<String> {
4728 match delim {
4729 None => line.split_whitespace().map(str::to_string).collect(),
4730 Some(d) => split_csv_row(line, d),
4731 }
4732}
4733
4734fn row_is_header(fields: &[String]) -> bool {
4736 fields
4737 .iter()
4738 .any(|f| !f.trim().is_empty() && f.trim().parse::<f64>().is_err())
4739}
4740
4741fn sanitize_header(s: &str, col_1based: usize) -> String {
4745 let s = s.trim();
4746 if s.is_empty() {
4747 return format!("x{col_1based}");
4748 }
4749 let mut out = String::new();
4750 for c in s.chars() {
4751 if c.is_alphanumeric() || c == '_' {
4752 out.push(c);
4753 } else if !out.ends_with('_') {
4754 out.push('_');
4755 }
4756 }
4757 let out = out.trim_end_matches('_').to_string();
4758 if out.is_empty() {
4759 return format!("x{col_1based}");
4760 }
4761 if out.chars().next().unwrap().is_ascii_digit() {
4762 format!("x{out}")
4763 } else {
4764 out
4765 }
4766}
4767
4768fn deduplicate_headers(headers: Vec<String>) -> Vec<String> {
4771 let mut count: HashMap<String, usize> = HashMap::new();
4772 for h in &headers {
4773 *count.entry(h.clone()).or_insert(0) += 1;
4774 }
4775 let mut seen: HashMap<String, usize> = HashMap::new();
4776 headers
4777 .into_iter()
4778 .map(|h| {
4779 if *count.get(&h).unwrap() == 1 {
4780 h
4781 } else {
4782 let idx = seen.entry(h.clone()).or_insert(0);
4783 *idx += 1;
4784 format!("{h}_{idx}")
4785 }
4786 })
4787 .collect()
4788}
4789
4790fn parse_delimiter_opt(
4793 fn_name: &str,
4794 args: &[Value],
4795 start: usize,
4796) -> Result<Option<String>, String> {
4797 if args.len() <= start {
4798 return Ok(None);
4799 }
4800 let key = string_arg(&args[start], fn_name, start + 1)?;
4801 if !key.eq_ignore_ascii_case("delimiter") {
4802 return Err(format!(
4803 "{fn_name}: expected 'Delimiter' option at argument {}, got '{key}'",
4804 start + 1
4805 ));
4806 }
4807 if args.len() <= start + 1 {
4808 return Err(format!("{fn_name}: 'Delimiter' option requires a value"));
4809 }
4810 let val = interpret_delim(string_arg(&args[start + 1], fn_name, start + 2)?);
4811 Ok(Some(val))
4812}
4813
4814fn readmatrix_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
4820 let content = std::fs::read_to_string(path)
4821 .map_err(|e| format!("readmatrix: cannot read '{path}': {e}"))?;
4822
4823 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
4824 if lines.is_empty() {
4825 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
4826 }
4827
4828 let delim = match explicit_delim {
4829 Some(d) => Some(d),
4830 None => auto_detect_delim(&lines),
4831 };
4832
4833 let first_fields = split_csv_row_opt(lines[0], &delim);
4834 let skip_header = row_is_header(&first_fields);
4835 let data_lines = if skip_header { &lines[1..] } else { &lines[..] };
4836
4837 if data_lines.is_empty() {
4838 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
4839 }
4840
4841 let mut rows: Vec<Vec<f64>> = Vec::new();
4842 for (i, line) in data_lines.iter().enumerate() {
4843 let fields = split_csv_row_opt(line, &delim);
4844 let mut row: Vec<f64> = Vec::with_capacity(fields.len());
4845 for f in &fields {
4846 let t = f.trim();
4847 if t.is_empty() {
4848 row.push(f64::NAN);
4849 } else {
4850 row.push(t.parse::<f64>().map_err(|_| {
4851 format!(
4852 "readmatrix: non-numeric value '{t}' on line {}",
4853 i + 1 + usize::from(skip_header)
4854 )
4855 })?);
4856 }
4857 }
4858 rows.push(row);
4859 }
4860
4861 if rows.is_empty() {
4862 return Ok(Value::Matrix(Array2::<f64>::zeros((0, 0))));
4863 }
4864
4865 let ncols = rows[0].len();
4866 for (i, row) in rows.iter().enumerate() {
4867 if row.len() != ncols {
4868 return Err(format!(
4869 "readmatrix: row {} has {} fields, expected {ncols}",
4870 i + 1,
4871 row.len()
4872 ));
4873 }
4874 }
4875
4876 let nrows = rows.len();
4877 let flat: Vec<f64> = rows.into_iter().flatten().collect();
4878 Array2::from_shape_vec((nrows, ncols), flat)
4879 .map_err(|e| format!("readmatrix: shape error: {e}"))
4880 .map(Value::Matrix)
4881}
4882
4883fn readtable_impl(path: &str, explicit_delim: Option<String>) -> Result<Value, String> {
4889 let content = std::fs::read_to_string(path)
4890 .map_err(|e| format!("readtable: cannot read '{path}': {e}"))?;
4891
4892 let lines: Vec<&str> = content.lines().filter(|l| !l.trim().is_empty()).collect();
4893 if lines.is_empty() {
4894 return Ok(Value::Struct(IndexMap::new()));
4895 }
4896
4897 let delim = match explicit_delim {
4898 Some(d) => Some(d),
4899 None => auto_detect_delim(&lines),
4900 };
4901
4902 let raw_headers = split_csv_row_opt(lines[0], &delim);
4903 let ncols = raw_headers.len();
4904 let headers: Vec<String> = deduplicate_headers(
4905 raw_headers
4906 .iter()
4907 .enumerate()
4908 .map(|(i, h)| sanitize_header(h.trim(), i + 1))
4909 .collect(),
4910 );
4911
4912 let data_lines = &lines[1..];
4913 if data_lines.is_empty() {
4914 let mut s: IndexMap<String, Value> = IndexMap::new();
4915 for h in &headers {
4916 s.insert(h.clone(), Value::Matrix(Array2::<f64>::zeros((0, 1))));
4917 }
4918 return Ok(Value::Struct(s));
4919 }
4920
4921 let mut all_rows: Vec<Vec<String>> = Vec::new();
4922 for (i, line) in data_lines.iter().enumerate() {
4923 let fields = split_csv_row_opt(line, &delim);
4924 if fields.len() != ncols {
4925 return Err(format!(
4926 "readtable: row {} has {} fields, expected {ncols}",
4927 i + 2,
4928 fields.len()
4929 ));
4930 }
4931 all_rows.push(fields.into_iter().map(|f| f.trim().to_string()).collect());
4932 }
4933
4934 let nrows = all_rows.len();
4935 let mut s: IndexMap<String, Value> = IndexMap::new();
4936 for col in 0..ncols {
4937 let all_numeric = all_rows.iter().all(|row| {
4938 let t = row[col].as_str();
4939 t.is_empty() || t.parse::<f64>().is_ok()
4940 });
4941 if all_numeric {
4942 let vals: Vec<f64> = all_rows
4943 .iter()
4944 .map(|row| {
4945 let t = row[col].as_str();
4946 if t.is_empty() {
4947 f64::NAN
4948 } else {
4949 t.parse::<f64>().unwrap()
4950 }
4951 })
4952 .collect();
4953 let col_mat = Array2::from_shape_vec((nrows, 1), vals)
4954 .map_err(|e| format!("readtable: shape error: {e}"))?;
4955 s.insert(headers[col].clone(), Value::Matrix(col_mat));
4956 } else {
4957 let vals: Vec<Value> = all_rows
4958 .iter()
4959 .map(|row| Value::Str(row[col].clone()))
4960 .collect();
4961 s.insert(headers[col].clone(), Value::Cell(vals));
4962 }
4963 }
4964 Ok(Value::Struct(s))
4965}
4966
4967fn csv_quote_cell(s: &str, delim: &str) -> String {
4969 if s.contains('"') || s.contains('\n') || s.contains(delim) {
4970 let escaped = s.replace('"', "\"\"");
4971 format!("\"{escaped}\"")
4972 } else {
4973 s.to_string()
4974 }
4975}
4976
4977fn col_nrows(v: &Value) -> Option<usize> {
4982 match v {
4983 Value::Matrix(m) if m.ncols() == 1 || m.nrows() == 0 => Some(m.nrows()),
4984 Value::Cell(c) => Some(c.len()),
4985 Value::Scalar(_) => Some(1),
4986 Value::Str(_) | Value::StringObj(_) => Some(1),
4987 _ => None,
4988 }
4989}
4990
4991fn col_cell_str(v: &Value, row: usize, delim: &str) -> Result<String, String> {
4993 match v {
4994 Value::Matrix(m) => Ok(csv_quote_cell(&fmt_dlm_number(m[[row, 0]]), delim)),
4995 Value::Cell(c) => match &c[row] {
4996 Value::Str(s) | Value::StringObj(s) => Ok(csv_quote_cell(s, delim)),
4997 Value::Scalar(n) => Ok(csv_quote_cell(&fmt_dlm_number(*n), delim)),
4998 _ => Err(format!(
4999 "writetable: cell element at row {} has unsupported type",
5000 row + 1
5001 )),
5002 },
5003 Value::Scalar(n) => Ok(csv_quote_cell(&fmt_dlm_number(*n), delim)),
5004 Value::Str(s) | Value::StringObj(s) => Ok(csv_quote_cell(s, delim)),
5005 _ => Err(format!(
5006 "writetable: unsupported column type at row {}",
5007 row + 1
5008 )),
5009 }
5010}
5011
5012fn writetable_impl(
5018 tbl: &Value,
5019 path: &str,
5020 explicit_delim: Option<String>,
5021) -> Result<Value, String> {
5022 let delim = explicit_delim.unwrap_or_else(|| ",".to_string());
5023 let fields = match tbl {
5024 Value::Struct(m) => m,
5025 _ => return Err("writetable: first argument must be a struct".to_string()),
5026 };
5027 if fields.is_empty() {
5028 std::fs::write(path, "").map_err(|e| format!("writetable: cannot write '{path}': {e}"))?;
5029 return Ok(Value::Void);
5030 }
5031
5032 let nrows = {
5033 let (first_name, first_val) = fields.iter().next().unwrap();
5034 col_nrows(first_val).ok_or_else(|| {
5035 format!("writetable: column '{first_name}' must be a Matrix (N×1), Cell, or scalar")
5036 })?
5037 };
5038 for (cname, cval) in fields.iter() {
5039 let n = col_nrows(cval).ok_or_else(|| {
5040 format!("writetable: column '{cname}' must be a Matrix (N×1), Cell, or scalar")
5041 })?;
5042 if n != nrows {
5043 return Err(format!(
5044 "writetable: column '{cname}' has {n} rows, expected {nrows}"
5045 ));
5046 }
5047 }
5048
5049 let mut out = String::new();
5050 let header_parts: Vec<String> = fields.keys().map(|k| csv_quote_cell(k, &delim)).collect();
5051 out.push_str(&header_parts.join(&delim));
5052 out.push('\n');
5053
5054 for row in 0..nrows {
5055 let mut parts: Vec<String> = Vec::with_capacity(fields.len());
5056 for cval in fields.values() {
5057 parts.push(col_cell_str(cval, row, &delim)?);
5058 }
5059 out.push_str(&parts.join(&delim));
5060 out.push('\n');
5061 }
5062
5063 std::fs::write(path, out).map_err(|e| format!("writetable: cannot write '{path}': {e}"))?;
5064 Ok(Value::Void)
5065}
5066
5067fn to_bits(v: f64, fname: &str, pos: usize) -> Result<u64, String> {
5070 if v < 0.0 {
5071 return Err(format!(
5072 "{fname}: argument {pos} must be non-negative, got {v}"
5073 ));
5074 }
5075 if v.fract() != 0.0 {
5076 return Err(format!(
5077 "{fname}: argument {pos} must be an integer, got {v}"
5078 ));
5079 }
5080 if v > u64::MAX as f64 {
5081 return Err(format!(
5082 "{fname}: argument {pos} is too large for bitwise operations"
5083 ));
5084 }
5085 Ok(v as u64)
5086}
5087
5088fn det_matrix(m: &Array2<f64>) -> Result<f64, String> {
5092 let n = m.nrows();
5093 if m.ncols() != n {
5094 return Err("det: matrix must be square".to_string());
5095 }
5096 if n == 0 {
5097 return Ok(1.0);
5098 }
5099 let mut a = m.clone();
5100 let mut sign: f64 = 1.0;
5101 for col in 0..n {
5102 let pivot = (col..n)
5104 .max_by(|&r1, &r2| a[[r1, col]].abs().partial_cmp(&a[[r2, col]].abs()).unwrap())
5105 .unwrap();
5106 if a[[pivot, col]].abs() < 1e-15 {
5107 return Ok(0.0); }
5109 if pivot != col {
5110 for j in 0..n {
5111 let tmp = a[[pivot, j]];
5112 a[[pivot, j]] = a[[col, j]];
5113 a[[col, j]] = tmp;
5114 }
5115 sign = -sign;
5116 }
5117 let pv = a[[col, col]];
5118 for row in (col + 1)..n {
5119 let factor = a[[row, col]] / pv;
5120 for j in col..n {
5121 let val = a[[col, j]] * factor;
5122 a[[row, j]] -= val;
5123 }
5124 }
5125 }
5126 Ok(sign * (0..n).map(|i| a[[i, i]]).product::<f64>())
5127}
5128
5129fn inv_matrix(m: &Array2<f64>) -> Result<Array2<f64>, String> {
5132 let n = m.nrows();
5133 if m.ncols() != n {
5134 return Err("inv: matrix must be square".to_string());
5135 }
5136 let cols = 2 * n;
5137 let mut aug = vec![0.0f64; n * cols];
5138 for i in 0..n {
5139 for j in 0..n {
5140 aug[i * cols + j] = m[[i, j]];
5141 }
5142 aug[i * cols + n + i] = 1.0;
5143 }
5144 for col in 0..n {
5145 let pivot = (col..n)
5147 .max_by(|&r1, &r2| {
5148 aug[r1 * cols + col]
5149 .abs()
5150 .partial_cmp(&aug[r2 * cols + col].abs())
5151 .unwrap()
5152 })
5153 .filter(|&r| aug[r * cols + col].abs() > 1e-12)
5154 .ok_or_else(|| "inv: matrix is singular".to_string())?;
5155 if pivot != col {
5156 for j in 0..cols {
5157 aug.swap(col * cols + j, pivot * cols + j);
5158 }
5159 }
5160 let pv = aug[col * cols + col];
5161 for j in 0..cols {
5162 aug[col * cols + j] /= pv;
5163 }
5164 for row in 0..n {
5165 if row == col {
5166 continue;
5167 }
5168 let factor = aug[row * cols + col];
5169 for j in 0..cols {
5170 let val = aug[col * cols + j] * factor;
5171 aug[row * cols + j] -= val;
5172 }
5173 }
5174 }
5175 let mut result = Array2::<f64>::zeros((n, n));
5176 for i in 0..n {
5177 for j in 0..n {
5178 result[[i, j]] = aug[i * cols + n + j];
5179 }
5180 }
5181 Ok(result)
5182}
5183
5184fn solve_linear(a: &Array2<f64>, b: &Array2<f64>) -> Result<Array2<f64>, String> {
5189 let n = a.nrows();
5190 if a.ncols() != n {
5191 return Err(format!(
5192 "\\: coefficient matrix must be square, got {}×{}",
5193 n,
5194 a.ncols()
5195 ));
5196 }
5197 let k = b.ncols();
5198 if b.nrows() != n {
5199 return Err(format!(
5200 "\\: size mismatch — A is {}×{} but b has {} rows",
5201 n,
5202 n,
5203 b.nrows()
5204 ));
5205 }
5206 if n == 0 {
5207 return Ok(Array2::zeros((0, k)));
5208 }
5209 let cols = n + k;
5210 let mut aug = vec![0.0f64; n * cols];
5211 for i in 0..n {
5212 for j in 0..n {
5213 aug[i * cols + j] = a[[i, j]];
5214 }
5215 for j in 0..k {
5216 aug[i * cols + n + j] = b[[i, j]];
5217 }
5218 }
5219 for col in 0..n {
5220 let pivot = (col..n)
5221 .max_by(|&r1, &r2| {
5222 aug[r1 * cols + col]
5223 .abs()
5224 .partial_cmp(&aug[r2 * cols + col].abs())
5225 .unwrap()
5226 })
5227 .filter(|&r| aug[r * cols + col].abs() > 1e-12)
5228 .ok_or_else(|| "\\: matrix is singular or nearly singular".to_string())?;
5229 if pivot != col {
5230 for j in 0..cols {
5231 aug.swap(col * cols + j, pivot * cols + j);
5232 }
5233 }
5234 let pv = aug[col * cols + col];
5235 for j in col..cols {
5236 aug[col * cols + j] /= pv;
5237 }
5238 for row in 0..n {
5239 if row == col {
5240 continue;
5241 }
5242 let factor = aug[row * cols + col];
5243 if factor == 0.0 {
5244 continue;
5245 }
5246 for j in col..cols {
5247 let val = aug[col * cols + j] * factor;
5248 aug[row * cols + j] -= val;
5249 }
5250 }
5251 }
5252 let mut result = Array2::<f64>::zeros((n, k));
5253 for i in 0..n {
5254 for j in 0..k {
5255 result[[i, j]] = aug[i * cols + n + j];
5256 }
5257 }
5258 Ok(result)
5259}
5260
5261fn qr_decompose(a: &Array2<f64>) -> Result<(Array2<f64>, Array2<f64>), String> {
5270 let m = a.nrows();
5271 let n = a.ncols();
5272 let k = m.min(n);
5273 let mut r = a.clone();
5274 let mut q = Array2::<f64>::eye(m);
5275
5276 for j in 0..k {
5277 let col_len = m - j;
5278 let mut v: Vec<f64> = (j..m).map(|i| r[[i, j]]).collect();
5279
5280 let norm_x = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
5281 if norm_x < 1e-14 {
5282 continue;
5283 }
5284 v[0] += if v[0] >= 0.0 { norm_x } else { -norm_x };
5286 let v_sq: f64 = v.iter().map(|&x| x * x).sum();
5287 if v_sq < 1e-28 {
5288 continue;
5289 }
5290
5291 for col in j..n {
5293 let dot: f64 = (0..col_len).map(|i| v[i] * r[[j + i, col]]).sum();
5294 let fac = 2.0 * dot / v_sq;
5295 for i in 0..col_len {
5296 r[[j + i, col]] -= fac * v[i];
5297 }
5298 }
5299 for row in 0..m {
5301 let dot: f64 = (0..col_len).map(|i| q[[row, j + i]] * v[i]).sum();
5302 let fac = 2.0 * dot / v_sq;
5303 for i in 0..col_len {
5304 q[[row, j + i]] -= fac * v[i];
5305 }
5306 }
5307 }
5308
5309 Ok((q, r))
5310}
5311
5312type LuResult = Result<(Array2<f64>, Array2<f64>, Array2<f64>), String>;
5318fn lu_decompose(a: &Array2<f64>) -> LuResult {
5319 let n = a.nrows();
5320 if a.ncols() != n {
5321 return Err("lu: matrix must be square".to_string());
5322 }
5323 let mut u = a.clone();
5324 let mut l = Array2::<f64>::eye(n);
5325 let mut perm: Vec<usize> = (0..n).collect();
5326
5327 for j in 0..n {
5328 let pivot = (j..n)
5329 .max_by(|&r1, &r2| {
5330 u[[r1, j]]
5331 .abs()
5332 .partial_cmp(&u[[r2, j]].abs())
5333 .unwrap_or(std::cmp::Ordering::Equal)
5334 })
5335 .unwrap();
5336
5337 if pivot != j {
5338 for col in 0..n {
5339 let tmp = u[[j, col]];
5340 u[[j, col]] = u[[pivot, col]];
5341 u[[pivot, col]] = tmp;
5342 }
5343 for col in 0..j {
5344 let tmp = l[[j, col]];
5345 l[[j, col]] = l[[pivot, col]];
5346 l[[pivot, col]] = tmp;
5347 }
5348 perm.swap(j, pivot);
5349 }
5350
5351 if u[[j, j]].abs() < 1e-15 {
5352 continue;
5353 }
5354 for i in (j + 1)..n {
5355 l[[i, j]] = u[[i, j]] / u[[j, j]];
5356 for k in j..n {
5357 let val = l[[i, j]] * u[[j, k]];
5358 u[[i, k]] -= val;
5359 }
5360 }
5361 }
5362
5363 let mut p = Array2::<f64>::zeros((n, n));
5364 for (i, &j) in perm.iter().enumerate() {
5365 p[[i, j]] = 1.0;
5366 }
5367 Ok((l, u, p))
5368}
5369
5370fn chol_decompose(a: &Array2<f64>) -> Result<Array2<f64>, String> {
5375 let n = a.nrows();
5376 if a.ncols() != n {
5377 return Err("chol: matrix must be square".to_string());
5378 }
5379 let mut r = Array2::<f64>::zeros((n, n));
5380 for j in 0..n {
5381 let mut s = a[[j, j]];
5382 for k in 0..j {
5383 s -= r[[k, j]] * r[[k, j]];
5384 }
5385 if s <= 0.0 {
5386 return Err("chol: matrix is not positive definite".to_string());
5387 }
5388 r[[j, j]] = s.sqrt();
5389 for i in (j + 1)..n {
5390 let mut t = a[[j, i]];
5391 for k in 0..j {
5392 t -= r[[k, j]] * r[[k, i]];
5393 }
5394 r[[j, i]] = t / r[[j, j]];
5395 }
5396 }
5397 Ok(r)
5398}
5399
5400type SvdResult = Result<(Array2<f64>, Vec<f64>, Array2<f64>), String>;
5409fn svd_compute(a: &Array2<f64>) -> SvdResult {
5410 let m = a.nrows();
5411 let n = a.ncols();
5412 if m < n {
5413 let (v, s, u) = svd_compute(&a.t().to_owned())?;
5414 return Ok((u, s, v));
5415 }
5416 let k = n;
5418 let mut b = a.clone();
5419 let mut v = Array2::<f64>::eye(k);
5420
5421 const MAX_ITER: usize = 200;
5422 const EPS: f64 = 1e-14;
5423
5424 'outer: for _ in 0..MAX_ITER {
5425 let mut changed = false;
5426 for p in 0..k {
5427 for q in (p + 1)..k {
5428 let alpha: f64 = (0..m).map(|i| b[[i, p]] * b[[i, p]]).sum();
5429 let beta: f64 = (0..m).map(|i| b[[i, q]] * b[[i, q]]).sum();
5430 let gamma: f64 = (0..m).map(|i| b[[i, p]] * b[[i, q]]).sum();
5431
5432 if gamma.abs() <= EPS * (alpha * beta).sqrt() {
5433 continue;
5434 }
5435 changed = true;
5436
5437 let zeta = (beta - alpha) / (2.0 * gamma);
5438 let t = zeta.signum() / (zeta.abs() + (1.0 + zeta * zeta).sqrt());
5439 let c = 1.0 / (1.0 + t * t).sqrt();
5440 let s = c * t;
5441
5442 for i in 0..m {
5443 let bp = b[[i, p]];
5444 let bq = b[[i, q]];
5445 b[[i, p]] = c * bp - s * bq;
5446 b[[i, q]] = s * bp + c * bq;
5447 }
5448 for i in 0..k {
5449 let vp = v[[i, p]];
5450 let vq = v[[i, q]];
5451 v[[i, p]] = c * vp - s * vq;
5452 v[[i, q]] = s * vp + c * vq;
5453 }
5454 }
5455 }
5456 if !changed {
5457 break 'outer;
5458 }
5459 }
5460
5461 let mut sigma: Vec<f64> = (0..k)
5462 .map(|j| (0..m).map(|i| b[[i, j]] * b[[i, j]]).sum::<f64>().sqrt())
5463 .collect();
5464 let mut u_mat = Array2::<f64>::zeros((m, k));
5465 for j in 0..k {
5466 if sigma[j] > EPS {
5467 for i in 0..m {
5468 u_mat[[i, j]] = b[[i, j]] / sigma[j];
5469 }
5470 }
5471 }
5472
5473 let mut order: Vec<usize> = (0..k).collect();
5475 order.sort_by(|&a, &b| {
5476 sigma[b]
5477 .partial_cmp(&sigma[a])
5478 .unwrap_or(std::cmp::Ordering::Equal)
5479 });
5480 let sigma_s: Vec<f64> = order.iter().map(|&i| sigma[i]).collect();
5481 let mut u_s = Array2::<f64>::zeros((m, k));
5482 let mut v_s = Array2::<f64>::zeros((n, k));
5483 for (ni, &oi) in order.iter().enumerate() {
5484 for r in 0..m {
5485 u_s[[r, ni]] = u_mat[[r, oi]];
5486 }
5487 for r in 0..k {
5488 v_s[[r, ni]] = v[[r, oi]];
5489 }
5490 }
5491 sigma = sigma_s;
5492
5493 Ok((u_s, sigma, v_s))
5494}
5495
5496fn complete_orthonormal_basis(u: &Array2<f64>) -> Array2<f64> {
5501 let m = u.nrows();
5502 let k = u.ncols();
5503 let mut basis: Vec<Vec<f64>> = (0..k).map(|j| u.column(j).to_vec()).collect();
5504
5505 let mut ei = 0usize;
5506 while basis.len() < m && ei < m {
5507 let mut v: Vec<f64> = vec![0.0; m];
5508 v[ei] = 1.0;
5509 ei += 1;
5510 for b in &basis {
5511 let dot: f64 = v.iter().zip(b.iter()).map(|(&a, &b)| a * b).sum();
5512 for (vi, &bi) in v.iter_mut().zip(b.iter()) {
5513 *vi -= dot * bi;
5514 }
5515 }
5516 let norm = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
5517 if norm > 1e-10 {
5518 for vi in &mut v {
5519 *vi /= norm;
5520 }
5521 basis.push(v);
5522 }
5523 }
5524
5525 let mut result = Array2::<f64>::zeros((m, m));
5526 for (j, b) in basis.iter().enumerate() {
5527 for (i, &val) in b.iter().enumerate() {
5528 result[[i, j]] = val;
5529 }
5530 }
5531 result
5532}
5533
5534fn eig_compute(a: &Array2<f64>) -> Result<(Vec<f64>, Array2<f64>), String> {
5542 let n = a.nrows();
5543 if a.ncols() != n {
5544 return Err("eig: matrix must be square".to_string());
5545 }
5546 if n == 0 {
5547 return Ok((vec![], Array2::zeros((0, 0))));
5548 }
5549 if n == 1 {
5550 return Ok((vec![a[[0, 0]]], Array2::eye(1)));
5551 }
5552
5553 let mut ak = a.clone();
5554 let mut evecs = Array2::<f64>::eye(n);
5555
5556 const MAX_ITER: usize = 2000;
5557 const EPS: f64 = 1e-12;
5558
5559 for _ in 0..MAX_ITER {
5560 let mu = {
5562 let d = ak[[n - 1, n - 1]];
5563 if n >= 2 {
5564 let a = ak[[n - 2, n - 2]];
5565 let b = ak[[n - 2, n - 1]];
5566 let delta = (a - d) / 2.0;
5567 if delta.abs() < 1e-30 {
5568 d - b.abs()
5569 } else {
5570 d - b * b / (delta + delta.signum() * (delta * delta + b * b).sqrt())
5571 }
5572 } else {
5573 d
5574 }
5575 };
5576
5577 for i in 0..n {
5578 ak[[i, i]] -= mu;
5579 }
5580 let (q, r) = qr_decompose(&ak)?;
5581 ak = r.dot(&q);
5582 for i in 0..n {
5583 ak[[i, i]] += mu;
5584 }
5585 evecs = evecs.dot(&q);
5586
5587 let max_sub = (0..(n - 1))
5588 .map(|i| ak[[i + 1, i]].abs())
5589 .fold(0.0_f64, f64::max);
5590 if max_sub < EPS {
5591 break;
5592 }
5593 }
5594
5595 let evals: Vec<f64> = (0..n).map(|i| ak[[i, i]]).collect();
5596 Ok((evals, evecs))
5597}
5598
5599fn env_with_end(env: &Env, dim_size: usize) -> Env {
5606 let mut e = env.clone();
5607 e.insert("end".to_string(), Value::Scalar(dim_size as f64));
5608 e
5609}
5610
5611fn eval_index(val: &Value, args: &[Expr], env: &Env) -> Result<Value, String> {
5616 match args.len() {
5617 0 => Err("Indexing requires at least one index".to_string()),
5618 1 => {
5619 match val {
5621 Value::Void => Err("Cannot index into void".to_string()),
5622 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => {
5623 Err("Cannot index into a function value".to_string())
5624 }
5625 Value::Cell(_) => Err("Use c{i} to index into a cell array, not c(i)".to_string()),
5626 Value::Struct(_) => {
5627 Err("Use s.field to access struct fields, not s(i)".to_string())
5628 }
5629 Value::StructArray(arr) => {
5630 let total = arr.len();
5631 let env1 = env_with_end(env, total);
5632 match resolve_dim(&args[0], total, &env1)? {
5633 DimIdx::All => {
5634 Ok(Value::StructArray(arr.clone()))
5636 }
5637 DimIdx::Indices(idxs) => {
5638 if idxs.len() == 1 {
5639 let i = idxs[0];
5640 if i >= total {
5641 return Err(format!(
5642 "Index {} out of range (1..{})",
5643 i + 1,
5644 total
5645 ));
5646 }
5647 Ok(Value::Struct(arr[i].clone()))
5648 } else {
5649 let mut selected = Vec::with_capacity(idxs.len());
5650 for &i in &idxs {
5651 if i >= total {
5652 return Err(format!(
5653 "Index {} out of range (1..{})",
5654 i + 1,
5655 total
5656 ));
5657 }
5658 selected.push(arr[i].clone());
5659 }
5660 Ok(Value::StructArray(selected))
5661 }
5662 }
5663 }
5664 }
5665 Value::Scalar(n) => {
5666 let env1 = env_with_end(env, 1);
5667 match resolve_dim(&args[0], 1, &env1)? {
5668 DimIdx::All | DimIdx::Indices(_) => Ok(Value::Scalar(*n)),
5669 }
5670 }
5671 Value::Complex(re, im) => {
5672 let env1 = env_with_end(env, 1);
5673 match resolve_dim(&args[0], 1, &env1)? {
5674 DimIdx::All | DimIdx::Indices(_) => Ok(Value::Complex(*re, *im)),
5675 }
5676 }
5677 Value::Matrix(m) => {
5678 let total = m.nrows() * m.ncols();
5679 let env1 = env_with_end(env, total);
5680 match resolve_dim(&args[0], total, &env1)? {
5681 DimIdx::All => {
5682 let mut flat = Vec::with_capacity(total);
5684 for col in 0..m.ncols() {
5685 for row in 0..m.nrows() {
5686 flat.push(m[[row, col]]);
5687 }
5688 }
5689 Ok(Value::Matrix(
5690 Array2::from_shape_vec((total, 1), flat).unwrap(),
5691 ))
5692 }
5693 DimIdx::Indices(idxs) => {
5694 let nrows = m.nrows();
5696 let ncols_m = m.ncols();
5697 let vals: Result<Vec<f64>, String> = idxs
5698 .iter()
5699 .map(|&i| {
5700 let row = i % nrows;
5702 let col = i / nrows;
5703 if col >= ncols_m {
5704 Err(format!("Index {} out of range (1..{})", i + 1, total))
5705 } else {
5706 Ok(m[[row, col]])
5707 }
5708 })
5709 .collect();
5710 let vals = vals?;
5711 if vals.len() == 1 {
5712 Ok(Value::Scalar(vals[0]))
5713 } else {
5714 let n = vals.len();
5715 Ok(Value::Matrix(Array2::from_shape_vec((1, n), vals).unwrap()))
5716 }
5717 }
5718 }
5719 }
5720 Value::Str(s) => {
5721 let chars: Vec<char> = s.chars().collect();
5723 let total = chars.len();
5724 let env1 = env_with_end(env, total);
5725 match resolve_dim(&args[0], total, &env1)? {
5726 DimIdx::All => {
5727 let codes: Vec<f64> = chars.iter().map(|&c| c as u32 as f64).collect();
5728 if codes.len() == 1 {
5729 Ok(Value::Scalar(codes[0]))
5730 } else {
5731 let n = codes.len();
5732 Ok(Value::Matrix(
5733 Array2::from_shape_vec((1, n), codes).unwrap(),
5734 ))
5735 }
5736 }
5737 DimIdx::Indices(idxs) => {
5738 let mut selected = String::new();
5739 for &i in &idxs {
5740 if i >= chars.len() {
5741 return Err(format!("Index {} out of range", i + 1));
5742 }
5743 selected.push(chars[i]);
5744 }
5745 if selected.chars().count() == 1 {
5746 Ok(Value::Scalar(selected.chars().next().unwrap() as u32 as f64))
5747 } else {
5748 Ok(Value::Str(selected))
5749 }
5750 }
5751 }
5752 }
5753 Value::StringObj(s) => {
5754 let env1 = env_with_end(env, 1);
5756 match resolve_dim(&args[0], 1, &env1)? {
5757 DimIdx::All | DimIdx::Indices(_) => Ok(Value::StringObj(s.clone())),
5758 }
5759 }
5760 }
5761 }
5762 2 => {
5763 if matches!(
5765 val,
5766 Value::Void
5767 | Value::Str(_)
5768 | Value::StringObj(_)
5769 | Value::Lambda(_)
5770 | Value::Function { .. }
5771 | Value::Tuple(_)
5772 | Value::Cell(_)
5773 | Value::Struct(_)
5774 | Value::StructArray(_)
5775 ) {
5776 return Err("2D indexing not supported for this type".to_string());
5777 }
5778 let (nrows, ncols) = match val {
5779 Value::Scalar(_) | Value::Complex(_, _) => (1, 1),
5780 Value::Matrix(m) => (m.nrows(), m.ncols()),
5781 Value::Void
5782 | Value::Str(_)
5783 | Value::StringObj(_)
5784 | Value::Lambda(_)
5785 | Value::Function { .. }
5786 | Value::Tuple(_)
5787 | Value::Cell(_)
5788 | Value::Struct(_)
5789 | Value::StructArray(_) => unreachable!(),
5790 };
5791 let env_r = env_with_end(env, nrows);
5792 let env_c = env_with_end(env, ncols);
5793 let row_idx = resolve_dim(&args[0], nrows, &env_r)?;
5794 let col_idx = resolve_dim(&args[1], ncols, &env_c)?;
5795
5796 let rows: Vec<usize> = match row_idx {
5797 DimIdx::All => (0..nrows).collect(),
5798 DimIdx::Indices(v) => v,
5799 };
5800 let cols: Vec<usize> = match col_idx {
5801 DimIdx::All => (0..ncols).collect(),
5802 DimIdx::Indices(v) => v,
5803 };
5804
5805 if rows.len() == 1 && cols.len() == 1 {
5806 match val {
5807 Value::Void
5808 | Value::Str(_)
5809 | Value::StringObj(_)
5810 | Value::Lambda(_)
5811 | Value::Function { .. }
5812 | Value::Tuple(_)
5813 | Value::Cell(_)
5814 | Value::Struct(_)
5815 | Value::StructArray(_) => unreachable!(),
5816 Value::Scalar(n) => Ok(Value::Scalar(*n)),
5817 Value::Complex(re, im) => Ok(Value::Complex(*re, *im)),
5818 Value::Matrix(m) => Ok(Value::Scalar(m[[rows[0], cols[0]]])),
5819 }
5820 } else {
5821 let out_r = rows.len();
5822 let out_c = cols.len();
5823 let flat: Vec<f64> = rows
5824 .iter()
5825 .flat_map(|&r| {
5826 cols.iter().map(move |&c| match val {
5827 Value::Void
5828 | Value::Str(_)
5829 | Value::StringObj(_)
5830 | Value::Lambda(_)
5831 | Value::Function { .. }
5832 | Value::Tuple(_)
5833 | Value::Cell(_)
5834 | Value::Struct(_)
5835 | Value::StructArray(_) => unreachable!(),
5836 Value::Scalar(n) => *n,
5837 Value::Complex(re, _) => *re,
5838 Value::Matrix(m) => m[[r, c]],
5839 })
5840 })
5841 .collect();
5842 Ok(Value::Matrix(
5843 Array2::from_shape_vec((out_r, out_c), flat).unwrap(),
5844 ))
5845 }
5846 }
5847 n => Err(format!(
5848 "Indexing with {n} indices is not supported (max 2)"
5849 )),
5850 }
5851}
5852
5853enum DimIdx {
5855 All,
5856 Indices(Vec<usize>),
5857}
5858
5859fn resolve_dim(expr: &Expr, dim_size: usize, env: &Env) -> Result<DimIdx, String> {
5865 if matches!(expr, Expr::Colon) {
5866 return Ok(DimIdx::All);
5867 }
5868 let val = eval(expr, env)?;
5869 let floats: Vec<f64> = match val {
5870 Value::Void => {
5871 return Err("Index must be numeric, not void".to_string());
5872 }
5873 Value::Scalar(n) => vec![n],
5874 Value::Complex(re, im) => {
5875 if im != 0.0 {
5876 return Err("Index must be real, not complex".to_string());
5877 }
5878 vec![re]
5879 }
5880 Value::Matrix(m) => {
5881 let total = m.nrows() * m.ncols();
5883 if m.nrows() > 1 && m.ncols() > 1 && total != dim_size {
5884 return Err("Index must be a scalar or vector, not a matrix".to_string());
5885 }
5886 if m.nrows() > 1 && m.ncols() > 1 {
5888 let mut v = Vec::with_capacity(total);
5889 for col in 0..m.ncols() {
5890 for row in 0..m.nrows() {
5891 v.push(m[[row, col]]);
5892 }
5893 }
5894 v
5895 } else {
5896 m.iter().copied().collect()
5897 }
5898 }
5899 Value::Str(_) | Value::StringObj(_) => {
5900 return Err("Index must be numeric, not a string".to_string());
5901 }
5902 Value::Lambda(_)
5903 | Value::Function { .. }
5904 | Value::Tuple(_)
5905 | Value::Cell(_)
5906 | Value::Struct(_)
5907 | Value::StructArray(_) => {
5908 return Err("Index must be numeric, not a function".to_string());
5909 }
5910 };
5911 if dim_size > 0 && floats.len() == dim_size && floats.iter().all(|&f| f == 0.0 || f == 1.0) {
5913 let idxs: Vec<usize> = floats
5914 .iter()
5915 .enumerate()
5916 .filter(|&(_, &f)| f == 1.0)
5917 .map(|(i, _)| i)
5918 .collect();
5919 return Ok(DimIdx::Indices(idxs));
5920 }
5921 let mut idxs = Vec::with_capacity(floats.len());
5922 for n in floats {
5923 let i = n.round() as i64;
5924 if i < 1 || i as usize > dim_size {
5925 return Err(format!("Index {i} out of range (1..{dim_size})"));
5926 }
5927 idxs.push(i as usize - 1);
5928 }
5929 Ok(DimIdx::Indices(idxs))
5930}
5931
5932pub fn format_number(n: f64) -> String {
5936 if n.fract() == 0.0 && n.abs() < 1e15 {
5937 format!("{}", n as i64)
5938 } else if n != 0.0 && (n.abs() >= 1e15 || n.abs() < 1e-9) {
5939 trim_sci(&format!("{:.15e}", n))
5940 } else {
5941 let s = format!("{:.10}", n);
5942 s.trim_end_matches('0').trim_end_matches('.').to_string()
5943 }
5944}
5945
5946pub fn format_scalar(n: f64, base: Base, mode: &FormatMode) -> String {
5948 if matches!(mode, FormatMode::Hex) {
5950 return format_decimal(n, mode);
5951 }
5952 match base {
5953 Base::Dec => format_decimal(n, mode),
5954 _ => format_non_dec(n, base),
5955 }
5956}
5957
5958pub fn format_complex(re: f64, im: f64, mode: &FormatMode) -> String {
5964 if im == 0.0 {
5965 return format_decimal(re, mode);
5966 }
5967 let im_abs = im.abs();
5968 let im_str = if im_abs == 1.0 {
5969 String::new()
5970 } else {
5971 format_decimal(im_abs, mode)
5972 };
5973 if re == 0.0 {
5974 if im < 0.0 {
5975 format!("-{}i", im_str)
5976 } else {
5977 format!("{}i", im_str)
5978 }
5979 } else {
5980 let re_str = format_decimal(re, mode);
5981 if im < 0.0 {
5982 format!("{} - {}i", re_str, im_str)
5983 } else {
5984 format!("{} + {}i", re_str, im_str)
5985 }
5986 }
5987}
5988
5989pub fn expr_to_string(e: &Expr) -> String {
5994 match e {
5995 Expr::Number(n) => {
5996 if n.is_nan() {
5997 "nan".to_string()
5998 } else if n.is_infinite() {
5999 if *n > 0.0 {
6000 "inf".to_string()
6001 } else {
6002 "-inf".to_string()
6003 }
6004 } else {
6005 format!("{n}")
6006 }
6007 }
6008 Expr::Var(name) => name.clone(),
6009 Expr::UnaryMinus(e) => format!("-{}", expr_to_string(e)),
6010 Expr::UnaryNot(e) => format!("~{}", expr_to_string(e)),
6011 Expr::BinOp(l, op, r) => {
6012 let op_str = match op {
6013 Op::Add => "+",
6014 Op::Sub => "-",
6015 Op::Mul => "*",
6016 Op::Div => "/",
6017 Op::Pow => "^",
6018 Op::ElemMul => ".*",
6019 Op::ElemDiv => "./",
6020 Op::ElemPow => ".^",
6021 Op::Eq => "==",
6022 Op::NotEq => "~=",
6023 Op::Lt => "<",
6024 Op::Gt => ">",
6025 Op::LtEq => "<=",
6026 Op::GtEq => ">=",
6027 Op::And => "&&",
6028 Op::Or => "||",
6029 Op::ElemAnd => "&",
6030 Op::ElemOr => "|",
6031 Op::LDiv => "\\",
6032 };
6033 format!("{} {op_str} {}", expr_to_string(l), expr_to_string(r))
6034 }
6035 Expr::Call(name, args) => {
6036 let args_str = args
6037 .iter()
6038 .map(expr_to_string)
6039 .collect::<Vec<_>>()
6040 .join(", ");
6041 format!("{name}({args_str})")
6042 }
6043 Expr::Transpose(e) => format!("{}'", expr_to_string(e)),
6044 Expr::PlainTranspose(e) => format!("{}.'", expr_to_string(e)),
6045 Expr::Range(start, step, stop) => {
6046 if let Some(step) = step {
6047 format!(
6048 "{}:{}:{}",
6049 expr_to_string(start),
6050 expr_to_string(step),
6051 expr_to_string(stop)
6052 )
6053 } else {
6054 format!("{}:{}", expr_to_string(start), expr_to_string(stop))
6055 }
6056 }
6057 Expr::StrLiteral(s) => format!("'{s}'"),
6058 Expr::StringObjLiteral(s) => format!("\"{s}\""),
6059 Expr::Lambda { params, body, .. } => {
6060 format!("@({}) {}", params.join(", "), expr_to_string(body))
6061 }
6062 Expr::FuncHandle(name) => format!("@{name}"),
6063 Expr::Matrix(_) => "[...]".to_string(),
6064 Expr::CellLiteral(_) => "{...}".to_string(),
6065 Expr::CellIndex(e, i) => format!("{}{{{}}}", expr_to_string(e), expr_to_string(i)),
6066 Expr::Colon => ":".to_string(),
6067 Expr::FieldGet(base, field) => format!("{}.{field}", expr_to_string(base)),
6068 Expr::DotCall(segs, args) => {
6069 let args_str = args
6070 .iter()
6071 .map(expr_to_string)
6072 .collect::<Vec<_>>()
6073 .join(", ");
6074 format!("{}({args_str})", segs.join("."))
6075 }
6076 }
6077}
6078
6079pub fn format_value(v: &Value, base: Base, mode: &FormatMode) -> String {
6081 match v {
6082 Value::Void => String::new(),
6083 Value::Scalar(n) => format_scalar(*n, base, mode),
6084 Value::Matrix(m) => format!("[{}x{} double]", m.nrows(), m.ncols()),
6085 Value::Complex(re, im) => format_complex(*re, *im, mode),
6086 Value::Str(s) => s.clone(),
6087 Value::StringObj(s) => s.clone(),
6088 Value::Lambda(lf) => lf.1.clone(),
6089 Value::Function {
6090 params, outputs, ..
6091 } => {
6092 let params_str = params.join(", ");
6093 let out_str = match outputs.len() {
6094 0 => String::new(),
6095 1 => format!("{} = ", outputs[0]),
6096 _ => format!("[{}] = ", outputs.join(", ")),
6097 };
6098 format!("@function {out_str}f({params_str})")
6099 }
6100 Value::Tuple(vals) => {
6101 let parts: Vec<String> = vals.iter().map(|v| format_value(v, base, mode)).collect();
6102 format!("({})", parts.join(", "))
6103 }
6104 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
6105 Value::Struct(_) => "[1×1 struct]".to_string(),
6106 Value::StructArray(arr) => format!("[1×{} struct]", arr.len()),
6107 }
6108}
6109
6110pub fn format_value_full(v: &Value, mode: &FormatMode) -> Option<String> {
6113 match v {
6114 Value::Void
6115 | Value::Scalar(_)
6116 | Value::Complex(_, _)
6117 | Value::Str(_)
6118 | Value::StringObj(_)
6119 | Value::Lambda(_)
6120 | Value::Function { .. }
6121 | Value::Tuple(_) => None,
6122 Value::Matrix(m) => Some(format_matrix(m, mode)),
6123 Value::Cell(elems) => Some(format_cell(elems, mode)),
6124 Value::Struct(map) => Some(format_struct(map, mode)),
6125 Value::StructArray(arr) => Some(format_struct_array(arr, mode)),
6126 }
6127}
6128
6129fn format_cell(elems: &[Value], mode: &FormatMode) -> String {
6131 if elems.is_empty() {
6132 return " {}".to_string();
6133 }
6134 let mut lines = vec![" {".to_string()];
6135 for (i, val) in elems.iter().enumerate() {
6136 let label = format!(" [1,{}]", i + 1);
6137 match val {
6138 Value::Matrix(_) => {
6139 lines.push(format!("{label}:"));
6140 if let Some(full) = format_value_full(val, mode) {
6141 for line in full.lines() {
6142 lines.push(format!(" {line}"));
6143 }
6144 }
6145 }
6146 Value::Cell(_) => {
6147 lines.push(format!("{label}: {}", format_value(val, Base::Dec, mode)));
6148 }
6149 _ => {
6150 lines.push(format!("{label}: {}", format_value(val, Base::Dec, mode)));
6151 }
6152 }
6153 }
6154 lines.push(" }".to_string());
6155 lines.join("\n")
6156}
6157
6158fn format_struct(map: &IndexMap<String, Value>, mode: &FormatMode) -> String {
6160 let mut lines = vec![
6161 String::new(),
6162 " struct with fields:".to_string(),
6163 String::new(),
6164 ];
6165 for (key, val) in map {
6166 let val_str = match val {
6167 Value::Struct(_) => "[1×1 struct]".to_string(),
6168 Value::StructArray(arr) => format!("[1×{} struct]", arr.len()),
6169 Value::Matrix(m) => format!("[{}×{} double]", m.nrows(), m.ncols()),
6170 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
6171 _ => format_value(val, Base::Dec, mode),
6172 };
6173 lines.push(format!(" {key}: {val_str}"));
6174 }
6175 lines.join("\n")
6176}
6177
6178fn format_struct_array(arr: &[IndexMap<String, Value>], mode: &FormatMode) -> String {
6180 let n = arr.len();
6181 let mut lines = vec![
6182 String::new(),
6183 format!(" 1×{n} struct array with fields:"),
6184 String::new(),
6185 ];
6186 if let Some(first) = arr.first() {
6188 for key in first.keys() {
6189 lines.push(format!(" {key}"));
6190 }
6191 }
6192 if n == 1
6194 && let Some(first) = arr.first()
6195 {
6196 lines.clear();
6197 lines.push(String::new());
6198 lines.push(" struct with fields:".to_string());
6199 lines.push(String::new());
6200 for (key, val) in first {
6201 let val_str = match val {
6202 Value::Struct(_) => "[1×1 struct]".to_string(),
6203 Value::StructArray(a) => format!("[1×{} struct]", a.len()),
6204 Value::Matrix(m) => format!("[{}×{} double]", m.nrows(), m.ncols()),
6205 Value::Cell(v) => format!("{{1×{} cell}}", v.len()),
6206 _ => format_value(val, Base::Dec, mode),
6207 };
6208 lines.push(format!(" {key}: {val_str}"));
6209 }
6210 }
6211 lines.join("\n")
6212}
6213
6214fn format_matrix(m: &Array2<f64>, mode: &FormatMode) -> String {
6217 if m.nrows() == 0 || m.ncols() == 0 {
6218 return " []".to_string();
6219 }
6220 if matches!(mode, FormatMode::Plus) {
6222 let lines: Vec<String> = m
6223 .rows()
6224 .into_iter()
6225 .map(|row| {
6226 let chars: String = row
6227 .iter()
6228 .map(|&x| {
6229 if x > 0.0 {
6230 '+'
6231 } else if x < 0.0 {
6232 '-'
6233 } else {
6234 '0'
6235 }
6236 })
6237 .collect();
6238 format!(" {}", chars)
6239 })
6240 .collect();
6241 return lines.join("\n");
6242 }
6243 let ncols = m.ncols();
6244 let cells: Vec<Vec<String>> = m
6245 .rows()
6246 .into_iter()
6247 .map(|row| row.iter().map(|&x| format_decimal(x, mode)).collect())
6248 .collect();
6249 let col_widths: Vec<usize> = (0..ncols)
6250 .map(|c| cells.iter().map(|row| row[c].len()).max().unwrap_or(0))
6251 .collect();
6252 let mut lines = Vec::new();
6253 for row in &cells {
6254 let mut line = String::from(" ");
6255 for (c, cell) in row.iter().enumerate() {
6256 if c > 0 {
6257 line.push_str(" ");
6258 }
6259 let pad = col_widths[c].saturating_sub(cell.len());
6260 for _ in 0..pad {
6261 line.push(' ');
6262 }
6263 line.push_str(cell);
6264 }
6265 lines.push(line);
6266 }
6267 lines.join("\n")
6268}
6269
6270pub fn format_non_dec(n: f64, base: Base) -> String {
6273 let i = n.round() as i64;
6274 let u = i.unsigned_abs();
6275 let sign = if i < 0 { "-" } else { "" };
6276 match base {
6277 Base::Hex => format!("{}0x{:X}", sign, u),
6278 Base::Bin => format!("{}0b{:b}", sign, u),
6279 Base::Oct => format!("{}0o{:o}", sign, u),
6280 Base::Dec => format_decimal(n, &FormatMode::default()),
6281 }
6282}
6283
6284fn format_decimal(n: f64, mode: &FormatMode) -> String {
6289 if n.is_nan() {
6290 return "NaN".to_string();
6291 }
6292 if n.is_infinite() {
6293 return if n > 0.0 { "Inf" } else { "-Inf" }.to_string();
6294 }
6295 match mode {
6296 FormatMode::Short | FormatMode::ShortG => fmt_auto_sig(n, 5),
6297 FormatMode::Long | FormatMode::LongG => fmt_auto_sig(n, 15),
6298 FormatMode::ShortE => fmt_sci_dp(n, 4),
6299 FormatMode::LongE => fmt_sci_dp(n, 14),
6300 FormatMode::Bank => format!("{:.2}", n),
6301 FormatMode::Rat => fmt_rat(n),
6302 FormatMode::Hex => fmt_hex_ieee754(n),
6303 FormatMode::Plus => fmt_plus_sign(n),
6304 FormatMode::Custom(prec) => fmt_custom_prec(n, *prec),
6305 }
6306}
6307
6308#[inline]
6310fn is_exact_int(n: f64) -> bool {
6311 n.fract() == 0.0 && n.abs() < 1e15
6312}
6313
6314fn fmt_auto_sig(n: f64, sig: usize) -> String {
6318 if is_exact_int(n) {
6319 return format!("{}", n as i64);
6320 }
6321 let abs_n = n.abs();
6322 let exp = if abs_n == 0.0 {
6323 0i32
6324 } else {
6325 abs_n.log10().floor() as i32
6326 };
6327 if exp >= -3 && exp < sig as i32 {
6328 let dp = (sig as i32 - 1 - exp) as usize;
6329 let s = format!("{:.prec$}", n, prec = dp);
6330 if s.contains('.') {
6332 s.trim_end_matches('0').trim_end_matches('.').to_string()
6333 } else {
6334 s
6335 }
6336 } else {
6337 let s = format!("{:.prec$e}", n, prec = sig - 1);
6338 trim_sci(&s)
6339 }
6340}
6341
6342fn fmt_sci_dp(n: f64, dp: usize) -> String {
6344 let s = format!("{:.prec$e}", n, prec = dp);
6345 trim_sci(&s)
6346}
6347
6348fn fmt_custom_prec(n: f64, prec: usize) -> String {
6350 if is_exact_int(n) {
6351 return format!("{}", n as i64);
6352 }
6353 if n.abs() >= 1e15 || (n != 0.0 && n.abs() < 1e-9) {
6354 let s = format!("{:.prec$e}", n, prec = prec);
6355 trim_sci(&s)
6356 } else {
6357 let s = format!("{:.prec$}", n, prec = prec);
6358 s.trim_end_matches('0').trim_end_matches('.').to_string()
6359 }
6360}
6361
6362fn fmt_rat(n: f64) -> String {
6364 if is_exact_int(n) {
6365 return format!("{}", n as i64);
6366 }
6367 let sign = if n < 0.0 { -1i64 } else { 1i64 };
6368 let x = n.abs();
6369 let (mut h1, mut h2): (i64, i64) = (1, 0);
6370 let (mut k1, mut k2): (i64, i64) = (0, 1);
6371 let mut b = x;
6372 for _ in 0..64 {
6373 let a = b.floor() as i64;
6374 let (nh, nk) = (a * h1 + h2, a * k1 + k2);
6375 if nk > 10_000 {
6376 break;
6377 }
6378 h2 = h1;
6379 h1 = nh;
6380 k2 = k1;
6381 k1 = nk;
6382 let frac = b - a as f64;
6383 if frac < 1e-12 || (h1 as f64 / k1 as f64 - x).abs() < 1e-6 {
6384 break;
6385 }
6386 b = 1.0 / frac;
6387 }
6388 let p = sign * h1;
6389 if k1 == 1 {
6390 format!("{}", p)
6391 } else {
6392 format!("{}/{}", p, k1)
6393 }
6394}
6395
6396fn fmt_hex_ieee754(n: f64) -> String {
6398 format!("{:016X}", n.to_bits())
6399}
6400
6401fn fmt_plus_sign(n: f64) -> String {
6403 if n > 0.0 {
6404 "+".to_string()
6405 } else if n < 0.0 {
6406 "-".to_string()
6407 } else {
6408 " ".to_string()
6409 }
6410}
6411
6412fn trim_sci(s: &str) -> String {
6413 if let Some(e_pos) = s.find('e') {
6414 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
6415 let exp_str = &s[e_pos + 1..];
6416 let (sign, digits) = if let Some(d) = exp_str.strip_prefix('-') {
6417 ("-", d)
6418 } else if let Some(d) = exp_str.strip_prefix('+') {
6419 ("+", d)
6420 } else {
6421 ("+", exp_str)
6422 };
6423 let exp_num: i32 = digits.parse().unwrap_or(0);
6424 format!("{}e{}{:02}", mantissa, sign, exp_num)
6425 } else {
6426 s.to_string()
6427 }
6428}
6429
6430pub fn load_mat_file(path: &str) -> Result<Value, String> {
6436 load_mat_file_impl(path)
6437}
6438
6439#[cfg(feature = "mat")]
6440fn load_mat_file_impl(path: &str) -> Result<Value, String> {
6441 crate::mat::mat_load(path)
6442}
6443
6444#[cfg(not(feature = "mat"))]
6445fn load_mat_file_impl(_path: &str) -> Result<Value, String> {
6446 Err("load: .mat support not available — rebuild with --features mat".to_string())
6447}
6448
6449#[cfg(feature = "json")]
6452fn jsondecode_impl(arg: &Value) -> Result<Value, String> {
6453 let s = match arg {
6454 Value::Str(s) | Value::StringObj(s) => s.as_str(),
6455 _ => return Err("jsondecode: argument must be a string".to_string()),
6456 };
6457 let jval: serde_json::Value =
6458 serde_json::from_str(s).map_err(|e| format!("jsondecode: invalid JSON: {e}"))?;
6459 Ok(crate::json::json_to_value(&jval))
6460}
6461
6462#[cfg(not(feature = "json"))]
6463fn jsondecode_impl(_arg: &Value) -> Result<Value, String> {
6464 Err("jsondecode: not available — rebuild with --features json".to_string())
6465}
6466
6467#[cfg(feature = "json")]
6468fn jsonencode_impl(arg: &Value) -> Result<Value, String> {
6469 let jval = crate::json::value_to_json(arg)?;
6470 let s = serde_json::to_string(&jval)
6471 .map_err(|e| format!("jsonencode: serialization error: {e}"))?;
6472 Ok(Value::Str(s))
6473}
6474
6475#[cfg(not(feature = "json"))]
6476fn jsonencode_impl(_arg: &Value) -> Result<Value, String> {
6477 Err("jsonencode: not available — rebuild with --features json".to_string())
6478}
6479
6480#[cfg(test)]
6481#[path = "eval_tests.rs"]
6482mod tests;