1use regex::Regex;
6use runmat_macros::runtime_builtin;
7use std::sync::{Mutex, OnceLock};
8
9#[runtime_builtin(name = "fprintf")]
11pub fn fprintf_string_builtin(format_str: String) -> Result<f64, String> {
12 print!("{format_str}");
13 use std::io::{self, Write};
14 io::stdout()
15 .flush()
16 .map_err(|e| format!("Failed to flush stdout: {e}"))?;
17 Ok(format_str.len() as f64) }
19
20pub fn fprintf_format_builtin(format_str: String, value: f64) -> Result<f64, String> {
23 let fmt = Regex::new(r"%(?P<prec>\.\d+)?(?P<spec>[df])").unwrap();
25 let output = if let Some(caps) = fmt.captures(&format_str) {
26 let spec = caps.name("spec").map(|m| m.as_str()).unwrap_or("f");
27 let prec = caps
28 .name("prec")
29 .and_then(|m| m.as_str().strip_prefix('.'))
30 .and_then(|n| n.parse::<usize>().ok())
31 .unwrap_or(6);
32 match spec {
33 "d" => format_str.replacen(&caps[0], &format!("{value:.0}"), 1),
34 _ => format_str.replacen(&caps[0], &format!("{value:.prec$}"), 1),
35 }
36 } else {
37 format_str.replace("\\n", "\n")
38 };
39
40 print!("{output}");
41 use std::io::{self, Write};
42 io::stdout()
43 .flush()
44 .map_err(|e| format!("Failed to flush stdout: {e}"))?;
45 Ok(output.len() as f64)
46}
47
48pub fn fprintf_format2_builtin(
50 format_str: String,
51 value1: f64,
52 value2: f64,
53) -> Result<f64, String> {
54 let fmt = Regex::new(r"%(?P<prec>\.\d+)?(?P<spec>[df])").unwrap();
56 let mut output = format_str;
57 for val in [value1, value2] {
58 if let Some(caps) = fmt.captures(&output) {
59 let spec = caps.name("spec").map(|m| m.as_str()).unwrap_or("f");
60 let prec = caps
61 .name("prec")
62 .and_then(|m| m.as_str().strip_prefix('.'))
63 .and_then(|n| n.parse::<usize>().ok())
64 .unwrap_or(6);
65 let rep = match spec {
66 "d" => format!("{val:.0}"),
67 _ => format!("{val:.prec$}"),
68 };
69 output = output.replacen(&caps[0], &rep, 1);
70 }
71 }
72 output = output.replace("\\n", "\n");
73 print!("{output}");
74 use std::io::{self, Write};
75 io::stdout()
76 .flush()
77 .map_err(|e| format!("Failed to flush stdout: {e}"))?;
78 Ok(output.len() as f64)
79}
80
81#[runtime_builtin(name = "disp")]
83pub fn disp_string_builtin(s: String) -> Result<f64, String> {
84 println!("{s}");
85 Ok(0.0)
86}
87
88pub fn disp_number_builtin(n: f64) -> Result<f64, String> {
91 println!("{n}");
92 Ok(0.0)
93}
94
95static TIMER_START: OnceLock<Mutex<Option<std::time::Instant>>> = OnceLock::new();
97
98#[runtime_builtin(name = "tic")]
100pub fn tic_builtin() -> Result<f64, String> {
101 let timer = TIMER_START.get_or_init(|| Mutex::new(None));
102 let mut start_time = timer.lock().map_err(|_| "Failed to acquire timer lock")?;
103 *start_time = Some(std::time::Instant::now());
104 Ok(0.0) }
106
107#[runtime_builtin(name = "toc")]
109pub fn toc_builtin() -> Result<f64, String> {
110 let timer = TIMER_START.get_or_init(|| Mutex::new(None));
111 let start_time = timer.lock().map_err(|_| "Failed to acquire timer lock")?;
112
113 match *start_time {
114 Some(start) => {
115 let elapsed = start.elapsed().as_secs_f64();
116 Ok(elapsed)
117 }
118 None => Err("tic must be called before toc".to_string()),
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_fprintf() {
128 let result = fprintf_string_builtin("Hello, world!".to_string());
129 assert!(result.is_ok());
130 assert_eq!(result.unwrap(), 13.0);
131 }
132
133 #[test]
134 fn test_disp_string() {
135 let result = disp_string_builtin("Test message".to_string());
136 assert!(result.is_ok());
137 }
138
139 #[test]
140 fn test_disp_number() {
141 let result = disp_number_builtin(std::f64::consts::PI);
142 assert!(result.is_ok());
143 }
144
145 #[test]
146 fn test_tic_toc() {
147 let result = tic_builtin();
149 assert!(result.is_ok());
150 assert_eq!(result.unwrap(), 0.0);
151
152 std::thread::sleep(std::time::Duration::from_millis(10));
154
155 let result = toc_builtin();
157 assert!(result.is_ok());
158 let elapsed = result.unwrap();
159 assert!(elapsed >= 0.01); assert!(elapsed < 1.0); }
162}