svmap 0.3.0

A library to parse SVMap, used to map memory for emulators.
Documentation
use std::ops::RangeInclusive;
use crate::AddressType;

/// Describes a region in memory.
///
/// The `MemoryRegion.upper_bound` is **inclusive**.
///
/// Biggest possible memory address: [`AddressType::MAX`](AddressType).
#[derive(PartialEq, Eq)]
#[derive(Debug)]
pub struct MemoryRegion<D: Clone> {
    lower_bound: AddressType,
    upper_bound: AddressType,
    description: D,
}

impl<D: Clone> MemoryRegion<D> {
    pub(crate) fn new(lower_bound: AddressType, upper_bound: AddressType, description: D) -> Result<Self, String> {
        if lower_bound >= upper_bound {
            return Err(format!("lower bound ({}) is bigger than or equal to upper bound ({})", lower_bound, upper_bound));
        }
        Ok(Self {
            lower_bound,
            upper_bound,
            description,
        })
    }

    /// Returns `true` if a address is inside of the `MemoryRegion`.
    pub fn address_in_region(&self, address: AddressType) -> bool {
        self.lower_bound <= address && address <= self.upper_bound
    }

    /// Returns `true` if a address comes after the upper bound of the `MemoryRegion`.
    pub fn address_comes_after(&self, address: AddressType) -> bool {
        self.upper_bound < address
    }

    /// Returns `true` if the given `MemoryRegion` overlaps with another `MemoryRegion`.
    pub fn overlaps(&self, other: &MemoryRegion<D>) -> bool {
        self.upper_bound >= other.lower_bound && self.lower_bound <= other.upper_bound
    }

    /// Returns `true` if the given `MemoryRegion`s lower bound is bigger than the other
    /// `MemoryRegion`s upper bound.
    pub fn comes_after(&self, other: &MemoryRegion<D>) -> bool {
        self.lower_bound > other.upper_bound
    }

    /// Returns `true` if the `MemoryRegion` starts at the beginning of memory.
    pub fn starts_at_zero(&self) -> bool {
        self.lower_bound == 0
    }

    /// Returns `true` if the given `MemoryRegion` is **directly** next to another `MemoryRegion`.
    pub fn is_adjacent_to(&self, other: &MemoryRegion<D>) -> bool {
        self.upper_bound.wrapping_add(1) == other.lower_bound || self.lower_bound == other.upper_bound.wrapping_add(1)
    }

    pub(crate) fn region_between_zero_and_self(&self, description: &D) -> Result<Option<MemoryRegion<D>>, String> {
        Ok(
            if self.starts_at_zero() {
                None
            } else {
                Some(Self::new(0, self.lower_bound.wrapping_sub(1), description.clone())?)
            }
        )
    }

    pub(crate) fn region_between(&self, other: &MemoryRegion<D>, description: &D) -> Result<Option<MemoryRegion<D>>, String> {
        Ok(
            if self.is_adjacent_to(other) {
                None
            } else {
                Some(Self::new(self.upper_bound, other.lower_bound.wrapping_sub(1), description.clone())?)
            }
        )
    }

    /// Lower bound of the `MemoryRegion`.
    pub fn lower_bound(&self) -> AddressType {
        self.lower_bound
    }

    /// Upper bound of the `MemoryRegion`.
    pub fn upper_bound(&self) -> AddressType {
        self.upper_bound
    }

    /// Description of the `MemoryRegion`.
    pub fn description(&self) -> D {
        self.description.clone()
    }

    /// Returns the `MemoryRegion` as a range.
    pub fn to_range(&self) -> RangeInclusive<AddressType> {
        self.lower_bound..=self.upper_bound
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const MR_1: MemoryRegion<&str> = MemoryRegion { lower_bound: 0, upper_bound: 255, description: "test" };
    const MR_2: MemoryRegion<&str> = MemoryRegion { lower_bound: 16, upper_bound: 31, description: "test" };
    const MR_3: MemoryRegion<&str> = MemoryRegion { lower_bound: 16, upper_bound: 32, description: "test" };
    const MR_4: MemoryRegion<&str> = MemoryRegion { lower_bound: 32, upper_bound: 64, description: "test" };

    #[test]
    fn memory_region_construction() {
        assert_eq!(
            MemoryRegion::new(0, 6, "test"),
            Ok(MemoryRegion {
                lower_bound: 0,
                upper_bound: 6,
                description: "test"
            })
        );

        assert!(MemoryRegion::new(6, 0, "test").is_err());
    }

    #[test]
    fn memory_region_overlap() {
        assert!(MR_1.overlaps(&MR_2));
        assert!(MR_1.overlaps(&MR_4));
        assert!(!MR_2.overlaps(&MR_4));
        assert!(MR_3.overlaps(&MR_4));

        // Reversed
        assert!(MR_2.overlaps(&MR_1));
        assert!(MR_4.overlaps(&MR_1));
        assert!(!MR_4.overlaps(&MR_2));
        assert!(MR_4.overlaps(&MR_3));

        assert!(MR_1.overlaps(&MR_1));
    }

    #[test]
    fn memory_region_adjacency() {
        assert!(!MR_1.is_adjacent_to(&MR_2));
        assert!(MR_2.is_adjacent_to(&MR_4));
        assert!(!MR_3.is_adjacent_to(&MR_4));
    }
}