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