1#![doc(hidden)]
15
16use anyhow::{Context, Result, anyhow};
17use parse_int::parse;
18use std::collections::HashMap;
19use std::fs::File;
20use std::io::Write;
21
22use ast::Ast;
24use astdb::AstDb;
25use diags::Diags;
26use exec_phase::ExecPhase;
27use extension_registry::{ExtensionRegistry, test_mocks::register_test_extensions};
28use ir::{ConstBuiltins, ParameterValue};
29use irdb::IRDb;
30use layout_phase::LayoutPhase;
31use layoutdb::LayoutDb;
32use map_phase::{format_c99, format_csv, format_json, format_rs};
33use regiondb::RegionDb;
34use validation_phase::ValidationPhase;
35
36#[allow(unused_imports)]
37use tracing::{debug, error, info, trace, warn};
38
39fn parse_define(s: &str) -> Result<(String, ParameterValue)> {
51 if s.is_empty() {
52 return Err(anyhow!("Empty name in define '{}'", s));
53 }
54 let (name, val_str) = match s.find('=') {
55 None => return Ok((s.to_string(), ParameterValue::Integer(1))),
56 Some(pos) => (&s[..pos], &s[pos + 1..]),
57 };
58 if name.is_empty() {
59 return Err(anyhow!("Empty name in define '{}'", s));
60 }
61 let value = if val_str.starts_with('"') || val_str.starts_with('\'') {
62 let inner = val_str.trim_matches(&['"', '\''][..]);
64 ParameterValue::QuotedString(inner.to_string())
65 } else if let Some(stripped) = val_str.strip_suffix('u') {
66 let v =
67 parse::<u64>(stripped).map_err(|e| anyhow!("Error parsing define '{}': {}", s, e))?;
68 ParameterValue::U64(v)
69 } else if let Some(stripped) = val_str.strip_suffix('i') {
70 let v = parse::<i64>(stripped)
71 .map_err(|_| anyhow!("Invalid I64 value in define '{}': '{}'", s, stripped))?;
72 ParameterValue::I64(v)
73 } else if val_str.starts_with('-') {
74 let v = parse::<i64>(val_str)
75 .map_err(|_| anyhow!("Invalid I64 value in define '{}': '{}'", s, val_str))?;
76 ParameterValue::I64(v)
77 } else if val_str.starts_with("0x")
78 || val_str.starts_with("0X")
79 || val_str.starts_with("0b")
80 || val_str.starts_with("0B")
81 {
82 let v = parse::<u64>(val_str)
83 .map_err(|_| anyhow!("Invalid U64 value in define '{}': '{}'", s, val_str))?;
84 ParameterValue::U64(v)
85 } else if val_str.chars().any(|c| !c.is_ascii_digit()) {
86 ParameterValue::QuotedString(val_str.to_string())
89 } else {
90 let v = parse::<i64>(val_str)
91 .map_err(|_| anyhow!("Invalid integer value in define '{}': '{}'", s, val_str))?;
92 ParameterValue::Integer(v)
93 };
94 Ok((name.to_string(), value))
95}
96
97pub fn list_extensions() -> Vec<String> {
100 let mut registry = ExtensionRegistry::new();
101 extensions::register_all(&mut registry);
102 registry
103 .sorted_names()
104 .iter()
105 .map(|s| s.to_string())
106 .collect()
107}
108
109#[allow(clippy::too_many_arguments)]
124pub fn process(
125 name: &str,
126 fstr: &str,
127 output_file: Option<&str>,
128 verbosity: u64,
129 noprint: bool,
130 defines: &[String],
131 max_output_size: u64,
132 map_csv: Option<&str>,
133 map_json: Option<&str>,
134 map_c99: Option<&str>,
135 map_rs: Option<&str>,
136) -> Result<()> {
137 info!("Processing {}", name);
138 ConstBuiltins::init();
139
140 let mut diags = Diags::new(name, fstr, verbosity, noprint);
141
142 let mut const_defines: HashMap<String, ParameterValue> = HashMap::new();
144 for d in defines {
145 let (n, v) = parse_define(d)?;
146 const_defines.insert(n, v);
147 }
148
149 let ast = Ast::new(name, fstr, &mut diags).context("[ERR_218]: Error detected, halting.")?;
150
151 if verbosity > 2 {
152 ast.dump("ast.dot")?;
153 }
154
155 let ast_db = AstDb::new(&mut diags, &ast, false)?;
159
160 let (mut symbol_table, pruned_ast) = const_eval::evaluate_and_prune(&mut diags, &ast, &ast_db, &const_defines)
161 .context("[ERR_219]: Error detected, halting.")?;
162
163 let pruned_ast_db =
166 AstDb::new(&mut diags, &pruned_ast, true).context("[ERR_220]: Error detected, halting.")?;
167
168 let Some(region_bindings) =
169 const_eval::evaluate_regions(&mut diags, &pruned_ast, &pruned_ast_db, &mut symbol_table)
170 else {
171 return Err(anyhow!("[ERR_226]: Error detected, halting."));
172 };
173
174 let Some(obj_props) =
175 const_eval::evaluate_obj_props(&mut diags, &pruned_ast, &pruned_ast_db, &mut symbol_table)
176 else {
177 return Err(anyhow!("[ERR_232]: Error detected, halting."));
178 };
179
180 let mut section_region_names: HashMap<String, String> = HashMap::new();
182 for (sec_name, section) in &pruned_ast_db.sections {
183 if let Some(region_name) = §ion.region {
184 section_region_names.insert(sec_name.to_string(), region_name.clone());
185 }
186 }
187
188 let layout_db = LayoutDb::new(&mut diags, &pruned_ast, &pruned_ast_db, &symbol_table, obj_props)
189 .context("[ERR_221]: Error detected, halting.")?;
190 if verbosity > 2 {
191 layout_db.dump();
192 }
193
194 let mut ext_registry = ExtensionRegistry::new();
195 register_test_extensions(&mut ext_registry);
196 extensions::register_all(&mut ext_registry);
197
198 let ir_db = IRDb::new(
199 symbol_table,
200 &layout_db,
201 &mut diags,
202 &ext_registry,
203 section_region_names,
204 region_bindings,
205 )
206 .context("[ERR_222]: Error detected, halting.")?;
207
208 debug!("Dumping ir_db");
209 if verbosity > 2 {
210 ir_db.dump();
211 }
212
213 let region_db = RegionDb::build(&ir_db, &mut diags)
214 .ok_or_else(|| anyhow!("[ERR_227]: Error detected, halting."))?;
215
216 let (location_db, argval_db) =
217 LayoutPhase::build(&ir_db, ®ion_db, &ext_registry, &mut diags)
218 .context("[ERR_223]: Error detected, halting.")?;
219 if verbosity > 2 {
220 }
222
223 let fname_str = String::from(output_file.unwrap_or("output.bin").trim_matches(' '));
227 debug!("process: output file name is {}", fname_str);
228
229 let map_db = map_phase::build(&location_db, &ir_db, &fname_str, &mut diags);
230 let final_size = map_db.sections.last().map_or(0, |d| d.file_offset + d.size);
231 if final_size > max_output_size {
232 let msg = format!(
233 "Output image size {} bytes exceeds maximum {} bytes. \
234 Use --max-output-size to increase the limit.",
235 final_size, max_output_size
236 );
237 diags.err0("ERR_224", &msg);
238 return Err(anyhow!("[ERR_224]: Error detected, halting."));
239 }
240
241 ValidationPhase::validate(&argval_db, &ir_db, &mut diags)
242 .context("[ERR_225]: Error detected, halting.")?;
243
244 let mut file = std::fs::OpenOptions::new()
245 .read(true)
246 .write(true)
247 .create(true)
248 .truncate(true)
249 .open(&fname_str)
250 .context(format!("Unable to create output file {}", fname_str))?;
251
252 if ExecPhase::execute(
253 &location_db,
254 &argval_db,
255 &map_db,
256 &ir_db,
257 &mut diags,
258 &mut file,
259 &ext_registry,
260 )
261 .is_err()
262 {
263 return Err(anyhow!("[ERR_223]: Error detected, halting."));
264 }
265
266 if map_csv.is_some() || map_json.is_some() || map_c99.is_some() || map_rs.is_some() {
269 emit_map(map_csv, &format_csv(&map_db))?;
270 emit_map(map_json, &format_json(&map_db))?;
271 emit_map(map_c99, &format_c99(&map_db))?;
272 emit_map(map_rs, &format_rs(&map_db))?;
273 }
274 Ok(())
275}
276
277fn emit_map(dest: Option<&str>, content: &str) -> Result<()> {
280 match dest {
281 None => {}
282 Some("-") => print!("{content}"),
283 Some(path) => {
284 let mut f = File::create(path).context(format!("Unable to create map file {path}"))?;
285 f.write_all(content.as_bytes())
286 .context(format!("Unable to write map file {path}"))?;
287 }
288 }
289 Ok(())
290}
291
292#[cfg(test)]
293mod tests {
294 use super::parse_define;
295 use ir::ParameterValue;
296
297 fn name_val(s: &str) -> (String, ParameterValue) {
298 parse_define(s).expect("parse_define failed")
299 }
300
301 #[test]
304 fn hex_no_suffix_is_u64() {
305 let (n, v) = name_val("BASE=0x1000");
306 assert_eq!(n, "BASE");
307 assert_eq!(v, ParameterValue::U64(0x1000));
308 }
309
310 #[test]
311 fn hex_u_suffix_is_u64() {
312 let (n, v) = name_val("BASE=0x1000u");
313 assert_eq!(n, "BASE");
314 assert_eq!(v, ParameterValue::U64(0x1000));
315 }
316
317 #[test]
318 fn hex_i_suffix_is_i64() {
319 let (n, v) = name_val("OFFSET=0x40i");
320 assert_eq!(n, "OFFSET");
321 assert_eq!(v, ParameterValue::I64(0x40));
322 }
323
324 #[test]
325 fn hex_uppercase_digits() {
326 let (n, v) = name_val("MASK=0xFF");
327 assert_eq!(n, "MASK");
328 assert_eq!(v, ParameterValue::U64(0xFF));
329 }
330
331 #[test]
332 fn hex_large_u64() {
333 let (n, v) = name_val("LIMIT=0xFFFFFFFFu");
335 assert_eq!(n, "LIMIT");
336 assert_eq!(v, ParameterValue::U64(0xFFFF_FFFF));
337 }
338
339 #[test]
340 fn hex_u64_max() {
341 let (n, v) = name_val("TOP=0xFFFFFFFFFFFFFFFFu");
343 assert_eq!(n, "TOP");
344 assert_eq!(v, ParameterValue::U64(u64::MAX));
345 }
346
347 #[test]
348 fn hex_u64_max_no_suffix() {
349 let (n, v) = name_val("TOP=0xFFFFFFFFFFFFFFFF");
351 assert_eq!(n, "TOP");
352 assert_eq!(v, ParameterValue::U64(u64::MAX));
353 }
354
355 #[test]
358 fn decimal_no_suffix_is_integer() {
359 let (n, v) = name_val("COUNT=42");
360 assert_eq!(n, "COUNT");
361 assert_eq!(v, ParameterValue::Integer(42));
362 }
363
364 #[test]
365 fn decimal_u_suffix_is_u64() {
366 let (n, v) = name_val("SIZE=64u");
367 assert_eq!(n, "SIZE");
368 assert_eq!(v, ParameterValue::U64(64));
369 }
370
371 #[test]
372 fn decimal_negative_is_i64() {
373 let (n, v) = name_val("SHIFT=-4");
374 assert_eq!(n, "SHIFT");
375 assert_eq!(v, ParameterValue::I64(-4));
376 }
377
378 #[test]
379 fn bare_name_is_integer_one() {
380 let (n, v) = name_val("FLAG");
381 assert_eq!(n, "FLAG");
382 assert_eq!(v, ParameterValue::Integer(1));
383 }
384
385 #[test]
386 fn empty_name_is_error() {
387 assert!(parse_define("").is_err());
388 assert!(parse_define("=").is_err());
389 assert!(parse_define("=42").is_err());
390 }
391
392 #[test]
393 fn quoted_string_is_parsed() {
394 let (n, v) = name_val("VERSION=\"1.0\"");
395 assert_eq!(n, "VERSION");
396 assert_eq!(v, ParameterValue::QuotedString("1.0".to_string()));
397
398 let (n2, v2) = name_val("LABEL='stable'");
399 assert_eq!(n2, "LABEL");
400 assert_eq!(v2, ParameterValue::QuotedString("stable".to_string()));
401 }
402
403 #[test]
404 fn bare_non_numeric_is_string() {
405 let (n, v) = name_val("PATH=./.pio/build/firmware.elf");
408 assert_eq!(n, "PATH");
409 assert_eq!(v, ParameterValue::QuotedString("./.pio/build/firmware.elf".to_string()));
410
411 let (n2, v2) = name_val(r"PATH=.\.pio\build\firmware.elf");
412 assert_eq!(n2, "PATH");
413 assert_eq!(v2, ParameterValue::QuotedString(r".\.pio\build\firmware.elf".to_string()));
414 }
415}