quoted_string/
parse.rs

1use spec::{ScanAutomaton, GeneralQSSpec,  PartialCodePoint};
2use error::CoreError;
3
4/// validates if input is a valid quoted-string
5///
6/// in difference to parse it requires the whole input to be one quoted-string
7///
8/// # Example
9///
10/// ```
11/// // use your own spec
12/// use quoted_string::test_utils::TestSpec;
13/// use quoted_string::validate;
14///
15/// assert!(validate::<TestSpec>("\"quoted string\""));
16/// assert!(!validate::<TestSpec>("\"not right\"really not"));
17/// ```
18///
19pub fn validate<Spec: GeneralQSSpec>(input: &str) -> bool {
20    parse::<Spec>(input)
21        .map(|res|res.tail.is_empty())
22        .unwrap_or(false)
23}
24
25/// the result of successfully parsing a quoted string
26#[derive(Debug, Clone, Eq, PartialEq, Hash)]
27pub struct Parsed<'a> {
28    /// the parsed quoted string
29    pub quoted_string: &'a str,
30    /// the rest of the input string, not parsed
31    pub tail: &'a str
32}
33
34/// parse a quoted string starting at the begin of `input` but possible ending earlier
35///
36/// To check if the whole string is a quoted-string (an nothing more) you have to
37/// additional check if `parsed.tail` is empty.
38///
39/// # Error
40///
41/// a error and the char index where it was triggered is returned if the input does not start
42/// with a valid quoted-string.
43///
44/// # Example
45///
46/// ```
47/// // use your own Spec
48/// use quoted_string::test_utils::TestSpec;
49/// use quoted_string::{parse, Parsed};
50///
51/// let parsed = parse::<TestSpec>("\"list of\"; \"quoted strings\"").unwrap();
52/// assert_eq!(parsed, Parsed {
53///     quoted_string: "\"list of\"",
54///     tail:  "; \"quoted strings\""
55/// });
56/// ```
57///
58pub fn parse<Impl: GeneralQSSpec>(input: &str) -> Result<Parsed, (usize, CoreError)> {
59    let mut automaton = ScanAutomaton::<Impl::Parsing>::new();
60
61    for (idx, bch) in input.bytes().enumerate() {
62        automaton.advance(PartialCodePoint::from_utf8_byte(bch))
63            .map_err(|err| (idx, err.into()))?;
64
65        if automaton.did_end() {
66            return Ok(Parsed {
67                //idx+1: idx is the idx of the ending '"' which has a byte len of 1
68                quoted_string: &input[0..idx + 1],
69                tail: &input[idx + 1..]
70            })
71        }
72    }
73    // if we reach heare it's a error as the closing '"' is missing.
74    // So .end() will _always_ trigger an error in this position
75    match automaton.end() {
76        Ok(_) =>
77            panic!("[BUG] automaton.did_end() == false but automaton.end() does not trigger error"),
78        Err(err) => {
79            Err((input.len(), err.into()))
80        }
81    }
82}
83
84
85#[cfg(test)]
86mod test {
87
88    mod parse {
89        use test_utils::*;
90        use error::CoreError;
91        use super::super::parse;
92
93        #[test]
94        fn parse_simple() {
95            let parsed = parse::<TestSpec>("\"simple\"").unwrap();
96            assert_eq!(parsed.quoted_string, "\"simple\"");
97            assert_eq!(parsed.tail, "");
98        }
99
100        #[test]
101        fn parse_with_tail() {
102            let parsed = parse::<TestSpec>("\"simple\"; abc").unwrap();
103            assert_eq!(parsed.quoted_string, "\"simple\"");
104            assert_eq!(parsed.tail, "; abc");
105        }
106
107        #[test]
108        fn parse_with_quoted_pairs() {
109            let parsed = parse::<TestSpec>("\"si\\\"m\\\\ple\"").unwrap();
110            assert_eq!(parsed.quoted_string, "\"si\\\"m\\\\ple\"");
111            assert_eq!(parsed.tail, "");
112        }
113
114        #[test]
115        fn parse_with_unnecessary_quoted_pairs() {
116            let parsed = parse::<TestSpec>("\"sim\\p\\le\"").unwrap();
117            assert_eq!(parsed.quoted_string, "\"sim\\p\\le\"");
118            assert_eq!(parsed.tail, "");
119        }
120
121        #[test]
122        fn reject_missing_quoted() {
123            let res = parse::<TestSpec>("simple");
124            assert_eq!(res, Err((0, CoreError::DoesNotStartWithDQuotes)));
125        }
126
127        #[test]
128        fn reject_tailing_escape() {
129            let res = parse::<TestSpec>("\"simple\\\"");
130            assert_eq!(res, Err((9, CoreError::DoesNotEndWithDQuotes)));
131        }
132
133        #[test]
134        fn reject_unquoted_quotable() {
135            let res = parse::<TestSpec>("\"simp\\\0le\"");
136            assert_eq!(res, Err((6, CoreError::UnquoteableCharQuoted)));
137        }
138
139        #[test]
140        fn reject_missing_closing_dquotes() {
141            let res = parse::<TestSpec>("\"simple");
142            assert_eq!(res, Err((7, CoreError::DoesNotEndWithDQuotes)));
143        }
144
145        #[test]
146        fn empty_string_does_not_panic() {
147            let res = parse::<TestSpec>("");
148            assert_eq!(res, Err((0, CoreError::DoesNotEndWithDQuotes)));
149        }
150
151    }
152
153    mod validate {
154        use test_utils::*;
155        use super::super::validate;
156
157        #[test]
158        fn accept_valid_quoted_string() {
159            assert!(validate::<TestSpec>("\"that\\\"s strange\""));
160        }
161
162        #[test]
163        fn reject_invalid_quoted_string() {
164            assert!(!validate::<TestSpec>("ups"))
165        }
166
167        #[test]
168        fn reject_quoted_string_shorter_than_input() {
169            assert!(!validate::<TestSpec>("\"nice!\"ups whats here?\""))
170        }
171
172    }
173
174
175
176}