Skip to main content

kaydle_primitives/
string.rs

1/*!
2Parsers and utility types for parsing KDL strings and identifiers.
3 */
4
5use std::{
6    borrow::Cow,
7    char::CharTryFromError,
8    convert::TryInto,
9    fmt::{self, Formatter},
10    hash::{Hash, Hasher},
11    iter::FromIterator,
12    ops::{Deref, DerefMut, Index, RangeFrom, RangeTo},
13};
14
15use memchr::{memchr, memchr2};
16use nom::{
17    branch::alt,
18    bytes::complete::take_while_m_n,
19    character::complete::char,
20    combinator::success,
21    error::{make_error, ErrorKind, FromExternalError, ParseError},
22    Err as NomErr, IResult, Parser,
23};
24use nom_supreme::{
25    context::ContextError,
26    multi::parse_separated_terminated,
27    tag::{complete::tag, TagError},
28    ParserExt,
29};
30use serde::{de, Deserialize, Serialize};
31
32/// A KDL string, parsed from an identifier, escaped string, or raw string.
33/// Exists in either Owned or Borrowed form, depending on whether there were
34/// escapes in the string. Doesn't track the origin of the string (identifier,
35/// escaped, or raw), because KDL semantics treat them all identically.
36#[derive(Debug, Clone, Eq)]
37pub struct KdlString<'a> {
38    inner: Cow<'a, str>,
39}
40
41impl<'a> KdlString<'a> {
42    /// Create a new, empty KDL String
43    pub fn new() -> Self {
44        Self::from_borrowed("")
45    }
46
47    /// Create a KDL string from a `Cow<str>`.
48    pub fn from_cow(cow: Cow<'a, str>) -> Self {
49        Self { inner: cow }
50    }
51
52    /// Create a borrowed KDL string
53    pub fn from_borrowed(s: &'a str) -> Self {
54        Self::from_cow(Cow::Borrowed(s))
55    }
56
57    /// Create an owned KDL string
58    pub fn from_string(s: String) -> Self {
59        Self::from_cow(Cow::Owned(s))
60    }
61
62    /// Convert this unconditionally into an owned string.
63    pub fn into_string(self) -> String {
64        self.inner.into_owned()
65    }
66
67    /// Get the `&str` contained in this string.
68    pub fn as_str(&self) -> &str {
69        self
70    }
71
72    /// Apply a KDL string to a visitor
73    pub fn visit_to<V, E>(self, visitor: V) -> Result<V::Value, E>
74    where
75        V: de::Visitor<'a>,
76        E: de::Error,
77    {
78        match self.inner {
79            Cow::Borrowed(value) => visitor.visit_borrowed_str(value),
80            Cow::Owned(value) => visitor.visit_string(value),
81        }
82    }
83}
84
85impl<T: AsRef<str>> PartialEq<T> for KdlString<'_> {
86    fn eq(&self, other: &T) -> bool {
87        self.as_ref() == other.as_ref()
88    }
89}
90
91impl Hash for KdlString<'_> {
92    fn hash<H: Hasher>(&self, state: &mut H) {
93        self.as_str().hash(state)
94    }
95}
96
97impl Default for KdlString<'_> {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl<'a> Deref for KdlString<'a> {
104    type Target = Cow<'a, str>;
105
106    fn deref(&self) -> &Cow<'a, str> {
107        &self.inner
108    }
109}
110
111impl DerefMut for KdlString<'_> {
112    fn deref_mut(&mut self) -> &mut Self::Target {
113        &mut self.inner
114    }
115}
116
117impl AsRef<str> for KdlString<'_> {
118    fn as_ref(&self) -> &str {
119        self
120    }
121}
122
123impl<'a> Extend<&'a str> for KdlString<'a> {
124    fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
125        iter.into_iter().for_each(|s| self.push_str(s))
126    }
127}
128
129impl Extend<char> for KdlString<'_> {
130    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
131        iter.into_iter().for_each(|c| self.push_char(c))
132    }
133}
134
135impl<'a, 'b> Extend<&'b char> for KdlString<'a> {
136    fn extend<T: IntoIterator<Item = &'b char>>(&mut self, iter: T) {
137        self.extend(iter.into_iter().copied())
138    }
139}
140
141impl<T> FromIterator<T> for KdlString<'_>
142where
143    Self: Extend<T>,
144{
145    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
146        let mut string = KdlString::new();
147        string.extend(iter);
148        string
149    }
150}
151
152impl Serialize for KdlString<'_> {
153    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154    where
155        S: serde::Serializer,
156    {
157        serializer.serialize_str(self)
158    }
159}
160
161impl<'de> Deserialize<'de> for KdlString<'de> {
162    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163    where
164        D: serde::Deserializer<'de>,
165    {
166        struct KdlStringVisitor;
167
168        impl<'de> de::Visitor<'de> for KdlStringVisitor {
169            type Value = KdlString<'de>;
170
171            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
172                write!(formatter, "a KDL string")
173            }
174
175            fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
176            where
177                E: de::Error,
178            {
179                Ok(KdlString::from_borrowed(value))
180            }
181
182            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
183            where
184                E: de::Error,
185            {
186                self.visit_string(value.to_owned())
187            }
188
189            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
190            where
191                E: de::Error,
192            {
193                Ok(KdlString::from_string(value))
194            }
195        }
196
197        deserializer.deserialize_string(KdlStringVisitor)
198    }
199
200    fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
201    where
202        D: serde::Deserializer<'de>,
203    {
204        struct KdlStringVisitor<'de, 'a>(&'a mut KdlString<'de>);
205
206        impl<'de> de::Visitor<'de> for KdlStringVisitor<'de, '_> {
207            type Value = ();
208
209            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
210                write!(formatter, "a KDL string")
211            }
212
213            fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
214            where
215                E: de::Error,
216            {
217                *self.0 = KdlString::from_borrowed(value);
218                Ok(())
219            }
220
221            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
222            where
223                E: de::Error,
224            {
225                match self.0.inner {
226                    Cow::Owned(ref mut s) => {
227                        s.clear();
228                        s.push_str(value);
229                        Ok(())
230                    }
231                    Cow::Borrowed(_) => self.visit_string(value.to_owned()),
232                }
233            }
234
235            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
236            where
237                E: de::Error,
238            {
239                *self.0 = KdlString::from_string(value);
240                Ok(())
241            }
242        }
243
244        deserializer.deserialize_string(KdlStringVisitor(place))
245    }
246}
247
248/// Helper trait for parsing strings with escape sequences. Allows for returning
249/// borrowed strings without any allocation if there are no escape sequences,
250/// or for recognizing strings without doing actual parsing / allocation.
251pub trait StringBuilder<'a>: Sized {
252    /// Add a borrowed string to the back of this string
253    fn push_str(&mut self, s: &'a str);
254
255    /// Add a char to the back of this string
256    fn push_char(&mut self, c: char);
257
258    /// Create a new instance from a borrowed string
259    fn from_str(s: &'a str) -> Self;
260
261    /// Create a new empty instance
262    fn empty() -> Self {
263        Self::from_str("")
264    }
265}
266
267/// The empty tuple can be used as a string builder in cases where it's only
268/// necessary to recognize a string and not to parse it
269impl<'a> StringBuilder<'a> for () {
270    fn push_str(&mut self, _s: &'a str) {}
271    fn push_char(&mut self, _c: char) {}
272    fn from_str(_s: &'a str) {}
273}
274
275/// Strings can, of course, be built
276impl<'a> StringBuilder<'a> for String {
277    fn push_str(&mut self, s: &'a str) {
278        self.push_str(s)
279    }
280
281    fn push_char(&mut self, c: char) {
282        self.push(c)
283    }
284
285    fn from_str(s: &'a str) -> Self {
286        s.to_owned()
287    }
288}
289
290impl<'a> StringBuilder<'a> for KdlString<'a> {
291    fn push_str(&mut self, s: &'a str) {
292        if self.is_empty() {
293            **self = Cow::Borrowed(s)
294        } else {
295            self.to_mut().push_str(s);
296        }
297    }
298
299    fn push_char(&mut self, c: char) {
300        self.to_mut().push(c)
301    }
302
303    fn from_str(s: &'a str) -> Self {
304        Self::from_borrowed(s)
305    }
306}
307
308struct SliceShifter<'a, T: ?Sized> {
309    base: &'a T,
310    point: usize,
311}
312
313impl<'a, T: ?Sized, A: ?Sized, B: ?Sized> SliceShifter<'a, T>
314where
315    T: Index<RangeTo<usize>, Output = A>,
316    T: Index<RangeFrom<usize>, Output = B>,
317{
318    fn new(base: &'a T) -> Self {
319        Self { base, point: 0 }
320    }
321
322    fn head(&self) -> &'a A {
323        &self.base[..self.point]
324    }
325
326    fn tail(&self) -> &'a B {
327        &self.base[self.point..]
328    }
329
330    fn shift(&mut self, amount: usize) {
331        self.point += amount
332    }
333}
334
335/// Parse a raw string, resembling `r##"abc"##`
336pub fn parse_raw_string<'i, E: ParseError<&'i str>>(input: &'i str) -> IResult<&'i str, &'i str, E>
337where
338    E: ParseError<&'i str>,
339{
340    let (input, hash_count) =
341        parse_separated_terminated(char('#'), success(()), char('"'), || 0, |n, _c| n + 1)
342            .or(char('"').value(0))
343            .preceded_by(char('r'))
344            .parse(input)?;
345
346    let mut shifter = SliceShifter::new(input);
347
348    loop {
349        match memchr(b'"', shifter.tail().as_bytes()) {
350            // Couldn't find any quotes; need more input
351            None => return Err(NomErr::Failure(make_error("", ErrorKind::Eof))),
352
353            // Found a quote; search the successor bytes for hashes
354            Some(quote_idx) => {
355                shifter.shift(quote_idx);
356                let payload = shifter.head();
357                shifter.shift(1);
358
359                match shifter.tail().as_bytes().get(0..hash_count) {
360                    // Bounds error here means the input isn't large enough to
361                    // contain the hash bytes; this is an unexpected EoF
362                    None => return Err(NomErr::Failure(make_error("", ErrorKind::Eof))),
363
364                    // Found our chunk; if it's all hashes, this is the end of
365                    // the string
366                    Some(chunk) => {
367                        if chunk.iter().all(|&b| b == b'#') {
368                            shifter.shift(hash_count);
369                            return Ok((shifter.tail(), payload));
370                        }
371                    }
372                }
373            }
374        }
375    }
376}
377
378#[cfg(test)]
379mod test_parse_raw {
380    use super::*;
381    use cool_asserts::assert_matches;
382    use nom::error::Error;
383
384    fn typed_parse_raw(input: &str) -> IResult<&str, &str, Error<&str>> {
385        parse_raw_string(input)
386    }
387
388    #[test]
389    fn hashless() {
390        assert_eq!(typed_parse_raw(r#"r"abc"def"#), Ok(("def", "abc")))
391    }
392
393    #[test]
394    fn hashed() {
395        assert_eq!(
396            typed_parse_raw(r####"r##"abc"##def"####),
397            Ok(("def", "abc"))
398        )
399    }
400
401    #[test]
402    fn inner_hashes() {
403        assert_eq!(
404            typed_parse_raw(r####"r##"abc"#abc"##def"####),
405            Ok(("def", r##"abc"#abc"##))
406        )
407    }
408
409    #[test]
410    fn extra_hashes() {
411        assert_eq!(typed_parse_raw(r####"r##"abc"###"####), Ok(("#", "abc")))
412    }
413
414    #[test]
415    fn unfinished() {
416        assert_matches!(
417            typed_parse_raw(r####"r###"abc"####),
418            Err(NomErr::Failure(Error { input: "", .. }))
419        )
420    }
421
422    #[test]
423    fn partially_finished() {
424        assert_matches!(
425            typed_parse_raw(r####"r###"abc"#"####),
426            Err(NomErr::Failure(Error { input: "", .. }))
427        )
428    }
429
430    #[test]
431    fn not_regular_string() {
432        assert_matches!(
433            typed_parse_raw(r##""abc""##),
434            Err(NomErr::Error(Error {
435                input: r##""abc""##,
436                ..
437            }))
438        )
439    }
440
441    #[test]
442    fn not_identifier() {
443        assert_matches!(
444            typed_parse_raw("abc"),
445            Err(NomErr::Error(Error { input: "abc", .. }))
446        )
447    }
448
449    #[test]
450    fn not_r_identifier() {
451        assert_matches!(
452            typed_parse_raw("raw"),
453            Err(NomErr::Error(Error { input: "aw", .. }))
454        )
455    }
456}
457
458/// Returns true if this is not considered a "non-identifier character"
459#[inline]
460pub fn is_identifier(c: char) -> bool {
461    let code_point: u32 = c.into();
462    (b"\\/(){}<>;[]=,\"".iter().all(|&b| code_point != b.into()))
463        && (code_point > 0x20)
464        && (code_point <= 0x10FFFF)
465}
466
467/// Returns true if this is not considered a "non-initial character"
468#[inline]
469pub fn is_initial_identifier(c: char) -> bool {
470    is_identifier(c) && !c.is_ascii_digit()
471}
472
473/// Parse a KDL bare identifier.
474///
475/// # Compatibility note:
476///
477/// Currently this parses only a subset of KDL identifiers: alphabetics followed
478/// by alphanumerics.
479pub fn parse_bare_identifier<'i, E: ParseError<&'i str>>(
480    input: &'i str,
481) -> IResult<&'i str, &'i str, E> {
482    let mut chars = input.chars();
483    match chars.next() {
484        Some(c) if is_initial_identifier(c) => {
485            let split_point = chars
486                .as_str()
487                .find(|c: char| !is_identifier(c))
488                .unwrap_or(0)
489                + c.len_utf8();
490            let (ident, tail) = input.split_at(split_point);
491            Ok((tail, ident))
492        }
493        _ => Err(NomErr::Error(make_error(input, ErrorKind::Alpha))),
494    }
495}
496
497#[cfg(test)]
498mod test_parse_identifier {
499    use super::*;
500    use nom::error::{Error, ErrorKind};
501
502    fn typed_parse_identifier(input: &str) -> IResult<&str, &str, Error<&str>> {
503        parse_bare_identifier(input)
504    }
505
506    #[test]
507    fn basic() {
508        assert_eq!(typed_parse_identifier("abc abc"), Ok((" abc", "abc")))
509    }
510
511    #[test]
512    fn with_num() {
513        assert_eq!(typed_parse_identifier("abc123 abc"), Ok((" abc", "abc123")))
514    }
515
516    #[test]
517    fn start_with_letter() {
518        assert_eq!(
519            typed_parse_identifier("123"),
520            Err(NomErr::Error(Error {
521                input: "123",
522                code: ErrorKind::Alpha
523            }))
524        )
525    }
526
527    #[test]
528    fn with_punctuation() {
529        assert_eq!(
530            typed_parse_identifier("abc-def_ghi 123"),
531            Ok((" 123", "abc-def_ghi"))
532        )
533    }
534
535    #[test]
536    fn is_dash() {
537        assert_eq!(typed_parse_identifier("- 10"), Ok((" 10", "-")))
538    }
539}
540
541// Parse a string matching u{00F1} as an escaped unicode code point
542fn parse_unicode_escape<'i, E>(input: &'i str) -> IResult<&'i str, char, E>
543where
544    E: ParseError<&'i str>,
545    E: TagError<&'i str, &'static str>,
546    E: FromExternalError<&'i str, CharTryFromError>,
547{
548    take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit())
549        .map(|s| u32::from_str_radix(s, 16).expect("failed to parse 1-6 hex digits to a u32?"))
550        .map_res(|c: u32| c.try_into())
551        .terminated(char('}'))
552        .cut()
553        .preceded_by(tag("u{"))
554        .parse(input)
555}
556
557fn parse_escape<'i, E>(input: &'i str) -> IResult<&'i str, char, E>
558where
559    E: ParseError<&'i str>,
560    E: TagError<&'i str, &'static str>,
561    E: FromExternalError<&'i str, CharTryFromError>,
562{
563    alt((
564        char('n').value('\n'),
565        char('r').value('\r'),
566        char('t').value('\t'),
567        char('\\').value('\\'),
568        char('/').value('/'),
569        char('"').value('"'),
570        char('b').value('\u{08}'),
571        char('f').value('\u{0C}'),
572        parse_unicode_escape,
573    ))
574    .preceded_by(char('\\'))
575    .parse(input)
576}
577
578/// Parse a chunk of an escaped string. Must be at least 1 character.
579fn parse_unescaped_chunk<'i, E>(input: &'i str) -> IResult<&'i str, &'i str, E>
580where
581    E: ParseError<&'i str>,
582{
583    match memchr2(b'"', b'\\', input.as_bytes()) {
584        None => Err(NomErr::Error(E::or(
585            make_error("", ErrorKind::Eof),
586            E::from_char("", '"'),
587        ))),
588
589        Some(0) => Err(NomErr::Error(make_error(input, ErrorKind::TakeWhile1))),
590        Some(n) => {
591            let (head, tail) = input.split_at(n);
592            Ok((tail, head))
593        }
594    }
595}
596
597enum StringChunk<'a> {
598    Chunk(&'a str),
599    Char(char),
600}
601
602fn parse_chunk<'i, E>(input: &'i str) -> IResult<&'i str, StringChunk<'i>, E>
603where
604    E: ParseError<&'i str>,
605    E: TagError<&'i str, &'static str>,
606    E: FromExternalError<&'i str, CharTryFromError>,
607{
608    alt((
609        parse_unescaped_chunk.map(StringChunk::Chunk),
610        parse_escape.map(StringChunk::Char),
611    ))
612    .parse(input)
613}
614
615/// Parse a regular, quoted string (with escape sequences)
616///
617/// "This" -> &str
618/// "This\nvalue" -> String
619pub fn parse_escaped_string<'i, T, E>(input: &'i str) -> IResult<&'i str, T, E>
620where
621    T: StringBuilder<'i>,
622    E: ParseError<&'i str>,
623    E: TagError<&'i str, &'static str>,
624    E: FromExternalError<&'i str, CharTryFromError>,
625{
626    parse_separated_terminated(
627        parse_chunk,
628        success(()),
629        char('"'),
630        T::empty,
631        |mut string, chunk| {
632            match chunk {
633                StringChunk::Chunk(chunk) => string.push_str(chunk),
634                StringChunk::Char(c) => string.push_char(c),
635            }
636            string
637        },
638    )
639    .or(char('"').map(|_| T::empty()))
640    .cut()
641    .preceded_by(char('"'))
642    .parse(input)
643}
644
645#[cfg(test)]
646mod test_parse_escaped_string {
647    use super::*;
648    use cool_asserts::assert_matches;
649    use nom::error::Error;
650
651    fn typed_parse_identifier(input: &str) -> IResult<&str, KdlString<'_>, Error<&str>> {
652        parse_escaped_string(input)
653    }
654
655    #[test]
656    fn basic() {
657        assert_matches!(
658            typed_parse_identifier("\"hello\" abc"),
659            Ok((
660                " abc",
661                KdlString {
662                    inner: Cow::Borrowed("hello")
663                }
664            ))
665        )
666    }
667
668    #[test]
669    fn with_escape() {
670        assert_matches!(
671            typed_parse_identifier("\"hello \\t world\" abc"),
672            Ok((
673                " abc",
674                KdlString { inner: Cow::Owned(s) }
675            )) => assert_eq!(s, "hello \t world")
676        );
677    }
678
679    #[test]
680    fn with_escaped_unicode() {
681        assert_matches!(
682            typed_parse_identifier("\"hello\\u{0A}world\" abc"),
683            Ok((
684                " abc",
685                KdlString {
686                    inner: Cow::Owned(s)
687                }
688            )) => assert_eq!(s, "hello\nworld")
689        );
690    }
691}
692
693/// Parse a KDL string, which is either a raw or escaped string
694pub fn parse_string<'i, T, E>(input: &'i str) -> IResult<&'i str, T, E>
695where
696    T: StringBuilder<'i>,
697    E: ParseError<&'i str>,
698    E: TagError<&'i str, &'static str>,
699    E: FromExternalError<&'i str, CharTryFromError>,
700    E: ContextError<&'i str, &'static str>,
701{
702    alt((
703        parse_escaped_string.context("escaped string"),
704        parse_raw_string.context("raw string").map(T::from_str),
705    ))
706    .parse(input)
707}
708
709/// Parse a KDL identifier, which is either a bare identifer or a string
710pub fn parse_identifier<'i, T, E>(input: &'i str) -> IResult<&'i str, T, E>
711where
712    T: StringBuilder<'i>,
713    E: ParseError<&'i str>,
714    E: TagError<&'i str, &'static str>,
715    E: FromExternalError<&'i str, CharTryFromError>,
716    E: ContextError<&'i str, &'static str>,
717{
718    alt((
719        parse_bare_identifier
720            .map(T::from_str)
721            .context("bare identifier"),
722        parse_string.context("string"),
723    ))
724    .parse(input)
725}