Skip to main content

virtual_rust/interpreter/
format.rs

1//! Format string processing (`{}`, `{:?}`, `{:.2}`, `{0}`, etc.).
2
3use crate::interpreter::error::RuntimeError;
4use crate::interpreter::value::Value;
5
6/// Processes a Rust-style format string with positional arguments.
7///
8/// Supported placeholders:
9/// - `{}` — display the next argument
10/// - `{0}`, `{1}` — indexed arguments
11/// - `{:?}` — debug format
12/// - `{:.N}` — float precision
13/// - `{{` / `}}` — escaped braces
14pub fn format_string(fmt: &str, args: &[Value]) -> Result<String, RuntimeError> {
15    let mut result = String::new();
16    let mut arg_index = 0;
17    let mut chars = fmt.chars().peekable();
18
19    while let Some(c) = chars.next() {
20        match c {
21            '{' => format_placeholder(&mut chars, &mut result, args, &mut arg_index),
22            '}' if chars.peek() == Some(&'}') => {
23                chars.next();
24                result.push('}');
25            }
26            _ => result.push(c),
27        }
28    }
29
30    Ok(result)
31}
32
33/// Parses and formats a single `{...}` placeholder.
34fn format_placeholder(
35    chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
36    result: &mut String,
37    args: &[Value],
38    arg_index: &mut usize,
39) {
40    if chars.peek() == Some(&'{') {
41        // Escaped brace: {{
42        chars.next();
43        result.push('{');
44        return;
45    }
46
47    if chars.peek() == Some(&'}') {
48        // Simple `{}` — use next positional argument
49        chars.next();
50        if *arg_index < args.len() {
51            result.push_str(&format!("{}", args[*arg_index]));
52            *arg_index += 1;
53        } else {
54            result.push_str("{}");
55        }
56        return;
57    }
58
59    // Collect the content between { and }
60    let mut placeholder = String::new();
61    while let Some(&c) = chars.peek() {
62        if c == '}' {
63            chars.next();
64            break;
65        }
66        placeholder.push(c);
67        chars.next();
68    }
69
70    if let Some(spec) = placeholder.strip_prefix(':') {
71        // Format specifier: {:?}, {:.2}, etc.
72        format_with_spec(spec, result, args, arg_index);
73    } else if let Ok(idx) = placeholder.parse::<usize>() {
74        // Indexed: {0}, {1}, etc.
75        if idx < args.len() {
76            result.push_str(&format!("{}", args[idx]));
77        }
78    } else {
79        // Named parameter — fall back to positional
80        if *arg_index < args.len() {
81            result.push_str(&format!("{}", args[*arg_index]));
82            *arg_index += 1;
83        } else {
84            result.push('{');
85            result.push_str(&placeholder);
86            result.push('}');
87        }
88    }
89}
90
91/// Applies a format specifier (the part after `:` inside `{:...}`).
92fn format_with_spec(spec: &str, result: &mut String, args: &[Value], arg_index: &mut usize) {
93    if *arg_index >= args.len() {
94        return;
95    }
96
97    let arg = &args[*arg_index];
98    *arg_index += 1;
99
100    if spec == "?" {
101        // Debug format
102        result.push_str(&arg.debug_fmt());
103    } else if let Some(prec_str) = spec.strip_prefix('.') {
104        // Precision format: {:.2}
105        if let Ok(precision) = prec_str.parse::<usize>() {
106            if let Value::Float(n) = arg {
107                result.push_str(&format!("{n:.prec$}", prec = precision));
108            } else {
109                result.push_str(&format!("{arg}"));
110            }
111        } else {
112            result.push_str(&format!("{arg}"));
113        }
114    } else {
115        result.push_str(&format!("{arg}"));
116    }
117}