Skip to main content

glyphs_reader/
plist.rs

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
9/// A plist dictionary
10pub type Dictionary = BTreeMap<SmolStr, Plist>;
11
12/// An array of plist values
13pub type Array = Vec<Plist>;
14
15/// An enum representing a property list.
16#[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    // https://github.com/opensource-apple/CF/blob/3cc41a76b1491f50813e28a4ec09954ffa359e6f/CFOldStylePList.c#L79
94    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
105// Used for serialization; make sure UUID's get quoted
106fn 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    // Prevent parsing of "infinity", "inf", "nan" as numbers, we
135    // want to keep them as strings (e.g. glyphname)
136    // https://doc.rust-lang.org/std/primitive.f64.html#grammar
137    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        // TODO: check that we're actually at eof
181        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                    // TODO: quote if needed?
370                    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        // kind of arbitrary but seems okay
407        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                            // End of string
473                            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    // checked before this is called
559    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        // unicode escapes
569        b'U' if s.len() >= 3 => {
570            // here we will parse up to 4 hexdigits:
571            // https://github.com/opensource-apple/CF/blob/3cc41a76b1491f5/CFOldStylePList.c#L150C2-L150C6
572            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                // we can't cast! this is a utf-16 value, not a codepoint
577                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            // octal escape
593            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
611// parse up to four hex digits as a u16
612// returns an error if the first byte is not a valid ascii hex digit,
613// then will read up to four bytes
614fn 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
679/// This type can be parsed from a Plist string
680pub 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    /// Jump over the next thing, regardless of whether it's simple (atom or string) or complex
756    /// (bracketed or braced construct)
757    ///
758    /// Named to match parse_rec.
759    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            // handle possible traliing separator
810            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
915/// Hand-written because Glyphs 2 points don't look like Glyphs 3 points
916impl 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
931/// Hand-written because it's a String that becomes a Thing
932impl 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    // in arrays the trailing comma is optional but supported
1067    #[test]
1068    fn array_optional_trailing_comma() {
1069        let _ = tracing_subscriber::fmt().with_test_writer().try_init();
1070        // we include a list that is not parsed in derive because that
1071        // takes a second codepath.
1072        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    // partially borrowed from from python: https://github.com/fonttools/openstep-plist/blob/2fa77b267d67/tests/test_parser.py#L135
1154    #[test]
1155    fn escape_parsing_good() {
1156        for (input, expected, expected_len) in [
1157            ("\\n", '\n', 2),                    // octal escape
1158            ("\\012", '\n', 4),                  // octal escape
1159            ("\\U2019", '\u{2019}', 6),          // unicode escape (’)
1160            ("\\UD83D\\UDCA9", '\u{1F4A9}', 12), // surrogate pair (💩)
1161        ] {
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}