#![doc(hidden)]
pub mod map_phase;
use anyhow::{Context, Result, anyhow};
use parse_int::parse;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use ast::Ast;
use ast::astdb::AstDb;
use diags::Diags;
use exec_phase::ExecPhase;
use firmion_extension::extension_registry::ExtensionRegistry;
use firmion_extension::test_mocks::register_test_extensions;
use ir::{ConstBuiltins, ParameterValue};
use irdb::IRDb;
use layout_phase::LayoutPhase;
use irdb::layoutdb::LayoutDb;
use crate::map_phase::{format_c99, format_csv, format_json, format_rs};
use irdb::regiondb::RegionDb;
use exec_phase::validation_phase::ValidationPhase;
#[allow(unused_imports)]
use tracing::{debug, error, info, trace, warn};
fn parse_define(s: &str) -> Result<(String, ParameterValue)> {
if s.is_empty() {
return Err(anyhow!("Empty name in define '{}'", s));
}
let (name, val_str) = match s.find('=') {
None => return Ok((s.to_string(), ParameterValue::Integer(1))),
Some(pos) => (&s[..pos], &s[pos + 1..]),
};
if name.is_empty() {
return Err(anyhow!("Empty name in define '{}'", s));
}
let value = if val_str.starts_with('"') || val_str.starts_with('\'') {
let inner = val_str.trim_matches(&['"', '\''][..]);
ParameterValue::QuotedString(inner.to_string())
} else if let Some(stripped) = val_str.strip_suffix('u') {
let v =
parse::<u64>(stripped).map_err(|e| anyhow!("Error parsing define '{}': {}", s, e))?;
ParameterValue::U64(v)
} else if let Some(stripped) = val_str.strip_suffix('i') {
let v = parse::<i64>(stripped)
.map_err(|_| anyhow!("Invalid I64 value in define '{}': '{}'", s, stripped))?;
ParameterValue::I64(v)
} else if val_str.starts_with('-') {
let v = parse::<i64>(val_str)
.map_err(|_| anyhow!("Invalid I64 value in define '{}': '{}'", s, val_str))?;
ParameterValue::I64(v)
} else if val_str.starts_with("0x")
|| val_str.starts_with("0X")
|| val_str.starts_with("0b")
|| val_str.starts_with("0B")
{
let v = parse::<u64>(val_str)
.map_err(|_| anyhow!("Invalid U64 value in define '{}': '{}'", s, val_str))?;
ParameterValue::U64(v)
} else if val_str.chars().any(|c| !c.is_ascii_digit()) {
ParameterValue::QuotedString(val_str.to_string())
} else {
let v = parse::<i64>(val_str)
.map_err(|_| anyhow!("Invalid integer value in define '{}': '{}'", s, val_str))?;
ParameterValue::Integer(v)
};
Ok((name.to_string(), value))
}
pub fn list_extensions() -> Vec<String> {
let mut registry = ExtensionRegistry::new();
extensions::register_all(&mut registry);
registry
.sorted_names()
.iter()
.map(|s| s.to_string())
.collect()
}
#[allow(clippy::too_many_arguments)]
pub fn process(
name: &str,
fstr: &str,
output_file: Option<&str>,
verbosity: u64,
noprint: bool,
defines: &[String],
max_output_size: u64,
map_csv: Option<&str>,
map_json: Option<&str>,
map_c99: Option<&str>,
map_rs: Option<&str>,
) -> Result<()> {
info!("Processing {}", name);
ConstBuiltins::init();
let mut diags = Diags::new(name, fstr, verbosity, noprint);
let mut const_defines: HashMap<String, ParameterValue> = HashMap::new();
for d in defines {
let (n, v) = parse_define(d)?;
const_defines.insert(n, v);
}
let ast = Ast::new(name, fstr, &mut diags).context("[ERR_218]: Error detected, halting.")?;
if verbosity > 2 {
ast.dump("ast.dot")?;
}
let ast_db = AstDb::new(&mut diags, &ast, false)?;
let (mut symbol_table, pruned_ast) = const_eval::evaluate_and_prune(&mut diags, &ast, &ast_db, &const_defines)
.context("[ERR_219]: Error detected, halting.")?;
let pruned_ast_db =
AstDb::new(&mut diags, &pruned_ast, true).context("[ERR_220]: Error detected, halting.")?;
let Some(region_bindings) =
const_eval::evaluate_regions(&mut diags, &pruned_ast, &pruned_ast_db, &mut symbol_table)
else {
return Err(anyhow!("[ERR_226]: Error detected, halting."));
};
let Some(obj_props) =
const_eval::evaluate_obj_props(&mut diags, &pruned_ast, &pruned_ast_db, &mut symbol_table)
else {
return Err(anyhow!("[ERR_232]: Error detected, halting."));
};
let mut section_region_names: HashMap<String, String> = HashMap::new();
for (sec_name, section) in &pruned_ast_db.sections {
if let Some(region_name) = §ion.region {
section_region_names.insert(sec_name.to_string(), region_name.clone());
}
}
let layout_db = LayoutDb::new(&mut diags, &pruned_ast, &pruned_ast_db, &symbol_table, obj_props)
.context("[ERR_221]: Error detected, halting.")?;
if verbosity > 2 {
layout_db.dump();
}
let mut ext_registry = ExtensionRegistry::new();
register_test_extensions(&mut ext_registry);
extensions::register_all(&mut ext_registry);
let ir_db = IRDb::new(
symbol_table,
&layout_db,
&mut diags,
&ext_registry,
section_region_names,
region_bindings,
)
.context("[ERR_222]: Error detected, halting.")?;
debug!("Dumping ir_db");
if verbosity > 2 {
ir_db.dump();
}
let region_db = RegionDb::build(&ir_db, &mut diags)
.ok_or_else(|| anyhow!("[ERR_227]: Error detected, halting."))?;
let (location_db, argval_db) =
LayoutPhase::build(&ir_db, ®ion_db, &ext_registry, &mut diags)
.context("[ERR_223]: Error detected, halting.")?;
if verbosity > 2 {
}
let fname_str = String::from(output_file.unwrap_or("output.bin").trim_matches(' '));
debug!("process: output file name is {}", fname_str);
let map_db = map_phase::build(&location_db, &ir_db, &fname_str, &mut diags);
let final_size = map_db.sections.last().map_or(0, |d| d.file_offset + d.size);
if final_size > max_output_size {
let msg = format!(
"Output image size {} bytes exceeds maximum {} bytes. \
Use --max-output-size to increase the limit.",
final_size, max_output_size
);
diags.err0("ERR_224", &msg);
return Err(anyhow!("[ERR_224]: Error detected, halting."));
}
ValidationPhase::validate(&argval_db, &ir_db, &mut diags)
.context("[ERR_225]: Error detected, halting.")?;
let mut file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&fname_str)
.context(format!("Unable to create output file {}", fname_str))?;
if ExecPhase::execute(
&location_db,
&argval_db,
&map_db,
&ir_db,
&mut diags,
&mut file,
&ext_registry,
)
.is_err()
{
return Err(anyhow!("[ERR_223]: Error detected, halting."));
}
if map_csv.is_some() || map_json.is_some() || map_c99.is_some() || map_rs.is_some() {
emit_map(map_csv, &format_csv(&map_db))?;
emit_map(map_json, &format_json(&map_db))?;
emit_map(map_c99, &format_c99(&map_db))?;
emit_map(map_rs, &format_rs(&map_db))?;
}
Ok(())
}
fn emit_map(dest: Option<&str>, content: &str) -> Result<()> {
match dest {
None => {}
Some("-") => print!("{content}"),
Some(path) => {
let mut f = File::create(path).context(format!("Unable to create map file {path}"))?;
f.write_all(content.as_bytes())
.context(format!("Unable to write map file {path}"))?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::parse_define;
use ir::ParameterValue;
fn name_val(s: &str) -> (String, ParameterValue) {
parse_define(s).expect("parse_define failed")
}
#[test]
fn hex_no_suffix_is_u64() {
let (n, v) = name_val("BASE=0x1000");
assert_eq!(n, "BASE");
assert_eq!(v, ParameterValue::U64(0x1000));
}
#[test]
fn hex_u_suffix_is_u64() {
let (n, v) = name_val("BASE=0x1000u");
assert_eq!(n, "BASE");
assert_eq!(v, ParameterValue::U64(0x1000));
}
#[test]
fn hex_i_suffix_is_i64() {
let (n, v) = name_val("OFFSET=0x40i");
assert_eq!(n, "OFFSET");
assert_eq!(v, ParameterValue::I64(0x40));
}
#[test]
fn hex_uppercase_digits() {
let (n, v) = name_val("MASK=0xFF");
assert_eq!(n, "MASK");
assert_eq!(v, ParameterValue::U64(0xFF));
}
#[test]
fn hex_large_u64() {
let (n, v) = name_val("LIMIT=0xFFFFFFFFu");
assert_eq!(n, "LIMIT");
assert_eq!(v, ParameterValue::U64(0xFFFF_FFFF));
}
#[test]
fn hex_u64_max() {
let (n, v) = name_val("TOP=0xFFFFFFFFFFFFFFFFu");
assert_eq!(n, "TOP");
assert_eq!(v, ParameterValue::U64(u64::MAX));
}
#[test]
fn hex_u64_max_no_suffix() {
let (n, v) = name_val("TOP=0xFFFFFFFFFFFFFFFF");
assert_eq!(n, "TOP");
assert_eq!(v, ParameterValue::U64(u64::MAX));
}
#[test]
fn decimal_no_suffix_is_integer() {
let (n, v) = name_val("COUNT=42");
assert_eq!(n, "COUNT");
assert_eq!(v, ParameterValue::Integer(42));
}
#[test]
fn decimal_u_suffix_is_u64() {
let (n, v) = name_val("SIZE=64u");
assert_eq!(n, "SIZE");
assert_eq!(v, ParameterValue::U64(64));
}
#[test]
fn decimal_negative_is_i64() {
let (n, v) = name_val("SHIFT=-4");
assert_eq!(n, "SHIFT");
assert_eq!(v, ParameterValue::I64(-4));
}
#[test]
fn bare_name_is_integer_one() {
let (n, v) = name_val("FLAG");
assert_eq!(n, "FLAG");
assert_eq!(v, ParameterValue::Integer(1));
}
#[test]
fn empty_name_is_error() {
assert!(parse_define("").is_err());
assert!(parse_define("=").is_err());
assert!(parse_define("=42").is_err());
}
#[test]
fn quoted_string_is_parsed() {
let (n, v) = name_val("VERSION=\"1.0\"");
assert_eq!(n, "VERSION");
assert_eq!(v, ParameterValue::QuotedString("1.0".to_string()));
let (n2, v2) = name_val("LABEL='stable'");
assert_eq!(n2, "LABEL");
assert_eq!(v2, ParameterValue::QuotedString("stable".to_string()));
}
#[test]
fn bare_non_numeric_is_string() {
let (n, v) = name_val("PATH=./.pio/build/firmware.elf");
assert_eq!(n, "PATH");
assert_eq!(v, ParameterValue::QuotedString("./.pio/build/firmware.elf".to_string()));
let (n2, v2) = name_val(r"PATH=.\.pio\build\firmware.elf");
assert_eq!(n2, "PATH");
assert_eq!(v2, ParameterValue::QuotedString(r".\.pio\build\firmware.elf".to_string()));
}
}