rigless 0.0.2

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model, and unit of virtual time in Rust; needs the rigless-sidecar package from rigless-labs/qemu
Documentation
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: Apache-2.0
//! What must hold before anything is written: no overlaps, and every name a distinct Rust identifier.

use std::collections::BTreeMap;

use crate::svd::Error;
use crate::svd::emit::{module_name, static_name, variant_name};
use crate::svd::model::{Model, Register};

const RESERVED_STATICS: [&str; 2] = ["CPU", "PERIPHERALS"];

pub(crate) fn check(model: &Model) -> Result<(), Error> {
    for pair in model.instances.windows(2) {
        let (lower, upper) = (&pair[0], &pair[1]);
        if upper.base < lower.base.saturating_add(lower.size) {
            return Err(Error::OverlappingInstances {
                lower: lower.name.clone(),
                upper: upper.name.clone(),
            });
        }
    }
    for (layout, registers) in &model.layouts {
        if let Some((lower, upper)) = first_undeclared_overlap(registers) {
            return Err(Error::OverlappingRegisters {
                lower: format!("{layout}.{}", lower.name),
                upper: format!("{layout}.{}", upper.name),
            });
        }
    }
    distinct(
        model
            .layouts
            .keys()
            .map(|layout| (module_name(layout), layout.as_str())),
    )?;
    distinct(
        RESERVED_STATICS
            .into_iter()
            .map(|reserved| (reserved.to_owned(), reserved))
            .chain(
                model
                    .instances
                    .iter()
                    .map(|instance| (static_name(&instance.name), instance.name.as_str())),
            ),
    )?;
    for registers in model.layouts.values() {
        distinct(
            registers
                .iter()
                .map(|register| (variant_name(&register.name), register.name.as_str())),
        )?;
        distinct(
            registers
                .iter()
                .map(|register| (module_name(&register.name), register.name.as_str())),
        )?;
        for register in registers {
            distinct(
                register
                    .fields
                    .iter()
                    .map(|field| (static_name(&field.name), field.name.as_str())),
            )?;
        }
    }
    Ok(())
}

/// Two registers that share bytes without either being declared an alternate, in a table sorted by offset.
fn first_undeclared_overlap(registers: &[Register]) -> Option<(&Register, &Register)> {
    let mut furthest: Option<&Register> = None;
    for register in registers.iter().filter(|register| !register.alternate) {
        if let Some(earlier) = furthest.filter(|earlier| register.offset < earlier.end()) {
            return Some((earlier, register));
        }
        if furthest.is_none_or(|earlier| register.end() > earlier.end()) {
            furthest = Some(register);
        }
    }
    None
}

/// Fails when two names in a scope fold to one identifier.
fn distinct<'a>(names: impl Iterator<Item = (String, &'a str)>) -> Result<(), Error> {
    let mut seen: BTreeMap<String, &str> = BTreeMap::new();
    for (identifier, name) in names {
        if let Some(first) = seen.insert(identifier, name) {
            return Err(Error::NameCollision {
                first: first.to_owned(),
                second: name.to_owned(),
            });
        }
    }
    Ok(())
}