1use std::collections::BTreeMap;
2use std::{borrow::Cow, fmt::Debug};
3
4use kurbo::{Affine, Point};
5use ordered_float::OrderedFloat;
6
7use smol_str::SmolStr;
8
9pub type Dictionary = BTreeMap<SmolStr, Plist>;
11
12pub type Array = Vec<Plist>;
14
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
17pub enum Plist {
18 Dictionary(Dictionary),
19 Array(Array),
20 String(String),
21 Integer(i64),
22 Float(OrderedFloat<f64>),
23 Data(Vec<u8>),
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
27pub enum Error {
28 #[error("Unexpected character '{0}'")]
29 UnexpectedChar(char),
30 #[error("Unterminated string")]
31 UnclosedString,
32 #[error("Unterminated data block")]
33 UnclosedData,
34 #[error("Data block did not contain valid paired hex digits")]
35 BadData,
36 #[error("Unknown escape code")]
37 UnknownEscape,
38 #[error("Invalid unicode escape sequence: '{0}'")]
39 InvalidUnicodeEscape(String),
40 #[error("Expected string, found '{token_name}")]
41 NotAString { token_name: &'static str },
42 #[error("Missing '='")]
43 ExpectedEquals,
44 #[error("Missing ','")]
45 ExpectedComma,
46 #[error("Missing ';'")]
47 ExpectedSemicolon,
48 #[error("Missing '{{'")]
49 ExpectedOpenBrace,
50 #[error("Missing '}}'")]
51 ExpectedCloseBrace,
52 #[error("Missing '('")]
53 ExpectedOpenParen,
54 #[error("Missing ')'")]
55 ExpectedCloseParen,
56 #[error("Expected character '{0}'")]
57 ExpectedChar(char),
58 #[error("Expected numeric value")]
59 ExpectedNumber,
60 #[error("Expected string value")]
61 ExpectedString,
62 #[error("Expected '{expected}', found '{found}'")]
63 UnexpectedDataType {
64 expected: &'static str,
65 found: &'static str,
66 },
67 #[error("Unexpected token '{name}'")]
68 UnexpectedToken { name: &'static str },
69 #[error("Expected {value_type}, found '{actual}'")]
70 UnexpectedNumberOfValues {
71 value_type: &'static str,
72 actual: usize,
73 },
74 #[error("parsing failed: '{0}'")]
75 Parse(String),
76}
77
78#[derive(Debug, PartialEq)]
79pub(crate) enum Token<'a> {
80 Eof,
81 OpenBrace,
82 OpenParen,
83 Data(Vec<u8>),
84 String(Cow<'a, str>),
85 Atom(&'a str),
86}
87
88fn is_numeric(b: u8) -> bool {
89 b.is_ascii_digit() || b == b'.' || b == b'-'
90}
91
92fn is_alnum(b: u8) -> bool {
93 is_numeric(b)
95 || b.is_ascii_uppercase()
96 || b.is_ascii_lowercase()
97 || b == b'_'
98 || b == b'$'
99 || b == b'/'
100 || b == b':'
101 || b == b'.'
102 || b == b'-'
103}
104
105fn is_alnum_strict(b: u8) -> bool {
107 is_alnum(b) && b != b'-'
108}
109
110fn is_hex_upper(b: u8) -> bool {
111 b.is_ascii_digit() || (b'A'..=b'F').contains(&b)
112}
113
114fn is_ascii_whitespace(b: u8) -> bool {
115 b == b' ' || b == b'\t' || b == b'\r' || b == b'\n'
116}
117
118fn numeric_ok(s: &str) -> bool {
119 let s = s.as_bytes();
120 if s.is_empty() {
121 return false;
122 }
123 let s = if s.len() > 1 && (*s.first().unwrap(), *s.last().unwrap()) == (b'"', b'"') {
124 &s[1..s.len()]
125 } else {
126 s
127 };
128 if s.iter().all(|&b| is_hex_upper(b)) && !s.iter().all(|&b| b.is_ascii_digit()) {
129 return false;
130 }
131 if s.len() > 1 && s[0] == b'0' {
132 return !s.iter().all(|&b| b.is_ascii_digit());
133 }
134 if s.eq_ignore_ascii_case(b"infinity")
138 || s.eq_ignore_ascii_case(b"inf")
139 || s.eq_ignore_ascii_case(b"nan")
140 {
141 return false;
142 }
143 true
144}
145
146fn skip_ws(s: &str, mut ix: usize) -> usize {
147 while ix < s.len() && is_ascii_whitespace(s.as_bytes()[ix]) {
148 ix += 1;
149 }
150 ix
151}
152
153fn escape_string(buf: &mut String, s: &str) {
154 if !s.is_empty() && s.as_bytes().iter().all(|&b| is_alnum_strict(b)) {
155 buf.push_str(s);
156 } else {
157 buf.push('"');
158 let mut start = 0;
159 let mut ix = start;
160 while ix < s.len() {
161 let b = s.as_bytes()[ix];
162 match b {
163 b'"' | b'\\' => {
164 buf.push_str(&s[start..ix]);
165 buf.push('\\');
166 start = ix;
167 }
168 _ => (),
169 }
170 ix += 1;
171 }
172 buf.push_str(&s[start..]);
173 buf.push('"');
174 }
175}
176
177impl Plist {
178 pub fn parse(s: &str) -> Result<Plist, Error> {
179 let (plist, _ix) = Plist::parse_rec(s, 0)?;
180 Ok(plist)
182 }
183
184 fn name(&self) -> &'static str {
185 match self {
186 Plist::Array(..) => "array",
187 Plist::Dictionary(..) => "dictionary",
188 Plist::Float(..) => "float",
189 Plist::Integer(..) => "integer",
190 Plist::String(..) => "string",
191 Plist::Data(..) => "data",
192 }
193 }
194
195 pub fn get(&self, key: &str) -> Option<&Plist> {
196 match self {
197 Plist::Dictionary(d) => d.get(key),
198 _ => None,
199 }
200 }
201
202 pub fn as_dict(&self) -> Option<&BTreeMap<SmolStr, Plist>> {
203 match self {
204 Plist::Dictionary(d) => Some(d),
205 _ => None,
206 }
207 }
208
209 pub fn as_array(&self) -> Option<&[Plist]> {
210 match self {
211 Plist::Array(a) => Some(a),
212 _ => None,
213 }
214 }
215
216 pub fn as_str(&self) -> Option<&str> {
217 match self {
218 Plist::String(s) => Some(s),
219 _ => None,
220 }
221 }
222
223 pub fn as_i64(&self) -> Option<i64> {
224 match self {
225 Plist::Integer(i) => Some(*i),
226 Plist::String(raw) => raw.parse().ok(),
227 _ => None,
228 }
229 }
230
231 pub fn as_f64(&self) -> Option<f64> {
232 match self {
233 Plist::Integer(i) => Some(*i as f64),
234 Plist::Float(f) => Some((*f).into_inner()),
235 Plist::String(raw) => raw.parse().ok(),
236 _ => None,
237 }
238 }
239
240 pub fn expect_dict(self) -> Result<Dictionary, Error> {
241 match self {
242 Plist::Dictionary(dict) => Ok(dict),
243 _other => Err(Error::UnexpectedDataType {
244 expected: "dictionary",
245 found: _other.name(),
246 }),
247 }
248 }
249
250 pub fn expect_array(self) -> Result<Array, Error> {
251 match self {
252 Plist::Array(array) => Ok(array),
253 _other => Err(Error::UnexpectedDataType {
254 expected: "array",
255 found: _other.name(),
256 }),
257 }
258 }
259
260 pub fn expect_string(self) -> Result<String, Error> {
261 match self {
262 Plist::String(string) => Ok(string),
263 _other => Err(Error::UnexpectedDataType {
264 expected: "string",
265 found: _other.name(),
266 }),
267 }
268 }
269
270 pub fn expect_data(self) -> Result<Vec<u8>, Error> {
271 match self {
272 Plist::Data(bytes) => Ok(bytes),
273 _other => Err(Error::UnexpectedDataType {
274 expected: "data",
275 found: _other.name(),
276 }),
277 }
278 }
279
280 fn parse_rec(s: &str, ix: usize) -> Result<(Plist, usize), Error> {
281 let (tok, mut ix) = Token::lex(s, ix)?;
282 match tok {
283 Token::Atom(s) => Ok((Plist::parse_atom(s), ix)),
284 Token::String(s) => Ok((Plist::String(s.into()), ix)),
285 Token::Data(bytes) => Ok((Plist::Data(bytes), ix)),
286 Token::OpenBrace => {
287 let mut dict = BTreeMap::new();
288 loop {
289 if let Some(ix) = Token::expect(s, ix, b'}') {
290 return Ok((Plist::Dictionary(dict), ix));
291 }
292 let (key, next) = Token::lex(s, ix)?;
293 let key_str = Token::try_into_smolstr(key)?;
294 let next = Token::expect(s, next, b'=');
295 if next.is_none() {
296 return Err(Error::ExpectedEquals);
297 }
298 let (val, next) = Self::parse_rec(s, next.unwrap())?;
299 dict.insert(key_str, val);
300 if let Some(next) = Token::expect(s, next, b';') {
301 ix = next;
302 } else {
303 return Err(Error::ExpectedSemicolon);
304 }
305 }
306 }
307 Token::OpenParen => {
308 let mut list = Vec::new();
309 loop {
310 if let Some(ix) = Token::expect(s, ix, b')') {
311 return Ok((Plist::Array(list), ix));
312 }
313 let (val, next) = Self::parse_rec(s, ix)?;
314 list.push(val);
315 if let Some(ix) = Token::expect(s, next, b')') {
316 return Ok((Plist::Array(list), ix));
317 }
318 if let Some(next) = Token::expect(s, next, b',') {
319 ix = next;
320 if let Some(next) = Token::expect(s, next, b')') {
321 return Ok((Plist::Array(list), next));
322 }
323 } else {
324 return Err(Error::ExpectedComma);
325 }
326 }
327 }
328 _ => Err(Error::UnexpectedToken { name: tok.name() }),
329 }
330 }
331
332 fn parse_atom(s: &str) -> Plist {
333 if numeric_ok(s) {
334 if let Ok(num) = s.parse() {
335 return Plist::Integer(num);
336 }
337 if let Ok(num) = s.parse() {
338 return Plist::Float(num);
339 }
340 }
341 Plist::String(s.into())
342 }
343
344 #[allow(clippy::inherent_to_string, unused)]
345 pub fn to_string(&self) -> String {
346 let mut s = String::new();
347 self.push_to_string(&mut s);
348 s
349 }
350
351 fn push_to_string(&self, s: &mut String) {
352 match self {
353 Plist::Array(a) => {
354 s.push('(');
355 let mut delim = "\n";
356 for el in a {
357 s.push_str(delim);
358 el.push_to_string(s);
359 delim = ",\n";
360 }
361 s.push_str("\n)");
362 }
363 Plist::Dictionary(a) => {
364 s.push_str("{\n");
365 let mut keys: Vec<_> = a.keys().collect();
366 keys.sort();
367 for k in keys {
368 let el = &a[k];
369 escape_string(s, k);
371 s.push_str(" = ");
372 el.push_to_string(s);
373 s.push_str(";\n");
374 }
375 s.push('}');
376 }
377 Plist::String(st) => escape_string(s, st),
378 Plist::Integer(i) => {
379 s.push_str(&format!("{i}"));
380 }
381 Plist::Float(f) => {
382 s.push_str(&format!("{f}"));
383 }
384 Plist::Data(data) => {
385 s.push('<');
386 for byte in data {
387 s.extend(hex_digits_for_byte(*byte))
388 }
389 s.push('>');
390 }
391 }
392 }
393}
394
395impl FromPlist for Plist {
396 fn parse(tokenizer: &mut Tokenizer) -> Result<Self, Error> {
397 let Tokenizer { content, idx } = tokenizer;
398 let (val, end_idx) = Self::parse_rec(content, *idx)?;
399 *idx = end_idx;
400 Ok(val)
401 }
402}
403
404impl Default for Plist {
405 fn default() -> Self {
406 Plist::Array(Vec::new())
408 }
409}
410
411fn hex_digits_for_byte(byte: u8) -> [char; 2] {
412 fn to_hex_digit(val: u8) -> char {
413 match val {
414 0..=9 => ('0' as u32 as u8 + val).into(),
415 10..=15 => (('a' as u32 as u8) + val - 10).into(),
416 _ => unreachable!("only called with values in range 0..=15"),
417 }
418 }
419
420 [to_hex_digit(byte >> 4), to_hex_digit(byte & 0x0f)]
421}
422
423fn byte_from_hex(hex: [u8; 2]) -> Result<u8, Error> {
424 fn hex_digit_to_byte(digit: u8) -> Result<u8, Error> {
425 match digit {
426 b'0'..=b'9' => Ok(digit - b'0'),
427 b'a'..=b'f' => Ok(digit - b'a' + 10),
428 b'A'..=b'F' => Ok(digit - b'A' + 10),
429 _ => Err(Error::BadData),
430 }
431 }
432 let maj = hex_digit_to_byte(hex[0])? << 4;
433 let min = hex_digit_to_byte(hex[1])?;
434 Ok(maj | min)
435}
436
437impl<'a> Token<'a> {
438 fn lex(s: &'a str, ix: usize) -> Result<(Token<'a>, usize), Error> {
439 let start = skip_ws(s, ix);
440 if start == s.len() {
441 return Ok((Token::Eof, start));
442 }
443 let b = s.as_bytes()[start];
444 match b {
445 b'{' => Ok((Token::OpenBrace, start + 1)),
446 b'(' => Ok((Token::OpenParen, start + 1)),
447 b'<' => {
448 let data_start = start + 1;
449 let data_end = data_start
450 + s.as_bytes()[data_start..]
451 .iter()
452 .position(|b| *b == b'>')
453 .ok_or(Error::UnclosedData)?;
454 let (chunks, remainder) = s.as_bytes()[data_start..data_end].as_chunks::<2>();
455 if !remainder.is_empty() {
456 return Err(Error::BadData);
457 }
458 let data = chunks
459 .iter()
460 .map(|x| byte_from_hex(*x))
461 .collect::<Result<_, _>>()?;
462 Ok((Token::Data(data), data_end + 1))
463 }
464 b'"' => {
465 let mut ix = start + 1;
466 let mut cow_start = ix;
467 let mut buf = String::new();
468 while ix < s.len() {
469 let b = s.as_bytes()[ix];
470 match b {
471 b'"' => {
472 let string = if buf.is_empty() {
474 s[cow_start..ix].into()
475 } else {
476 buf.push_str(&s[cow_start..ix]);
477 buf.into()
478 };
479 return Ok((Token::String(string), ix + 1));
480 }
481 b'\\' => {
482 buf.push_str(&s[cow_start..ix]);
483 if ix + 1 == s.len() {
484 return Err(Error::UnclosedString);
485 }
486 let (c, len) = parse_escape(&s[ix..])?;
487 buf.push(c);
488 ix += len;
489 cow_start = ix;
490 }
491 _ => ix += 1,
492 }
493 }
494 Err(Error::UnclosedString)
495 }
496 _ => {
497 if is_alnum(b) {
498 let mut ix = start + 1;
499 while ix < s.len() {
500 if !is_alnum(s.as_bytes()[ix]) {
501 break;
502 }
503 ix += 1;
504 }
505 Ok((Token::Atom(&s[start..ix]), ix))
506 } else {
507 Err(Error::UnexpectedChar(s[start..].chars().next().unwrap()))
508 }
509 }
510 }
511 }
512
513 fn try_into_smolstr(self) -> Result<SmolStr, Error> {
514 match self {
515 Token::Atom(s) => Ok(s.into()),
516 Token::String(s) => Ok(s.into()),
517 _ => Err(Error::NotAString {
518 token_name: self.name(),
519 }),
520 }
521 }
522
523 pub fn as_str(&self) -> Option<&str> {
524 match self {
525 Token::Atom(s) => Some(*s),
526 Token::String(s) => Some(s),
527 Token::Eof => None,
528 Token::OpenBrace => None,
529 Token::OpenParen => None,
530 Token::Data(_) => None,
531 }
532 }
533
534 fn expect(s: &str, ix: usize, delim: u8) -> Option<usize> {
535 let ix = skip_ws(s, ix);
536 if ix < s.len() {
537 let b = s.as_bytes()[ix];
538 if b == delim {
539 return Some(ix + 1);
540 }
541 }
542 None
543 }
544
545 pub(crate) fn name(&self) -> &'static str {
546 match self {
547 Token::Atom(..) => "Atom",
548 Token::String(..) => "String",
549 Token::Eof => "Eof",
550 Token::OpenBrace => "OpenBrace",
551 Token::OpenParen => "OpenParen",
552 Token::Data(_) => "Data",
553 }
554 }
555}
556
557fn parse_escape(s: &str) -> Result<(char, usize), Error> {
558 assert!(s.starts_with('\\') && s.len() > 1);
560
561 let mut ix = 1;
562 let b = s.as_bytes()[ix];
563 match b {
564 b'"' | b'\\' => Ok((b as _, 2)),
565 b'n' => Ok(('\n', 2)),
566 b'r' => Ok(('\r', 2)),
567 b't' => Ok(('\t', 2)),
568 b'U' if s.len() >= 3 => {
570 ix += 1;
573 let (val, len) = parse_hex_digit(&s.as_bytes()[ix..])?;
574 ix += len;
575 let result = if !is_surrogate(val) || !s.as_bytes()[ix..].starts_with(b"\\U") {
576 char::decode_utf16([val]).next()
578 } else {
579 ix += 2;
580 let (val2, len) = parse_hex_digit(&s.as_bytes()[ix..])?;
581 ix += len;
582 char::decode_utf16([val, val2]).next()
583 };
584 result
585 .transpose()
586 .ok()
587 .flatten()
588 .ok_or_else(|| Error::InvalidUnicodeEscape(s[..ix].to_string()))
589 .map(|c| (c, ix))
590 }
591 b'0'..=b'3' if s.len() >= 4 => {
592 let b1 = s.as_bytes()[ix + 1];
594 let b2 = s.as_bytes()[ix + 2];
595 if (b'0'..=b'7').contains(&b1) && (b'0'..=b'7').contains(&b2) {
596 let oct = (b - b'0') * 64 + (b1 - b'0') * 8 + (b2 - b'0');
597 ix += 3;
598 Ok((oct as _, ix))
599 } else {
600 Err(Error::UnknownEscape)
601 }
602 }
603 _ => Err(Error::UnknownEscape),
604 }
605}
606
607fn is_surrogate(val: u16) -> bool {
608 matches!(val, 0xD800..=0xDFFF)
609}
610
611fn parse_hex_digit(bytes: &[u8]) -> Result<(u16, usize), Error> {
615 match bytes {
616 &[] => Err(Error::UnknownEscape),
617 &[one, ..] if !one.is_ascii_hexdigit() => Err(Error::UnknownEscape),
618 other => Ok(other
619 .iter()
620 .take(4)
621 .map_while(|b| (*b as char).to_digit(16).map(|x| x as u16))
622 .fold((0u16, 0usize), |(num, len), hexval| {
623 ((num << 4) + hexval, len + 1)
624 })),
625 }
626}
627
628impl From<String> for Plist {
629 fn from(x: String) -> Plist {
630 Plist::String(x)
631 }
632}
633
634impl From<i64> for Plist {
635 fn from(x: i64) -> Plist {
636 Plist::Integer(x)
637 }
638}
639
640impl From<f64> for Plist {
641 fn from(x: f64) -> Plist {
642 Plist::Float(x.into())
643 }
644}
645
646impl From<Vec<Plist>> for Plist {
647 fn from(x: Vec<Plist>) -> Plist {
648 Plist::Array(x)
649 }
650}
651
652impl From<Dictionary> for Plist {
653 fn from(x: Dictionary) -> Plist {
654 Plist::Dictionary(x)
655 }
656}
657
658pub(crate) fn parse_int(s: &str) -> Result<i64, Error> {
659 if let Ok(num) = s.parse::<i64>() {
660 return Ok(num);
661 }
662 if let Ok(num) = s.parse::<f64>()
663 && num.is_finite()
664 {
665 return Ok(num as i64);
666 }
667 Err(Error::ExpectedNumber)
668}
669
670pub(crate) fn parse_float(s: &str) -> Result<f64, Error> {
671 if let Ok(num) = s.parse::<f64>()
672 && num.is_finite()
673 {
674 return Ok(num);
675 }
676 Err(Error::ExpectedNumber)
677}
678
679pub trait FromPlist
681where
682 Self: Sized,
683{
684 fn parse(tokenizer: &mut Tokenizer) -> Result<Self, Error>;
685
686 fn parse_plist(plist: &str) -> Result<Self, Error> {
687 Tokenizer::new(plist).parse()
688 }
689}
690
691impl<T> FromPlist for Vec<T>
692where
693 T: FromPlist,
694{
695 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, crate::plist::Error> {
696 tokenizer.parse_delimited_vec(VecDelimiters::CSV_IN_PARENS)
697 }
698}
699
700impl<T: FromPlist> FromPlist for BTreeMap<SmolStr, T> {
701 fn parse(tokenizer: &mut Tokenizer) -> Result<Self, Error> {
702 tokenizer.parse_map()
703 }
704}
705
706impl<T> FromPlist for Option<T>
707where
708 T: FromPlist,
709{
710 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, crate::plist::Error> {
711 Ok(Some(tokenizer.parse()?))
712 }
713}
714
715pub struct Tokenizer<'a> {
716 content: &'a str,
717 idx: usize,
718}
719
720impl Debug for Tokenizer<'_> {
721 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722 let start = self.idx;
723 let end = (start + 16).min(self.content.len());
724 f.debug_struct("Tokenizer")
725 .field("content", &&self.content[start..end])
726 .field("idx", &self.idx)
727 .finish()
728 }
729}
730
731impl<'a> Tokenizer<'a> {
732 pub fn new(content: &'a str) -> Tokenizer<'a> {
733 Tokenizer { content, idx: 0 }
734 }
735
736 pub(crate) fn peek(&mut self) -> Result<Token<'a>, Error> {
737 let (tok, _) = Token::lex(self.content, self.idx)?;
738 Ok(tok)
739 }
740
741 pub(crate) fn lex(&mut self) -> Result<Token<'a>, Error> {
742 let (tok, idx) = Token::lex(self.content, self.idx)?;
743 self.idx = idx;
744 Ok(tok)
745 }
746
747 pub(crate) fn eat(&mut self, delim: u8) -> Result<(), Error> {
748 let Some(idx) = Token::expect(self.content, self.idx, delim) else {
749 return Err(Error::ExpectedChar(delim as char));
750 };
751 self.idx = idx;
752 Ok(())
753 }
754
755 pub(crate) fn skip_rec(&mut self) -> Result<(), Error> {
760 match self.lex()? {
761 Token::Atom(..) | Token::String(..) | Token::Data(..) => Ok(()),
762 Token::OpenBrace => loop {
763 if self.eat(b'}').is_ok() {
764 return Ok(());
765 }
766 let key = self.lex()?;
767 Token::try_into_smolstr(key)?;
768 self.eat(b'=')?;
769 self.skip_rec()?;
770 self.eat(b';')?;
771 },
772 Token::OpenParen => {
773 if self.eat(b')').is_ok() {
774 return Ok(());
775 }
776 loop {
777 self.skip_rec()?;
778 if self.eat(b')').is_ok() {
779 return Ok(());
780 }
781 self.eat(b',')?;
782 if self.eat(b')').is_ok() {
783 return Ok(());
784 }
785 }
786 }
787 other => Err(Error::UnexpectedToken { name: other.name() }),
788 }
789 }
790
791 pub(crate) fn parse_delimited_vec<T>(
792 &mut self,
793 delim: VecDelimiters,
794 ) -> Result<Vec<T>, crate::plist::Error>
795 where
796 T: FromPlist,
797 {
798 let mut list = Vec::new();
799 self.eat(delim.start)?;
800 loop {
801 if self.eat(delim.end).is_ok() {
802 return Ok(list);
803 }
804 list.push(self.parse()?);
805 if self.eat(delim.end).is_ok() {
806 return Ok(list);
807 }
808 self.eat(delim.sep)?;
809 if self.eat(delim.end).is_ok() {
811 return Ok(list);
812 }
813 }
814 }
815
816 pub(crate) fn parse_map<T: FromPlist>(&mut self) -> Result<BTreeMap<SmolStr, T>, Error> {
817 self.eat(b'{')?;
818 let mut map = BTreeMap::new();
819 loop {
820 if self.eat(b'}').is_ok() {
821 break;
822 }
823 let key = self.parse::<SmolStr>()?;
824 self.eat(b'=')?;
825 map.insert(key, self.parse()?);
826 self.eat(b';')?;
827 }
828 Ok(map)
829 }
830
831 pub(crate) fn parse<T>(&mut self) -> Result<T, crate::plist::Error>
832 where
833 T: FromPlist,
834 {
835 T::parse(self)
836 }
837}
838
839pub(crate) struct VecDelimiters {
840 start: u8,
841 end: u8,
842 sep: u8,
843}
844
845impl VecDelimiters {
846 pub(crate) const CSV_IN_PARENS: VecDelimiters = VecDelimiters {
847 start: b'(',
848 end: b')',
849 sep: b',',
850 };
851 pub(crate) const CSV_IN_BRACES: VecDelimiters = VecDelimiters {
852 start: b'{',
853 end: b'}',
854 sep: b',',
855 };
856}
857
858impl FromPlist for OrderedFloat<f64> {
859 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
860 let val: f64 = tokenizer.parse()?;
861 Ok(val.into())
862 }
863}
864
865impl FromPlist for f64 {
866 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
867 match tokenizer.lex()? {
868 Token::Atom(val) => parse_float(val),
869 Token::String(val) => parse_float(&val),
870 _ => Err(Error::ExpectedNumber),
871 }
872 }
873}
874
875impl FromPlist for i64 {
876 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
877 match tokenizer.lex()? {
878 Token::Atom(val) => parse_int(val),
879 Token::String(val) => parse_int(&val),
880 _ => Err(Error::ExpectedNumber),
881 }
882 }
883}
884
885impl FromPlist for bool {
886 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
887 match tokenizer.lex()? {
888 Token::Atom(val) => parse_int(val).map(|v| v == 1),
889 Token::String(val) => parse_int(&val).map(|v| v == 1),
890 _ => Err(Error::ExpectedNumber),
891 }
892 }
893}
894
895impl FromPlist for String {
896 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
897 match tokenizer.lex()? {
898 Token::Atom(val) => Ok(val.to_string()),
899 Token::String(val) => Ok(val.to_string()),
900 _ => Err(Error::ExpectedString),
901 }
902 }
903}
904
905impl FromPlist for SmolStr {
906 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
907 match tokenizer.lex()? {
908 Token::Atom(val) => Ok(val.into()),
909 Token::String(val) => Ok(val.into()),
910 _ => Err(Error::ExpectedString),
911 }
912 }
913}
914
915impl FromPlist for Point {
917 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
918 let delims = if let Token::OpenBrace = tokenizer.peek()? {
919 VecDelimiters::CSV_IN_BRACES
920 } else {
921 VecDelimiters::CSV_IN_PARENS
922 };
923 let coords: Vec<f64> = tokenizer.parse_delimited_vec(delims)?;
924 if coords.len() != 2 {
925 return Err(Error::Parse("wrong number of coords in point".to_string()));
926 }
927 Ok((coords[0], coords[1]).into())
928 }
929}
930
931impl FromPlist for Affine {
933 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, Error> {
934 let tok = tokenizer.lex()?;
935 let raw = match &tok {
936 Token::Atom(val) => *val,
937 Token::String(val) => val,
938 _ => return Err(Error::ExpectedString),
939 };
940 let raw = &raw[1..raw.len() - 1];
941 let coords: Vec<f64> = raw.split(", ").map(|c| c.parse().unwrap()).collect();
942 Ok(Affine::new([
943 coords[0], coords[1], coords[2], coords[3], coords[4], coords[5],
944 ]))
945 }
946}
947
948#[cfg(test)]
949mod tests {
950 use ascii_plist_derive::FromPlist;
951 use std::collections::BTreeMap;
952
953 use super::*;
954
955 #[test]
956 fn parse_unquoted_strings() {
957 let contents = r#"
958 {
959 name = "UFO Filename";
960 value1 = ../../build/instance_ufos/Testing_Rg.ufo;
961 value2 = _;
962 value3 = $;
963 value4 = /;
964 value5 = :;
965 value6 = .;
966 value7 = -;
967 }
968 "#;
969
970 let plist = Plist::parse(contents).unwrap();
971 let plist_expected = Plist::Dictionary(BTreeMap::from_iter([
972 ("name".into(), Plist::String("UFO Filename".into())),
973 (
974 "value1".into(),
975 Plist::String("../../build/instance_ufos/Testing_Rg.ufo".into()),
976 ),
977 ("value2".into(), Plist::String("_".into())),
978 ("value3".into(), Plist::String("$".into())),
979 ("value4".into(), Plist::String("/".into())),
980 ("value5".into(), Plist::String(":".into())),
981 ("value6".into(), Plist::String(".".into())),
982 ("value7".into(), Plist::String("-".into())),
983 ]));
984 assert_eq!(plist, plist_expected);
985 }
986
987 #[test]
988 fn parse_int_map() {
989 let contents = r#"
990 {
991 foo = 5;
992 bar = 32;
993 }"#;
994
995 let foobar = BTreeMap::<SmolStr, i64>::parse_plist(contents).unwrap();
996 assert_eq!(foobar.get("foo"), Some(&5));
997 assert_eq!(foobar.get("bar"), Some(&32));
998 }
999
1000 #[test]
1001 fn parse_int_leading_zero() {
1002 let contents = r#"
1003 {
1004 versionMinor = 011;
1005 other = 0;
1006 }"#;
1007
1008 let map = BTreeMap::<SmolStr, i64>::parse_plist(contents).unwrap();
1009 assert_eq!(map.get("versionMinor"), Some(&11));
1010 assert_eq!(map.get("other"), Some(&0));
1011 }
1012
1013 #[test]
1014 #[should_panic(expected = "ExpectedNumber")]
1015 fn parse_int_rejects_infinity() {
1016 let contents = r#"
1017 {
1018 bad = infinity;
1019 }"#;
1020
1021 let _map = BTreeMap::<SmolStr, i64>::parse_plist(contents).unwrap();
1022 }
1023
1024 #[test]
1025 #[should_panic(expected = "ExpectedNumber")]
1026 fn parse_map_fail() {
1027 let contents = r#"
1028 {
1029 foo = hello;
1030 bar = 32;
1031 }"#;
1032
1033 let _foobar = BTreeMap::<SmolStr, i64>::parse_plist(contents).unwrap();
1034 }
1035
1036 #[test]
1037 fn parse_binary_data() {
1038 let contents = r#"
1039 {
1040 mydata = <deadbeef>;
1041 }
1042 "#;
1043 let plist = Plist::parse(contents).unwrap();
1044 let data = plist.get("mydata").unwrap().clone().expect_data().unwrap();
1045 assert_eq!(data, [0xde, 0xad, 0xbe, 0xef])
1046 }
1047
1048 #[test]
1049 fn hex_to_ascii() {
1050 assert_eq!(hex_digits_for_byte(0x01), ['0', '1']);
1051 assert_eq!(hex_digits_for_byte(0x00), ['0', '0']);
1052 assert_eq!(hex_digits_for_byte(0xff), ['f', 'f']);
1053 assert_eq!(hex_digits_for_byte(0xf0), ['f', '0']);
1054 assert_eq!(hex_digits_for_byte(0x0f), ['0', 'f']);
1055 }
1056
1057 #[test]
1058 fn ascii_to_hex() {
1059 assert_eq!(byte_from_hex(*b"01"), Ok(0x01));
1060 assert_eq!(byte_from_hex(*b"00"), Ok(0x00));
1061 assert_eq!(byte_from_hex(*b"ff"), Ok(0xff));
1062 assert_eq!(byte_from_hex(*b"f0"), Ok(0xf0));
1063 assert_eq!(byte_from_hex(*b"0f"), Ok(0x0f));
1064 }
1065
1066 #[test]
1068 fn array_optional_trailing_comma() {
1069 let _ = tracing_subscriber::fmt().with_test_writer().try_init();
1070 let trailing = r#"
1073 {
1074 items = (
1075 "a",
1076 "b",
1077 );
1078 skip_me = (
1079 "c",
1080 "d",
1081 );
1082 }"#;
1083
1084 let no_trailing = r#"
1085 {
1086 items = (
1087 "a",
1088 "b"
1089 );
1090 skip_me = (
1091 "c",
1092 "d"
1093 );
1094 }"#;
1095
1096 #[derive(Default, FromPlist)]
1097 struct TestMe {
1098 items: Vec<String>,
1099 }
1100
1101 let trailing = TestMe::parse_plist(trailing).unwrap();
1102 assert_eq!(trailing.items, ["a", "b"]);
1103 let no_trailing = TestMe::parse_plist(no_trailing).unwrap();
1104 assert_eq!(trailing.items, no_trailing.items);
1105 }
1106
1107 #[test]
1108 fn parse_to_plist_type() {
1109 let plist_str = r#"
1110 {
1111 name = "meta";
1112 value = (
1113 {
1114 data = latn;
1115 tag = dlng;
1116 num = 5;
1117 },
1118 {
1119 data = "latn,cyrl";
1120 tag = slng;
1121 num = -3.0;
1122 }
1123 );
1124 }"#;
1125
1126 let plist = Plist::parse_plist(plist_str).unwrap();
1127 let root = plist.expect_dict().unwrap();
1128 assert_eq!(root.get("name").unwrap().as_str(), Some("meta"));
1129 let value = root.get("value").unwrap().as_array().unwrap();
1130 assert_eq!(value.len(), 2);
1131 let first = value[0].as_dict().unwrap();
1132 assert_eq!(first.get("data").and_then(Plist::as_str), Some("latn"));
1133 assert_eq!(first.get("tag").and_then(Plist::as_str), Some("dlng"));
1134 assert_eq!(first.get("num").and_then(Plist::as_i64), Some(5));
1135 let second = value[1].as_dict().unwrap();
1136 assert_eq!(
1137 second.get("data").and_then(Plist::as_str),
1138 Some("latn,cyrl")
1139 );
1140 assert_eq!(second.get("tag").and_then(Plist::as_str), Some("slng"));
1141 assert_eq!(second.get("num").and_then(Plist::as_f64), Some(-3.0));
1142 }
1143
1144 #[test]
1145 fn parse_hex_digit_sanity() {
1146 assert_eq!(parse_hex_digit(b"2019"), Ok((0x2019, 4)));
1147 assert_eq!(parse_hex_digit(b"201"), Ok((0x201, 3)));
1148 assert_eq!(parse_hex_digit(b"201z"), Ok((0x201, 3)));
1149 assert_eq!(parse_hex_digit(b"fu"), Ok((0xf, 1)));
1150 assert_eq!(parse_hex_digit(b"z"), Err(Error::UnknownEscape));
1151 }
1152
1153 #[test]
1155 fn escape_parsing_good() {
1156 for (input, expected, expected_len) in [
1157 ("\\n", '\n', 2), ("\\012", '\n', 4), ("\\U2019", '\u{2019}', 6), ("\\UD83D\\UDCA9", '\u{1F4A9}', 12), ] {
1162 let (result, len) = parse_escape(input).unwrap();
1163 {
1164 assert_eq!((result, len), (expected, expected_len));
1165 }
1166 }
1167 }
1168
1169 #[test]
1170 fn escape_parsing_bad() {
1171 assert_eq!(
1172 parse_escape("\\UD83D"),
1173 Err(Error::InvalidUnicodeEscape("\\UD83D".to_string()))
1174 );
1175 }
1176
1177 #[test]
1178 fn parsing_escape_in_string() {
1179 for (input, expected, expected_len) in [
1180 ("\"a\\012b\"", "a\nb", 8),
1181 ("\"a\\nb\"", "a\nb", 6),
1182 ("\"a\\U000Ab\"", "a\nb", 10),
1183 ] {
1184 let (token, len) = Token::lex(input, 0).unwrap();
1185 assert_eq!(token, Token::String(Cow::Borrowed(expected)));
1186 assert_eq!(len, expected_len);
1187 }
1188 }
1189
1190 #[test]
1191 fn parse_quoted_and_unquoted() {
1192 assert_eq!(
1193 (
1194 Ok(1),
1195 Ok(1),
1196 Ok(true),
1197 Ok(true),
1198 Ok(false),
1199 Ok(false),
1200 Ok(1.0),
1201 Ok(1.0)
1202 ),
1203 (
1204 Tokenizer::new("1").parse::<i64>(),
1205 Tokenizer::new("\"1\"").parse::<i64>(),
1206 Tokenizer::new("1").parse::<bool>(),
1207 Tokenizer::new("\"1\"").parse::<bool>(),
1208 Tokenizer::new("0").parse::<bool>(),
1209 Tokenizer::new("\"0\"").parse::<bool>(),
1210 Tokenizer::new("1").parse::<f64>(),
1211 Tokenizer::new("\"1\"").parse::<f64>(),
1212 )
1213 );
1214 }
1215}