1use std::str::FromStr;
5
6use pest::{iterators::Pair, Parser};
7use pest_derive::Parser;
8use thiserror::Error;
9
10#[derive(Error, Debug)]
11pub enum Error {
12 #[error("syntax error")]
13 ParseError(#[from] pest::error::Error<Rule>),
14 #[error("we didn't successfully parse a top level syntax object")]
15 ParseFailure,
16 #[error("numeric format error")]
17 NumericFormatError(#[from] std::num::ParseIntError),
18}
19
20pub enum ContentType {
22 Hex,
24 Binary,
26}
27
28#[derive(Parser)]
29#[grammar = "grammar.pest"]
30struct ReadmemParser;
31
32#[derive(Debug)]
33enum Item<I> {
34 Address(usize),
35 Number(I),
36}
37
38#[doc(hidden)]
41pub trait Integral: Sized {
42 fn from_str_radix(src: &str, radix: u32) -> Result<Self, std::num::ParseIntError>;
43 fn zero() -> Self;
44}
45
46macro_rules! integrate {
47 ($t:ty) => {
48 impl Integral for $t {
49 fn from_str_radix(src: &str, radix: u32) -> Result<Self, std::num::ParseIntError> {
50 <$t>::from_str_radix(src, radix)
51 }
52 fn zero() -> Self {
53 0
54 }
55 }
56 };
57}
58
59integrate!(u8);
60integrate!(u16);
61integrate!(u32);
62integrate!(u64);
63integrate!(u128);
64
65fn parse_value<I>(pair: Pair<Rule>) -> Result<Item<I>, Error>
67where
68 I: Integral,
69{
70 let is_zx = |c| c == 'x' || c == 'z' || c == 'X' || c == 'Z';
71 Ok(match pair.as_rule() {
72 Rule::addr => Item::Address(usize::from_str_radix(&pair.as_str()[1..], 16)?),
73 Rule::hexnum => {
74 let without_underscore = pair.as_str().replace("_", "");
75 let without_zx = without_underscore.replace(is_zx, "0");
76 Item::Number(Integral::from_str_radix(&without_zx, 16)?)
77 }
78 Rule::binnum => {
79 let without_underscore = pair.as_str().replace("_", "");
80 let without_zx = without_underscore.replace(is_zx, "0");
81 Item::Number(Integral::from_str_radix(&without_zx, 2)?)
82 }
83 r => unreachable!(
84 "should not hit this rule {:?}, all our other rules are silent",
85 r
86 ),
87 })
88}
89
90pub fn readmem<I>(content: &str, content_type: ContentType) -> Result<Vec<I>, Error>
110where
111 I: Integral + FromStr + Clone,
112{
113 let rule = match content_type {
114 ContentType::Hex => Rule::readmemh,
115 ContentType::Binary => Rule::readmemb,
116 };
117 let content = ReadmemParser::parse(rule, content)?;
118
119 let mut result = Vec::new();
120 let mut pos = 0;
121 for val in content {
122 if let Rule::EOI = val.as_rule() {
123 continue;
124 }
125 let val = parse_value::<I>(val)?;
126 match val {
127 Item::Address(a) => pos = a,
128 Item::Number(n) => {
129 if pos + 1 >= result.len() {
130 result.resize(pos + 1, I::zero());
131 }
132 result[pos] = n;
133 pos += 1;
134 }
135 }
136 }
137 Ok(result)
138}
139
140#[cfg(doctest)]
141mod doctest {
142 use doc_comment::doctest;
143 doctest!("../README.md");
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 #[test]
150 fn test_hex() {
151 let hex = include_str!("testdata/hex.txt");
152 let arr = readmem::<u8>(hex, ContentType::Hex).unwrap();
153 let expected = 0..255u8;
154 assert_eq!(256, arr.len());
155 for (&item, exp) in arr.iter().zip(expected) {
156 assert_eq!(item, exp);
157 }
158 }
159
160 #[test]
161 fn test_bin() {
162 let bin = include_str!("testdata/bin.txt");
163 let arr = readmem::<u16>(bin, ContentType::Binary).unwrap();
164 assert_eq!(257, arr.len());
165 let expected = 0..256u16;
166 for (&item, exp) in arr.iter().zip(expected) {
167 assert_eq!(item, exp);
168 }
169 }
170
171 #[test]
172 fn test_addressing() {
173 let testdata = r#"
176 f
177 @4
178 1 2 3
179 @4
180 4 5 6
181 @8
182 e
183 "#;
184 let expected = vec![0xf, 0, 0, 0, 4, 5, 6, 0, 0xe];
185 assert_eq!(
186 expected,
187 readmem::<u32>(testdata, ContentType::Hex).unwrap()
188 );
189 }
190}