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