1use std::char;
4use std::iter::Peekable;
5use std::str::Chars;
6
7use runmat_builtins::{IntValue, LogicalArray, StringArray, Value};
8
9use crate::gather_if_needed;
10
11#[derive(Debug)]
13pub struct ArgCursor<'a> {
14 args: &'a [Value],
15 index: usize,
16}
17
18impl<'a> ArgCursor<'a> {
19 pub fn new(args: &'a [Value]) -> Self {
20 Self { args, index: 0 }
21 }
22
23 pub fn remaining(&self) -> usize {
24 self.args.len().saturating_sub(self.index)
25 }
26
27 pub fn index(&self) -> usize {
28 self.index
29 }
30
31 fn next(&mut self) -> Result<Value, String> {
32 if self.index >= self.args.len() {
33 return Err("sprintf: not enough input arguments for format specifier".to_string());
34 }
35 let value = self.args[self.index].clone();
36 self.index += 1;
37 Ok(value)
38 }
39}
40
41#[derive(Debug, Default, Clone)]
43pub struct FormatStepResult {
44 pub output: String,
45 pub consumed: usize,
46}
47
48#[derive(Clone, Copy, Default)]
49struct FormatFlags {
50 alternate: bool,
51 zero_pad: bool,
52 left_align: bool,
53 sign_plus: bool,
54 sign_space: bool,
55}
56
57#[derive(Clone, Copy)]
58enum Count {
59 Value(isize),
60 FromArgument,
61}
62
63#[derive(Clone, Copy)]
64struct FormatSpec {
65 flags: FormatFlags,
66 width: Option<Count>,
67 precision: Option<Count>,
68 conversion: char,
69}
70
71pub fn format_variadic(fmt: &str, args: &[Value]) -> Result<String, String> {
78 let mut cursor = ArgCursor::new(args);
79 let step = format_variadic_with_cursor(fmt, &mut cursor)?;
80 Ok(step.output)
81}
82
83pub fn format_variadic_with_cursor(
86 fmt: &str,
87 cursor: &mut ArgCursor<'_>,
88) -> Result<FormatStepResult, String> {
89 format_once(fmt, cursor)
90}
91
92fn format_once(fmt: &str, cursor: &mut ArgCursor<'_>) -> Result<FormatStepResult, String> {
93 let mut chars = fmt.chars().peekable();
94 let mut out = String::with_capacity(fmt.len());
95 let mut consumed = 0usize;
96
97 while let Some(ch) = chars.next() {
98 if ch != '%' {
99 out.push(ch);
100 continue;
101 }
102
103 if let Some('%') = chars.peek() {
104 chars.next();
105 out.push('%');
106 continue;
107 }
108
109 let spec = parse_format_spec(&mut chars)?;
110 let (formatted, used) = apply_format_spec(spec, cursor)?;
111 consumed += used;
112 out.push_str(&formatted);
113 }
114
115 Ok(FormatStepResult {
116 output: out,
117 consumed,
118 })
119}
120
121fn parse_format_spec(chars: &mut Peekable<Chars<'_>>) -> Result<FormatSpec, String> {
122 let mut flags = FormatFlags::default();
123 loop {
124 match chars.peek().copied() {
125 Some('#') => {
126 flags.alternate = true;
127 chars.next();
128 }
129 Some('0') => {
130 flags.zero_pad = true;
131 chars.next();
132 }
133 Some('-') => {
134 flags.left_align = true;
135 chars.next();
136 }
137 Some(' ') => {
138 flags.sign_space = true;
139 chars.next();
140 }
141 Some('+') => {
142 flags.sign_plus = true;
143 chars.next();
144 }
145 _ => break,
146 }
147 }
148
149 let width = if let Some('*') = chars.peek() {
150 chars.next();
151 Some(Count::FromArgument)
152 } else {
153 parse_number(chars).map(Count::Value)
154 };
155
156 let precision = if let Some('.') = chars.peek() {
157 chars.next();
158 if let Some('*') = chars.peek() {
159 chars.next();
160 Some(Count::FromArgument)
161 } else {
162 Some(Count::Value(parse_number(chars).unwrap_or(0)))
163 }
164 } else {
165 None
166 };
167
168 if let Some(&('h' | 'l' | 'L' | 'z' | 'j' | 't')) = chars.peek() {
170 let current = chars.next().unwrap();
171 if matches!(current, 'h' | 'l') && chars.peek() == Some(¤t) {
172 chars.next();
173 }
174 }
175
176 let conversion = chars
177 .next()
178 .ok_or_else(|| "sprintf: incomplete format specifier".to_string())?;
179
180 Ok(FormatSpec {
181 flags,
182 width,
183 precision,
184 conversion,
185 })
186}
187
188fn parse_number(chars: &mut Peekable<Chars<'_>>) -> Option<isize> {
189 let mut value: i128 = 0;
190 let mut seen = false;
191 while let Some(&ch) = chars.peek() {
192 if !ch.is_ascii_digit() {
193 break;
194 }
195 seen = true;
196 value = value * 10 + i128::from((ch as u8 - b'0') as i16);
197 chars.next();
198 }
199 if seen {
200 let capped = value
201 .clamp(isize::MIN as i128, isize::MAX as i128)
202 .try_into()
203 .unwrap_or(isize::MAX);
204 Some(capped)
205 } else {
206 None
207 }
208}
209
210fn apply_format_spec(
211 spec: FormatSpec,
212 cursor: &mut ArgCursor<'_>,
213) -> Result<(String, usize), String> {
214 let mut consumed = 0usize;
215 let mut flags = spec.flags;
216
217 let mut width = match spec.width {
218 Some(Count::Value(w)) => Some(w),
219 Some(Count::FromArgument) => {
220 let value = cursor.next()?;
221 consumed += 1;
222 let w = value_to_isize(&value)?;
223 Some(w)
224 }
225 None => None,
226 };
227
228 let precision = match spec.precision {
229 Some(Count::Value(p)) => Some(p),
230 Some(Count::FromArgument) => {
231 let value = cursor.next()?;
232 consumed += 1;
233 let p = value_to_isize(&value)?;
234 if p < 0 {
235 None
236 } else {
237 Some(p)
238 }
239 }
240 None => None,
241 };
242
243 if let Some(w) = width {
244 if w < 0 {
245 flags.left_align = true;
246 width = Some(-w);
247 }
248 }
249
250 let conversion = spec.conversion;
251 let formatted = match conversion {
252 'd' | 'i' => {
253 let value = cursor.next()?;
254 consumed += 1;
255 let int_value = value_to_i128(&value)?;
256 format_integer(
257 int_value,
258 int_value.is_negative(),
259 10,
260 flags,
261 width,
262 precision,
263 false,
264 false,
265 )
266 }
267 'u' => {
268 let value = cursor.next()?;
269 consumed += 1;
270 let uint_value = value_to_u128(&value)?;
271 format_unsigned(uint_value, 10, flags, width, precision, false, false)
272 }
273 'o' => {
274 let value = cursor.next()?;
275 consumed += 1;
276 let uint_value = value_to_u128(&value)?;
277 format_unsigned(
278 uint_value,
279 8,
280 flags,
281 width,
282 precision,
283 spec.flags.alternate,
284 false,
285 )
286 }
287 'x' => {
288 let value = cursor.next()?;
289 consumed += 1;
290 let uint_value = value_to_u128(&value)?;
291 format_unsigned(
292 uint_value,
293 16,
294 flags,
295 width,
296 precision,
297 spec.flags.alternate,
298 false,
299 )
300 }
301 'X' => {
302 let value = cursor.next()?;
303 consumed += 1;
304 let uint_value = value_to_u128(&value)?;
305 format_unsigned(
306 uint_value,
307 16,
308 flags,
309 width,
310 precision,
311 spec.flags.alternate,
312 true,
313 )
314 }
315 'b' => {
316 let value = cursor.next()?;
317 consumed += 1;
318 let uint_value = value_to_u128(&value)?;
319 format_unsigned(
320 uint_value,
321 2,
322 flags,
323 width,
324 precision,
325 spec.flags.alternate,
326 false,
327 )
328 }
329 'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
330 let value = cursor.next()?;
331 consumed += 1;
332 let float_value = value_to_f64(&value)?;
333 format_float(
334 float_value,
335 conversion,
336 flags,
337 width,
338 precision,
339 spec.flags.alternate,
340 )
341 }
342 's' => {
343 let value = cursor.next()?;
344 consumed += 1;
345 format_string(value, flags, width, precision)
346 }
347 'c' => {
348 let value = cursor.next()?;
349 consumed += 1;
350 format_char(value, flags, width)
351 }
352 other => {
353 return Err(format!("sprintf: unsupported format %{other}"));
354 }
355 }?;
356
357 Ok((formatted, consumed))
358}
359
360#[allow(clippy::too_many_arguments)]
361fn format_integer(
362 value: i128,
363 is_negative: bool,
364 base: u32,
365 mut flags: FormatFlags,
366 width: Option<isize>,
367 precision: Option<isize>,
368 alternate: bool,
369 uppercase: bool,
370) -> Result<String, String> {
371 let mut sign = String::new();
372 let abs_val = value.unsigned_abs();
373
374 if is_negative {
375 sign.push('-');
376 } else if flags.sign_plus {
377 sign.push('+');
378 } else if flags.sign_space {
379 sign.push(' ');
380 }
381
382 if precision.is_some() {
383 flags.zero_pad = false;
384 }
385
386 let mut digits = to_base_string(abs_val, base, uppercase);
387 let precision_value = precision.unwrap_or(-1);
388 if precision_value == 0 && abs_val == 0 {
389 digits.clear();
390 }
391 if precision_value > 0 {
392 let required = precision_value as usize;
393 if digits.len() < required {
394 let mut buf = String::with_capacity(required);
395 for _ in 0..(required - digits.len()) {
396 buf.push('0');
397 }
398 buf.push_str(&digits);
399 digits = buf;
400 }
401 }
402
403 let mut prefix = String::new();
404 if alternate && abs_val != 0 {
405 match base {
406 8 => prefix.push('0'),
407 16 => {
408 prefix.push('0');
409 prefix.push(if uppercase { 'X' } else { 'x' });
410 }
411 2 => {
412 prefix.push('0');
413 prefix.push('b');
414 }
415 _ => {}
416 }
417 }
418
419 apply_width(sign, prefix, digits, flags, width, flags.zero_pad)
420}
421
422fn format_unsigned(
423 value: u128,
424 base: u32,
425 mut flags: FormatFlags,
426 width: Option<isize>,
427 precision: Option<isize>,
428 alternate: bool,
429 uppercase: bool,
430) -> Result<String, String> {
431 if precision.is_some() {
432 flags.zero_pad = false;
433 }
434
435 let mut digits = to_base_string(value, base, uppercase);
436 let precision_value = precision.unwrap_or(-1);
437 if precision_value == 0 && value == 0 {
438 digits.clear();
439 }
440 if precision_value > 0 {
441 let required = precision_value as usize;
442 if digits.len() < required {
443 let mut buf = String::with_capacity(required);
444 for _ in 0..(required - digits.len()) {
445 buf.push('0');
446 }
447 buf.push_str(&digits);
448 digits = buf;
449 }
450 }
451
452 let mut prefix = String::new();
453 if alternate && value != 0 {
454 match base {
455 8 => prefix.push('0'),
456 16 => {
457 prefix.push_str(if uppercase { "0X" } else { "0x" });
458 }
459 2 => prefix.push_str("0b"),
460 _ => {}
461 }
462 }
463
464 apply_width(String::new(), prefix, digits, flags, width, flags.zero_pad)
465}
466
467fn format_float(
468 value: f64,
469 conversion: char,
470 flags: FormatFlags,
471 width: Option<isize>,
472 precision: Option<isize>,
473 alternate: bool,
474) -> Result<String, String> {
475 let mut sign = String::new();
476 let mut magnitude = value;
477
478 if value.is_nan() {
479 return apply_width(
480 String::new(),
481 String::new(),
482 "NaN".to_string(),
483 flags,
484 width,
485 false,
486 );
487 }
488
489 if value.is_infinite() {
490 if value.is_sign_negative() {
491 sign.push('-');
492 } else if flags.sign_plus {
493 sign.push('+');
494 } else if flags.sign_space {
495 sign.push(' ');
496 }
497 let text = "Inf".to_string();
498 return apply_width(sign, String::new(), text, flags, width, false);
499 }
500
501 if value.is_sign_negative() || (value == 0.0 && (1.0 / value).is_sign_negative()) {
502 sign.push('-');
503 magnitude = -value;
504 } else if flags.sign_plus {
505 sign.push('+');
506 } else if flags.sign_space {
507 sign.push(' ');
508 }
509
510 let prec = precision.unwrap_or(6).max(0) as usize;
511 let mut body = match conversion {
512 'f' | 'F' => format!("{magnitude:.prec$}"),
513 'e' => format!("{magnitude:.prec$e}"),
514 'E' => format!("{magnitude:.prec$E}"),
515 'g' | 'G' => format_float_general(magnitude, prec, conversion.is_uppercase()),
516 _ => {
517 return Err(format!(
518 "sprintf: unsupported float conversion %{}",
519 conversion
520 ))
521 }
522 };
523
524 if alternate && !body.contains('.') && matches!(conversion, 'f' | 'F' | 'g' | 'G') {
525 body.push('.');
526 }
527
528 let zero_pad_allowed = flags.zero_pad && !flags.left_align;
529 apply_width(sign, String::new(), body, flags, width, zero_pad_allowed)
530}
531
532fn format_float_general(value: f64, precision: usize, uppercase: bool) -> String {
533 if value == 0.0 {
534 if precision == 0 {
535 return "0".to_string();
536 }
537 let mut zero = String::from("0");
538 if precision > 0 {
539 zero.push('.');
540 zero.push_str(&"0".repeat(precision.saturating_sub(1)));
541 }
542 return zero;
543 }
544
545 let mut prec = precision;
546 if prec == 0 {
547 prec = 1;
548 }
549
550 let abs_val = value.abs();
551 let exp = abs_val.log10().floor() as i32;
552 let use_exp = exp < -4 || exp >= prec as i32;
553
554 if use_exp {
555 let mut s = format!("{:.*e}", prec - 1, value);
556 if uppercase {
557 s = s.to_uppercase();
558 }
559 trim_trailing_zeros(&mut s, true);
560 s
561 } else {
562 let mut s = format!("{:.*}", prec.max(1) - 1, value);
563 trim_trailing_zeros(&mut s, false);
564 s
565 }
566}
567
568fn trim_trailing_zeros(text: &mut String, keep_exponent: bool) {
569 if let Some(dot_idx) = text.find('.') {
570 let mut end = text.len();
571 while end > dot_idx + 1 {
572 let byte = text.as_bytes()[end - 1];
573 if byte == b'0' {
574 end -= 1;
575 } else {
576 break;
577 }
578 }
579 if end > dot_idx + 1 && text.as_bytes()[end - 1] == b'.' {
580 end -= 1;
581 }
582 if keep_exponent {
583 if let Some(exp_idx) = text.find(['e', 'E']) {
584 let exponent = text[exp_idx..].to_string();
585 text.truncate(end.min(exp_idx));
586 text.push_str(&exponent);
587 return;
588 }
589 }
590 text.truncate(end);
591 }
592}
593
594fn format_string(
595 value: Value,
596 flags: FormatFlags,
597 width: Option<isize>,
598 precision: Option<isize>,
599) -> Result<String, String> {
600 let mut text = value_to_string(&value)?;
601 if let Some(p) = precision {
602 if p >= 0 {
603 let mut chars = text.chars();
604 let mut truncated = String::with_capacity(text.len());
605 for _ in 0..(p as usize) {
606 if let Some(ch) = chars.next() {
607 truncated.push(ch);
608 } else {
609 break;
610 }
611 }
612 text = truncated;
613 }
614 }
615
616 apply_width(String::new(), String::new(), text, flags, width, false)
617}
618
619fn format_char(value: Value, flags: FormatFlags, width: Option<isize>) -> Result<String, String> {
620 let ch = value_to_char(&value)?;
621 let text = ch.to_string();
622 apply_width(String::new(), String::new(), text, flags, width, false)
623}
624
625fn apply_width(
626 sign: String,
627 prefix: String,
628 digits: String,
629 flags: FormatFlags,
630 width: Option<isize>,
631 zero_pad: bool,
632) -> Result<String, String> {
633 let mut result = String::new();
634 let sign_prefix_len = sign.len() + prefix.len();
635 let total_len = sign_prefix_len + digits.len();
636 let target_width = width.unwrap_or(0).max(0) as usize;
637
638 if target_width <= total_len {
639 result.push_str(&sign);
640 result.push_str(&prefix);
641 result.push_str(&digits);
642 return Ok(result);
643 }
644
645 let pad_len = target_width - total_len;
646 if flags.left_align {
647 result.push_str(&sign);
648 result.push_str(&prefix);
649 result.push_str(&digits);
650 for _ in 0..pad_len {
651 result.push(' ');
652 }
653 return Ok(result);
654 }
655
656 if zero_pad {
657 result.push_str(&sign);
658 result.push_str(&prefix);
659 for _ in 0..pad_len {
660 result.push('0');
661 }
662 result.push_str(&digits);
663 } else {
664 for _ in 0..pad_len {
665 result.push(' ');
666 }
667 result.push_str(&sign);
668 result.push_str(&prefix);
669 result.push_str(&digits);
670 }
671 Ok(result)
672}
673
674fn value_to_isize(value: &Value) -> Result<isize, String> {
675 match value {
676 Value::Int(i) => Ok(i.to_i64().clamp(isize::MIN as i64, isize::MAX as i64) as isize),
677 Value::Num(n) => {
678 if !n.is_finite() {
679 return Err("sprintf: width/precision specifier must be finite".to_string());
680 }
681 Ok(n.trunc().clamp(isize::MIN as f64, isize::MAX as f64) as isize)
682 }
683 Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
684 other => Err(format!(
685 "sprintf: width/precision specifier expects numeric value, got {other:?}"
686 )),
687 }
688}
689
690fn value_to_i128(value: &Value) -> Result<i128, String> {
691 match value {
692 Value::Int(i) => Ok(match i {
693 IntValue::I8(v) => i128::from(*v),
694 IntValue::I16(v) => i128::from(*v),
695 IntValue::I32(v) => i128::from(*v),
696 IntValue::I64(v) => i128::from(*v),
697 IntValue::U8(v) => i128::from(*v),
698 IntValue::U16(v) => i128::from(*v),
699 IntValue::U32(v) => i128::from(*v),
700 IntValue::U64(v) => i128::from(*v),
701 }),
702 Value::Num(n) => {
703 if !n.is_finite() {
704 return Err("sprintf: numeric conversion requires finite input".to_string());
705 }
706 Ok(n.trunc().clamp(i128::MIN as f64, i128::MAX as f64) as i128)
707 }
708 Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
709 other => Err(format!("sprintf: expected numeric argument, got {other:?}")),
710 }
711}
712
713fn value_to_u128(value: &Value) -> Result<u128, String> {
714 match value {
715 Value::Int(i) => match i {
716 IntValue::I8(v) if *v < 0 => Err("sprintf: expected non-negative value".to_string()),
717 IntValue::I16(v) if *v < 0 => Err("sprintf: expected non-negative value".to_string()),
718 IntValue::I32(v) if *v < 0 => Err("sprintf: expected non-negative value".to_string()),
719 IntValue::I64(v) if *v < 0 => Err("sprintf: expected non-negative value".to_string()),
720 IntValue::I8(v) => Ok((*v) as u128),
721 IntValue::I16(v) => Ok((*v) as u128),
722 IntValue::I32(v) => Ok((*v) as u128),
723 IntValue::I64(v) => Ok((*v) as u128),
724 IntValue::U8(v) => Ok((*v) as u128),
725 IntValue::U16(v) => Ok((*v) as u128),
726 IntValue::U32(v) => Ok((*v) as u128),
727 IntValue::U64(v) => Ok((*v) as u128),
728 },
729 Value::Num(n) => {
730 if !n.is_finite() {
731 return Err("sprintf: numeric conversion requires finite input".to_string());
732 }
733 if *n < 0.0 {
734 return Err("sprintf: expected non-negative value".to_string());
735 }
736 Ok(n.trunc().clamp(0.0, u128::MAX as f64) as u128)
737 }
738 Value::Bool(b) => Ok(if *b { 1 } else { 0 }),
739 other => Err(format!(
740 "sprintf: expected non-negative numeric value, got {other:?}"
741 )),
742 }
743}
744
745fn value_to_f64(value: &Value) -> Result<f64, String> {
746 match value {
747 Value::Num(n) => Ok(*n),
748 Value::Int(i) => Ok(i.to_f64()),
749 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
750 other => Err(format!("sprintf: expected numeric value, got {other:?}")),
751 }
752}
753
754fn value_to_string(value: &Value) -> Result<String, String> {
755 match value {
756 Value::String(s) => Ok(s.clone()),
757 Value::CharArray(ca) => {
758 let mut s = String::with_capacity(ca.data.len());
759 for ch in &ca.data {
760 s.push(*ch);
761 }
762 Ok(s)
763 }
764 Value::StringArray(sa) if sa.data.len() == 1 => Ok(sa.data[0].clone()),
765 Value::Num(n) => Ok(Value::Num(*n).to_string()),
766 Value::Int(i) => Ok(i.to_i64().to_string()),
767 Value::Bool(b) => Ok(if *b { "true" } else { "false" }.to_string()),
768 Value::Complex(re, im) => Ok(Value::Complex(*re, *im).to_string()),
769 other => Err(format!(
770 "sprintf: expected text or scalar value for %s conversion, got {other:?}"
771 )),
772 }
773}
774
775fn value_to_char(value: &Value) -> Result<char, String> {
776 match value {
777 Value::String(s) => s
778 .chars()
779 .next()
780 .ok_or_else(|| "sprintf: %c conversion requires non-empty character input".to_string()),
781 Value::CharArray(ca) => ca
782 .data
783 .first()
784 .copied()
785 .ok_or_else(|| "sprintf: %c conversion requires non-empty char input".to_string()),
786 Value::Num(n) => {
787 if !n.is_finite() {
788 return Err("sprintf: %c conversion needs finite numeric value".to_string());
789 }
790 let code = n.trunc() as u32;
791 std::char::from_u32(code)
792 .ok_or_else(|| "sprintf: numeric value outside valid character range".to_string())
793 }
794 Value::Int(i) => {
795 let code = i.to_i64();
796 if code < 0 {
797 return Err("sprintf: negative value for %c conversion".to_string());
798 }
799 std::char::from_u32(code as u32)
800 .ok_or_else(|| "sprintf: numeric value outside valid character range".to_string())
801 }
802 other => Err(format!(
803 "sprintf: %c conversion expects character data, got {other:?}"
804 )),
805 }
806}
807
808fn to_base_string(mut value: u128, base: u32, uppercase: bool) -> String {
809 if value == 0 {
810 return "0".to_string();
811 }
812 let mut buf = Vec::new();
813 while value > 0 {
814 let digit = (value % base as u128) as u8;
815 let ch = match digit {
816 0..=9 => b'0' + digit,
817 _ => {
818 if uppercase {
819 b'A' + (digit - 10)
820 } else {
821 b'a' + (digit - 10)
822 }
823 }
824 };
825 buf.push(ch as char);
826 value /= base as u128;
827 }
828 buf.iter().rev().collect()
829}
830
831pub fn extract_format_string(value: &Value, context: &str) -> Result<String, String> {
834 match value {
835 Value::String(s) => Ok(s.clone()),
836 Value::CharArray(ca) => {
837 if ca.rows != 1 {
838 return Err(format!(
839 "{context}: formatSpec must be a character row vector or string scalar"
840 ));
841 }
842 Ok(ca.data.iter().collect())
843 }
844 Value::StringArray(sa) if sa.data.len() == 1 => Ok(sa.data[0].clone()),
845 _ => Err(format!(
846 "{context}: formatSpec must be a character row vector or string scalar"
847 )),
848 }
849}
850
851pub fn decode_escape_sequences(context: &str, input: &str) -> Result<String, String> {
853 let mut result = String::with_capacity(input.len());
854 let mut chars = input.chars().peekable();
855 while let Some(ch) = chars.next() {
856 if ch != '\\' {
857 result.push(ch);
858 continue;
859 }
860 let Some(next) = chars.next() else {
861 result.push('\\');
862 break;
863 };
864 match next {
865 '\\' => result.push('\\'),
866 'a' => result.push('\u{0007}'),
867 'b' => result.push('\u{0008}'),
868 'f' => result.push('\u{000C}'),
869 'n' => result.push('\n'),
870 'r' => result.push('\r'),
871 't' => result.push('\t'),
872 'v' => result.push('\u{000B}'),
873 'x' => {
874 let mut hex = String::new();
875 for _ in 0..2 {
876 match chars.peek().copied() {
877 Some(c) if c.is_ascii_hexdigit() => {
878 hex.push(chars.next().unwrap());
879 }
880 _ => break,
881 }
882 }
883 if hex.is_empty() {
884 result.push('\\');
885 result.push('x');
886 } else {
887 let value = u32::from_str_radix(&hex, 16)
888 .map_err(|_| format!("{context}: invalid hexadecimal escape \\x{hex}"))?;
889 if let Some(chr) = char::from_u32(value) {
890 result.push(chr);
891 } else {
892 return Err(format!(
893 "{context}: \\x{hex} escape outside valid Unicode range"
894 ));
895 }
896 }
897 }
898 '0'..='7' => {
899 let mut oct = String::new();
900 oct.push(next);
901 for _ in 0..2 {
902 match chars.peek().copied() {
903 Some(c) if ('0'..='7').contains(&c) => {
904 oct.push(chars.next().unwrap());
905 }
906 _ => break,
907 }
908 }
909 let value = u32::from_str_radix(&oct, 8)
910 .map_err(|_| format!("{context}: invalid octal escape \\{oct}"))?;
911 if let Some(chr) = char::from_u32(value) {
912 result.push(chr);
913 } else {
914 return Err(format!(
915 "{context}: \\{oct} escape outside valid Unicode range"
916 ));
917 }
918 }
919 other => {
920 result.push('\\');
921 result.push(other);
922 }
923 }
924 }
925 Ok(result)
926}
927
928pub fn flatten_arguments(args: &[Value], context: &str) -> Result<Vec<Value>, String> {
932 let mut flattened = Vec::new();
933 for value in args {
934 let gathered = gather_if_needed(value).map_err(|e| format!("{context}: {e}"))?;
935 flatten_value(gathered, &mut flattened, context)?;
936 }
937 Ok(flattened)
938}
939
940fn flatten_value(value: Value, output: &mut Vec<Value>, context: &str) -> Result<(), String> {
941 match value {
942 Value::Num(_)
943 | Value::Int(_)
944 | Value::Bool(_)
945 | Value::String(_)
946 | Value::Complex(_, _) => {
947 output.push(value);
948 }
949 Value::Tensor(tensor) => {
950 for &elem in &tensor.data {
951 output.push(Value::Num(elem));
952 }
953 }
954 Value::ComplexTensor(tensor) => {
955 for &(re, im) in &tensor.data {
956 output.push(Value::Complex(re, im));
957 }
958 }
959 Value::LogicalArray(LogicalArray { data, .. }) => {
960 for byte in data {
961 output.push(Value::Bool(byte != 0));
962 }
963 }
964 Value::StringArray(StringArray { data, .. }) => {
965 for s in data {
966 output.push(Value::String(s));
967 }
968 }
969 Value::CharArray(ca) => {
970 if ca.rows == 1 {
971 output.push(Value::String(ca.data.iter().collect()));
972 } else {
973 for row in 0..ca.rows {
974 let mut line = String::with_capacity(ca.cols);
975 for col in 0..ca.cols {
976 line.push(ca.data[row * ca.cols + col]);
977 }
978 output.push(Value::String(line));
979 }
980 }
981 }
982 Value::Cell(cell) => {
983 for col in 0..cell.cols {
984 for row in 0..cell.rows {
985 let idx = row * cell.cols + col;
986 let inner = (*cell.data[idx]).clone();
987 let gathered =
988 gather_if_needed(&inner).map_err(|e| format!("{context}: {e}"))?;
989 flatten_value(gathered, output, context)?;
990 }
991 }
992 }
993 Value::GpuTensor(handle) => {
994 let gathered = gather_if_needed(&Value::GpuTensor(handle))
995 .map_err(|e| format!("{context}: {e}"))?;
996 flatten_value(gathered, output, context)?;
997 }
998 Value::MException(_)
999 | Value::HandleObject(_)
1000 | Value::Listener(_)
1001 | Value::Object(_)
1002 | Value::Struct(_)
1003 | Value::FunctionHandle(_)
1004 | Value::Closure(_)
1005 | Value::ClassRef(_) => {
1006 return Err(format!("{context}: unsupported argument type"));
1007 }
1008 }
1009 Ok(())
1010}