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 ascii_plist_derive::FromPlist;
8use smol_str::SmolStr;
9
10/// A plist dictionary
11pub type Dictionary = BTreeMap<SmolStr, Plist>;
12
13/// An array of plist values
14pub type Array = Vec<Plist>;
15
16/// An enum representing a property list.
17#[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    // https://github.com/opensource-apple/CF/blob/3cc41a76b1491f50813e28a4ec09954ffa359e6f/CFOldStylePList.c#L79
95    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
106// Used for serialization; make sure UUID's get quoted
107fn 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    // Prevent parsing of "infinity", "inf", "nan" as numbers, we
136    // want to keep them as strings (e.g. glyphname)
137    // https://doc.rust-lang.org/std/primitive.f64.html#grammar
138    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        // TODO: check that we're actually at eof
182        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                    // TODO: quote if needed?
369                    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        // kind of arbitrary but seems okay
406        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                            // End of string
471                            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    // checked before this is called
557    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        // unicode escapes
567        b'U' if s.len() >= 3 => {
568            // here we will parse up to 4 hexdigits:
569            // https://github.com/opensource-apple/CF/blob/3cc41a76b1491f5/CFOldStylePList.c#L150C2-L150C6
570            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                // we can't cast! this is a utf-16 value, not a codepoint
575                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            // octal escape
591            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
609// parse up to four hex digits as a u16
610// returns an error if the first byte is not a valid ascii hex digit,
611// then will read up to four bytes
612fn 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
677/// This type can be parsed from a Plist string
678pub 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    /// Jump over the next thing, regardless of whether it's simple (atom or string) or complex
754    /// (bracketed or braced construct)
755    ///
756    /// Named to match parse_rec.
757    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            // handle possible traliing separator
808            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
926/// Hand-written because Glyphs 2 points don't look like Glyphs 3 points
927impl 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
942/// Hand-written because it's a String that becomes a Thing
943impl 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    // in arrays the trailing comma is optional but supported
1053    #[test]
1054    fn array_optional_trailing_comma() {
1055        let _ = env_logger::builder().is_test(true).try_init();
1056        // we include a list that is not parsed in derive because that
1057        // takes a second codepath.
1058        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    // partially borrowed from from python: https://github.com/fonttools/openstep-plist/blob/2fa77b267d67/tests/test_parser.py#L135
1140    #[test]
1141    fn escape_parsing_good() {
1142        for (input, expected, expected_len) in [
1143            ("\\n", '\n', 2),                    // octal escape
1144            ("\\012", '\n', 4),                  // octal escape
1145            ("\\U2019", '\u{2019}', 6),          // unicode escape (’)
1146            ("\\UD83D\\UDCA9", '\u{1F4A9}', 12), // surrogate pair (💩)
1147        ] {
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}