1use std::collections::HashMap;
2use std::rc::Rc;
3
4type BodyCache = HashMap<String, Rc<Vec<StmtEntry>>>;
6
7type ChunkCache = HashMap<String, Option<Rc<crate::vm::Chunk>>>;
13
14struct FrameCacheEntry {
16 chunk: Rc<crate::vm::Chunk>,
17}
18
19type FrameChunkCache = HashMap<String, Option<FrameCacheEntry>>;
23
24fn expand_tilde(path: &str) -> String {
29 if path == "~" || path.starts_with("~/") || path.starts_with("~\\") {
30 let home = std::env::var("HOME")
31 .or_else(|_| std::env::var("USERPROFILE"))
32 .unwrap_or_default();
33 if home.is_empty() {
34 return path.to_string();
35 }
36 if path == "~" {
37 home
38 } else {
39 format!("{}{}", home, &path[1..])
40 }
41 } else {
42 path.to_string()
43 }
44}
45
46use indexmap::IndexMap;
47use ndarray::Array2;
48use num_complex::Complex;
49
50use crate::env::{Env, Value};
51use crate::env::{load_workspace, save_workspace, save_workspace_vars};
52use crate::eval::{
53 Base, Expr, FormatMode, autoload_cache_insert, current_func_name, eval_with_io, format_complex,
54 format_scalar, format_value_full, get_display_base, get_display_compact, get_display_fmt,
55 global_declare, global_frame_pop, global_frame_push, global_get, global_init_if_absent,
56 global_refresh_into_env, global_set, is_global, is_persistent, persistent_declare,
57 persistent_frame_pop, persistent_frame_push, persistent_load, persistent_save,
58 set_autoload_hook, set_display_ctx, set_eval_str_hook, set_fn_call_hook, set_last_err,
59 set_nargout,
60};
61use crate::io::IoContext;
62use crate::parser::{Stmt, StmtEntry, parse_stmts};
63
64const MAX_CALL_DEPTH: u32 = 64;
71
72thread_local! {
73 static RUN_DEPTH: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
75
76 static CALL_DEPTH: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
78
79 static SCRIPT_DIR_STACK: std::cell::RefCell<Vec<std::path::PathBuf>> =
85 const { std::cell::RefCell::new(Vec::new()) };
86
87 static SESSION_PATH: std::cell::RefCell<Vec<std::path::PathBuf>> =
92 const { std::cell::RefCell::new(Vec::new()) };
93
94 static BODY_CACHE: std::cell::RefCell<BodyCache> =
104 std::cell::RefCell::new(HashMap::new());
105
106 static BODY_CHUNK_CACHE: std::cell::RefCell<ChunkCache> =
111 std::cell::RefCell::new(HashMap::new());
112
113 static BODY_FRAME_CACHE: std::cell::RefCell<FrameChunkCache> =
119 std::cell::RefCell::new(HashMap::new());
120
121 static LEAF_SCRATCH_ENV: std::cell::RefCell<crate::env::Env> =
128 std::cell::RefCell::new(crate::env::Env::new());
129}
130
131struct CallDepthGuard(u32);
136impl Drop for CallDepthGuard {
137 fn drop(&mut self) {
138 CALL_DEPTH.with(|c| c.set(self.0));
139 }
140}
141
142fn silence_all(stmts: Vec<StmtEntry>) -> Vec<StmtEntry> {
150 stmts
151 .into_iter()
152 .map(|(stmt, _, line)| {
153 let stmt = match stmt {
154 Stmt::If {
155 cond,
156 body,
157 elseif_branches,
158 else_body,
159 } => Stmt::If {
160 cond,
161 body: silence_all(body),
162 elseif_branches: elseif_branches
163 .into_iter()
164 .map(|(c, b)| (c, silence_all(b)))
165 .collect(),
166 else_body: else_body.map(silence_all),
167 },
168 Stmt::For {
169 var,
170 range_expr,
171 body,
172 } => Stmt::For {
173 var,
174 range_expr,
175 body: silence_all(body),
176 },
177 Stmt::While { cond, body } => Stmt::While {
178 cond,
179 body: silence_all(body),
180 },
181 Stmt::DoUntil { body, cond } => Stmt::DoUntil {
182 body: silence_all(body),
183 cond,
184 },
185 Stmt::Switch {
186 expr,
187 cases,
188 otherwise_body,
189 } => Stmt::Switch {
190 expr,
191 cases: cases
192 .into_iter()
193 .map(|(v, b)| (v, silence_all(b)))
194 .collect(),
195 otherwise_body: otherwise_body.map(silence_all),
196 },
197 Stmt::TryCatch {
198 try_body,
199 catch_var,
200 catch_body,
201 } => Stmt::TryCatch {
202 try_body: silence_all(try_body),
203 catch_var,
204 catch_body: silence_all(catch_body),
205 },
206 other => other,
207 };
208 (stmt, true, line)
209 })
210 .collect()
211}
212
213fn get_or_parse_body(body_source: &str) -> Result<Rc<Vec<StmtEntry>>, String> {
219 BODY_CACHE.with(|cache| {
220 let mut cache = cache.borrow_mut();
221 if let Some(body) = cache.get(body_source) {
222 return Ok(Rc::clone(body));
223 }
224 let stmts =
225 parse_stmts(body_source).map_err(|e| format!("function body parse error: {e}"))?;
226 let silent = silence_all(stmts);
227 let rc = Rc::new(silent);
228 cache.insert(body_source.to_string(), Rc::clone(&rc));
229 Ok(rc)
230 })
231}
232
233fn annotate_line(e: String, line: usize) -> String {
237 if line == 0 || e.contains("near line") {
238 e
239 } else {
240 format!("{e} near line {line}")
241 }
242}
243
244fn strip_near_line(s: String) -> String {
249 if let Some(pos) = s.rfind(" near line ") {
250 let after = &s[pos + " near line ".len()..];
251 if !after.is_empty() && after.bytes().all(|b| b.is_ascii_digit()) {
252 return s[..pos].to_string();
253 }
254 }
255 s
256}
257
258pub enum Signal {
264 Break,
266 Continue,
268 Return,
270}
271
272pub fn init() {
276 set_fn_call_hook(call_user_function);
277 set_autoload_hook(try_autoload);
278 set_eval_str_hook(eval_str_impl);
279}
280
281fn eval_str_impl(code: &str, env: &crate::env::Env) -> Result<crate::env::Value, String> {
287 let mut env_clone = env.clone();
288 env_clone
289 .entry("ans".to_string())
290 .or_insert(crate::env::Value::Scalar(0.0));
291 let mut tmp_io = crate::io::IoContext::new();
292 let fmt = get_display_fmt();
293 let base = get_display_base();
294 let compact = get_display_compact();
295 let stmts = parse_stmts(code).map_err(|e| format!("eval: parse error: {e}"))?;
296 exec_stmts(&stmts, &mut env_clone, &mut tmp_io, &fmt, base, compact)?;
297 Ok(env_clone
298 .get("ans")
299 .cloned()
300 .unwrap_or(crate::env::Value::Void))
301}
302
303fn try_autoload(name: &str) -> bool {
310 if name.contains('.') {
311 return try_autoload_pkg(name);
312 }
313 let candidates = [format!("{name}.calc"), format!("{name}.m")];
314 for candidate in &candidates {
315 let Some(path) = resolve_script_path(candidate) else {
316 continue;
317 };
318 let Ok(content) = std::fs::read_to_string(&path) else {
319 continue;
320 };
321 let Ok(stmts) = parse_stmts(&content) else {
322 continue;
323 };
324 if !matches!(stmts.first(), Some((Stmt::FunctionDef { .. }, _, _))) {
325 continue;
326 }
327 let primary_name = match &stmts[0].0 {
328 Stmt::FunctionDef { name, .. } => name.clone(),
329 _ => continue,
330 };
331 let mut locals: IndexMap<String, Value> = IndexMap::new();
332 for (stmt, _, _) in &stmts {
333 if let Stmt::FunctionDef {
334 name: n,
335 outputs,
336 params,
337 body_source,
338 doc,
339 } = stmt
340 && n != &primary_name
341 {
342 locals.insert(
343 n.clone(),
344 Value::Function(Box::new(crate::env::FunctionData {
345 outputs: outputs.clone(),
346 params: params.clone(),
347 body_source: body_source.clone(),
348 locals: IndexMap::new(),
349 doc: doc.clone(),
350 })),
351 );
352 }
353 }
354 if let Stmt::FunctionDef {
355 outputs,
356 params,
357 body_source,
358 doc,
359 ..
360 } = &stmts[0].0
361 {
362 autoload_cache_insert(
363 primary_name,
364 Value::Function(Box::new(crate::env::FunctionData {
365 outputs: outputs.clone(),
366 params: params.clone(),
367 body_source: body_source.clone(),
368 locals,
369 doc: doc.clone(),
370 })),
371 );
372 return true;
373 }
374 }
375 false
376}
377
378fn try_autoload_pkg(qualified: &str) -> bool {
384 let parts: Vec<&str> = qualified.split('.').collect();
385 if parts.len() < 2 {
386 return false;
387 }
388 let func_name = *parts.last().unwrap();
389 let pkg_parts = &parts[..parts.len() - 1];
390
391 let mut rel_prefix = std::path::PathBuf::new();
393 for pkg in pkg_parts {
394 rel_prefix.push(format!("+{pkg}"));
395 }
396
397 let candidates = [
398 rel_prefix.join(format!("{func_name}.calc")),
399 rel_prefix.join(format!("{func_name}.m")),
400 ];
401
402 let mut search_dirs: Vec<std::path::PathBuf> = Vec::new();
404 SCRIPT_DIR_STACK.with(|s| search_dirs.extend(s.borrow().iter().cloned()));
405 search_dirs.push(std::path::PathBuf::from("."));
406 SESSION_PATH.with(|s| search_dirs.extend(s.borrow().iter().cloned()));
407
408 for dir in &search_dirs {
409 for candidate in &candidates {
410 let full = dir.join(candidate);
411 let Ok(content) = std::fs::read_to_string(&full) else {
412 continue;
413 };
414 let Ok(stmts) = parse_stmts(&content) else {
415 continue;
416 };
417 if !matches!(stmts.first(), Some((Stmt::FunctionDef { .. }, _, _))) {
418 continue;
419 }
420 let primary_name = match &stmts[0].0 {
421 Stmt::FunctionDef { name, .. } => name.clone(),
422 _ => continue,
423 };
424 let mut locals: IndexMap<String, Value> = IndexMap::new();
425 for (stmt, _, _) in &stmts {
426 if let Stmt::FunctionDef {
427 name: n,
428 outputs,
429 params,
430 body_source,
431 doc,
432 } = stmt
433 && n != &primary_name
434 {
435 locals.insert(
436 n.clone(),
437 Value::Function(Box::new(crate::env::FunctionData {
438 outputs: outputs.clone(),
439 params: params.clone(),
440 body_source: body_source.clone(),
441 locals: IndexMap::new(),
442 doc: doc.clone(),
443 })),
444 );
445 }
446 }
447 if let Stmt::FunctionDef {
448 outputs,
449 params,
450 body_source,
451 doc,
452 ..
453 } = &stmts[0].0
454 {
455 autoload_cache_insert(
456 qualified.to_string(),
457 Value::Function(Box::new(crate::env::FunctionData {
458 outputs: outputs.clone(),
459 params: params.clone(),
460 body_source: body_source.clone(),
461 locals,
462 doc: doc.clone(),
463 })),
464 );
465 return true;
466 }
467 }
468 }
469 false
470}
471
472pub fn script_dir_push(dir: &std::path::Path) {
478 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().push(dir.to_path_buf()));
479}
480
481pub fn script_dir_pop() {
483 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().pop());
484}
485
486pub fn session_path_init(paths: Vec<std::path::PathBuf>) {
491 SESSION_PATH.with(|p| *p.borrow_mut() = paths);
492}
493
494pub fn session_path_add(path: std::path::PathBuf, append: bool) {
499 SESSION_PATH.with(|p| {
500 let mut v = p.borrow_mut();
501 v.retain(|e| e != &path);
502 if append {
503 v.push(path);
504 } else {
505 v.insert(0, path);
506 }
507 });
508}
509
510pub fn session_path_remove(path: &std::path::Path) {
512 SESSION_PATH.with(|p| p.borrow_mut().retain(|e| e.as_path() != path));
513}
514
515pub fn session_path_list() -> Vec<std::path::PathBuf> {
517 SESSION_PATH.with(|p| p.borrow().clone())
518}
519
520pub(crate) fn call_user_function(
527 name: &str,
528 func: &Value,
529 args: &[Value],
530 caller_env: &Env,
531 io: &mut IoContext,
532) -> Result<Value, String> {
533 let Value::Function(fd) = func else {
534 return Err("call_user_function: not a Function value".to_string());
535 };
536 let depth = CALL_DEPTH.with(|c| c.get());
538 if depth >= MAX_CALL_DEPTH {
539 return Err(format!(
540 "'{name}': maximum function call depth ({MAX_CALL_DEPTH}) exceeded"
541 ));
542 }
543 CALL_DEPTH.with(|c| c.set(depth + 1));
544 let _depth_guard = CallDepthGuard(depth);
546 let (outputs, params, body_source, locals) =
547 (&fd.outputs, &fd.params, &fd.body_source, &fd.locals);
548
549 global_frame_push();
551 persistent_frame_push(name); let has_varargin = params.last().is_some_and(|p| p == "varargin");
555 let fixed_params = if has_varargin {
556 ¶ms[..params.len() - 1]
557 } else {
558 params.as_slice()
559 };
560
561 let effective_args = if args.len() > params.len() {
566 if !has_varargin && args.len() > params.len() + 1 {
567 return Err(format!(
568 "Too many arguments: expected at most {}, got {}",
569 params.len(),
570 args.len()
571 ));
572 }
573 if has_varargin {
574 args
575 } else {
576 &args[..params.len()]
578 }
579 } else {
580 args
581 };
582
583 if !has_varargin && outputs.last().is_none_or(|o| o != "varargout") {
588 let cached = BODY_FRAME_CACHE.with(|cc| {
590 cc.borrow()
591 .get(body_source.as_str())
592 .map(|e| e.as_ref().map(|entry| Rc::clone(&entry.chunk)))
593 });
594
595 let frame_chunk_opt: Option<Rc<crate::vm::Chunk>> = match cached {
596 Some(rc_opt) => rc_opt,
598 None => {
600 let rc_opt = get_or_parse_body(body_source)
601 .ok()
602 .and_then(|body| {
603 crate::vm::compile::compile_fn_body(&body, params, outputs).ok()
604 })
605 .and_then(|chunk| {
606 if crate::vm::compile::is_leaf_fn(&chunk) && chunk.n_params == params.len()
607 {
608 Some(Rc::new(chunk))
609 } else {
610 None
611 }
612 });
613 BODY_FRAME_CACHE.with(|cc| {
614 let mut cache = cc.borrow_mut();
615 cache.insert(
616 body_source.to_string(),
617 rc_opt.as_ref().map(|rc| FrameCacheEntry {
618 chunk: Rc::clone(rc),
619 }),
620 );
621 });
622 rc_opt
623 }
624 };
625
626 if let Some(chunk) = frame_chunk_opt {
627 let fmt = get_display_fmt();
628 let base = get_display_base();
629 let compact = get_display_compact();
630
631 let mut frame = vec![Value::Void; chunk.slot_names.len()];
633 for (i, arg) in effective_args.iter().take(params.len()).enumerate() {
634 frame[i] = arg.clone();
635 }
636
637 let exec_result = LEAF_SCRATCH_ENV.with(|cell| {
639 let mut scratch = cell.borrow_mut();
640 crate::vm::exec::vm_exec_with_frame(
641 &chunk,
642 frame,
643 &mut scratch,
644 io,
645 &fmt,
646 base,
647 compact,
648 )
649 });
650
651 let (func_name_saved, persistent_names) = persistent_frame_pop();
653 for var_name in &persistent_names {
654 if let Some(val) = LEAF_SCRATCH_ENV.with(|c| c.borrow().get(var_name).cloned()) {
656 persistent_save(&func_name_saved, var_name, val);
657 }
658 }
659 global_frame_pop();
660
661 let (signal, mut frame) = exec_result?;
662 match signal {
663 None | Some(Signal::Return) => {}
664 Some(Signal::Break) => return Err("'break' outside loop".to_string()),
665 Some(Signal::Continue) => return Err("'continue' outside loop".to_string()),
666 }
667
668 if outputs.is_empty() {
670 return Ok(Value::Void);
671 }
672 if outputs.len() == 1 {
673 let out_slot = chunk.slot_names.iter().position(|n| n == &outputs[0]);
674 return Ok(out_slot
675 .map(|s| std::mem::replace(&mut frame[s], Value::Void))
676 .unwrap_or(Value::Void));
677 }
678 let vals: Vec<Value> = outputs
679 .iter()
680 .map(|o| {
681 chunk
682 .slot_names
683 .iter()
684 .position(|n| n == o)
685 .map(|s| std::mem::replace(&mut frame[s], Value::Void))
686 .unwrap_or(Value::Void)
687 })
688 .collect();
689 return Ok(Value::Tuple(vals));
690 }
691 }
692 let mut local_env = Env::new();
698 local_env.insert("i".to_string(), Value::Complex(0.0, 1.0));
699 local_env.insert("j".to_string(), Value::Complex(0.0, 1.0));
700 local_env.insert("ans".to_string(), Value::Scalar(0.0));
701 for (fn_name, val) in locals.iter() {
704 local_env.insert(fn_name.clone(), val.clone());
705 }
706 for (var_name, val) in caller_env.iter() {
707 if matches!(val, Value::Function(_) | Value::Lambda(_)) {
708 local_env.insert(var_name.clone(), val.clone());
709 }
710 }
711
712 for (p, a) in fixed_params.iter().zip(effective_args.iter()) {
714 local_env.insert(p.clone(), a.clone());
715 }
716
717 if has_varargin {
719 let extra: Vec<Value> = effective_args
723 .get(fixed_params.len()..)
724 .unwrap_or(&[])
725 .to_vec();
726 let varargin = Value::Cell(Box::new(extra));
727 local_env.insert("varargin".to_string(), varargin);
728 }
729
730 let nargin = effective_args.len().min(params.len());
731 local_env.insert("nargin".to_string(), Value::Scalar(nargin as f64));
732 local_env.insert("nargout".to_string(), Value::Scalar(outputs.len() as f64));
733
734 let body = get_or_parse_body(body_source)?;
736 let fmt = get_display_fmt();
737 let base = get_display_base();
738 let compact = get_display_compact();
739
740 let maybe_chunk: Option<Rc<crate::vm::Chunk>> = BODY_CHUNK_CACHE.with(|cc| {
742 let mut cache = cc.borrow_mut();
743 if let Some(entry) = cache.get(body_source) {
744 return entry.clone();
745 }
746 let result: Option<Rc<crate::vm::Chunk>> = match crate::vm::compile::compile(&body) {
747 Ok(chunk) => Some(Rc::new(chunk)),
748 Err(_) => None,
749 };
750 cache.insert(body_source.to_string(), result.clone());
751 result
752 });
753
754 let exec_result = if let Some(chunk) = maybe_chunk {
755 crate::vm::exec::vm_exec(&chunk, &mut local_env, io, &fmt, base, compact)
756 } else {
757 exec_stmts(&body, &mut local_env, io, &fmt, base, compact)
758 };
759
760 let (func_name_saved, persistent_names) = persistent_frame_pop();
762 for var_name in &persistent_names {
763 if let Some(val) = local_env.get(var_name) {
764 persistent_save(&func_name_saved, var_name, val.clone());
765 }
766 }
767 global_frame_pop();
769
770 match exec_result? {
772 None | Some(Signal::Return) => {}
773 Some(Signal::Break) => return Err("'break' outside loop".to_string()),
774 Some(Signal::Continue) => return Err("'continue' outside loop".to_string()),
775 }
776
777 if outputs.is_empty() {
779 return Ok(Value::Void);
780 }
781
782 if outputs.len() == 1 && outputs[0] == "varargout" {
784 let cell = local_env
785 .remove("varargout")
786 .unwrap_or(Value::Cell(Box::default()));
787 return match cell {
788 Value::Cell(mut v) => {
789 if v.is_empty() {
790 Ok(Value::Void)
791 } else if v.len() == 1 {
792 Ok(v.remove(0))
793 } else {
794 Ok(Value::Tuple(*v))
795 }
796 }
797 other => Ok(other),
798 };
799 }
800
801 if outputs.len() == 1 {
802 return Ok(local_env.remove(&outputs[0]).unwrap_or(Value::Void));
803 }
804 let vals: Vec<Value> = outputs
805 .iter()
806 .map(|o| local_env.remove(o).unwrap_or(Value::Void))
807 .collect();
808 Ok(Value::Tuple(vals))
809}
810
811pub(crate) fn dispatch_user_call_for_vm(
821 name: &str,
822 args: &[Value],
823 env: &mut Env,
824 io: &mut IoContext,
825) -> Result<Value, String> {
826 if matches!(env.get(name), Some(Value::Function(_))) {
830 let f = env.get(name).unwrap();
831 return call_user_function(name, f, args, env, io);
832 }
833 call_via_eval_with_args(name, args, env, io)
836}
837
838fn call_via_eval_with_args(
845 name: &str,
846 args: &[Value],
847 env: &mut Env,
848 io: &mut IoContext,
849) -> Result<Value, String> {
850 let arg_keys: Vec<String> = (0..args.len()).map(|i| format!("__vm_cu_{i}__")).collect();
852 let mut saved: Vec<(String, Option<Value>)> = Vec::with_capacity(args.len());
853 for (key, val) in arg_keys.iter().zip(args.iter()) {
854 saved.push((key.clone(), env.get(key.as_str()).cloned()));
855 env.insert(key.clone(), val.clone());
856 }
857 let call_expr = crate::eval::Expr::Call(
859 name.to_string(),
860 arg_keys
861 .iter()
862 .map(|k| crate::eval::Expr::Var(k.clone()))
863 .collect(),
864 );
865 let result = eval_with_io(&call_expr, env, io);
866 for (key, saved_val) in saved {
868 match saved_val {
869 Some(v) => {
870 env.insert(key, v);
871 }
872 None => {
873 env.remove(&key);
874 }
875 }
876 }
877 result
878}
879
880pub fn resolve_script_path(name: &str) -> Option<std::path::PathBuf> {
886 let p = std::path::Path::new(name);
895 let mut bases: Vec<std::path::PathBuf> = Vec::new();
896
897 SCRIPT_DIR_STACK.with(|stack| {
899 for dir in stack.borrow().iter().rev() {
900 bases.push(dir.join("private").join(p));
901 bases.push(dir.join(p));
902 }
903 });
904
905 bases.push(p.to_path_buf());
907
908 SESSION_PATH.with(|sp| {
910 for dir in sp.borrow().iter() {
911 bases.push(dir.join(p));
912 }
913 });
914
915 for base in &bases {
916 if base.extension().is_some() {
917 if base.exists() {
918 return Some(base.clone());
919 }
920 continue;
922 }
923 let with_calc = base.with_extension("calc");
924 if with_calc.exists() {
925 return Some(with_calc);
926 }
927 let with_m = base.with_extension("m");
928 if with_m.exists() {
929 return Some(with_m);
930 }
931 }
932 None
933}
934
935pub(crate) fn is_truthy(val: &Value) -> bool {
943 match val {
944 Value::Scalar(n) => *n != 0.0 && !n.is_nan(),
945 Value::Matrix(m) => m.iter().all(|&x| x != 0.0 && !x.is_nan()),
946 Value::Complex(re, im) => *re != 0.0 || *im != 0.0,
947 Value::ComplexMatrix(m) => m.iter().all(|c| c.re != 0.0 || c.im != 0.0),
948 Value::Str(s) | Value::StringObj(s) => !s.is_empty(),
949 Value::Void => false,
950 Value::Lambda(_) | Value::Function(_) | Value::Tuple(_) => true,
953 Value::Cell(v) => !v.is_empty(),
955 Value::Struct(_) | Value::StructArray(_) => true,
957 Value::DateTime(ts) => !ts.is_nan(),
959 Value::Duration(s) => *s != 0.0,
960 Value::DateTimeArray(v) | Value::DurationArray(v) => !v.is_empty(),
961 Value::Map(m) => !m.is_empty(),
963 }
964}
965
966pub(crate) fn print_value(
971 label: Option<&str>,
972 val: &Value,
973 fmt: &FormatMode,
974 base: Base,
975 compact: bool,
976) {
977 match val {
978 Value::Void => {}
979 Value::Scalar(n) => {
980 if let Some(name) = label {
981 println!("{name} = {}", format_scalar(*n, base, fmt));
982 } else {
983 println!("{}", format_scalar(*n, base, fmt));
984 }
985 }
986 Value::Matrix(_) => {
987 if let Some(full) = format_value_full(val, fmt) {
988 let prefix = label.unwrap_or("ans");
989 println!("{prefix} =");
990 println!("{full}");
991 if !compact {
992 println!();
993 }
994 }
995 }
996 Value::Complex(re, im) => {
997 if let Some(name) = label {
998 println!("{name} = {}", format_complex(*re, *im, fmt));
999 } else {
1000 println!("{}", format_complex(*re, *im, fmt));
1001 }
1002 }
1003 Value::Str(s) | Value::StringObj(s) => {
1004 if let Some(name) = label {
1005 println!("{name} = {s}");
1006 } else {
1007 println!("{s}");
1008 }
1009 }
1010 Value::Lambda(_) => {
1011 if let Some(name) = label {
1012 println!("{name} = @<lambda>");
1013 } else {
1014 println!("@<lambda>");
1015 }
1016 }
1017 Value::Function(fd) => {
1018 let params_str = fd.params.join(", ");
1019 let out_str = match fd.outputs.len() {
1020 0 => String::new(),
1021 1 => format!("{} = ", fd.outputs[0]),
1022 _ => format!("[{}] = ", fd.outputs.join(", ")),
1023 };
1024 if let Some(name) = label {
1025 println!("{name} = @function {out_str}{name}({params_str})");
1026 } else {
1027 println!("@function {out_str}f({params_str})");
1028 }
1029 }
1030 Value::Tuple(vals) => {
1031 for (i, v) in vals.iter().enumerate() {
1034 print_value(label.map(|_| "ans").or(Some("ans")), v, fmt, base, compact);
1035 let _ = i;
1036 }
1037 }
1038 Value::ComplexMatrix(_) => {
1039 if let Some(full) = format_value_full(val, fmt) {
1040 let prefix = label.unwrap_or("ans");
1041 println!("{prefix} =");
1042 println!("{full}");
1043 if !compact {
1044 println!();
1045 }
1046 }
1047 }
1048 Value::Cell(_) | Value::Struct(_) | Value::StructArray(_) => {
1049 if let Some(full) = format_value_full(val, fmt) {
1050 let prefix = label.unwrap_or("ans");
1051 println!("{prefix} =");
1052 println!("{full}");
1053 if !compact {
1054 println!();
1055 }
1056 }
1057 }
1058 Value::DateTime(ts) => {
1059 let s = crate::datetime::format_datetime(*ts);
1060 if let Some(name) = label {
1061 println!("{name} = {s}");
1062 } else {
1063 println!("{s}");
1064 }
1065 }
1066 Value::Duration(secs) => {
1067 let s = crate::datetime::format_duration(*secs);
1068 if let Some(name) = label {
1069 println!("{name} = {s}");
1070 } else {
1071 println!("{s}");
1072 }
1073 }
1074 Value::DateTimeArray(_) | Value::DurationArray(_) => {
1075 if let Some(full) = format_value_full(val, fmt) {
1076 let prefix = label.unwrap_or("ans");
1077 println!("{prefix} =");
1078 println!("{full}");
1079 if !compact {
1080 println!();
1081 }
1082 }
1083 }
1084 Value::Map(_) => {
1085 if let Some(full) = format_value_full(val, fmt) {
1086 let prefix = label.unwrap_or("ans");
1087 println!("{prefix} =");
1088 println!("{full}");
1089 if !compact {
1090 println!();
1091 }
1092 }
1093 }
1094 }
1095}
1096
1097fn set_nested(
1102 mut map: IndexMap<String, Value>,
1103 path: &[String],
1104 val: Value,
1105) -> Result<IndexMap<String, Value>, String> {
1106 let (first, rest) = path.split_first().expect("set_nested: empty path");
1107 if rest.is_empty() {
1108 map.insert(first.clone(), val);
1109 } else {
1110 let inner = match map.shift_remove(first) {
1111 Some(Value::Struct(m)) => *m,
1112 None => IndexMap::new(),
1113 Some(other) => {
1114 map.insert(first.clone(), other);
1115 return Err(format!("'{first}' is not a struct"));
1116 }
1117 };
1118 let updated = set_nested(inner, rest, val)?;
1119 map.insert(first.clone(), Value::Struct(Box::new(updated)));
1120 }
1121 Ok(map)
1122}
1123
1124fn hoist_functions(stmts: &[StmtEntry], env: &mut Env) {
1131 for (stmt, _, _) in stmts {
1132 if let Stmt::FunctionDef {
1133 name,
1134 outputs,
1135 params,
1136 body_source,
1137 doc,
1138 } = stmt
1139 {
1140 env.insert(
1141 name.clone(),
1142 Value::Function(Box::new(crate::env::FunctionData {
1143 outputs: outputs.clone(),
1144 params: params.clone(),
1145 body_source: body_source.clone(),
1146 locals: IndexMap::new(),
1147 doc: doc.clone(),
1148 })),
1149 );
1150 }
1151 }
1152}
1153
1154pub fn exec_script(
1160 stmts: &[StmtEntry],
1161 env: &mut Env,
1162 io: &mut IoContext,
1163 fmt: &FormatMode,
1164 base: Base,
1165 compact: bool,
1166) -> Result<Option<Signal>, String> {
1167 hoist_functions(stmts, env);
1168 exec_stmts(stmts, env, io, fmt, base, compact)
1169}
1170
1171pub fn exec_stmts(
1178 stmts: &[StmtEntry],
1179 env: &mut Env,
1180 io: &mut IoContext,
1181 fmt: &FormatMode,
1182 base: Base,
1183 compact: bool,
1184) -> Result<Option<Signal>, String> {
1185 if crate::vm::compile::is_compilable(stmts) {
1189 match crate::vm::compile::compile(stmts) {
1190 Ok(chunk) => {
1191 return crate::vm::exec::vm_exec(&chunk, env, io, fmt, base, compact);
1192 }
1193 Err(crate::vm::CompileError::Unsupported) => {}
1194 }
1195 }
1196
1197 set_display_ctx(fmt, base, compact);
1199
1200 for (stmt, silent, stmt_line) in stmts {
1201 match stmt {
1202 Stmt::Assign(name, expr) => {
1203 set_nargout(1);
1204 let val = eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1205 env.insert(name.clone(), val.clone());
1206 if is_global(name) {
1208 global_set(name, val.clone());
1209 }
1210 if is_persistent(name) {
1212 persistent_save(¤t_func_name(), name, val.clone());
1213 }
1214 if !silent && !matches!(val, Value::Void) {
1215 print_value(Some(name), &val, fmt, base, compact);
1216 }
1217 }
1218
1219 Stmt::Global(names) => {
1220 for name in names {
1221 global_declare(name);
1222 global_init_if_absent(name);
1223 if let Some(local_val) = env.remove(name) {
1226 global_set(name, local_val.clone());
1227 env.insert(name.clone(), local_val);
1228 } else if let Some(global_val) = global_get(name) {
1229 env.insert(name.clone(), global_val);
1230 }
1231 }
1232 }
1233
1234 Stmt::Persistent(names) => {
1235 let func = current_func_name();
1238 for name in names {
1239 persistent_declare(name);
1240 if let Some(saved) = persistent_load(&func, name) {
1241 env.insert(name.clone(), saved);
1243 } else {
1244 env.insert(
1248 name.clone(),
1249 Value::Matrix(Box::new(ndarray::Array2::zeros((0, 0)))),
1250 );
1251 }
1252 }
1253 }
1254
1255 Stmt::Expr(expr) => {
1256 if let Expr::Call(fn_name, args) = expr
1258 && matches!(fn_name.as_str(), "addpath" | "rmpath" | "path")
1259 {
1260 match fn_name.as_str() {
1261 "addpath" => {
1262 if args.is_empty() || args.len() > 2 {
1263 return Err(
1264 "addpath: expects 1 or 2 arguments: addpath(dir) or addpath(dir, '-end')".to_string()
1265 );
1266 }
1267 let path_val = eval_with_io(&args[0], env, io)?;
1268 let path_str = match &path_val {
1269 Value::Str(s) | Value::StringObj(s) => s.clone(),
1270 _ => {
1271 return Err(
1272 "addpath: argument must be a string (directory path)"
1273 .to_string(),
1274 );
1275 }
1276 };
1277 let append = if args.len() == 2 {
1278 let flag_val = eval_with_io(&args[1], env, io)?;
1279 match &flag_val {
1280 Value::Str(s) | Value::StringObj(s) if s == "-end" => true,
1281 Value::Str(_) | Value::StringObj(_) => {
1282 return Err(
1283 "addpath: second argument must be '-end' (to append) or omitted (to prepend)".to_string()
1284 );
1285 }
1286 _ => {
1287 return Err(
1288 "addpath: second argument must be a string '-end'"
1289 .to_string(),
1290 );
1291 }
1292 }
1293 } else {
1294 false
1295 };
1296 let expanded = expand_tilde(&path_str);
1297 let pb = std::path::PathBuf::from(&expanded);
1298 session_path_add(pb, append);
1299 if !silent {
1300 for p in session_path_list() {
1301 println!("{}", p.display());
1302 }
1303 }
1304 }
1305 "rmpath" => {
1306 if args.len() != 1 {
1307 return Err("rmpath: expects exactly 1 argument".to_string());
1308 }
1309 let path_val = eval_with_io(&args[0], env, io)?;
1310 let path_str = match &path_val {
1311 Value::Str(s) | Value::StringObj(s) => s.clone(),
1312 _ => {
1313 return Err(
1314 "rmpath: argument must be a string (directory path)"
1315 .to_string(),
1316 );
1317 }
1318 };
1319 let expanded = expand_tilde(&path_str);
1320 session_path_remove(std::path::Path::new(&expanded));
1321 }
1322 "path" => {
1323 if !args.is_empty() {
1324 return Err("path: takes no arguments".to_string());
1325 }
1326 if !silent {
1327 let paths = session_path_list();
1328 if paths.is_empty() {
1329 println!("(search path is empty)");
1330 } else {
1331 for p in &paths {
1332 println!("{}", p.display());
1333 }
1334 }
1335 }
1336 }
1337 _ => unreachable!(),
1338 }
1339 continue;
1340 }
1341
1342 if let Expr::Call(fn_name, args) = expr
1345 && matches!(fn_name.as_str(), "run" | "source")
1346 && args.len() == 1
1347 {
1348 let path_val = eval_with_io(&args[0], env, io)?;
1349 let filename = match &path_val {
1350 Value::Str(s) | Value::StringObj(s) => s.clone(),
1351 _ => {
1352 return Err(format!("{fn_name}: argument must be a string (filename)"));
1353 }
1354 };
1355 let script_path = resolve_script_path(&filename)
1356 .ok_or_else(|| format!("{fn_name}: script not found: '{filename}'"))?;
1357 let content = std::fs::read_to_string(&script_path).map_err(|e| {
1358 format!("{fn_name}: cannot read '{}': {e}", script_path.display())
1359 })?;
1360 let depth = RUN_DEPTH.with(|d| d.get());
1361 if depth >= 64 {
1362 return Err(format!(
1363 "{fn_name}: maximum script nesting depth (64) exceeded"
1364 ));
1365 }
1366 RUN_DEPTH.with(|d| d.set(depth + 1));
1367 if let Some(dir) = script_path.parent() {
1370 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().push(dir.to_path_buf()));
1371 }
1372 let run_stmts = parse_stmts(&content).map_err(|e| {
1373 format!("{fn_name}: parse error in '{}': {e}", script_path.display())
1374 })?;
1375
1376 let is_fn_file = !run_stmts.is_empty()
1382 && run_stmts
1383 .iter()
1384 .all(|(s, _, _)| matches!(s, Stmt::FunctionDef { .. }));
1385 let result = if is_fn_file {
1386 let primary_name = match &run_stmts[0].0 {
1387 Stmt::FunctionDef { name, .. } => name.clone(),
1388 _ => unreachable!(),
1389 };
1390 let mut locals: IndexMap<String, Value> = IndexMap::new();
1391 for (stmt, _, _) in &run_stmts {
1392 if let Stmt::FunctionDef {
1393 name,
1394 outputs,
1395 params,
1396 body_source,
1397 doc,
1398 } = stmt
1399 && name != &primary_name
1400 {
1401 locals.insert(
1402 name.clone(),
1403 Value::Function(Box::new(crate::env::FunctionData {
1404 outputs: outputs.clone(),
1405 params: params.clone(),
1406 body_source: body_source.clone(),
1407 locals: IndexMap::new(),
1408 doc: doc.clone(),
1409 })),
1410 );
1411 }
1412 }
1413 if let Stmt::FunctionDef {
1414 outputs,
1415 params,
1416 body_source,
1417 doc,
1418 ..
1419 } = &run_stmts[0].0
1420 {
1421 env.insert(
1422 primary_name,
1423 Value::Function(Box::new(crate::env::FunctionData {
1424 outputs: outputs.clone(),
1425 params: params.clone(),
1426 body_source: body_source.clone(),
1427 locals,
1428 doc: doc.clone(),
1429 })),
1430 );
1431 }
1432 Ok(None)
1433 } else {
1434 for (stmt, _, _) in run_stmts.iter() {
1437 if let Stmt::FunctionDef {
1438 name,
1439 outputs,
1440 params,
1441 body_source,
1442 doc,
1443 } = stmt
1444 {
1445 env.insert(
1446 name.clone(),
1447 Value::Function(Box::new(crate::env::FunctionData {
1448 outputs: outputs.clone(),
1449 params: params.clone(),
1450 body_source: body_source.clone(),
1451 locals: IndexMap::new(),
1452 doc: doc.clone(),
1453 })),
1454 );
1455 }
1456 }
1457 exec_stmts(&run_stmts, env, io, fmt, base, compact)
1458 };
1459 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().pop());
1460 RUN_DEPTH.with(|d| d.set(depth));
1461 match result? {
1464 None => {}
1465 Some(sig) => return Ok(Some(sig)),
1466 }
1467 continue;
1468 }
1469
1470 if let Expr::Call(fn_name, args) = expr
1474 && fn_name == "eval"
1475 && (args.len() == 1 || args.len() == 2)
1476 {
1477 let code_val = eval_with_io(&args[0], env, io)?;
1478 let code_str = match code_val {
1479 Value::Str(s) | Value::StringObj(s) => s,
1480 _ => return Err("eval: argument must be a string".to_string()),
1481 };
1482 let depth = RUN_DEPTH.with(|d| d.get());
1483 if depth >= 64 {
1484 return Err("eval: maximum nesting depth (64) exceeded".to_string());
1485 }
1486 RUN_DEPTH.with(|d| d.set(depth + 1));
1487 let run_result = (|| -> Result<Option<Signal>, String> {
1488 let stmts = parse_stmts(&code_str)
1489 .map_err(|e| format!("eval: parse error: {e}"))?;
1490 exec_script(&stmts, env, io, fmt, base, compact)
1491 })();
1492 RUN_DEPTH.with(|d| d.set(depth));
1493 match run_result {
1494 Err(e) if args.len() == 2 => {
1495 set_last_err(&e);
1496 let catch_val = eval_with_io(&args[1], env, io)?;
1497 let catch_str = match catch_val {
1498 Value::Str(s) | Value::StringObj(s) => s,
1499 _ => {
1500 return Err("eval: catch argument must be a string".to_string());
1501 }
1502 };
1503 let catch_stmts = parse_stmts(&catch_str)
1504 .map_err(|e| format!("eval: catch parse error: {e}"))?;
1505 match exec_stmts(&catch_stmts, env, io, fmt, base, compact)? {
1506 None => {}
1507 Some(sig) => return Ok(Some(sig)),
1508 }
1509 }
1510 Err(e) => return Err(e),
1511 Ok(None) => {}
1512 Ok(Some(sig)) => return Ok(Some(sig)),
1513 }
1514 continue;
1515 }
1516
1517 if let Expr::Call(fn_name, args) = expr
1519 && fn_name == "clear"
1520 {
1521 if args.is_empty() {
1522 env.clear();
1523 } else {
1524 for arg in args {
1525 let key = match arg {
1526 Expr::StrLiteral(s) | Expr::StringObjLiteral(s) => s.clone(),
1527 other => match eval_with_io(other, env, io)? {
1528 Value::Str(s) | Value::StringObj(s) => s,
1529 _ => continue,
1530 },
1531 };
1532 env.remove(&key);
1533 }
1534 }
1535 continue;
1536 }
1537
1538 if let Expr::Call(fn_name, args) = expr
1540 && fn_name == "remove"
1541 && args.len() == 2
1542 && let Expr::Var(map_name) = &args[0]
1543 {
1544 let map_name = map_name.clone();
1545 let key_val = eval_with_io(&args[1], env, io)
1546 .map_err(|e| annotate_line(e, *stmt_line))?;
1547 let key = match key_val {
1548 Value::Str(s) | Value::StringObj(s) => s,
1549 _ => {
1550 return Err(annotate_line(
1551 "remove: key must be a string".to_string(),
1552 *stmt_line,
1553 ));
1554 }
1555 };
1556 match env.get_mut(&map_name) {
1557 Some(Value::Map(map)) => {
1558 map.shift_remove(&key);
1559 }
1560 Some(_) => {
1561 return Err(annotate_line(
1562 format!("remove: '{map_name}' is not a containers.Map"),
1563 *stmt_line,
1564 ));
1565 }
1566 None => {
1567 return Err(annotate_line(
1568 format!("remove: undefined variable '{map_name}'"),
1569 *stmt_line,
1570 ));
1571 }
1572 }
1573 continue;
1574 }
1575
1576 if let Expr::Call(fn_name, args) = expr
1578 && fn_name == "format"
1579 {
1580 let arg = match args.first() {
1581 Some(Expr::StrLiteral(s)) => s.as_str(),
1582 None => "",
1583 _ => "",
1584 };
1585 let new_fmt = match arg {
1586 "" | "short" => FormatMode::Short,
1587 "long" => FormatMode::Long,
1588 "shorte" | "shortE" => FormatMode::ShortE,
1589 "longe" | "longE" => FormatMode::LongE,
1590 "shortg" | "shortG" => FormatMode::ShortG,
1591 "longg" | "longG" => FormatMode::LongG,
1592 "bank" => FormatMode::Bank,
1593 "rat" => FormatMode::Rat,
1594 "hex" => FormatMode::Hex,
1595 "+" => FormatMode::Plus,
1596 "compact" | "loose" => get_display_fmt(),
1597 s => s
1598 .parse::<usize>()
1599 .map(FormatMode::Custom)
1600 .unwrap_or_else(|_| get_display_fmt()),
1601 };
1602 let new_compact = match arg {
1603 "compact" => true,
1604 "loose" => false,
1605 _ => get_display_compact(),
1606 };
1607 set_display_ctx(&new_fmt, base, new_compact);
1608 continue;
1609 }
1610
1611 if let Expr::Call(fn_name, args) = expr
1613 && matches!(fn_name.as_str(), "save" | "load" | "ws" | "wl")
1614 {
1615 let is_save = matches!(fn_name.as_str(), "save" | "ws");
1616 if is_save {
1617 let (path_opt, var_names) = if args.is_empty() {
1618 (None, vec![])
1619 } else {
1620 let path_val = eval_with_io(&args[0], env, io)?;
1621 let path_str = match path_val {
1622 Value::Str(s) | Value::StringObj(s) => s,
1623 _ => return Err("save: path argument must be a string".to_string()),
1624 };
1625 let mut vars: Vec<String> = Vec::new();
1626 for a in &args[1..] {
1627 let v = eval_with_io(a, env, io)?;
1628 match v {
1629 Value::Str(s) | Value::StringObj(s) => vars.push(s),
1630 _ => {
1631 return Err(
1632 "save: variable names must be strings".to_string()
1633 );
1634 }
1635 }
1636 }
1637 (Some(path_str), vars)
1638 };
1639 let result = match &path_opt {
1640 None => {
1641 let home = std::env::var("HOME")
1642 .or_else(|_| std::env::var("USERPROFILE"))
1643 .unwrap_or_default();
1644 let p = std::path::Path::new(&home)
1645 .join(".config")
1646 .join("ccalc")
1647 .join("workspace.toml");
1648 save_workspace(env, &p)
1649 }
1650 Some(p) if var_names.is_empty() => {
1651 save_workspace(env, std::path::Path::new(p))
1652 }
1653 Some(p) => {
1654 let refs: Vec<&str> =
1655 var_names.iter().map(String::as_str).collect();
1656 save_workspace_vars(env, std::path::Path::new(p), &refs)
1657 }
1658 };
1659 if let Err(e) = result {
1660 return Err(format!("save: {e}"));
1661 }
1662 } else {
1663 let loaded = if args.is_empty() {
1665 let home = std::env::var("HOME")
1666 .or_else(|_| std::env::var("USERPROFILE"))
1667 .unwrap_or_default();
1668 let p = std::path::Path::new(&home)
1669 .join(".config")
1670 .join("ccalc")
1671 .join("workspace.toml");
1672 load_workspace(&p)
1673 } else {
1674 let path_val = eval_with_io(&args[0], env, io)?;
1675 let path_str = match path_val {
1676 Value::Str(s) | Value::StringObj(s) => s,
1677 _ => return Err("load: path argument must be a string".to_string()),
1678 };
1679 load_workspace(std::path::Path::new(&path_str))
1680 };
1681 match loaded {
1682 Ok(ws) => env.extend(ws),
1683 Err(e) => return Err(format!("load: {e}")),
1684 }
1685 }
1686 continue;
1687 }
1688
1689 let expr_label = if let Expr::Var(name) = expr {
1690 Some(name.as_str())
1691 } else {
1692 None
1693 };
1694 let val = eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1695 env.insert("ans".to_string(), val.clone());
1696 if !silent && !matches!(val, Value::Void) {
1697 print_value(expr_label, &val, fmt, base, compact);
1698 }
1699 }
1700
1701 Stmt::If {
1702 cond,
1703 body,
1704 elseif_branches,
1705 else_body,
1706 } => {
1707 let cond_val =
1708 eval_with_io(cond, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1709 let chosen: Option<&[StmtEntry]> = if is_truthy(&cond_val) {
1710 Some(body)
1711 } else {
1712 let mut found = None;
1713 for (ei_cond, ei_body) in elseif_branches {
1714 if is_truthy(
1715 &eval_with_io(ei_cond, env, io)
1716 .map_err(|e| annotate_line(e, *stmt_line))?,
1717 ) {
1718 found = Some(ei_body.as_slice());
1719 break;
1720 }
1721 }
1722 if found.is_none() {
1723 found = else_body.as_deref();
1724 }
1725 found
1726 };
1727 if let Some(body_stmts) = chosen
1728 && let Some(sig) = exec_stmts(body_stmts, env, io, fmt, base, compact)?
1729 {
1730 return Ok(Some(sig));
1731 }
1732 }
1733
1734 Stmt::For {
1735 var,
1736 range_expr,
1737 body,
1738 } => {
1739 let range_val =
1740 eval_with_io(range_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1741 let iter_cols: Vec<Value> = match range_val {
1742 Value::Scalar(n) => vec![Value::Scalar(n)],
1743 Value::Matrix(m) => {
1744 let nrows = m.nrows();
1745 let ncols = m.ncols();
1746 (0..ncols)
1747 .map(|j| {
1748 if nrows == 1 {
1749 Value::Scalar(m[[0, j]])
1751 } else {
1752 let mut col = Array2::zeros((nrows, 1));
1754 for i in 0..nrows {
1755 col[[i, 0]] = m[[i, j]];
1756 }
1757 Value::Matrix(Box::new(col))
1758 }
1759 })
1760 .collect()
1761 }
1762 _ => return Err("'for' range must evaluate to a scalar or matrix".to_string()),
1763 };
1764
1765 'for_loop: for col_val in iter_cols {
1766 env.insert(var.clone(), col_val);
1767 match exec_stmts(body, env, io, fmt, base, compact)? {
1768 None => {}
1769 Some(Signal::Break) => break 'for_loop,
1770 Some(Signal::Continue) => continue 'for_loop,
1771 Some(Signal::Return) => return Ok(Some(Signal::Return)),
1772 }
1773 }
1774 }
1775
1776 Stmt::While { cond, body } => loop {
1777 if !is_truthy(
1778 &eval_with_io(cond, env, io).map_err(|e| annotate_line(e, *stmt_line))?,
1779 ) {
1780 break;
1781 }
1782 match exec_stmts(body, env, io, fmt, base, compact)? {
1783 None => {}
1784 Some(Signal::Break) => break,
1785 Some(Signal::Continue) => continue,
1786 Some(Signal::Return) => return Ok(Some(Signal::Return)),
1787 }
1788 },
1789
1790 Stmt::Break => return Ok(Some(Signal::Break)),
1791 Stmt::Continue => return Ok(Some(Signal::Continue)),
1792
1793 Stmt::Switch {
1795 expr,
1796 cases,
1797 otherwise_body,
1798 } => {
1799 let switch_val =
1800 eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1801 let mut matched = false;
1802 'switch_loop: for (case_exprs, case_body) in cases {
1803 for case_expr in case_exprs {
1804 let case_val = eval_with_io(case_expr, env, io)
1805 .map_err(|e| annotate_line(e, *stmt_line))?;
1806 let is_match = if let Value::Cell(cell_elems) = &case_val {
1809 cell_elems.iter().any(|elem| match (&switch_val, elem) {
1810 (Value::Scalar(a), Value::Scalar(b)) => a == b,
1811 _ => {
1812 let sv = match &switch_val {
1813 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1814 _ => None,
1815 };
1816 let cv = match elem {
1817 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1818 _ => None,
1819 };
1820 matches!((sv, cv), (Some(a), Some(b)) if a == b)
1821 }
1822 })
1823 } else {
1824 match (&switch_val, &case_val) {
1825 (Value::Scalar(a), Value::Scalar(b)) => a == b,
1826 _ => {
1827 let sv = match &switch_val {
1828 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1829 _ => None,
1830 };
1831 let cv = match &case_val {
1832 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1833 _ => None,
1834 };
1835 matches!((sv, cv), (Some(a), Some(b)) if a == b)
1836 }
1837 }
1838 };
1839 if is_match {
1840 if let Some(sig) = exec_stmts(case_body, env, io, fmt, base, compact)? {
1841 return Ok(Some(sig));
1842 }
1843 matched = true;
1844 break 'switch_loop;
1845 }
1846 }
1847 }
1848 if !matched
1849 && let Some(ob) = otherwise_body
1850 && let Some(sig) = exec_stmts(ob, env, io, fmt, base, compact)?
1851 {
1852 return Ok(Some(sig));
1853 }
1854 }
1855
1856 Stmt::DoUntil { body, cond } => loop {
1858 match exec_stmts(body, env, io, fmt, base, compact)? {
1859 Some(Signal::Break) => break,
1860 Some(Signal::Continue) | None => {}
1861 Some(Signal::Return) => return Ok(Some(Signal::Return)),
1862 }
1863 if is_truthy(
1864 &eval_with_io(cond, env, io).map_err(|e| annotate_line(e, *stmt_line))?,
1865 ) {
1866 break;
1867 }
1868 },
1869
1870 Stmt::TryCatch {
1872 try_body,
1873 catch_var,
1874 catch_body,
1875 } => match exec_stmts(try_body, env, io, fmt, base, compact) {
1876 Ok(None) => {}
1877 Ok(Some(sig)) => return Ok(Some(sig)),
1878 Err(msg) => {
1879 let clean = strip_near_line(msg);
1880 set_last_err(&clean);
1881 if let Some(var) = catch_var {
1882 let mut map = IndexMap::new();
1883 map.insert("message".to_string(), Value::Str(clean));
1884 env.insert(var.clone(), Value::Struct(Box::new(map)));
1885 }
1886 if let Some(sig) = exec_stmts(catch_body, env, io, fmt, base, compact)? {
1887 return Ok(Some(sig));
1888 }
1889 }
1890 },
1891
1892 Stmt::FunctionDef {
1894 name,
1895 outputs,
1896 params,
1897 body_source,
1898 doc,
1899 } => {
1900 env.insert(
1901 name.clone(),
1902 Value::Function(Box::new(crate::env::FunctionData {
1903 outputs: outputs.clone(),
1904 params: params.clone(),
1905 body_source: body_source.clone(),
1906 locals: IndexMap::new(),
1907 doc: doc.clone(),
1908 })),
1909 );
1910 }
1911
1912 Stmt::Return => return Ok(Some(Signal::Return)),
1914
1915 Stmt::CellSet(cell_name, idx_expr, val_expr) => {
1917 let cell_len = match env.get(cell_name) {
1919 Some(Value::Cell(v)) => v.len(),
1920 _ => 0,
1921 };
1922 let _owned_end;
1923 let env_end: &Env = if crate::eval::contains_end(idx_expr) {
1924 _owned_end = write_env_with_end(env, cell_len);
1925 &_owned_end
1926 } else {
1927 env
1928 };
1929 let idx = eval_with_io(idx_expr, env_end, io)
1930 .map_err(|e| annotate_line(e, *stmt_line))?;
1931 let rhs =
1932 eval_with_io(val_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1933 let i = match idx {
1934 Value::Scalar(n) => n as isize,
1935 _ => return Err(format!("{cell_name}{{}}: index must be a scalar integer")),
1936 };
1937 match env.get_mut(cell_name) {
1938 Some(Value::Cell(v)) => {
1939 if i < 1 {
1940 return Err(format!(
1941 "{cell_name}{{}}: index {i} out of range (1..{})",
1942 v.len()
1943 ));
1944 }
1945 let idx = (i - 1) as usize;
1946 if idx >= v.len() {
1948 v.resize(idx + 1, Value::Scalar(0.0));
1949 }
1950 v[idx] = rhs.clone();
1951 }
1952 Some(_) => {
1953 return Err(format!(
1954 "'{cell_name}' is not a cell array; use () for regular indexing"
1955 ));
1956 }
1957 None => {
1958 if i < 1 {
1960 return Err(format!("{cell_name}{{}}: index {i} must be >= 1"));
1961 }
1962 let idx = (i - 1) as usize;
1963 let mut v = vec![Value::Scalar(0.0); idx + 1];
1964 v[idx] = rhs.clone();
1965 env.insert(cell_name.clone(), Value::Cell(Box::new(v)));
1966 }
1967 }
1968 if !silent && let Some(val) = env.get(cell_name) {
1969 print_value(Some(cell_name), val, fmt, base, compact);
1970 }
1971 }
1972
1973 Stmt::FieldSet(base_name, path, rhs_expr) => {
1975 let rhs =
1976 eval_with_io(rhs_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1977 let root = match env.remove(base_name) {
1978 Some(Value::Struct(m)) => *m,
1979 None => IndexMap::new(),
1980 Some(other) => {
1981 env.insert(base_name.clone(), other);
1982 return Err(format!("'{base_name}' is not a struct"));
1983 }
1984 };
1985 let updated = set_nested(root, path, rhs)?;
1986 let struct_val = Value::Struct(Box::new(updated));
1987 if !silent {
1988 print_value(Some(base_name), &struct_val, fmt, base, compact);
1989 }
1990 env.insert(base_name.clone(), struct_val);
1991 }
1992
1993 Stmt::DynFieldSet(base_name, field_expr, rhs_expr) => {
1995 let field_val =
1996 eval_with_io(field_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1997 let field = match &field_val {
1998 Value::Str(s) | Value::StringObj(s) => s.clone(),
1999 _ => {
2000 return Err(annotate_line(
2001 "Dynamic field name must be a string".to_string(),
2002 *stmt_line,
2003 ));
2004 }
2005 };
2006 let rhs =
2007 eval_with_io(rhs_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
2008 let root = match env.remove(base_name) {
2009 Some(Value::Struct(m)) => *m,
2010 None => IndexMap::new(),
2011 Some(other) => {
2012 env.insert(base_name.clone(), other);
2013 return Err(annotate_line(
2014 format!("'{base_name}' is not a struct"),
2015 *stmt_line,
2016 ));
2017 }
2018 };
2019 let mut updated = root;
2020 updated.insert(field, rhs);
2021 let struct_val = Value::Struct(Box::new(updated));
2022 if !silent {
2023 print_value(Some(base_name), &struct_val, fmt, base, compact);
2024 }
2025 env.insert(base_name.clone(), struct_val);
2026 }
2027
2028 Stmt::StructArrayFieldSet(base_name, idx_expr, path, rhs_expr) => {
2030 let rhs =
2031 eval_with_io(rhs_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
2032 let idx_val =
2033 eval_with_io(idx_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
2034 let idx = match &idx_val {
2035 Value::Scalar(n) => {
2036 let i = *n as isize;
2037 if i < 1 {
2038 return Err(format!(
2039 "Struct array index must be a positive integer, got {n}"
2040 ));
2041 }
2042 i as usize
2043 }
2044 _ => return Err("Struct array index must be a scalar integer".to_string()),
2045 };
2046 let mut arr: Vec<IndexMap<String, Value>> = match env.remove(base_name) {
2048 Some(Value::StructArray(v)) => *v,
2049 Some(Value::Struct(m)) => vec![*m],
2051 None => Vec::new(),
2052 Some(other) => {
2053 env.insert(base_name.clone(), other);
2054 return Err(format!("'{base_name}' is not a struct array"));
2055 }
2056 };
2057 while arr.len() < idx {
2059 arr.push(IndexMap::new());
2060 }
2061 let elem = arr[idx - 1].clone();
2063 let updated_elem = set_nested(elem, path, rhs)?;
2064 arr[idx - 1] = updated_elem;
2065 let arr_val = Value::StructArray(Box::new(arr));
2066 if !silent {
2067 print_value(Some(base_name), &arr_val, fmt, base, compact);
2068 }
2069 env.insert(base_name.clone(), arr_val);
2070 }
2071
2072 Stmt::IndexSet {
2074 name,
2075 indices,
2076 value,
2077 } => {
2078 let rhs = eval_with_io(value, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
2079 if is_persistent(name) {
2082 let func = current_func_name();
2083 if let Some(fresh) = persistent_load(&func, name) {
2084 env.insert(name.clone(), fresh);
2085 }
2086 }
2087 exec_index_set(name, indices, rhs, env, io)?;
2088 if is_persistent(name)
2090 && let Some(val) = env.get(name)
2091 {
2092 persistent_save(¤t_func_name(), name, val.clone());
2093 }
2094 if !silent && let Some(val) = env.get(name) {
2095 print_value(Some(name), val, fmt, base, compact);
2096 }
2097 }
2098
2099 Stmt::MultiAssign { targets, expr } => {
2101 set_nargout(targets.len());
2102 let val = eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
2103 let vals: Vec<Value> = match val {
2104 Value::Tuple(v) => v,
2105 other => vec![other],
2106 };
2107 for (i, target) in targets.iter().enumerate() {
2108 if target == "~" {
2109 continue; }
2111 let v = vals.get(i).cloned().unwrap_or(Value::Void);
2112 env.insert(target.clone(), v.clone());
2113 if !silent && !matches!(v, Value::Void) {
2114 print_value(Some(target), &v, fmt, base, compact);
2115 }
2116 }
2117 }
2118 }
2119 }
2120 global_refresh_into_env(env);
2124 Ok(None)
2125}
2126
2127enum WriteIdx {
2131 All,
2133 Positions(Vec<usize>),
2135}
2136
2137fn resolve_write_dim(
2143 expr: &crate::eval::Expr,
2144 dim_size: usize,
2145 env: &Env,
2146 io: &mut IoContext,
2147) -> Result<WriteIdx, String> {
2148 if matches!(expr, crate::eval::Expr::Colon) {
2149 return Ok(WriteIdx::All);
2150 }
2151 let val = eval_with_io(expr, env, io)?;
2152 let floats: Vec<f64> = match val {
2153 Value::Scalar(n) => vec![n],
2154 Value::Complex(re, im) => {
2155 if im != 0.0 {
2156 return Err("Index must be real, not complex".to_string());
2157 }
2158 vec![re]
2159 }
2160 Value::Matrix(m) => {
2161 let total = m.nrows() * m.ncols();
2162 if m.nrows() > 1 && m.ncols() > 1 && total != dim_size {
2163 return Err("Index must be a scalar or vector, not a 2-D matrix".to_string());
2164 }
2165 if m.nrows() > 1 && m.ncols() > 1 {
2167 let mut v = Vec::with_capacity(total);
2168 for col in 0..m.ncols() {
2169 for row in 0..m.nrows() {
2170 v.push(m[[row, col]]);
2171 }
2172 }
2173 v
2174 } else {
2175 m.iter().copied().collect()
2176 }
2177 }
2178 _ => return Err("Index must be numeric".to_string()),
2179 };
2180 if dim_size > 0 && floats.len() == dim_size && floats.iter().all(|&f| f == 0.0 || f == 1.0) {
2182 let positions: Vec<usize> = floats
2183 .iter()
2184 .enumerate()
2185 .filter(|&(_, &f)| f == 1.0)
2186 .map(|(i, _)| i)
2187 .collect();
2188 return Ok(WriteIdx::Positions(positions));
2189 }
2190 let positions: Result<Vec<usize>, String> = floats
2192 .iter()
2193 .map(|&n| {
2194 let i = n.round() as i64;
2195 if i < 1 {
2196 return Err(format!("Index {i} must be >= 1"));
2197 }
2198 Ok(i as usize - 1)
2199 })
2200 .collect();
2201 Ok(WriteIdx::Positions(positions?))
2202}
2203
2204fn write_env_with_end(env: &Env, dim_size: usize) -> Env {
2206 let mut e = env.clone();
2207 e.insert("end".to_string(), Value::Scalar(dim_size as f64));
2208 e
2209}
2210
2211pub(crate) fn exec_index_set(
2219 name: &str,
2220 indices: &[crate::eval::Expr],
2221 rhs: Value,
2222 env: &mut Env,
2223 io: &mut IoContext,
2224) -> Result<(), String> {
2225 if indices.len() == 1 && matches!(env.get(name), Some(Value::Map(_))) {
2227 let key_val = eval_with_io(&indices[0], env, io)?;
2228 let key = match key_val {
2229 Value::Str(s) | Value::StringObj(s) => s,
2230 _ => return Err("Map key assignment requires a string key".to_string()),
2231 };
2232 match env.get_mut(name) {
2233 Some(Value::Map(map)) => {
2234 map.insert(key, rhs);
2235 return Ok(());
2236 }
2237 _ => unreachable!(),
2238 }
2239 }
2240
2241 let needs_complex = matches!(env.get(name), Some(Value::ComplexMatrix(_)))
2243 || matches!(&rhs, Value::Complex(_, _) | Value::ComplexMatrix(_));
2244
2245 if needs_complex {
2246 return exec_index_set_complex(name, indices, rhs, env, io);
2247 }
2248
2249 match indices.len() {
2250 1 => {
2251 let (total, nrows_hint, ncols_hint) = match env.get(name) {
2253 Some(Value::Matrix(m)) => (m.nrows() * m.ncols(), m.nrows(), m.ncols()),
2254 Some(Value::Scalar(_)) => (1, 1, 1),
2255 None | Some(Value::Void) => (0, 0, 0),
2256 Some(_) => {
2257 return Err(format!(
2258 "'{name}' is not a matrix; cannot use () indexed assignment"
2259 ));
2260 }
2261 };
2262
2263 let widx = {
2266 let _owned_end;
2267 let env_end: &Env = if crate::eval::contains_end(&indices[0]) {
2268 _owned_end = write_env_with_end(env, total);
2269 &_owned_end
2270 } else {
2271 env
2272 };
2273 resolve_write_dim(&indices[0], total, env_end, io)?
2274 };
2275
2276 let positions: Vec<usize> = match widx {
2278 WriteIdx::All => (0..total).collect(),
2279 WriteIdx::Positions(p) => p,
2280 };
2281
2282 let rhs_vals: Vec<f64> = match &rhs {
2284 Value::Scalar(n) => vec![*n; positions.len()],
2285 Value::Matrix(m) => {
2286 let flat: Vec<f64> = m.iter().copied().collect();
2287 if flat.len() != positions.len() {
2288 return Err(format!(
2289 "Assignment dimension mismatch: {} positions but {} values",
2290 positions.len(),
2291 flat.len()
2292 ));
2293 }
2294 flat
2295 }
2296 _ => {
2297 return Err(
2298 "Indexed assignment: RHS must be a numeric scalar or matrix".to_string()
2299 );
2300 }
2301 };
2302
2303 let required = positions.iter().copied().max().map(|m| m + 1).unwrap_or(0);
2305 let required = required.max(total);
2306
2307 let (out_rows, out_cols) = if nrows_hint == 0 || ncols_hint == 0 {
2309 (1, required)
2311 } else if nrows_hint == 1 {
2312 (1, required)
2313 } else if ncols_hint == 1 {
2314 (required, 1)
2315 } else if required > total {
2316 return Err("Cannot grow a 2-D matrix with linear indexing".to_string());
2317 } else {
2318 (nrows_hint, ncols_hint)
2319 };
2320
2321 let mut mat = match env.remove(name) {
2324 Some(Value::Matrix(m)) => *m,
2325 Some(Value::Scalar(n)) => Array2::from_elem((1, 1), n),
2326 None | Some(Value::Void) => Array2::zeros((0, 0)),
2327 Some(other) => {
2328 env.insert(name.to_string(), other);
2329 return Err(format!(
2330 "'{name}' is not a matrix; cannot use () indexed assignment"
2331 ));
2332 }
2333 };
2334
2335 if required > total || out_rows != mat.nrows() || out_cols != mat.ncols() {
2337 let mut new_mat = Array2::<f64>::zeros((out_rows, out_cols));
2338 for old_p in 0..total {
2339 let old_row = old_p % mat.nrows().max(1);
2340 let old_col = old_p / mat.nrows().max(1);
2341 let new_row = old_p % out_rows;
2342 let new_col = old_p / out_rows;
2343 if old_row < mat.nrows() && old_col < mat.ncols() {
2344 new_mat[[new_row, new_col]] = mat[[old_row, old_col]];
2345 }
2346 }
2347 mat = new_mat;
2348 }
2349
2350 for (&pos, &val) in positions.iter().zip(rhs_vals.iter()) {
2352 let row = pos % mat.nrows();
2353 let col = pos / mat.nrows();
2354 mat[[row, col]] = val;
2355 }
2356
2357 let result = if mat.nrows() == 1 && mat.ncols() == 1 {
2358 Value::Scalar(mat[[0, 0]])
2359 } else {
2360 Value::Matrix(Box::new(mat))
2361 };
2362 env.insert(name.to_string(), result);
2363 }
2364 2 => {
2365 let (nrows, ncols) = match env.get(name) {
2367 Some(Value::Matrix(m)) => (m.nrows(), m.ncols()),
2368 Some(Value::Scalar(_)) => (1, 1),
2369 None | Some(Value::Void) => (0, 0),
2370 Some(_) => {
2371 return Err(format!(
2372 "'{name}' is not a matrix; cannot use () indexed assignment"
2373 ));
2374 }
2375 };
2376
2377 let (ridx, cidx) = {
2379 let _owned_r;
2380 let env_r: &Env = if crate::eval::contains_end(&indices[0]) {
2381 _owned_r = write_env_with_end(env, nrows);
2382 &_owned_r
2383 } else {
2384 env
2385 };
2386 let _owned_c;
2387 let env_c: &Env = if crate::eval::contains_end(&indices[1]) {
2388 _owned_c = write_env_with_end(env, ncols);
2389 &_owned_c
2390 } else {
2391 env
2392 };
2393 (
2394 resolve_write_dim(&indices[0], nrows, env_r, io)?,
2395 resolve_write_dim(&indices[1], ncols, env_c, io)?,
2396 )
2397 };
2398
2399 let rows: Vec<usize> = match ridx {
2400 WriteIdx::All => (0..nrows.max(1)).collect(),
2401 WriteIdx::Positions(p) => p,
2402 };
2403 let cols: Vec<usize> = match cidx {
2404 WriteIdx::All => (0..ncols.max(1)).collect(),
2405 WriteIdx::Positions(p) => p,
2406 };
2407
2408 let req_rows = rows
2409 .iter()
2410 .copied()
2411 .max()
2412 .map(|m| m + 1)
2413 .unwrap_or(0)
2414 .max(nrows);
2415 let req_cols = cols
2416 .iter()
2417 .copied()
2418 .max()
2419 .map(|m| m + 1)
2420 .unwrap_or(0)
2421 .max(ncols);
2422
2423 let n_sel = rows.len() * cols.len();
2425 let rhs_vals: Vec<f64> = match &rhs {
2426 Value::Scalar(n) => vec![*n; n_sel],
2427 Value::Matrix(m) => {
2428 let flat: Vec<f64> = m.iter().copied().collect();
2429 if flat.len() != n_sel {
2430 return Err(format!(
2431 "Assignment dimension mismatch: {}×{} = {} positions but {} values",
2432 rows.len(),
2433 cols.len(),
2434 n_sel,
2435 flat.len()
2436 ));
2437 }
2438 flat
2439 }
2440 _ => {
2441 return Err(
2442 "Indexed assignment: RHS must be a numeric scalar or matrix".to_string()
2443 );
2444 }
2445 };
2446
2447 let mut mat = match env.remove(name) {
2449 Some(Value::Matrix(m)) => *m,
2450 Some(Value::Scalar(n)) => Array2::from_elem((1, 1), n),
2451 None | Some(Value::Void) => Array2::zeros((0, 0)),
2452 Some(other) => {
2453 env.insert(name.to_string(), other);
2454 return Err(format!(
2455 "'{name}' is not a matrix; cannot use () indexed assignment"
2456 ));
2457 }
2458 };
2459
2460 if req_rows != nrows || req_cols != ncols {
2462 let mut new_mat = Array2::<f64>::zeros((req_rows, req_cols));
2463 for r in 0..nrows {
2464 for c in 0..ncols {
2465 new_mat[[r, c]] = mat[[r, c]];
2466 }
2467 }
2468 mat = new_mat;
2469 }
2470
2471 let mut k = 0;
2473 for &r in &rows {
2474 for &c in &cols {
2475 mat[[r, c]] = rhs_vals[k];
2476 k += 1;
2477 }
2478 }
2479
2480 let result = if mat.nrows() == 1 && mat.ncols() == 1 {
2481 Value::Scalar(mat[[0, 0]])
2482 } else {
2483 Value::Matrix(Box::new(mat))
2484 };
2485 env.insert(name.to_string(), result);
2486 }
2487 _ => return Err("Indexed assignment supports at most 2 indices".to_string()),
2488 }
2489 Ok(())
2490}
2491
2492fn exec_index_set_complex(
2497 name: &str,
2498 indices: &[crate::eval::Expr],
2499 rhs: Value,
2500 env: &mut Env,
2501 io: &mut IoContext,
2502) -> Result<(), String> {
2503 fn rhs_to_complex(rhs: &Value, n_slots: usize) -> Result<Vec<Complex<f64>>, String> {
2505 match rhs {
2506 Value::Scalar(n) => Ok(vec![Complex::new(*n, 0.0); n_slots]),
2507 Value::Complex(re, im) => Ok(vec![Complex::new(*re, *im); n_slots]),
2508 Value::Matrix(m) => {
2509 let flat: Vec<Complex<f64>> = m.iter().map(|&x| Complex::new(x, 0.0)).collect();
2510 if flat.len() != n_slots {
2511 return Err(format!(
2512 "Assignment dimension mismatch: {} positions but {} values",
2513 n_slots,
2514 flat.len()
2515 ));
2516 }
2517 Ok(flat)
2518 }
2519 Value::ComplexMatrix(m) => {
2520 let flat: Vec<Complex<f64>> = m.iter().copied().collect();
2521 if flat.len() != n_slots {
2522 return Err(format!(
2523 "Assignment dimension mismatch: {} positions but {} values",
2524 n_slots,
2525 flat.len()
2526 ));
2527 }
2528 Ok(flat)
2529 }
2530 _ => Err("Indexed assignment: RHS must be a numeric value".to_string()),
2531 }
2532 }
2533
2534 fn take_as_complex(name: &str, env: &mut Env) -> Result<Array2<Complex<f64>>, String> {
2536 match env.remove(name) {
2537 Some(Value::ComplexMatrix(m)) => Ok(*m),
2538 Some(Value::Matrix(m)) => Ok(m.mapv(|x| Complex::new(x, 0.0))),
2539 Some(Value::Scalar(n)) => Ok(Array2::from_elem((1, 1), Complex::new(n, 0.0))),
2540 None | Some(Value::Void) => Ok(Array2::zeros((0, 0))),
2541 Some(other) => {
2542 env.insert(name.to_string(), other);
2543 Err(format!(
2544 "'{name}' is not a matrix; cannot use () indexed assignment"
2545 ))
2546 }
2547 }
2548 }
2549
2550 match indices.len() {
2551 1 => {
2552 let (total, nrows_hint, ncols_hint) = match env.get(name) {
2553 Some(Value::ComplexMatrix(m)) => (m.nrows() * m.ncols(), m.nrows(), m.ncols()),
2554 Some(Value::Matrix(m)) => (m.nrows() * m.ncols(), m.nrows(), m.ncols()),
2555 Some(Value::Scalar(_)) => (1, 1, 1),
2556 None | Some(Value::Void) => (0, 0, 0),
2557 Some(_) => {
2558 return Err(format!(
2559 "'{name}' is not a matrix; cannot use () indexed assignment"
2560 ));
2561 }
2562 };
2563
2564 let widx = {
2565 let _owned_end;
2566 let env_end: &Env = if crate::eval::contains_end(&indices[0]) {
2567 _owned_end = write_env_with_end(env, total);
2568 &_owned_end
2569 } else {
2570 env
2571 };
2572 resolve_write_dim(&indices[0], total, env_end, io)?
2573 };
2574
2575 let positions: Vec<usize> = match widx {
2576 WriteIdx::All => (0..total).collect(),
2577 WriteIdx::Positions(p) => p,
2578 };
2579
2580 let rhs_vals = rhs_to_complex(&rhs, positions.len())?;
2581
2582 let required = positions.iter().copied().max().map(|m| m + 1).unwrap_or(0);
2583 let required = required.max(total);
2584
2585 let (out_rows, out_cols) = if nrows_hint == 0 || ncols_hint == 0 || nrows_hint == 1 {
2586 (1, required)
2587 } else if ncols_hint == 1 {
2588 (required, 1)
2589 } else if required > total {
2590 return Err("Cannot grow a 2-D matrix with linear indexing".to_string());
2591 } else {
2592 (nrows_hint, ncols_hint)
2593 };
2594
2595 let mut mat = take_as_complex(name, env)?;
2596
2597 if required > total || out_rows != mat.nrows() || out_cols != mat.ncols() {
2598 let mut new_mat = Array2::<Complex<f64>>::zeros((out_rows, out_cols));
2599 for old_p in 0..total {
2600 let old_row = old_p % mat.nrows().max(1);
2601 let old_col = old_p / mat.nrows().max(1);
2602 let new_row = old_p % out_rows;
2603 let new_col = old_p / out_rows;
2604 if old_row < mat.nrows() && old_col < mat.ncols() {
2605 new_mat[[new_row, new_col]] = mat[[old_row, old_col]];
2606 }
2607 }
2608 mat = new_mat;
2609 }
2610
2611 for (&pos, &val) in positions.iter().zip(rhs_vals.iter()) {
2612 let row = pos % mat.nrows();
2613 let col = pos / mat.nrows();
2614 mat[[row, col]] = val;
2615 }
2616
2617 env.insert(name.to_string(), Value::ComplexMatrix(Box::new(mat)));
2618 }
2619 2 => {
2620 let (nrows, ncols) = match env.get(name) {
2621 Some(Value::ComplexMatrix(m)) => (m.nrows(), m.ncols()),
2622 Some(Value::Matrix(m)) => (m.nrows(), m.ncols()),
2623 Some(Value::Scalar(_)) => (1, 1),
2624 None | Some(Value::Void) => (0, 0),
2625 Some(_) => {
2626 return Err(format!(
2627 "'{name}' is not a matrix; cannot use () indexed assignment"
2628 ));
2629 }
2630 };
2631
2632 let (ridx, cidx) = {
2633 let _owned_r;
2634 let env_r: &Env = if crate::eval::contains_end(&indices[0]) {
2635 _owned_r = write_env_with_end(env, nrows);
2636 &_owned_r
2637 } else {
2638 env
2639 };
2640 let _owned_c;
2641 let env_c: &Env = if crate::eval::contains_end(&indices[1]) {
2642 _owned_c = write_env_with_end(env, ncols);
2643 &_owned_c
2644 } else {
2645 env
2646 };
2647 (
2648 resolve_write_dim(&indices[0], nrows, env_r, io)?,
2649 resolve_write_dim(&indices[1], ncols, env_c, io)?,
2650 )
2651 };
2652
2653 let rows: Vec<usize> = match ridx {
2654 WriteIdx::All => (0..nrows.max(1)).collect(),
2655 WriteIdx::Positions(p) => p,
2656 };
2657 let cols: Vec<usize> = match cidx {
2658 WriteIdx::All => (0..ncols.max(1)).collect(),
2659 WriteIdx::Positions(p) => p,
2660 };
2661
2662 let req_rows = rows
2663 .iter()
2664 .copied()
2665 .max()
2666 .map(|m| m + 1)
2667 .unwrap_or(0)
2668 .max(nrows);
2669 let req_cols = cols
2670 .iter()
2671 .copied()
2672 .max()
2673 .map(|m| m + 1)
2674 .unwrap_or(0)
2675 .max(ncols);
2676
2677 let n_sel = rows.len() * cols.len();
2678 let rhs_vals = rhs_to_complex(&rhs, n_sel)?;
2679
2680 let mut mat = take_as_complex(name, env)?;
2681
2682 if req_rows != nrows || req_cols != ncols {
2683 let mut new_mat = Array2::<Complex<f64>>::zeros((req_rows, req_cols));
2684 for r in 0..nrows {
2685 for c in 0..ncols {
2686 if r < mat.nrows() && c < mat.ncols() {
2687 new_mat[[r, c]] = mat[[r, c]];
2688 }
2689 }
2690 }
2691 mat = new_mat;
2692 }
2693
2694 let mut k = 0;
2695 for &r in &rows {
2696 for &c in &cols {
2697 mat[[r, c]] = rhs_vals[k];
2698 k += 1;
2699 }
2700 }
2701
2702 env.insert(name.to_string(), Value::ComplexMatrix(Box::new(mat)));
2703 }
2704 _ => return Err("Indexed assignment supports at most 2 indices".to_string()),
2705 }
2706 Ok(())
2707}