1use crate::error::DecodeError;
10use crate::profile::{Encoding, Profile};
11
12pub fn parse_profiles_json(input: &str) -> Result<Vec<Profile>, DecodeError> {
18 let mut p = Parser {
19 bytes: input.as_bytes(),
20 pos: 0,
21 };
22 p.skip_ws();
23 p.parse_array()
24}
25
26struct Parser<'a> {
31 bytes: &'a [u8],
32 pos: usize,
33}
34
35impl Parser<'_> {
36 fn peek(&self) -> Option<u8> {
39 self.bytes.get(self.pos).copied()
40 }
41
42 fn advance(&mut self) -> Result<u8, DecodeError> {
43 let b = self
44 .bytes
45 .get(self.pos)
46 .copied()
47 .ok_or_else(|| DecodeError::Profile("unexpected end of input".into()))?;
48 self.pos += 1;
49 Ok(b)
50 }
51
52 fn expect(&mut self, want: u8) -> Result<(), DecodeError> {
53 let got = self.advance()?;
54 if got != want {
55 return Err(DecodeError::Profile(format!(
56 "expected '{}' (0x{:02X}), got '{}' (0x{:02X}) at offset {}",
57 want as char,
58 want,
59 got as char,
60 got,
61 self.pos - 1,
62 )));
63 }
64 Ok(())
65 }
66
67 fn skip_ws(&mut self) {
68 while let Some(b) = self.peek() {
69 if b != b' ' && b != b'\t' && b != b'\n' && b != b'\r' {
70 break;
71 }
72 self.pos += 1;
73 }
74 }
75
76 fn parse_string(&mut self) -> Result<String, DecodeError> {
79 self.expect(b'"')?;
80 let mut s = String::new();
81 loop {
82 let b = self.advance()?;
83 match b {
84 b'"' => return Ok(s),
85 b'\\' => {
86 let esc = self.advance()?;
87 match esc {
88 b'"' => s.push('"'),
89 b'\\' => s.push('\\'),
90 b'/' => s.push('/'),
91 b'b' => s.push('\u{0008}'),
92 b'f' => s.push('\u{000C}'),
93 b'n' => s.push('\n'),
94 b'r' => s.push('\r'),
95 b't' => s.push('\t'),
96 b'u' => {
97 let code = self.parse_hex4()?;
98 let c = char::from_u32(code).ok_or_else(|| {
99 DecodeError::Profile(format!("invalid unicode escape: \\u{code:04X}"))
100 })?;
101 s.push(c);
102 }
103 _ => {
104 return Err(DecodeError::Profile(format!(
105 "invalid escape sequence: \\{}",
106 esc as char
107 )));
108 }
109 }
110 }
111 0x20..=0x7E | 0x80..=0xFF => s.push(b as char),
113 _ => {
114 return Err(DecodeError::Profile(format!("invalid character in string: 0x{b:02X}")));
115 }
116 }
117 }
118 }
119
120 fn parse_hex4(&mut self) -> Result<u32, DecodeError> {
121 let mut val: u32 = 0;
122 for _ in 0..4 {
123 let b = self.advance()?;
124 val <<= 4;
125 val += match b {
126 b'0'..=b'9' => u32::from(b - b'0'),
127 b'a'..=b'f' => u32::from(b - b'a' + 10),
128 b'A'..=b'F' => u32::from(b - b'A' + 10),
129 _ => {
130 return Err(DecodeError::Profile(format!("invalid hex digit: '{}'", b as char)));
131 }
132 };
133 }
134 Ok(val)
135 }
136
137 fn parse_number_i32(&mut self) -> Result<i32, DecodeError> {
138 self.skip_ws();
139 let start = self.pos;
140 if self.pos >= self.bytes.len() {
141 return Err(DecodeError::Profile(format!("expected number at offset {}", self.pos)));
142 }
143 if self.peek() == Some(b'-') {
144 self.pos += 1;
145 }
146 if self.peek().is_none_or(|b| !b.is_ascii_digit()) {
147 return Err(DecodeError::Profile(format!("expected number at offset {start}")));
148 }
149 while self.peek().is_some_and(|b| b.is_ascii_digit()) {
150 self.pos += 1;
151 }
152 let s = std::str::from_utf8(&self.bytes[start..self.pos])
153 .map_err(|e| DecodeError::Profile(format!("invalid number encoding: {e}")))?;
154 s.parse::<i32>()
155 .map_err(|e| DecodeError::Profile(format!("invalid number '{s}': {e}")))
156 }
157
158 fn parse_bool(&mut self) -> Result<bool, DecodeError> {
159 self.skip_ws();
160 if self.pos >= self.bytes.len() {
161 return Err(DecodeError::Profile(format!("expected bool at offset {}", self.pos)));
162 }
163 if self.bytes[self.pos..].starts_with(b"true") {
164 self.pos += 4;
165 Ok(true)
166 } else if self.bytes[self.pos..].starts_with(b"false") {
167 self.pos += 5;
168 Ok(false)
169 } else {
170 Err(DecodeError::Profile(format!("expected bool at offset {}", self.pos)))
171 }
172 }
173
174 fn skip_value(&mut self) -> Result<(), DecodeError> {
175 self.skip_ws();
176 match self.peek() {
177 Some(b'"') => {
178 self.parse_string()?;
179 }
180 Some(b't' | b'f') => {
181 self.parse_bool()?;
182 }
183 Some(b'n') => {
184 self.parse_null()?;
185 }
186 Some(b'[') => {
187 self.skip_array()?;
188 }
189 Some(b'{') => {
190 self.skip_object()?;
191 }
192 Some(b'-' | b'0'..=b'9') => {
193 self.parse_number_i32()?;
194 }
195 Some(c) => {
196 return Err(DecodeError::Profile(format!(
197 "unexpected character '{c}' at offset {}",
198 self.pos
199 )));
200 }
201 None => {
202 return Err(DecodeError::Profile("unexpected end of input".into()));
203 }
204 }
205 Ok(())
206 }
207
208 fn parse_null(&mut self) -> Result<(), DecodeError> {
209 self.skip_ws();
210 if self.bytes[self.pos..].starts_with(b"null") {
211 self.pos += 4;
212 Ok(())
213 } else {
214 Err(DecodeError::Profile(format!("expected null at offset {}", self.pos)))
215 }
216 }
217
218 fn skip_array(&mut self) -> Result<(), DecodeError> {
219 self.expect(b'[')?;
220 loop {
221 self.skip_ws();
222 if self.peek() == Some(b']') {
223 self.pos += 1;
224 return Ok(());
225 }
226 if self.peek() != Some(b'[') {
227 self.skip_value()?;
228 }
229 self.skip_ws();
230 if self.peek() == Some(b',') {
231 self.pos += 1;
232 }
233 }
234 }
235
236 fn skip_object(&mut self) -> Result<(), DecodeError> {
237 self.expect(b'{')?;
238 loop {
239 self.skip_ws();
240 if self.peek() == Some(b'}') {
241 self.pos += 1;
242 return Ok(());
243 }
244 self.parse_string()?;
245 self.skip_ws();
246 self.expect(b':')?;
247 self.skip_value()?;
248 self.skip_ws();
249 if self.peek() == Some(b',') {
250 self.pos += 1;
251 }
252 }
253 }
254
255 fn parse_array(&mut self) -> Result<Vec<Profile>, DecodeError> {
258 self.expect(b'[')?;
259 let mut profiles: Vec<Profile> = Vec::new();
260 loop {
261 self.skip_ws();
262 if self.peek() == Some(b']') {
263 self.pos += 1;
264 return Ok(profiles);
265 }
266 if !profiles.is_empty() {
267 self.expect(b',')?;
268 self.skip_ws();
269 }
270 profiles.push(self.parse_object()?);
271 }
272 }
273
274 fn parse_object(&mut self) -> Result<Profile, DecodeError> {
275 self.expect(b'{')?;
276 let mut profile = Profile::default();
277 loop {
278 self.skip_ws();
279 if self.peek() == Some(b'}') {
280 self.pos += 1;
281 return Ok(profile);
282 }
283 if self.peek() != Some(b'"') {
284 self.skip_value()?;
286 continue;
287 }
288 let key = self.parse_string()?;
289 self.skip_ws();
290 self.expect(b':')?;
291 self.set_field(&key, &mut profile)?;
292 self.skip_ws();
293 if self.peek() == Some(b',') {
294 self.pos += 1;
295 }
296 }
297 }
298
299 fn set_field(&mut self, key: &str, p: &mut Profile) -> Result<(), DecodeError> {
300 match key {
301 "prefix" => p.prefix = self.parse_number_i32()?,
302 "width" => p.width = self.parse_number_i32()?,
303 "height" => p.height = self.parse_number_i32()?,
304 "encoding" => p.encoding = self.parse_encoding()?,
305 "frame_byte_length" => p.frame_byte_length = self.parse_number_i32()?,
306 "swaps_dimensions" => p.swaps_dimensions = self.parse_bool()?,
307 "little_endian" => p.little_endian = self.parse_bool()?,
308 "is_padded" => p.is_padded = self.parse_bool()?,
309 "is_interlaced" => p.is_interlaced = self.parse_bool()?,
310 "clcl_chroma" => p.clcl_chroma = self.parse_bool()?,
311 "swap_chroma_planes" => p.swap_chroma_planes = self.parse_bool()?,
312 "cl_chroma" => p.cl_chroma = self.parse_bool()?,
313 "swap_rgb_channels" => p.swap_rgb_channels = self.parse_bool()?,
314 "rotation" => p.rotation = self.parse_number_i32()?,
315 "crop_x" => p.crop_x = self.parse_number_i32()?,
316 "crop_y" => p.crop_y = self.parse_number_i32()?,
317 "crop_width" => p.crop_width = self.parse_number_i32()?,
318 "crop_height" => p.crop_height = self.parse_number_i32()?,
319 "slot_size" => p.slot_size = self.parse_number_i32()?,
320 "use_mhni_dimensions" => p.use_mhni_dimensions = self.parse_bool()?,
321 "fallback_encodings" => {
322 if self.peek() == Some(b'n') {
323 self.parse_null()?;
324 } else {
325 p.fallback_encodings = Some(self.parse_encoding_array()?);
326 }
327 }
328 _ => self.skip_value()?,
331 }
332 Ok(())
333 }
334
335 fn parse_encoding(&mut self) -> Result<Encoding, DecodeError> {
336 let s = self.parse_string()?;
337 match s.to_lowercase().as_str() {
338 "rgb565" => Ok(Encoding::Rgb565),
339 "rgb555" => Ok(Encoding::Rgb555),
340 "reorderedrgb555" => Ok(Encoding::ReorderedRgb555),
341 "yuv422" => Ok(Encoding::Yuv422),
342 "ycbcr420" => Ok(Encoding::Ycbcr420),
343 "jpeg" => Ok(Encoding::Jpeg),
344 _ => Err(DecodeError::Profile(format!("unknown encoding: '{s}'"))),
345 }
346 }
347
348 fn parse_encoding_array(&mut self) -> Result<Vec<Encoding>, DecodeError> {
349 self.expect(b'[')?;
350 let mut encodings: Vec<Encoding> = Vec::new();
351 loop {
352 self.skip_ws();
353 if self.peek() == Some(b']') {
354 self.pos += 1;
355 return Ok(encodings);
356 }
357 if !encodings.is_empty() {
358 self.expect(b',')?;
359 self.skip_ws();
360 }
361 encodings.push(self.parse_encoding()?);
362 }
363 }
364}
365
366#[cfg(test)]
371#[allow(clippy::unwrap_used)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn parse_full_profiles_json() {
377 let json = r#"[
378 {"prefix":1007,"width":480,"height":864,"encoding":"Rgb565","frame_byte_length":829440},
379 {"prefix":1019,"width":720,"height":480,"encoding":"Yuv422","frame_byte_length":691200,"is_interlaced":true}
380 ]"#;
381 let profiles = parse_profiles_json(json).unwrap();
382 assert_eq!(profiles.len(), 2);
383 assert_eq!(profiles[0].prefix, 1007);
384 assert_eq!(profiles[0].width, 480);
385 assert_eq!(profiles[0].height, 864);
386 assert_eq!(profiles[0].encoding, Encoding::Rgb565);
387 assert_eq!(profiles[0].frame_byte_length, 829_440);
388 assert!(!profiles[0].is_interlaced);
389
390 assert_eq!(profiles[1].prefix, 1019);
391 assert_eq!(profiles[1].encoding, Encoding::Yuv422);
392 assert!(profiles[1].is_interlaced);
393 }
394
395 #[test]
396 fn parse_with_fallback_encodings() {
397 let json = r#"[
398 {"prefix":1081,"width":640,"height":480,"encoding":"Rgb565","frame_byte_length":614400,"fallback_encodings":["Jpeg"]}
399 ]"#;
400 let profiles = parse_profiles_json(json).unwrap();
401 assert_eq!(profiles.len(), 1);
402 let fb = profiles[0].fallback_encodings.as_ref().unwrap();
403 assert_eq!(fb.len(), 1);
404 assert_eq!(fb[0], Encoding::Jpeg);
405 }
406
407 #[test]
408 fn parse_all_54_profiles() {
409 let json = include_str!("../data/profiles.json");
410 let profiles = parse_profiles_json(json).unwrap();
411 assert_eq!(profiles.len(), 54);
412 let p1007 = profiles.iter().find(|p| p.prefix == 1007).unwrap();
414 assert_eq!(p1007.width, 480);
415 assert_eq!(p1007.height, 864);
416 assert_eq!(p1007.encoding, Encoding::Rgb565);
417
418 let p3004 = profiles.iter().find(|p| p.prefix == 3004).unwrap();
419 assert!(p3004.is_padded);
420 assert_eq!(p3004.slot_size, 8192);
421 assert_eq!(p3004.encoding, Encoding::Rgb555);
422
423 let p1042 = profiles.iter().find(|p| p.prefix == 1042).unwrap();
424 assert_eq!(p1042.width, 320);
425
426 let p1081 = profiles.iter().find(|p| p.prefix == 1081).unwrap();
428 let fb = p1081.fallback_encodings.as_ref().unwrap();
429 assert_eq!(fb.as_slice(), &[Encoding::Jpeg]);
430
431 let p2002 = profiles.iter().find(|p| p.prefix == 2002).unwrap();
433 assert!(!p2002.little_endian);
434 }
435
436 #[test]
437 fn parse_error_on_bad_json() {
438 let result = parse_profiles_json("not json at all");
439 assert!(result.is_err());
440 }
441}