1use std::collections::HashMap;
2use std::rc::Rc;
3
4type BodyCache = HashMap<String, Rc<Vec<StmtEntry>>>;
6
7fn expand_tilde(path: &str) -> String {
12 if path == "~" || path.starts_with("~/") || path.starts_with("~\\") {
13 let home = std::env::var("HOME")
14 .or_else(|_| std::env::var("USERPROFILE"))
15 .unwrap_or_default();
16 if home.is_empty() {
17 return path.to_string();
18 }
19 if path == "~" {
20 home
21 } else {
22 format!("{}{}", home, &path[1..])
23 }
24 } else {
25 path.to_string()
26 }
27}
28
29use indexmap::IndexMap;
30use ndarray::Array2;
31use num_complex::Complex;
32
33use crate::env::{Env, Value};
34use crate::env::{load_workspace, save_workspace, save_workspace_vars};
35use crate::eval::{
36 Base, Expr, FormatMode, autoload_cache_insert, current_func_name, eval_with_io, format_complex,
37 format_scalar, format_value_full, get_display_base, get_display_compact, get_display_fmt,
38 global_declare, global_frame_pop, global_frame_push, global_get, global_init_if_absent,
39 global_refresh_into_env, global_set, is_global, is_persistent, persistent_declare,
40 persistent_frame_pop, persistent_frame_push, persistent_load, persistent_save,
41 set_autoload_hook, set_display_ctx, set_eval_str_hook, set_fn_call_hook, set_last_err,
42 set_nargout,
43};
44use crate::io::IoContext;
45use crate::parser::{Stmt, StmtEntry, parse_stmts};
46
47thread_local! {
48 static RUN_DEPTH: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
50
51 static SCRIPT_DIR_STACK: std::cell::RefCell<Vec<std::path::PathBuf>> =
57 const { std::cell::RefCell::new(Vec::new()) };
58
59 static SESSION_PATH: std::cell::RefCell<Vec<std::path::PathBuf>> =
64 const { std::cell::RefCell::new(Vec::new()) };
65
66 static BODY_CACHE: std::cell::RefCell<BodyCache> =
76 std::cell::RefCell::new(HashMap::new());
77}
78
79fn silence_all(stmts: Vec<StmtEntry>) -> Vec<StmtEntry> {
87 stmts
88 .into_iter()
89 .map(|(stmt, _, line)| {
90 let stmt = match stmt {
91 Stmt::If {
92 cond,
93 body,
94 elseif_branches,
95 else_body,
96 } => Stmt::If {
97 cond,
98 body: silence_all(body),
99 elseif_branches: elseif_branches
100 .into_iter()
101 .map(|(c, b)| (c, silence_all(b)))
102 .collect(),
103 else_body: else_body.map(silence_all),
104 },
105 Stmt::For {
106 var,
107 range_expr,
108 body,
109 } => Stmt::For {
110 var,
111 range_expr,
112 body: silence_all(body),
113 },
114 Stmt::While { cond, body } => Stmt::While {
115 cond,
116 body: silence_all(body),
117 },
118 Stmt::DoUntil { body, cond } => Stmt::DoUntil {
119 body: silence_all(body),
120 cond,
121 },
122 Stmt::Switch {
123 expr,
124 cases,
125 otherwise_body,
126 } => Stmt::Switch {
127 expr,
128 cases: cases
129 .into_iter()
130 .map(|(v, b)| (v, silence_all(b)))
131 .collect(),
132 otherwise_body: otherwise_body.map(silence_all),
133 },
134 Stmt::TryCatch {
135 try_body,
136 catch_var,
137 catch_body,
138 } => Stmt::TryCatch {
139 try_body: silence_all(try_body),
140 catch_var,
141 catch_body: silence_all(catch_body),
142 },
143 other => other,
144 };
145 (stmt, true, line)
146 })
147 .collect()
148}
149
150fn get_or_parse_body(body_source: &str) -> Result<Rc<Vec<StmtEntry>>, String> {
156 BODY_CACHE.with(|cache| {
157 let mut cache = cache.borrow_mut();
158 if let Some(body) = cache.get(body_source) {
159 return Ok(Rc::clone(body));
160 }
161 let stmts =
162 parse_stmts(body_source).map_err(|e| format!("function body parse error: {e}"))?;
163 let silent = silence_all(stmts);
164 let rc = Rc::new(silent);
165 cache.insert(body_source.to_string(), Rc::clone(&rc));
166 Ok(rc)
167 })
168}
169
170fn annotate_line(e: String, line: usize) -> String {
174 if line == 0 || e.contains("near line") {
175 e
176 } else {
177 format!("{e} near line {line}")
178 }
179}
180
181fn strip_near_line(s: String) -> String {
186 if let Some(pos) = s.rfind(" near line ") {
187 let after = &s[pos + " near line ".len()..];
188 if !after.is_empty() && after.bytes().all(|b| b.is_ascii_digit()) {
189 return s[..pos].to_string();
190 }
191 }
192 s
193}
194
195pub enum Signal {
201 Break,
203 Continue,
205 Return,
207}
208
209pub fn init() {
213 set_fn_call_hook(call_user_function);
214 set_autoload_hook(try_autoload);
215 set_eval_str_hook(eval_str_impl);
216}
217
218fn eval_str_impl(code: &str, env: &crate::env::Env) -> Result<crate::env::Value, String> {
224 let mut env_clone = env.clone();
225 env_clone
226 .entry("ans".to_string())
227 .or_insert(crate::env::Value::Scalar(0.0));
228 let mut tmp_io = crate::io::IoContext::new();
229 let fmt = get_display_fmt();
230 let base = get_display_base();
231 let compact = get_display_compact();
232 let stmts = parse_stmts(code).map_err(|e| format!("eval: parse error: {e}"))?;
233 exec_stmts(&stmts, &mut env_clone, &mut tmp_io, &fmt, base, compact)?;
234 Ok(env_clone
235 .get("ans")
236 .cloned()
237 .unwrap_or(crate::env::Value::Void))
238}
239
240fn try_autoload(name: &str) -> bool {
247 if name.contains('.') {
248 return try_autoload_pkg(name);
249 }
250 let candidates = [format!("{name}.calc"), format!("{name}.m")];
251 for candidate in &candidates {
252 let Some(path) = resolve_script_path(candidate) else {
253 continue;
254 };
255 let Ok(content) = std::fs::read_to_string(&path) else {
256 continue;
257 };
258 let Ok(stmts) = parse_stmts(&content) else {
259 continue;
260 };
261 if !matches!(stmts.first(), Some((Stmt::FunctionDef { .. }, _, _))) {
262 continue;
263 }
264 let primary_name = match &stmts[0].0 {
265 Stmt::FunctionDef { name, .. } => name.clone(),
266 _ => continue,
267 };
268 let mut locals: IndexMap<String, Value> = IndexMap::new();
269 for (stmt, _, _) in &stmts {
270 if let Stmt::FunctionDef {
271 name: n,
272 outputs,
273 params,
274 body_source,
275 doc,
276 } = stmt
277 && n != &primary_name
278 {
279 locals.insert(
280 n.clone(),
281 Value::Function {
282 outputs: outputs.clone(),
283 params: params.clone(),
284 body_source: body_source.clone(),
285 locals: IndexMap::new(),
286 doc: doc.clone(),
287 },
288 );
289 }
290 }
291 if let Stmt::FunctionDef {
292 outputs,
293 params,
294 body_source,
295 doc,
296 ..
297 } = &stmts[0].0
298 {
299 autoload_cache_insert(
300 primary_name,
301 Value::Function {
302 outputs: outputs.clone(),
303 params: params.clone(),
304 body_source: body_source.clone(),
305 locals,
306 doc: doc.clone(),
307 },
308 );
309 return true;
310 }
311 }
312 false
313}
314
315fn try_autoload_pkg(qualified: &str) -> bool {
321 let parts: Vec<&str> = qualified.split('.').collect();
322 if parts.len() < 2 {
323 return false;
324 }
325 let func_name = *parts.last().unwrap();
326 let pkg_parts = &parts[..parts.len() - 1];
327
328 let mut rel_prefix = std::path::PathBuf::new();
330 for pkg in pkg_parts {
331 rel_prefix.push(format!("+{pkg}"));
332 }
333
334 let candidates = [
335 rel_prefix.join(format!("{func_name}.calc")),
336 rel_prefix.join(format!("{func_name}.m")),
337 ];
338
339 let mut search_dirs: Vec<std::path::PathBuf> = Vec::new();
341 SCRIPT_DIR_STACK.with(|s| search_dirs.extend(s.borrow().iter().cloned()));
342 search_dirs.push(std::path::PathBuf::from("."));
343 SESSION_PATH.with(|s| search_dirs.extend(s.borrow().iter().cloned()));
344
345 for dir in &search_dirs {
346 for candidate in &candidates {
347 let full = dir.join(candidate);
348 let Ok(content) = std::fs::read_to_string(&full) else {
349 continue;
350 };
351 let Ok(stmts) = parse_stmts(&content) else {
352 continue;
353 };
354 if !matches!(stmts.first(), Some((Stmt::FunctionDef { .. }, _, _))) {
355 continue;
356 }
357 let primary_name = match &stmts[0].0 {
358 Stmt::FunctionDef { name, .. } => name.clone(),
359 _ => continue,
360 };
361 let mut locals: IndexMap<String, Value> = IndexMap::new();
362 for (stmt, _, _) in &stmts {
363 if let Stmt::FunctionDef {
364 name: n,
365 outputs,
366 params,
367 body_source,
368 doc,
369 } = stmt
370 && n != &primary_name
371 {
372 locals.insert(
373 n.clone(),
374 Value::Function {
375 outputs: outputs.clone(),
376 params: params.clone(),
377 body_source: body_source.clone(),
378 locals: IndexMap::new(),
379 doc: doc.clone(),
380 },
381 );
382 }
383 }
384 if let Stmt::FunctionDef {
385 outputs,
386 params,
387 body_source,
388 doc,
389 ..
390 } = &stmts[0].0
391 {
392 autoload_cache_insert(
393 qualified.to_string(),
394 Value::Function {
395 outputs: outputs.clone(),
396 params: params.clone(),
397 body_source: body_source.clone(),
398 locals,
399 doc: doc.clone(),
400 },
401 );
402 return true;
403 }
404 }
405 }
406 false
407}
408
409pub fn script_dir_push(dir: &std::path::Path) {
415 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().push(dir.to_path_buf()));
416}
417
418pub fn script_dir_pop() {
420 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().pop());
421}
422
423pub fn session_path_init(paths: Vec<std::path::PathBuf>) {
428 SESSION_PATH.with(|p| *p.borrow_mut() = paths);
429}
430
431pub fn session_path_add(path: std::path::PathBuf, append: bool) {
436 SESSION_PATH.with(|p| {
437 let mut v = p.borrow_mut();
438 v.retain(|e| e != &path);
439 if append {
440 v.push(path);
441 } else {
442 v.insert(0, path);
443 }
444 });
445}
446
447pub fn session_path_remove(path: &std::path::Path) {
449 SESSION_PATH.with(|p| p.borrow_mut().retain(|e| e.as_path() != path));
450}
451
452pub fn session_path_list() -> Vec<std::path::PathBuf> {
454 SESSION_PATH.with(|p| p.borrow().clone())
455}
456
457fn call_user_function(
464 name: &str,
465 func: &Value,
466 args: &[Value],
467 caller_env: &Env,
468 io: &mut IoContext,
469) -> Result<Value, String> {
470 let Value::Function {
471 outputs,
472 params,
473 body_source,
474 locals,
475 ..
476 } = func
477 else {
478 return Err("call_user_function: not a Function value".to_string());
479 };
480
481 global_frame_push();
483 persistent_frame_push(name); let mut local_env = Env::new();
489 local_env.insert("i".to_string(), Value::Complex(0.0, 1.0));
490 local_env.insert("j".to_string(), Value::Complex(0.0, 1.0));
491 local_env.insert("ans".to_string(), Value::Scalar(0.0));
492 for (fn_name, val) in locals.iter() {
495 local_env.insert(fn_name.clone(), val.clone());
496 }
497 for (var_name, val) in caller_env.iter() {
498 if matches!(val, Value::Function { .. } | Value::Lambda(_)) {
499 local_env.insert(var_name.clone(), val.clone());
500 }
501 }
502
503 let has_varargin = params.last().is_some_and(|p| p == "varargin");
505 let fixed_params = if has_varargin {
506 ¶ms[..params.len() - 1]
507 } else {
508 params.as_slice()
509 };
510
511 let effective_args = if args.len() > params.len() {
516 if !has_varargin && args.len() > params.len() + 1 {
517 return Err(format!(
518 "Too many arguments: expected at most {}, got {}",
519 params.len(),
520 args.len()
521 ));
522 }
523 if has_varargin {
524 args
525 } else {
526 &args[..params.len()]
528 }
529 } else {
530 args
531 };
532
533 for (p, a) in fixed_params.iter().zip(effective_args.iter()) {
535 local_env.insert(p.clone(), a.clone());
536 }
537
538 if has_varargin {
540 let extra: Vec<Value> = effective_args
544 .get(fixed_params.len()..)
545 .unwrap_or(&[])
546 .to_vec();
547 let varargin = Value::Cell(extra);
548 local_env.insert("varargin".to_string(), varargin);
549 }
550
551 let nargin = effective_args.len().min(params.len());
552 local_env.insert("nargin".to_string(), Value::Scalar(nargin as f64));
553 local_env.insert("nargout".to_string(), Value::Scalar(outputs.len() as f64));
554
555 let body = get_or_parse_body(body_source)?;
557 let fmt = get_display_fmt();
558 let base = get_display_base();
559 let compact = get_display_compact();
560 let exec_result = exec_stmts(&body, &mut local_env, io, &fmt, base, compact);
561
562 let (func_name_saved, persistent_names) = persistent_frame_pop();
564 for var_name in &persistent_names {
565 if let Some(val) = local_env.get(var_name) {
566 persistent_save(&func_name_saved, var_name, val.clone());
567 }
568 }
569 global_frame_pop();
571
572 match exec_result? {
574 None | Some(Signal::Return) => {}
575 Some(Signal::Break) => return Err("'break' outside loop".to_string()),
576 Some(Signal::Continue) => return Err("'continue' outside loop".to_string()),
577 }
578
579 if outputs.is_empty() {
581 return Ok(Value::Void);
582 }
583
584 if outputs.len() == 1 && outputs[0] == "varargout" {
586 let cell = local_env.remove("varargout").unwrap_or(Value::Cell(vec![]));
587 return match cell {
588 Value::Cell(mut v) => {
589 if v.is_empty() {
590 Ok(Value::Void)
591 } else if v.len() == 1 {
592 Ok(v.remove(0))
593 } else {
594 Ok(Value::Tuple(v))
595 }
596 }
597 other => Ok(other),
598 };
599 }
600
601 if outputs.len() == 1 {
602 return Ok(local_env.remove(&outputs[0]).unwrap_or(Value::Void));
603 }
604 let vals: Vec<Value> = outputs
605 .iter()
606 .map(|o| local_env.remove(o).unwrap_or(Value::Void))
607 .collect();
608 Ok(Value::Tuple(vals))
609}
610
611pub fn resolve_script_path(name: &str) -> Option<std::path::PathBuf> {
617 let p = std::path::Path::new(name);
626 let mut bases: Vec<std::path::PathBuf> = Vec::new();
627
628 SCRIPT_DIR_STACK.with(|stack| {
630 for dir in stack.borrow().iter().rev() {
631 bases.push(dir.join("private").join(p));
632 bases.push(dir.join(p));
633 }
634 });
635
636 bases.push(p.to_path_buf());
638
639 SESSION_PATH.with(|sp| {
641 for dir in sp.borrow().iter() {
642 bases.push(dir.join(p));
643 }
644 });
645
646 for base in &bases {
647 if base.extension().is_some() {
648 if base.exists() {
649 return Some(base.clone());
650 }
651 continue;
653 }
654 let with_calc = base.with_extension("calc");
655 if with_calc.exists() {
656 return Some(with_calc);
657 }
658 let with_m = base.with_extension("m");
659 if with_m.exists() {
660 return Some(with_m);
661 }
662 }
663 None
664}
665
666fn is_truthy(val: &Value) -> bool {
674 match val {
675 Value::Scalar(n) => *n != 0.0 && !n.is_nan(),
676 Value::Matrix(m) => m.iter().all(|&x| x != 0.0 && !x.is_nan()),
677 Value::Complex(re, im) => *re != 0.0 || *im != 0.0,
678 Value::ComplexMatrix(m) => m.iter().all(|c| c.re != 0.0 || c.im != 0.0),
679 Value::Str(s) | Value::StringObj(s) => !s.is_empty(),
680 Value::Void => false,
681 Value::Lambda(_) | Value::Function { .. } | Value::Tuple(_) => true,
684 Value::Cell(v) => !v.is_empty(),
686 Value::Struct(_) | Value::StructArray(_) => true,
688 Value::DateTime(ts) => !ts.is_nan(),
690 Value::Duration(s) => *s != 0.0,
691 Value::DateTimeArray(v) | Value::DurationArray(v) => !v.is_empty(),
692 }
693}
694
695fn print_value(label: Option<&str>, val: &Value, fmt: &FormatMode, base: Base, compact: bool) {
700 match val {
701 Value::Void => {}
702 Value::Scalar(n) => {
703 if let Some(name) = label {
704 println!("{name} = {}", format_scalar(*n, base, fmt));
705 } else {
706 println!("{}", format_scalar(*n, base, fmt));
707 }
708 }
709 Value::Matrix(_) => {
710 if let Some(full) = format_value_full(val, fmt) {
711 let prefix = label.unwrap_or("ans");
712 println!("{prefix} =");
713 println!("{full}");
714 if !compact {
715 println!();
716 }
717 }
718 }
719 Value::Complex(re, im) => {
720 if let Some(name) = label {
721 println!("{name} = {}", format_complex(*re, *im, fmt));
722 } else {
723 println!("{}", format_complex(*re, *im, fmt));
724 }
725 }
726 Value::Str(s) | Value::StringObj(s) => {
727 if let Some(name) = label {
728 println!("{name} = {s}");
729 } else {
730 println!("{s}");
731 }
732 }
733 Value::Lambda(_) => {
734 if let Some(name) = label {
735 println!("{name} = @<lambda>");
736 } else {
737 println!("@<lambda>");
738 }
739 }
740 Value::Function {
741 outputs, params, ..
742 } => {
743 let params_str = params.join(", ");
744 let out_str = match outputs.len() {
745 0 => String::new(),
746 1 => format!("{} = ", outputs[0]),
747 _ => format!("[{}] = ", outputs.join(", ")),
748 };
749 if let Some(name) = label {
750 println!("{name} = @function {out_str}{name}({params_str})");
751 } else {
752 println!("@function {out_str}f({params_str})");
753 }
754 }
755 Value::Tuple(vals) => {
756 for (i, v) in vals.iter().enumerate() {
759 print_value(label.map(|_| "ans").or(Some("ans")), v, fmt, base, compact);
760 let _ = i;
761 }
762 }
763 Value::ComplexMatrix(_) => {
764 if let Some(full) = format_value_full(val, fmt) {
765 let prefix = label.unwrap_or("ans");
766 println!("{prefix} =");
767 println!("{full}");
768 if !compact {
769 println!();
770 }
771 }
772 }
773 Value::Cell(_) | Value::Struct(_) | Value::StructArray(_) => {
774 if let Some(full) = format_value_full(val, fmt) {
775 let prefix = label.unwrap_or("ans");
776 println!("{prefix} =");
777 println!("{full}");
778 if !compact {
779 println!();
780 }
781 }
782 }
783 Value::DateTime(ts) => {
784 let s = crate::datetime::format_datetime(*ts);
785 if let Some(name) = label {
786 println!("{name} = {s}");
787 } else {
788 println!("{s}");
789 }
790 }
791 Value::Duration(secs) => {
792 let s = crate::datetime::format_duration(*secs);
793 if let Some(name) = label {
794 println!("{name} = {s}");
795 } else {
796 println!("{s}");
797 }
798 }
799 Value::DateTimeArray(_) | Value::DurationArray(_) => {
800 if let Some(full) = format_value_full(val, fmt) {
801 let prefix = label.unwrap_or("ans");
802 println!("{prefix} =");
803 println!("{full}");
804 if !compact {
805 println!();
806 }
807 }
808 }
809 }
810}
811
812fn set_nested(
817 mut map: IndexMap<String, Value>,
818 path: &[String],
819 val: Value,
820) -> Result<IndexMap<String, Value>, String> {
821 let (first, rest) = path.split_first().expect("set_nested: empty path");
822 if rest.is_empty() {
823 map.insert(first.clone(), val);
824 } else {
825 let inner = match map.shift_remove(first) {
826 Some(Value::Struct(m)) => m,
827 None => IndexMap::new(),
828 Some(other) => {
829 map.insert(first.clone(), other);
830 return Err(format!("'{first}' is not a struct"));
831 }
832 };
833 let updated = set_nested(inner, rest, val)?;
834 map.insert(first.clone(), Value::Struct(updated));
835 }
836 Ok(map)
837}
838
839fn hoist_functions(stmts: &[StmtEntry], env: &mut Env) {
846 for (stmt, _, _) in stmts {
847 if let Stmt::FunctionDef {
848 name,
849 outputs,
850 params,
851 body_source,
852 doc,
853 } = stmt
854 {
855 env.insert(
856 name.clone(),
857 Value::Function {
858 outputs: outputs.clone(),
859 params: params.clone(),
860 body_source: body_source.clone(),
861 locals: IndexMap::new(),
862 doc: doc.clone(),
863 },
864 );
865 }
866 }
867}
868
869pub fn exec_script(
875 stmts: &[StmtEntry],
876 env: &mut Env,
877 io: &mut IoContext,
878 fmt: &FormatMode,
879 base: Base,
880 compact: bool,
881) -> Result<Option<Signal>, String> {
882 hoist_functions(stmts, env);
883 exec_stmts(stmts, env, io, fmt, base, compact)
884}
885
886pub fn exec_stmts(
893 stmts: &[StmtEntry],
894 env: &mut Env,
895 io: &mut IoContext,
896 fmt: &FormatMode,
897 base: Base,
898 compact: bool,
899) -> Result<Option<Signal>, String> {
900 set_display_ctx(fmt, base, compact);
902
903 for (stmt, silent, stmt_line) in stmts {
904 match stmt {
905 Stmt::Assign(name, expr) => {
906 set_nargout(1);
907 let val = eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
908 env.insert(name.clone(), val.clone());
909 if is_global(name) {
911 global_set(name, val.clone());
912 }
913 if is_persistent(name) {
915 persistent_save(¤t_func_name(), name, val.clone());
916 }
917 if !silent && !matches!(val, Value::Void) {
918 print_value(Some(name), &val, fmt, base, compact);
919 }
920 }
921
922 Stmt::Global(names) => {
923 for name in names {
924 global_declare(name);
925 global_init_if_absent(name);
926 if let Some(local_val) = env.remove(name) {
929 global_set(name, local_val.clone());
930 env.insert(name.clone(), local_val);
931 } else if let Some(global_val) = global_get(name) {
932 env.insert(name.clone(), global_val);
933 }
934 }
935 }
936
937 Stmt::Persistent(names) => {
938 let func = current_func_name();
941 for name in names {
942 persistent_declare(name);
943 if let Some(saved) = persistent_load(&func, name) {
944 env.insert(name.clone(), saved);
946 } else {
947 env.insert(name.clone(), Value::Matrix(ndarray::Array2::zeros((0, 0))));
951 }
952 }
953 }
954
955 Stmt::Expr(expr) => {
956 if let Expr::Call(fn_name, args) = expr
958 && matches!(fn_name.as_str(), "addpath" | "rmpath" | "path")
959 {
960 match fn_name.as_str() {
961 "addpath" => {
962 if args.is_empty() || args.len() > 2 {
963 return Err(
964 "addpath: expects 1 or 2 arguments: addpath(dir) or addpath(dir, '-end')".to_string()
965 );
966 }
967 let path_val = eval_with_io(&args[0], env, io)?;
968 let path_str = match &path_val {
969 Value::Str(s) | Value::StringObj(s) => s.clone(),
970 _ => {
971 return Err(
972 "addpath: argument must be a string (directory path)"
973 .to_string(),
974 );
975 }
976 };
977 let append = if args.len() == 2 {
978 let flag_val = eval_with_io(&args[1], env, io)?;
979 match &flag_val {
980 Value::Str(s) | Value::StringObj(s) if s == "-end" => true,
981 Value::Str(_) | Value::StringObj(_) => {
982 return Err(
983 "addpath: second argument must be '-end' (to append) or omitted (to prepend)".to_string()
984 );
985 }
986 _ => {
987 return Err(
988 "addpath: second argument must be a string '-end'"
989 .to_string(),
990 );
991 }
992 }
993 } else {
994 false
995 };
996 let expanded = expand_tilde(&path_str);
997 let pb = std::path::PathBuf::from(&expanded);
998 session_path_add(pb, append);
999 if !silent {
1000 for p in session_path_list() {
1001 println!("{}", p.display());
1002 }
1003 }
1004 }
1005 "rmpath" => {
1006 if args.len() != 1 {
1007 return Err("rmpath: expects exactly 1 argument".to_string());
1008 }
1009 let path_val = eval_with_io(&args[0], env, io)?;
1010 let path_str = match &path_val {
1011 Value::Str(s) | Value::StringObj(s) => s.clone(),
1012 _ => {
1013 return Err(
1014 "rmpath: argument must be a string (directory path)"
1015 .to_string(),
1016 );
1017 }
1018 };
1019 let expanded = expand_tilde(&path_str);
1020 session_path_remove(std::path::Path::new(&expanded));
1021 }
1022 "path" => {
1023 if !args.is_empty() {
1024 return Err("path: takes no arguments".to_string());
1025 }
1026 if !silent {
1027 let paths = session_path_list();
1028 if paths.is_empty() {
1029 println!("(search path is empty)");
1030 } else {
1031 for p in &paths {
1032 println!("{}", p.display());
1033 }
1034 }
1035 }
1036 }
1037 _ => unreachable!(),
1038 }
1039 continue;
1040 }
1041
1042 if let Expr::Call(fn_name, args) = expr
1045 && matches!(fn_name.as_str(), "run" | "source")
1046 && args.len() == 1
1047 {
1048 let path_val = eval_with_io(&args[0], env, io)?;
1049 let filename = match &path_val {
1050 Value::Str(s) | Value::StringObj(s) => s.clone(),
1051 _ => {
1052 return Err(format!("{fn_name}: argument must be a string (filename)"));
1053 }
1054 };
1055 let script_path = resolve_script_path(&filename)
1056 .ok_or_else(|| format!("{fn_name}: script not found: '{filename}'"))?;
1057 let content = std::fs::read_to_string(&script_path).map_err(|e| {
1058 format!("{fn_name}: cannot read '{}': {e}", script_path.display())
1059 })?;
1060 let depth = RUN_DEPTH.with(|d| d.get());
1061 if depth >= 64 {
1062 return Err(format!(
1063 "{fn_name}: maximum script nesting depth (64) exceeded"
1064 ));
1065 }
1066 RUN_DEPTH.with(|d| d.set(depth + 1));
1067 if let Some(dir) = script_path.parent() {
1070 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().push(dir.to_path_buf()));
1071 }
1072 let run_stmts = parse_stmts(&content).map_err(|e| {
1073 format!("{fn_name}: parse error in '{}': {e}", script_path.display())
1074 })?;
1075
1076 let is_fn_file = !run_stmts.is_empty()
1082 && run_stmts
1083 .iter()
1084 .all(|(s, _, _)| matches!(s, Stmt::FunctionDef { .. }));
1085 let result = if is_fn_file {
1086 let primary_name = match &run_stmts[0].0 {
1087 Stmt::FunctionDef { name, .. } => name.clone(),
1088 _ => unreachable!(),
1089 };
1090 let mut locals: IndexMap<String, Value> = IndexMap::new();
1091 for (stmt, _, _) in &run_stmts {
1092 if let Stmt::FunctionDef {
1093 name,
1094 outputs,
1095 params,
1096 body_source,
1097 doc,
1098 } = stmt
1099 && name != &primary_name
1100 {
1101 locals.insert(
1102 name.clone(),
1103 Value::Function {
1104 outputs: outputs.clone(),
1105 params: params.clone(),
1106 body_source: body_source.clone(),
1107 locals: IndexMap::new(),
1108 doc: doc.clone(),
1109 },
1110 );
1111 }
1112 }
1113 if let Stmt::FunctionDef {
1114 outputs,
1115 params,
1116 body_source,
1117 doc,
1118 ..
1119 } = &run_stmts[0].0
1120 {
1121 env.insert(
1122 primary_name,
1123 Value::Function {
1124 outputs: outputs.clone(),
1125 params: params.clone(),
1126 body_source: body_source.clone(),
1127 locals,
1128 doc: doc.clone(),
1129 },
1130 );
1131 }
1132 Ok(None)
1133 } else {
1134 for (stmt, _, _) in run_stmts.iter() {
1137 if let Stmt::FunctionDef {
1138 name,
1139 outputs,
1140 params,
1141 body_source,
1142 doc,
1143 } = stmt
1144 {
1145 env.insert(
1146 name.clone(),
1147 Value::Function {
1148 outputs: outputs.clone(),
1149 params: params.clone(),
1150 body_source: body_source.clone(),
1151 locals: IndexMap::new(),
1152 doc: doc.clone(),
1153 },
1154 );
1155 }
1156 }
1157 exec_stmts(&run_stmts, env, io, fmt, base, compact)
1158 };
1159 SCRIPT_DIR_STACK.with(|s| s.borrow_mut().pop());
1160 RUN_DEPTH.with(|d| d.set(depth));
1161 match result? {
1164 None => {}
1165 Some(sig) => return Ok(Some(sig)),
1166 }
1167 continue;
1168 }
1169
1170 if let Expr::Call(fn_name, args) = expr
1174 && fn_name == "eval"
1175 && (args.len() == 1 || args.len() == 2)
1176 {
1177 let code_val = eval_with_io(&args[0], env, io)?;
1178 let code_str = match code_val {
1179 Value::Str(s) | Value::StringObj(s) => s,
1180 _ => return Err("eval: argument must be a string".to_string()),
1181 };
1182 let depth = RUN_DEPTH.with(|d| d.get());
1183 if depth >= 64 {
1184 return Err("eval: maximum nesting depth (64) exceeded".to_string());
1185 }
1186 RUN_DEPTH.with(|d| d.set(depth + 1));
1187 let run_result = (|| -> Result<Option<Signal>, String> {
1188 let stmts = parse_stmts(&code_str)
1189 .map_err(|e| format!("eval: parse error: {e}"))?;
1190 exec_script(&stmts, env, io, fmt, base, compact)
1191 })();
1192 RUN_DEPTH.with(|d| d.set(depth));
1193 match run_result {
1194 Err(e) if args.len() == 2 => {
1195 set_last_err(&e);
1196 let catch_val = eval_with_io(&args[1], env, io)?;
1197 let catch_str = match catch_val {
1198 Value::Str(s) | Value::StringObj(s) => s,
1199 _ => {
1200 return Err("eval: catch argument must be a string".to_string());
1201 }
1202 };
1203 let catch_stmts = parse_stmts(&catch_str)
1204 .map_err(|e| format!("eval: catch parse error: {e}"))?;
1205 match exec_stmts(&catch_stmts, env, io, fmt, base, compact)? {
1206 None => {}
1207 Some(sig) => return Ok(Some(sig)),
1208 }
1209 }
1210 Err(e) => return Err(e),
1211 Ok(None) => {}
1212 Ok(Some(sig)) => return Ok(Some(sig)),
1213 }
1214 continue;
1215 }
1216
1217 if let Expr::Call(fn_name, args) = expr
1219 && fn_name == "clear"
1220 {
1221 if args.is_empty() {
1222 env.clear();
1223 } else {
1224 for arg in args {
1225 let key = match arg {
1226 Expr::StrLiteral(s) | Expr::StringObjLiteral(s) => s.clone(),
1227 other => match eval_with_io(other, env, io)? {
1228 Value::Str(s) | Value::StringObj(s) => s,
1229 _ => continue,
1230 },
1231 };
1232 env.remove(&key);
1233 }
1234 }
1235 continue;
1236 }
1237
1238 if let Expr::Call(fn_name, args) = expr
1240 && fn_name == "format"
1241 {
1242 let arg = match args.first() {
1243 Some(Expr::StrLiteral(s)) => s.as_str(),
1244 None => "",
1245 _ => "",
1246 };
1247 let new_fmt = match arg {
1248 "" | "short" => FormatMode::Short,
1249 "long" => FormatMode::Long,
1250 "shorte" | "shortE" => FormatMode::ShortE,
1251 "longe" | "longE" => FormatMode::LongE,
1252 "shortg" | "shortG" => FormatMode::ShortG,
1253 "longg" | "longG" => FormatMode::LongG,
1254 "bank" => FormatMode::Bank,
1255 "rat" => FormatMode::Rat,
1256 "hex" => FormatMode::Hex,
1257 "+" => FormatMode::Plus,
1258 "compact" | "loose" => get_display_fmt(),
1259 s => s
1260 .parse::<usize>()
1261 .map(FormatMode::Custom)
1262 .unwrap_or_else(|_| get_display_fmt()),
1263 };
1264 let new_compact = match arg {
1265 "compact" => true,
1266 "loose" => false,
1267 _ => get_display_compact(),
1268 };
1269 set_display_ctx(&new_fmt, base, new_compact);
1270 continue;
1271 }
1272
1273 if let Expr::Call(fn_name, args) = expr
1275 && matches!(fn_name.as_str(), "save" | "load" | "ws" | "wl")
1276 {
1277 let is_save = matches!(fn_name.as_str(), "save" | "ws");
1278 if is_save {
1279 let (path_opt, var_names) = if args.is_empty() {
1280 (None, vec![])
1281 } else {
1282 let path_val = eval_with_io(&args[0], env, io)?;
1283 let path_str = match path_val {
1284 Value::Str(s) | Value::StringObj(s) => s,
1285 _ => return Err("save: path argument must be a string".to_string()),
1286 };
1287 let mut vars: Vec<String> = Vec::new();
1288 for a in &args[1..] {
1289 let v = eval_with_io(a, env, io)?;
1290 match v {
1291 Value::Str(s) | Value::StringObj(s) => vars.push(s),
1292 _ => {
1293 return Err(
1294 "save: variable names must be strings".to_string()
1295 );
1296 }
1297 }
1298 }
1299 (Some(path_str), vars)
1300 };
1301 let result = match &path_opt {
1302 None => {
1303 let home = std::env::var("HOME")
1304 .or_else(|_| std::env::var("USERPROFILE"))
1305 .unwrap_or_default();
1306 let p = std::path::Path::new(&home)
1307 .join(".config")
1308 .join("ccalc")
1309 .join("workspace.toml");
1310 save_workspace(env, &p)
1311 }
1312 Some(p) if var_names.is_empty() => {
1313 save_workspace(env, std::path::Path::new(p))
1314 }
1315 Some(p) => {
1316 let refs: Vec<&str> =
1317 var_names.iter().map(String::as_str).collect();
1318 save_workspace_vars(env, std::path::Path::new(p), &refs)
1319 }
1320 };
1321 if let Err(e) = result {
1322 return Err(format!("save: {e}"));
1323 }
1324 } else {
1325 let loaded = if args.is_empty() {
1327 let home = std::env::var("HOME")
1328 .or_else(|_| std::env::var("USERPROFILE"))
1329 .unwrap_or_default();
1330 let p = std::path::Path::new(&home)
1331 .join(".config")
1332 .join("ccalc")
1333 .join("workspace.toml");
1334 load_workspace(&p)
1335 } else {
1336 let path_val = eval_with_io(&args[0], env, io)?;
1337 let path_str = match path_val {
1338 Value::Str(s) | Value::StringObj(s) => s,
1339 _ => return Err("load: path argument must be a string".to_string()),
1340 };
1341 load_workspace(std::path::Path::new(&path_str))
1342 };
1343 match loaded {
1344 Ok(ws) => env.extend(ws),
1345 Err(e) => return Err(format!("load: {e}")),
1346 }
1347 }
1348 continue;
1349 }
1350
1351 let expr_label = if let Expr::Var(name) = expr {
1352 Some(name.as_str())
1353 } else {
1354 None
1355 };
1356 let val = eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1357 env.insert("ans".to_string(), val.clone());
1358 if !silent && !matches!(val, Value::Void) {
1359 print_value(expr_label, &val, fmt, base, compact);
1360 }
1361 }
1362
1363 Stmt::If {
1364 cond,
1365 body,
1366 elseif_branches,
1367 else_body,
1368 } => {
1369 let cond_val =
1370 eval_with_io(cond, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1371 let chosen: Option<&[StmtEntry]> = if is_truthy(&cond_val) {
1372 Some(body)
1373 } else {
1374 let mut found = None;
1375 for (ei_cond, ei_body) in elseif_branches {
1376 if is_truthy(
1377 &eval_with_io(ei_cond, env, io)
1378 .map_err(|e| annotate_line(e, *stmt_line))?,
1379 ) {
1380 found = Some(ei_body.as_slice());
1381 break;
1382 }
1383 }
1384 if found.is_none() {
1385 found = else_body.as_deref();
1386 }
1387 found
1388 };
1389 if let Some(body_stmts) = chosen
1390 && let Some(sig) = exec_stmts(body_stmts, env, io, fmt, base, compact)?
1391 {
1392 return Ok(Some(sig));
1393 }
1394 }
1395
1396 Stmt::For {
1397 var,
1398 range_expr,
1399 body,
1400 } => {
1401 let range_val =
1402 eval_with_io(range_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1403 let iter_cols: Vec<Value> = match range_val {
1404 Value::Scalar(n) => vec![Value::Scalar(n)],
1405 Value::Matrix(m) => {
1406 let nrows = m.nrows();
1407 let ncols = m.ncols();
1408 (0..ncols)
1409 .map(|j| {
1410 if nrows == 1 {
1411 Value::Scalar(m[[0, j]])
1413 } else {
1414 let mut col = Array2::zeros((nrows, 1));
1416 for i in 0..nrows {
1417 col[[i, 0]] = m[[i, j]];
1418 }
1419 Value::Matrix(col)
1420 }
1421 })
1422 .collect()
1423 }
1424 _ => return Err("'for' range must evaluate to a scalar or matrix".to_string()),
1425 };
1426
1427 'for_loop: for col_val in iter_cols {
1428 env.insert(var.clone(), col_val);
1429 match exec_stmts(body, env, io, fmt, base, compact)? {
1430 None => {}
1431 Some(Signal::Break) => break 'for_loop,
1432 Some(Signal::Continue) => continue 'for_loop,
1433 Some(Signal::Return) => return Ok(Some(Signal::Return)),
1434 }
1435 }
1436 }
1437
1438 Stmt::While { cond, body } => loop {
1439 if !is_truthy(
1440 &eval_with_io(cond, env, io).map_err(|e| annotate_line(e, *stmt_line))?,
1441 ) {
1442 break;
1443 }
1444 match exec_stmts(body, env, io, fmt, base, compact)? {
1445 None => {}
1446 Some(Signal::Break) => break,
1447 Some(Signal::Continue) => continue,
1448 Some(Signal::Return) => return Ok(Some(Signal::Return)),
1449 }
1450 },
1451
1452 Stmt::Break => return Ok(Some(Signal::Break)),
1453 Stmt::Continue => return Ok(Some(Signal::Continue)),
1454
1455 Stmt::Switch {
1457 expr,
1458 cases,
1459 otherwise_body,
1460 } => {
1461 let switch_val =
1462 eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1463 let mut matched = false;
1464 'switch_loop: for (case_exprs, case_body) in cases {
1465 for case_expr in case_exprs {
1466 let case_val = eval_with_io(case_expr, env, io)
1467 .map_err(|e| annotate_line(e, *stmt_line))?;
1468 let is_match = if let Value::Cell(cell_elems) = &case_val {
1471 cell_elems.iter().any(|elem| match (&switch_val, elem) {
1472 (Value::Scalar(a), Value::Scalar(b)) => a == b,
1473 _ => {
1474 let sv = match &switch_val {
1475 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1476 _ => None,
1477 };
1478 let cv = match elem {
1479 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1480 _ => None,
1481 };
1482 matches!((sv, cv), (Some(a), Some(b)) if a == b)
1483 }
1484 })
1485 } else {
1486 match (&switch_val, &case_val) {
1487 (Value::Scalar(a), Value::Scalar(b)) => a == b,
1488 _ => {
1489 let sv = match &switch_val {
1490 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1491 _ => None,
1492 };
1493 let cv = match &case_val {
1494 Value::Str(s) | Value::StringObj(s) => Some(s.as_str()),
1495 _ => None,
1496 };
1497 matches!((sv, cv), (Some(a), Some(b)) if a == b)
1498 }
1499 }
1500 };
1501 if is_match {
1502 if let Some(sig) = exec_stmts(case_body, env, io, fmt, base, compact)? {
1503 return Ok(Some(sig));
1504 }
1505 matched = true;
1506 break 'switch_loop;
1507 }
1508 }
1509 }
1510 if !matched
1511 && let Some(ob) = otherwise_body
1512 && let Some(sig) = exec_stmts(ob, env, io, fmt, base, compact)?
1513 {
1514 return Ok(Some(sig));
1515 }
1516 }
1517
1518 Stmt::DoUntil { body, cond } => loop {
1520 match exec_stmts(body, env, io, fmt, base, compact)? {
1521 Some(Signal::Break) => break,
1522 Some(Signal::Continue) | None => {}
1523 Some(Signal::Return) => return Ok(Some(Signal::Return)),
1524 }
1525 if is_truthy(
1526 &eval_with_io(cond, env, io).map_err(|e| annotate_line(e, *stmt_line))?,
1527 ) {
1528 break;
1529 }
1530 },
1531
1532 Stmt::TryCatch {
1534 try_body,
1535 catch_var,
1536 catch_body,
1537 } => match exec_stmts(try_body, env, io, fmt, base, compact) {
1538 Ok(None) => {}
1539 Ok(Some(sig)) => return Ok(Some(sig)),
1540 Err(msg) => {
1541 let clean = strip_near_line(msg);
1542 set_last_err(&clean);
1543 if let Some(var) = catch_var {
1544 let mut map = IndexMap::new();
1545 map.insert("message".to_string(), Value::Str(clean));
1546 env.insert(var.clone(), Value::Struct(map));
1547 }
1548 if let Some(sig) = exec_stmts(catch_body, env, io, fmt, base, compact)? {
1549 return Ok(Some(sig));
1550 }
1551 }
1552 },
1553
1554 Stmt::FunctionDef {
1556 name,
1557 outputs,
1558 params,
1559 body_source,
1560 doc,
1561 } => {
1562 env.insert(
1563 name.clone(),
1564 Value::Function {
1565 outputs: outputs.clone(),
1566 params: params.clone(),
1567 body_source: body_source.clone(),
1568 locals: IndexMap::new(),
1569 doc: doc.clone(),
1570 },
1571 );
1572 }
1573
1574 Stmt::Return => return Ok(Some(Signal::Return)),
1576
1577 Stmt::CellSet(cell_name, idx_expr, val_expr) => {
1579 let cell_len = match env.get(cell_name) {
1581 Some(Value::Cell(v)) => v.len(),
1582 _ => 0,
1583 };
1584 let _owned_end;
1585 let env_end: &Env = if crate::eval::contains_end(idx_expr) {
1586 _owned_end = write_env_with_end(env, cell_len);
1587 &_owned_end
1588 } else {
1589 env
1590 };
1591 let idx = eval_with_io(idx_expr, env_end, io)
1592 .map_err(|e| annotate_line(e, *stmt_line))?;
1593 let rhs =
1594 eval_with_io(val_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1595 let i = match idx {
1596 Value::Scalar(n) => n as isize,
1597 _ => return Err(format!("{cell_name}{{}}: index must be a scalar integer")),
1598 };
1599 match env.get_mut(cell_name) {
1600 Some(Value::Cell(v)) => {
1601 if i < 1 {
1602 return Err(format!(
1603 "{cell_name}{{}}: index {i} out of range (1..{})",
1604 v.len()
1605 ));
1606 }
1607 let idx = (i - 1) as usize;
1608 if idx >= v.len() {
1610 v.resize(idx + 1, Value::Scalar(0.0));
1611 }
1612 v[idx] = rhs.clone();
1613 }
1614 Some(_) => {
1615 return Err(format!(
1616 "'{cell_name}' is not a cell array; use () for regular indexing"
1617 ));
1618 }
1619 None => {
1620 if i < 1 {
1622 return Err(format!("{cell_name}{{}}: index {i} must be >= 1"));
1623 }
1624 let idx = (i - 1) as usize;
1625 let mut v = vec![Value::Scalar(0.0); idx + 1];
1626 v[idx] = rhs.clone();
1627 env.insert(cell_name.clone(), Value::Cell(v));
1628 }
1629 }
1630 if !silent && let Some(val) = env.get(cell_name) {
1631 print_value(Some(cell_name), val, fmt, base, compact);
1632 }
1633 }
1634
1635 Stmt::FieldSet(base_name, path, rhs_expr) => {
1637 let rhs =
1638 eval_with_io(rhs_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1639 let root = match env.remove(base_name) {
1640 Some(Value::Struct(m)) => m,
1641 None => IndexMap::new(),
1642 Some(other) => {
1643 env.insert(base_name.clone(), other);
1644 return Err(format!("'{base_name}' is not a struct"));
1645 }
1646 };
1647 let updated = set_nested(root, path, rhs)?;
1648 let struct_val = Value::Struct(updated);
1649 if !silent {
1650 print_value(Some(base_name), &struct_val, fmt, base, compact);
1651 }
1652 env.insert(base_name.clone(), struct_val);
1653 }
1654
1655 Stmt::StructArrayFieldSet(base_name, idx_expr, path, rhs_expr) => {
1657 let rhs =
1658 eval_with_io(rhs_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1659 let idx_val =
1660 eval_with_io(idx_expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1661 let idx = match &idx_val {
1662 Value::Scalar(n) => {
1663 let i = *n as isize;
1664 if i < 1 {
1665 return Err(format!(
1666 "Struct array index must be a positive integer, got {n}"
1667 ));
1668 }
1669 i as usize
1670 }
1671 _ => return Err("Struct array index must be a scalar integer".to_string()),
1672 };
1673 let mut arr: Vec<IndexMap<String, Value>> = match env.remove(base_name) {
1675 Some(Value::StructArray(v)) => v,
1676 Some(Value::Struct(m)) => vec![m],
1678 None => Vec::new(),
1679 Some(other) => {
1680 env.insert(base_name.clone(), other);
1681 return Err(format!("'{base_name}' is not a struct array"));
1682 }
1683 };
1684 while arr.len() < idx {
1686 arr.push(IndexMap::new());
1687 }
1688 let elem = arr[idx - 1].clone();
1690 let updated_elem = set_nested(elem, path, rhs)?;
1691 arr[idx - 1] = updated_elem;
1692 let arr_val = Value::StructArray(arr);
1693 if !silent {
1694 print_value(Some(base_name), &arr_val, fmt, base, compact);
1695 }
1696 env.insert(base_name.clone(), arr_val);
1697 }
1698
1699 Stmt::IndexSet {
1701 name,
1702 indices,
1703 value,
1704 } => {
1705 let rhs = eval_with_io(value, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1706 if is_persistent(name) {
1709 let func = current_func_name();
1710 if let Some(fresh) = persistent_load(&func, name) {
1711 env.insert(name.clone(), fresh);
1712 }
1713 }
1714 exec_index_set(name, indices, rhs, env, io)?;
1715 if is_persistent(name)
1717 && let Some(val) = env.get(name)
1718 {
1719 persistent_save(¤t_func_name(), name, val.clone());
1720 }
1721 if !silent && let Some(val) = env.get(name) {
1722 print_value(Some(name), val, fmt, base, compact);
1723 }
1724 }
1725
1726 Stmt::MultiAssign { targets, expr } => {
1728 set_nargout(targets.len());
1729 let val = eval_with_io(expr, env, io).map_err(|e| annotate_line(e, *stmt_line))?;
1730 let vals: Vec<Value> = match val {
1731 Value::Tuple(v) => v,
1732 other => vec![other],
1733 };
1734 for (i, target) in targets.iter().enumerate() {
1735 if target == "~" {
1736 continue; }
1738 let v = vals.get(i).cloned().unwrap_or(Value::Void);
1739 env.insert(target.clone(), v.clone());
1740 if !silent && !matches!(v, Value::Void) {
1741 print_value(Some(target), &v, fmt, base, compact);
1742 }
1743 }
1744 }
1745 }
1746 }
1747 global_refresh_into_env(env);
1751 Ok(None)
1752}
1753
1754enum WriteIdx {
1758 All,
1760 Positions(Vec<usize>),
1762}
1763
1764fn resolve_write_dim(
1770 expr: &crate::eval::Expr,
1771 dim_size: usize,
1772 env: &Env,
1773 io: &mut IoContext,
1774) -> Result<WriteIdx, String> {
1775 if matches!(expr, crate::eval::Expr::Colon) {
1776 return Ok(WriteIdx::All);
1777 }
1778 let val = eval_with_io(expr, env, io)?;
1779 let floats: Vec<f64> = match val {
1780 Value::Scalar(n) => vec![n],
1781 Value::Complex(re, im) => {
1782 if im != 0.0 {
1783 return Err("Index must be real, not complex".to_string());
1784 }
1785 vec![re]
1786 }
1787 Value::Matrix(m) => {
1788 let total = m.nrows() * m.ncols();
1789 if m.nrows() > 1 && m.ncols() > 1 && total != dim_size {
1790 return Err("Index must be a scalar or vector, not a 2-D matrix".to_string());
1791 }
1792 if m.nrows() > 1 && m.ncols() > 1 {
1794 let mut v = Vec::with_capacity(total);
1795 for col in 0..m.ncols() {
1796 for row in 0..m.nrows() {
1797 v.push(m[[row, col]]);
1798 }
1799 }
1800 v
1801 } else {
1802 m.iter().copied().collect()
1803 }
1804 }
1805 _ => return Err("Index must be numeric".to_string()),
1806 };
1807 if dim_size > 0 && floats.len() == dim_size && floats.iter().all(|&f| f == 0.0 || f == 1.0) {
1809 let positions: Vec<usize> = floats
1810 .iter()
1811 .enumerate()
1812 .filter(|&(_, &f)| f == 1.0)
1813 .map(|(i, _)| i)
1814 .collect();
1815 return Ok(WriteIdx::Positions(positions));
1816 }
1817 let positions: Result<Vec<usize>, String> = floats
1819 .iter()
1820 .map(|&n| {
1821 let i = n.round() as i64;
1822 if i < 1 {
1823 return Err(format!("Index {i} must be >= 1"));
1824 }
1825 Ok(i as usize - 1)
1826 })
1827 .collect();
1828 Ok(WriteIdx::Positions(positions?))
1829}
1830
1831fn write_env_with_end(env: &Env, dim_size: usize) -> Env {
1833 let mut e = env.clone();
1834 e.insert("end".to_string(), Value::Scalar(dim_size as f64));
1835 e
1836}
1837
1838fn exec_index_set(
1846 name: &str,
1847 indices: &[crate::eval::Expr],
1848 rhs: Value,
1849 env: &mut Env,
1850 io: &mut IoContext,
1851) -> Result<(), String> {
1852 let needs_complex = matches!(env.get(name), Some(Value::ComplexMatrix(_)))
1854 || matches!(&rhs, Value::Complex(_, _) | Value::ComplexMatrix(_));
1855
1856 if needs_complex {
1857 return exec_index_set_complex(name, indices, rhs, env, io);
1858 }
1859
1860 match indices.len() {
1861 1 => {
1862 let (total, nrows_hint, ncols_hint) = match env.get(name) {
1864 Some(Value::Matrix(m)) => (m.nrows() * m.ncols(), m.nrows(), m.ncols()),
1865 Some(Value::Scalar(_)) => (1, 1, 1),
1866 None | Some(Value::Void) => (0, 0, 0),
1867 Some(_) => {
1868 return Err(format!(
1869 "'{name}' is not a matrix; cannot use () indexed assignment"
1870 ));
1871 }
1872 };
1873
1874 let widx = {
1877 let _owned_end;
1878 let env_end: &Env = if crate::eval::contains_end(&indices[0]) {
1879 _owned_end = write_env_with_end(env, total);
1880 &_owned_end
1881 } else {
1882 env
1883 };
1884 resolve_write_dim(&indices[0], total, env_end, io)?
1885 };
1886
1887 let positions: Vec<usize> = match widx {
1889 WriteIdx::All => (0..total).collect(),
1890 WriteIdx::Positions(p) => p,
1891 };
1892
1893 let rhs_vals: Vec<f64> = match &rhs {
1895 Value::Scalar(n) => vec![*n; positions.len()],
1896 Value::Matrix(m) => {
1897 let flat: Vec<f64> = m.iter().copied().collect();
1898 if flat.len() != positions.len() {
1899 return Err(format!(
1900 "Assignment dimension mismatch: {} positions but {} values",
1901 positions.len(),
1902 flat.len()
1903 ));
1904 }
1905 flat
1906 }
1907 _ => {
1908 return Err(
1909 "Indexed assignment: RHS must be a numeric scalar or matrix".to_string()
1910 );
1911 }
1912 };
1913
1914 let required = positions.iter().copied().max().map(|m| m + 1).unwrap_or(0);
1916 let required = required.max(total);
1917
1918 let (out_rows, out_cols) = if nrows_hint == 0 || ncols_hint == 0 {
1920 (1, required)
1922 } else if nrows_hint == 1 {
1923 (1, required)
1924 } else if ncols_hint == 1 {
1925 (required, 1)
1926 } else if required > total {
1927 return Err("Cannot grow a 2-D matrix with linear indexing".to_string());
1928 } else {
1929 (nrows_hint, ncols_hint)
1930 };
1931
1932 let mut mat = match env.remove(name) {
1935 Some(Value::Matrix(m)) => m,
1936 Some(Value::Scalar(n)) => Array2::from_elem((1, 1), n),
1937 None | Some(Value::Void) => Array2::zeros((0, 0)),
1938 Some(other) => {
1939 env.insert(name.to_string(), other);
1940 return Err(format!(
1941 "'{name}' is not a matrix; cannot use () indexed assignment"
1942 ));
1943 }
1944 };
1945
1946 if required > total || out_rows != mat.nrows() || out_cols != mat.ncols() {
1948 let mut new_mat = Array2::<f64>::zeros((out_rows, out_cols));
1949 for old_p in 0..total {
1950 let old_row = old_p % mat.nrows().max(1);
1951 let old_col = old_p / mat.nrows().max(1);
1952 let new_row = old_p % out_rows;
1953 let new_col = old_p / out_rows;
1954 if old_row < mat.nrows() && old_col < mat.ncols() {
1955 new_mat[[new_row, new_col]] = mat[[old_row, old_col]];
1956 }
1957 }
1958 mat = new_mat;
1959 }
1960
1961 for (&pos, &val) in positions.iter().zip(rhs_vals.iter()) {
1963 let row = pos % mat.nrows();
1964 let col = pos / mat.nrows();
1965 mat[[row, col]] = val;
1966 }
1967
1968 let result = if mat.nrows() == 1 && mat.ncols() == 1 {
1969 Value::Scalar(mat[[0, 0]])
1970 } else {
1971 Value::Matrix(mat)
1972 };
1973 env.insert(name.to_string(), result);
1974 }
1975 2 => {
1976 let (nrows, ncols) = match env.get(name) {
1978 Some(Value::Matrix(m)) => (m.nrows(), m.ncols()),
1979 Some(Value::Scalar(_)) => (1, 1),
1980 None | Some(Value::Void) => (0, 0),
1981 Some(_) => {
1982 return Err(format!(
1983 "'{name}' is not a matrix; cannot use () indexed assignment"
1984 ));
1985 }
1986 };
1987
1988 let (ridx, cidx) = {
1990 let _owned_r;
1991 let env_r: &Env = if crate::eval::contains_end(&indices[0]) {
1992 _owned_r = write_env_with_end(env, nrows);
1993 &_owned_r
1994 } else {
1995 env
1996 };
1997 let _owned_c;
1998 let env_c: &Env = if crate::eval::contains_end(&indices[1]) {
1999 _owned_c = write_env_with_end(env, ncols);
2000 &_owned_c
2001 } else {
2002 env
2003 };
2004 (
2005 resolve_write_dim(&indices[0], nrows, env_r, io)?,
2006 resolve_write_dim(&indices[1], ncols, env_c, io)?,
2007 )
2008 };
2009
2010 let rows: Vec<usize> = match ridx {
2011 WriteIdx::All => (0..nrows.max(1)).collect(),
2012 WriteIdx::Positions(p) => p,
2013 };
2014 let cols: Vec<usize> = match cidx {
2015 WriteIdx::All => (0..ncols.max(1)).collect(),
2016 WriteIdx::Positions(p) => p,
2017 };
2018
2019 let req_rows = rows
2020 .iter()
2021 .copied()
2022 .max()
2023 .map(|m| m + 1)
2024 .unwrap_or(0)
2025 .max(nrows);
2026 let req_cols = cols
2027 .iter()
2028 .copied()
2029 .max()
2030 .map(|m| m + 1)
2031 .unwrap_or(0)
2032 .max(ncols);
2033
2034 let n_sel = rows.len() * cols.len();
2036 let rhs_vals: Vec<f64> = match &rhs {
2037 Value::Scalar(n) => vec![*n; n_sel],
2038 Value::Matrix(m) => {
2039 let flat: Vec<f64> = m.iter().copied().collect();
2040 if flat.len() != n_sel {
2041 return Err(format!(
2042 "Assignment dimension mismatch: {}×{} = {} positions but {} values",
2043 rows.len(),
2044 cols.len(),
2045 n_sel,
2046 flat.len()
2047 ));
2048 }
2049 flat
2050 }
2051 _ => {
2052 return Err(
2053 "Indexed assignment: RHS must be a numeric scalar or matrix".to_string()
2054 );
2055 }
2056 };
2057
2058 let mut mat = match env.remove(name) {
2060 Some(Value::Matrix(m)) => m,
2061 Some(Value::Scalar(n)) => Array2::from_elem((1, 1), n),
2062 None | Some(Value::Void) => Array2::zeros((0, 0)),
2063 Some(other) => {
2064 env.insert(name.to_string(), other);
2065 return Err(format!(
2066 "'{name}' is not a matrix; cannot use () indexed assignment"
2067 ));
2068 }
2069 };
2070
2071 if req_rows != nrows || req_cols != ncols {
2073 let mut new_mat = Array2::<f64>::zeros((req_rows, req_cols));
2074 for r in 0..nrows {
2075 for c in 0..ncols {
2076 new_mat[[r, c]] = mat[[r, c]];
2077 }
2078 }
2079 mat = new_mat;
2080 }
2081
2082 let mut k = 0;
2084 for &r in &rows {
2085 for &c in &cols {
2086 mat[[r, c]] = rhs_vals[k];
2087 k += 1;
2088 }
2089 }
2090
2091 let result = if mat.nrows() == 1 && mat.ncols() == 1 {
2092 Value::Scalar(mat[[0, 0]])
2093 } else {
2094 Value::Matrix(mat)
2095 };
2096 env.insert(name.to_string(), result);
2097 }
2098 _ => return Err("Indexed assignment supports at most 2 indices".to_string()),
2099 }
2100 Ok(())
2101}
2102
2103fn exec_index_set_complex(
2108 name: &str,
2109 indices: &[crate::eval::Expr],
2110 rhs: Value,
2111 env: &mut Env,
2112 io: &mut IoContext,
2113) -> Result<(), String> {
2114 fn rhs_to_complex(rhs: &Value, n_slots: usize) -> Result<Vec<Complex<f64>>, String> {
2116 match rhs {
2117 Value::Scalar(n) => Ok(vec![Complex::new(*n, 0.0); n_slots]),
2118 Value::Complex(re, im) => Ok(vec![Complex::new(*re, *im); n_slots]),
2119 Value::Matrix(m) => {
2120 let flat: Vec<Complex<f64>> = m.iter().map(|&x| Complex::new(x, 0.0)).collect();
2121 if flat.len() != n_slots {
2122 return Err(format!(
2123 "Assignment dimension mismatch: {} positions but {} values",
2124 n_slots,
2125 flat.len()
2126 ));
2127 }
2128 Ok(flat)
2129 }
2130 Value::ComplexMatrix(m) => {
2131 let flat: Vec<Complex<f64>> = m.iter().copied().collect();
2132 if flat.len() != n_slots {
2133 return Err(format!(
2134 "Assignment dimension mismatch: {} positions but {} values",
2135 n_slots,
2136 flat.len()
2137 ));
2138 }
2139 Ok(flat)
2140 }
2141 _ => Err("Indexed assignment: RHS must be a numeric value".to_string()),
2142 }
2143 }
2144
2145 fn take_as_complex(name: &str, env: &mut Env) -> Result<Array2<Complex<f64>>, String> {
2147 match env.remove(name) {
2148 Some(Value::ComplexMatrix(m)) => Ok(m),
2149 Some(Value::Matrix(m)) => Ok(m.mapv(|x| Complex::new(x, 0.0))),
2150 Some(Value::Scalar(n)) => Ok(Array2::from_elem((1, 1), Complex::new(n, 0.0))),
2151 None | Some(Value::Void) => Ok(Array2::zeros((0, 0))),
2152 Some(other) => {
2153 env.insert(name.to_string(), other);
2154 Err(format!(
2155 "'{name}' is not a matrix; cannot use () indexed assignment"
2156 ))
2157 }
2158 }
2159 }
2160
2161 match indices.len() {
2162 1 => {
2163 let (total, nrows_hint, ncols_hint) = match env.get(name) {
2164 Some(Value::ComplexMatrix(m)) => (m.nrows() * m.ncols(), m.nrows(), m.ncols()),
2165 Some(Value::Matrix(m)) => (m.nrows() * m.ncols(), m.nrows(), m.ncols()),
2166 Some(Value::Scalar(_)) => (1, 1, 1),
2167 None | Some(Value::Void) => (0, 0, 0),
2168 Some(_) => {
2169 return Err(format!(
2170 "'{name}' is not a matrix; cannot use () indexed assignment"
2171 ));
2172 }
2173 };
2174
2175 let widx = {
2176 let _owned_end;
2177 let env_end: &Env = if crate::eval::contains_end(&indices[0]) {
2178 _owned_end = write_env_with_end(env, total);
2179 &_owned_end
2180 } else {
2181 env
2182 };
2183 resolve_write_dim(&indices[0], total, env_end, io)?
2184 };
2185
2186 let positions: Vec<usize> = match widx {
2187 WriteIdx::All => (0..total).collect(),
2188 WriteIdx::Positions(p) => p,
2189 };
2190
2191 let rhs_vals = rhs_to_complex(&rhs, positions.len())?;
2192
2193 let required = positions.iter().copied().max().map(|m| m + 1).unwrap_or(0);
2194 let required = required.max(total);
2195
2196 let (out_rows, out_cols) = if nrows_hint == 0 || ncols_hint == 0 || nrows_hint == 1 {
2197 (1, required)
2198 } else if ncols_hint == 1 {
2199 (required, 1)
2200 } else if required > total {
2201 return Err("Cannot grow a 2-D matrix with linear indexing".to_string());
2202 } else {
2203 (nrows_hint, ncols_hint)
2204 };
2205
2206 let mut mat = take_as_complex(name, env)?;
2207
2208 if required > total || out_rows != mat.nrows() || out_cols != mat.ncols() {
2209 let mut new_mat = Array2::<Complex<f64>>::zeros((out_rows, out_cols));
2210 for old_p in 0..total {
2211 let old_row = old_p % mat.nrows().max(1);
2212 let old_col = old_p / mat.nrows().max(1);
2213 let new_row = old_p % out_rows;
2214 let new_col = old_p / out_rows;
2215 if old_row < mat.nrows() && old_col < mat.ncols() {
2216 new_mat[[new_row, new_col]] = mat[[old_row, old_col]];
2217 }
2218 }
2219 mat = new_mat;
2220 }
2221
2222 for (&pos, &val) in positions.iter().zip(rhs_vals.iter()) {
2223 let row = pos % mat.nrows();
2224 let col = pos / mat.nrows();
2225 mat[[row, col]] = val;
2226 }
2227
2228 env.insert(name.to_string(), Value::ComplexMatrix(mat));
2229 }
2230 2 => {
2231 let (nrows, ncols) = match env.get(name) {
2232 Some(Value::ComplexMatrix(m)) => (m.nrows(), m.ncols()),
2233 Some(Value::Matrix(m)) => (m.nrows(), m.ncols()),
2234 Some(Value::Scalar(_)) => (1, 1),
2235 None | Some(Value::Void) => (0, 0),
2236 Some(_) => {
2237 return Err(format!(
2238 "'{name}' is not a matrix; cannot use () indexed assignment"
2239 ));
2240 }
2241 };
2242
2243 let (ridx, cidx) = {
2244 let _owned_r;
2245 let env_r: &Env = if crate::eval::contains_end(&indices[0]) {
2246 _owned_r = write_env_with_end(env, nrows);
2247 &_owned_r
2248 } else {
2249 env
2250 };
2251 let _owned_c;
2252 let env_c: &Env = if crate::eval::contains_end(&indices[1]) {
2253 _owned_c = write_env_with_end(env, ncols);
2254 &_owned_c
2255 } else {
2256 env
2257 };
2258 (
2259 resolve_write_dim(&indices[0], nrows, env_r, io)?,
2260 resolve_write_dim(&indices[1], ncols, env_c, io)?,
2261 )
2262 };
2263
2264 let rows: Vec<usize> = match ridx {
2265 WriteIdx::All => (0..nrows.max(1)).collect(),
2266 WriteIdx::Positions(p) => p,
2267 };
2268 let cols: Vec<usize> = match cidx {
2269 WriteIdx::All => (0..ncols.max(1)).collect(),
2270 WriteIdx::Positions(p) => p,
2271 };
2272
2273 let req_rows = rows
2274 .iter()
2275 .copied()
2276 .max()
2277 .map(|m| m + 1)
2278 .unwrap_or(0)
2279 .max(nrows);
2280 let req_cols = cols
2281 .iter()
2282 .copied()
2283 .max()
2284 .map(|m| m + 1)
2285 .unwrap_or(0)
2286 .max(ncols);
2287
2288 let n_sel = rows.len() * cols.len();
2289 let rhs_vals = rhs_to_complex(&rhs, n_sel)?;
2290
2291 let mut mat = take_as_complex(name, env)?;
2292
2293 if req_rows != nrows || req_cols != ncols {
2294 let mut new_mat = Array2::<Complex<f64>>::zeros((req_rows, req_cols));
2295 for r in 0..nrows {
2296 for c in 0..ncols {
2297 if r < mat.nrows() && c < mat.ncols() {
2298 new_mat[[r, c]] = mat[[r, c]];
2299 }
2300 }
2301 }
2302 mat = new_mat;
2303 }
2304
2305 let mut k = 0;
2306 for &r in &rows {
2307 for &c in &cols {
2308 mat[[r, c]] = rhs_vals[k];
2309 k += 1;
2310 }
2311 }
2312
2313 env.insert(name.to_string(), Value::ComplexMatrix(mat));
2314 }
2315 _ => return Err("Indexed assignment supports at most 2 indices".to_string()),
2316 }
2317 Ok(())
2318}