1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use core::marker::PhantomData;

#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// A location in memory starting at `ADDR` and ending at `ADDR + SIZE`.
/// This is a zero-sized type to encode addresses that are known at compile time.
pub struct MemLoc<const ADDR: usize, const SIZE: usize>;

impl<const ADDR: usize, const SIZE: usize> MemLoc<ADDR, SIZE> {
    /// Creates a new memory location.
    pub const fn new() -> Self {
        Self
    }

    /// Returns the address of the memory location.
    pub const fn addr(&self) -> usize {
        ADDR
    }

    /// Returns the size of the memory location.
    pub const fn size(&self) -> usize {
        SIZE
    }

    /// Returns the range represented by the memory location.
    pub const fn range(&self) -> core::ops::Range<usize> {
        ADDR..ADDR + SIZE
    }
}

/// Trait that combines a memory location and a type.
pub trait MemLocType<const ADDR: usize, const SIZE: usize> {
    /// The type at this memory location.
    type Type;

    /// The memory locations address.
    const ADDR: usize = ADDR;

    /// The memory locations size.
    const SIZE: usize = SIZE;

    /// The memory location.
    const LOC: MemLoc<ADDR, SIZE> = MemLoc::new();

    /// Combine a memory location and a type.
    /// This will only work if this trait is defined for the memory location.
    fn layout(loc: MemLoc<ADDR, SIZE>) -> LayoutType<ADDR, SIZE, Self> {
        LayoutType(loc, PhantomData)
    }
}

/// A memory location combined with a type.
pub struct LayoutType<const ADDR: usize, const SIZE: usize, T>(
    MemLoc<ADDR, SIZE>,
    PhantomData<T>,
)
where
    T: MemLocType<ADDR, SIZE> + ?Sized;

impl<const ADDR: usize, const SIZE: usize, T> LayoutType<ADDR, SIZE, T>
where
    T: MemLocType<ADDR, SIZE>,
{
    /// Create a new layout type.
    pub const fn new() -> Self {
        Self(MemLoc::new(), PhantomData)
    }

    /// The memory location of this type.
    pub const fn loc(&self) -> MemLoc<ADDR, SIZE> {
        self.0
    }
}

/// Trait that defines a memory layout.
pub trait MemLayout {
    /// The associated memory layout type.
    type Type;
    /// A constant instance of the memory layout.
    const LAYOUT: Self::Type;
    /// The length of the memory layout.
    const LEN: usize;
}

#[macro_export]
/// Defines a memory layout for a type.
/// The address starts at 0 and is incremented by the size of each field.
/// The syntax is `field_name: field_type = field_size_in_bytes, ...`.
macro_rules! mem_layout {
    () => {};
    (@accum () -> ($s:ident for $o:ident $($f:ident: $t:ty = $si:expr, $a:expr);*) -> ($($addr:tt)*)) => {
        mem_layout!(@as_expr ($s $o $($f, $si, $t, $a)*) -> ($($addr)*));
    };
    (@accum ($field:ident: $t:ty = $size:expr, $($tail:tt)*) -> ($s:ident for $o:ident $($f:ident: $typ:ty = $si:expr, $a:expr);*) -> ($($addr:tt)*)) => {
        mem_layout!(@accum ($($tail)*) -> ($s for $o $($f: $typ = $si, $a);*; $field: $t = $size, $($addr)*) -> ($($addr)* + $size ));

    };
    (@as_expr ($s:ident $o:ident $($field:ident, $size:expr, $t:ty, $addr:expr)+) -> ($($len:tt)*)) => {
        #[derive(Debug, Default)]
        #[allow(missing_docs)]
        pub struct $s {
            $(pub $field: $crate::MemLoc<{$addr}, $size>,)+
        }
        impl $s {
            #[allow(missing_docs)]
            pub const fn new() -> Self {
                Self {
                    $($field: $crate::MemLoc::new(),)+
                }
            }
            #[allow(missing_docs)]
            pub const LEN: usize = $($len)*;
        }
        impl $crate::MemLayout for $o {
            type Type = $s;
            const LAYOUT: Self::Type = $s::new();
            const LEN: usize = Self::Type::LEN;
        }
        $(
            impl $crate::MemLocType<{ <$o as $crate::MemLayout>::LAYOUT.$field.addr() }, { <$o as $crate::MemLayout>::LAYOUT.$field.size() }> for $o {
                type Type = $t;
            }
        )+

    };
    ($s:ident for $o:ident $field:ident: $t:ty = $size:expr, $($tail:tt)*) => {
        mem_layout!(@accum ($($tail)*,) -> ($s for $o $field: $t = $size, 0) -> (0 + $size));
    };
}