figment/value/
parse.rs

1use pear::{parse_error, parsers::*};
2use pear::combinators::*;
3use pear::macros::{parse, parser, switch};
4use pear::input::{Pear, Text};
5
6use crate::value::{Value, Dict, escape::escape};
7
8type Input<'a> = Pear<Text<'a>>;
9type Result<'a, T> = pear::input::Result<T, Input<'a>>;
10
11#[inline(always)]
12fn is_whitespace(&byte: &char) -> bool {
13    byte.is_ascii_whitespace()
14}
15
16#[inline(always)]
17fn is_not_separator(&byte: &char) -> bool {
18    !matches!(byte, ',' | '{' | '}' | '[' | ']')
19}
20
21// TODO: Be more permissive here?
22#[inline(always)]
23fn is_ident_char(&byte: &char) -> bool {
24    byte.is_ascii_alphanumeric() || byte == '_' || byte == '-'
25}
26
27#[parser]
28fn string<'a>(input: &mut Input<'a>) -> Result<'a, String> {
29    let mut is_escaped = false;
30    let str_char = |&c: &char| -> bool {
31        if is_escaped { is_escaped = false; return true; }
32        if c == '\\' { is_escaped = true; return true; }
33        c != '"'
34    };
35
36    let inner = (eat('"')?, take_while(str_char)?, eat('"')?).1;
37    match escape(inner) {
38        Ok(string) => string.into_owned(),
39        Err(e) => parse_error!("invalid string: {}", e)?,
40    }
41}
42
43#[parser]
44fn key<'a>(input: &mut Input<'a>) -> Result<'a, String> {
45    switch! {
46        peek('"') => Ok(string()?),
47        _ => Ok(take_some_while(is_ident_char)?.to_string())
48    }
49}
50
51#[parser]
52fn key_value<'a>(input: &mut Input<'a>) -> Result<'a, (String, Value)> {
53    let key = (surrounded(key, is_whitespace)?, eat('=')?).0;
54    (key, surrounded(value, is_whitespace)?)
55}
56
57#[parser]
58fn array<'a>(input: &mut Input<'a>) -> Result<'a, Vec<Value>> {
59    Ok(delimited_collect('[', value, ',', ']')?)
60}
61
62#[parser]
63fn dict<'a>(input: &mut Input<'a>) -> Result<'a, Dict> {
64    Ok(delimited_collect('{', key_value, ',', '}')?)
65}
66
67#[parser]
68fn value<'a>(input: &mut Input<'a>) -> Result<'a, Value> {
69    skip_while(is_whitespace)?;
70    let val = switch! {
71        eat_slice("true") => Value::from(true),
72        eat_slice("false") => Value::from(false),
73        peek('{') => Value::from(dict()?),
74        peek('[') => Value::from(array()?),
75        peek('"') => Value::from(string()?),
76        peek('\'') => Value::from((eat('\'')?, eat_any()?, eat('\'')?).1),
77        _ => {
78            let value = take_while(is_not_separator)?.trim();
79            if value.contains('.') {
80                if let Ok(float) = value.parse::<f64>() {
81                    return Ok(Value::from(float));
82                }
83            }
84
85            if let Ok(int) = value.parse::<usize>() {
86                Value::from(int)
87            } else if let Ok(int) = value.parse::<isize>() {
88                Value::from(int)
89            } else {
90                Value::from(value.to_string())
91            }
92        }
93    };
94
95    skip_while(is_whitespace)?;
96    val
97}
98
99impl std::str::FromStr for Value {
100    type Err = std::convert::Infallible;
101
102    fn from_str(s: &str) -> std::result::Result<Self, std::convert::Infallible> {
103        Ok(parse!(value: Text::from(s))
104            .unwrap_or_else(|_| Value::from(s.to_string())))
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::util::map;
112
113    macro_rules! assert_parse_eq {
114        ($string:expr => $expected:expr) => ({
115            let expected = Value::from($expected);
116            let actual: Value = $string.parse().unwrap();
117            assert_eq!(actual, expected, "got {:?}, expected {:?}", actual, expected);
118        });
119
120        ($($string:expr => $expected:expr $(,)?)*) => (
121            $(assert_parse_eq!($string => $expected);)*
122        )
123    }
124
125    #[test]
126    #[allow(clippy::approx_constant)] // false positive: using the PI constant would be wrong here
127    fn check_simple_values_parse() {
128        assert_parse_eq! {
129            "true" => true,
130            "false" => false,
131            "\"false\"" => "false",
132            "\"true\"" => "true",
133            "  false" => false,
134            "  false  " => false,
135            "true  " => true,
136            "1" => 1u8,
137            " 0" => 0u8,
138            " -0" => 0i8,
139            " -2" => -2,
140            " 123 " => 123u8,
141            "\"a\"" => "a",
142            "a " => "a",
143            "   a " => "a",
144            "\" a\"" => " a",
145            "\"a  \"" => "a  ",
146            "\" a  \"" => " a  ",
147            "1.2" => 1.2,
148            "  1.2" => 1.2,
149            "3.14159" => 3.14159,
150            "\"\\t\"" => "\t",
151            "\"abc\\td\"" => "abc\td",
152            "\"abc\\td\\n\"" => "abc\td\n",
153            "\"abc\\td\\n\\n\"" => "abc\td\n\n",
154            "\"abc\\td\"" => "abc\td",
155            "\"\\\"\"" => "\"",
156            "\"\\n\\f\\b\\\\\\r\\\"\"" => "\n\u{c}\u{8}\\\r\"",
157            "\"\\\"hi\\\"\"" => "\"hi\"",
158            "\"hi\\u1234there\"" => "hi\u{1234}there",
159            "\"\\\\\"" => "\\",
160            // unterminated strings pass through as themselves
161            "\"\\\"" => "\"\\\"",
162        }
163    }
164
165    #[test]
166    fn check_compund_values_parse() {
167        fn v<T: Into<Value>>(v: T) -> Value { v.into() }
168
169        assert_parse_eq! {
170            "[1,2,3]" => vec![1u8, 2u8, 3u8],
171            "{a=b}" => map!["a" => "b"],
172            "{\"a\"=b}" => map!["a" => "b"],
173            "{\"a.b.c\"=b}" => map!["a.b.c" => "b"],
174            "{a=1,b=3}" => map!["a" => 1u8, "b" => 3u8],
175            "{a=1,b=hi}" => map!["a" => v(1u8), "b" => v("hi")],
176            "[1,[2],3]" => vec![v(1u8), v(vec![2u8]), v(3u8)],
177            "{a=[[-2]]}" => map!["a" => vec![vec![-2]]],
178            "{a=[[-2]],b=\" hi\"}" => map!["a" => v(vec![vec![-2]]), "b" => v(" hi")],
179            "[1,true,hi,\"a \"]" => vec![v(1u8), v(true), v("hi"), v("a ")],
180            "[1,{a=b},hi]" => vec![v(1u8), v(map!["a" => "b"]), v("hi")],
181            "[[ -1], {a=[ b ]},  hi ]" =>
182                vec![v(vec![-1]), v(map!["a" => vec!["b"]]), v("hi")],
183        }
184    }
185}