nmbrs_runtime/scope_synth/
helpers.rs1use std::collections::HashSet;
25
26use polydat::ast::{PortType, Value};
27
28pub fn workload_param_type_name(value: &str) -> &'static str {
33 let trimmed = value.trim();
34 if trimmed.parse::<u64>().is_ok() {
35 "u64"
36 } else if trimmed.parse::<f64>().is_ok() {
37 "f64"
38 } else if trimmed == "true" || trimmed == "false" {
39 "bool"
40 } else {
41 "String"
42 }
43}
44
45pub fn format_value_as_final_literal(v: &Value) -> Option<String> {
51 match v {
52 Value::U64(n) => Some(n.to_string()),
53 Value::F64(f) => {
54 if f.fract() == 0.0 && f.is_finite() {
55 Some(format!("{f:.1}"))
56 } else {
57 Some(format!("{f}"))
58 }
59 }
60 Value::Bool(b) => Some(b.to_string()),
61 Value::Str(s) => {
62 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
63 Some(format!("\"{escaped}\""))
64 }
65 _ => None,
66 }
67}
68
69pub fn format_value_as_polydat_literal(v: &Value) -> String {
76 match v {
77 Value::U64(n) => n.to_string(),
78 Value::F64(f) => {
79 if f.fract() == 0.0 && f.is_finite() {
82 format!("{f:.1}")
83 } else {
84 format!("{f}")
85 }
86 }
87 Value::Bool(b) => b.to_string(),
88 Value::Str(s) => {
89 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
90 format!("\"{escaped}\"")
91 }
92 _ => {
93 let display = v.to_display_string();
94 let escaped = display.replace('\\', "\\\\").replace('"', "\\\"");
95 format!("\"{escaped}\"")
96 }
97 }
98}
99
100pub fn format_workload_param_as_polydat_literal(value: &str) -> String {
108 let trimmed = value.trim();
109 if trimmed.parse::<u64>().is_ok() || trimmed.parse::<f64>().is_ok() {
110 trimmed.to_string()
111 } else {
112 let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
113 format!("\"{escaped}\"")
114 }
115}
116
117pub fn value_to_param_string(v: &Value) -> Option<String> {
123 match v {
124 Value::U64(n) => Some(n.to_string()),
125 Value::F64(n) => Some(n.to_string()),
126 Value::Bool(b) => Some(b.to_string()),
127 Value::Str(s) => Some(s.to_string()),
128 _ => None,
129 }
130}
131
132pub fn port_type_to_extern_name(t: PortType) -> &'static str {
138 t.to_keyword()
139}
140
141pub fn collect_leaf_placeholders(texts: &[String]) -> HashSet<String> {
149 let mut out = HashSet::new();
150 for text in texts {
151 scan_one(text, &mut out);
152 }
153 out
154}
155
156pub fn scan_one(text: &str, out: &mut HashSet<String>) {
160 let bytes = text.as_bytes();
161 let n = bytes.len();
162 let mut i = 0;
163 while i < n {
164 if bytes[i] == b'\\' && i + 1 < n && (bytes[i + 1] == b'{' || bytes[i + 1] == b'}') {
165 i += 2;
166 continue;
167 }
168 if bytes[i] == b'{' {
169 let mut j = i + 1;
170 let mut nested = false;
171 while j < n {
172 if bytes[j] == b'\\' && j + 1 < n && (bytes[j + 1] == b'{' || bytes[j + 1] == b'}')
173 {
174 j += 2;
175 continue;
176 }
177 if bytes[j] == b'{' {
178 nested = true;
179 break;
180 }
181 if bytes[j] == b'}' {
182 break;
183 }
184 j += 1;
185 }
186 if !nested && j < n && bytes[j] == b'}' {
187 let name = &text[i + 1..j];
188 if !name.is_empty() {
189 out.insert(name.to_string());
190 }
191 i = j + 1;
192 continue;
193 }
194 i += 1;
195 continue;
196 }
197 i += 1;
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn workload_param_type_name_classifies_basic_scalars() {
207 assert_eq!(workload_param_type_name("42"), "u64");
208 assert_eq!(workload_param_type_name("3.14"), "f64");
209 assert_eq!(workload_param_type_name("true"), "bool");
210 assert_eq!(workload_param_type_name("false"), "bool");
211 assert_eq!(workload_param_type_name("hello"), "String");
212 }
213
214 #[test]
215 fn format_value_as_polydat_literal_renders_scalars() {
216 assert_eq!(format_value_as_polydat_literal(&Value::U64(42)), "42");
217 assert_eq!(format_value_as_polydat_literal(&Value::Bool(true)), "true");
218 assert_eq!(
219 format_value_as_polydat_literal(&Value::Str("x".into())),
220 "\"x\""
221 );
222 assert_eq!(format_value_as_polydat_literal(&Value::F64(2.0)), "2.0");
225 }
226
227 #[test]
228 fn format_workload_param_quotes_non_numeric() {
229 assert_eq!(format_workload_param_as_polydat_literal("42"), "42");
230 assert_eq!(
231 format_workload_param_as_polydat_literal("hello"),
232 "\"hello\""
233 );
234 assert_eq!(format_workload_param_as_polydat_literal("true"), "\"true\"");
237 }
238
239 #[test]
240 fn value_to_param_string_handles_scalars_only() {
241 assert_eq!(value_to_param_string(&Value::U64(7)), Some("7".to_string()));
242 assert_eq!(
243 value_to_param_string(&Value::Str("y".into())),
244 Some("y".to_string())
245 );
246 assert_eq!(value_to_param_string(&Value::None), None);
248 }
249
250 #[test]
251 fn collect_leaf_placeholders_extracts_simple_names() {
252 let names = collect_leaf_placeholders(&[
253 "k in 1..{n}".to_string(),
254 "{profile} matches {prefix}".to_string(),
255 ]);
256 assert!(names.contains("n"));
257 assert!(names.contains("profile"));
258 assert!(names.contains("prefix"));
259 }
260
261 #[test]
262 fn collect_leaf_placeholders_skips_nested() {
263 let names = collect_leaf_placeholders(&["{a_{b}_c}".to_string()]);
266 assert!(names.contains("b"));
267 assert!(!names.contains("a_{b}_c"));
268 }
269
270 #[test]
271 fn collect_leaf_placeholders_honors_escapes() {
272 let names = collect_leaf_placeholders(&["\\{not_a_placeholder\\}".to_string()]);
273 assert!(names.is_empty());
274 }
275}