use std::fs;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub name: String,
pub origin: u64,
pub length: u64,
}
#[derive(Debug, Clone)]
pub struct MemoryConfig {
pub regions: Vec<MemoryRegion>,
}
impl MemoryConfig {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
let content = fs::read_to_string(path.as_ref())
.map_err(|e| format!("Failed to read {}: {}", path.as_ref().display(), e))?;
Self::parse(&content)
}
pub fn parse(content: &str) -> Result<Self, String> {
let mut regions = Vec::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty()
|| line.starts_with("/*")
|| line.starts_with('*')
|| line.starts_with("//")
|| line.starts_with('#')
{
continue;
}
if line.starts_with("MEMORY") || line == "{" || line == "}" {
continue;
}
if let Some(region) = parse_region_line(line) {
regions.push(region);
}
}
if regions.is_empty() {
return Err("No memory regions found in memory.x".to_string());
}
Ok(MemoryConfig { regions })
}
pub fn find(&self, name: &str) -> Option<&MemoryRegion> {
let name_lower = name.to_lowercase();
self.regions.iter().find(|r| r.name.to_lowercase() == name_lower)
}
pub fn flash(&self) -> Option<&MemoryRegion> {
self.find("FLASH")
}
pub fn ram(&self) -> Option<&MemoryRegion> {
self.find("RAM")
}
}
fn parse_region_line(line: &str) -> Option<MemoryRegion> {
let colon_pos = line.find(':')?;
let name_part = line[..colon_pos].trim();
let name = if let Some(paren_pos) = name_part.find('(') {
name_part[..paren_pos].trim().to_string()
} else {
name_part.to_string()
};
let rest = &line[colon_pos + 1..];
let origin = parse_origin(rest)?;
let length = parse_length(rest)?;
Some(MemoryRegion { name, origin, length })
}
fn parse_origin(s: &str) -> Option<u64> {
let idx = s.find("ORIGIN")?;
let after = s[idx + "ORIGIN".len()..].trim();
let after = after.trim_start_matches('=').trim();
parse_number(after.split(|c: char| c == ',').next()?.trim())
}
fn parse_length(s: &str) -> Option<u64> {
let idx = s.find("LENGTH")?;
let after = s[idx + "LENGTH".len()..].trim();
let after = after.trim_start_matches('=').trim();
let val = after.split(|c: char| c == '}').next()?.trim();
let val = val.trim_end_matches(',').trim();
parse_size(val)
}
fn parse_number(s: &str) -> Option<u64> {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
u64::from_str_radix(&s[2..], 16).ok()
} else if s.starts_with("0o") || s.starts_with("0O") {
u64::from_str_radix(&s[2..], 8).ok()
} else if s.starts_with("0b") || s.starts_with("0B") {
u64::from_str_radix(&s[2..], 2).ok()
} else {
s.parse::<u64>().ok()
}
}
pub fn parse_size(s: &str) -> Option<u64> {
let s = s.trim().to_uppercase();
if s.ends_with('K') {
let num: f64 = s[..s.len() - 1].trim().parse().ok()?;
Some((num * 1024.0) as u64)
} else if s.ends_with('M') {
let num: f64 = s[..s.len() - 1].trim().parse().ok()?;
Some((num * 1024.0 * 1024.0) as u64)
} else {
s.parse::<u64>().ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_basic() {
let content = r#"
/* Linker script for the STM32F103C8T6 */
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
"#;
let config = MemoryConfig::parse(content).unwrap();
assert_eq!(config.regions.len(), 2);
let flash = config.flash().unwrap();
assert_eq!(flash.origin, 0x08000000);
assert_eq!(flash.length, 64 * 1024);
let ram = config.ram().unwrap();
assert_eq!(ram.origin, 0x20000000);
assert_eq!(ram.length, 20 * 1024);
}
#[test]
fn test_parse_with_flags() {
let content = r#"
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
"#;
let config = MemoryConfig::parse(content).unwrap();
let flash = config.flash().unwrap();
assert_eq!(flash.length, 128 * 1024);
}
#[test]
fn test_parse_size() {
assert_eq!(parse_size("64K"), Some(64 * 1024));
assert_eq!(parse_size("20K"), Some(20 * 1024));
assert_eq!(parse_size("1M"), Some(1024 * 1024));
assert_eq!(parse_size("512"), Some(512));
assert_eq!(parse_size("0.5K"), Some(512));
}
}