m_o/value/
parse.rs

1use std::convert::TryFrom;
2
3use nom::{
4    branch::alt,
5    bytes::complete::{escaped, is_not, tag},
6    character::complete::{char, digit1, multispace0, one_of},
7    combinator::{map, not, opt, recognize},
8    multi::separated_list,
9    number::complete::double,
10    re_find,
11    sequence::{delimited, preceded, terminated, tuple},
12    IResult,
13};
14
15use super::Value;
16use crate::value::Arg;
17use crate::value::Arg::Kwarg;
18
19pub fn parse_bool(input: &str) -> IResult<&str, Value> {
20    alt((
21        map(tag("True"), |_| Value::Bool(true)),
22        map(tag("False"), |_| Value::Bool(false)),
23    ))(input)
24}
25
26pub fn parse_str(input: &str) -> IResult<&str, Value> {
27    // See: https://python-reference.readthedocs.io/en/latest/docs/str/escapes.html
28    //
29    // \a           ASCII bell
30    // \b           ASCII backspace
31    // \f           ASCII formfeed
32    // \n           ASCII linefeed
33    // \N{name}     character named NAME in the Unicode database
34    // \r           ASCII carriage return
35    // \t           ASCII horizontal tab
36    // \uxxxx       character with 16-bit hex value XXXX
37    // \Uxxxxxxxx   character with 32-bit hex value XXXXXXXX
38    // \v           ASCII vertical tab
39    // \ooo         character with octal value OOO
40    // \hxx         Character with hex value XX
41    let single_quoted_str_escape = r#"\'abfnNrtuUvx01234567"#;
42    let double_quoted_str_escape = r#"\"abfnNrtuUvx01234567"#;
43
44    let single_quoted = recognize(delimited(
45        char('\''),
46        escaped(is_not(r#"'\"#), '\\', one_of(single_quoted_str_escape)),
47        char('\''),
48    ));
49    let double_quoted = recognize(delimited(
50        char('"'),
51        escaped(is_not(r#""\"#), '\\', one_of(double_quoted_str_escape)),
52        char('"'),
53    ));
54    map(alt((single_quoted, double_quoted)), Value::Str)(input)
55}
56
57pub fn parse_int(input: &str) -> IResult<&str, Value> {
58    map(
59        tuple((opt(tag("-")), terminated(digit1, not(tag("."))))),
60        |(sign, s): (Option<&str>, &str)| {
61            let sign = if sign.is_some() { -1 } else { 1 };
62            let i = s
63                .parse::<i64>()
64                .expect("sequence of digits can parse to int");
65            Value::Int(sign * i)
66        },
67    )(input)
68}
69
70pub fn parse_float(input: &str) -> IResult<&str, Value> {
71    map(double, Value::Float)(input)
72}
73
74fn parse_seq<'a>(
75    open: char,
76    f: impl Fn(Vec<Value<'a>>) -> Value<'a>,
77    close: char,
78) -> impl Fn(&'a str) -> IResult<&'a str, Value<'a>> {
79    move |input: &'a str| -> IResult<&'a str, Value> {
80        map(
81            delimited(
82                char(open),
83                separated_list(comma_space, parse_value),
84                char(close),
85            ),
86            &f,
87        )(input)
88    }
89}
90
91pub fn parse_list(input: &str) -> IResult<&str, Value> {
92    parse_seq('[', Value::List, ']')(input)
93}
94
95pub fn parse_tuple(input: &str) -> IResult<&str, Value> {
96    parse_seq('(', Value::Tuple, ')')(input)
97}
98
99pub fn parse_set(input: &str) -> IResult<&str, Value> {
100    parse_seq('{', Value::Set, '}')(input)
101}
102
103fn colon_space(input: &str) -> IResult<&str, ()> {
104    map(preceded(char(':'), multispace0), |_| ())(input)
105}
106
107fn parse_dict_key_value(input: &str) -> IResult<&str, (Value, Value)> {
108    tuple((parse_value, preceded(colon_space, parse_value)))(input)
109}
110
111fn comma_space(input: &str) -> IResult<&str, ()> {
112    map(preceded(char(','), multispace0), |_| ())(input)
113}
114
115pub fn parse_dict(input: &str) -> IResult<&str, Value> {
116    map(
117        delimited(
118            char('{'),
119            separated_list(comma_space, parse_dict_key_value),
120            char('}'),
121        ),
122        Value::Dict,
123    )(input)
124}
125
126fn identifier(input: &str) -> IResult<&str, &str> {
127    re_find!(
128        input,
129        r"^([a-zA-Z_][a-zA-Z0-9_]*)(\.[a-zA-Z_][a-zA-Z0-9_]*)*"
130    )
131}
132
133pub fn parse_symbol(input: &str) -> IResult<&str, Value> {
134    map(identifier, Value::Symbol)(input)
135}
136
137fn parse_arg(input: &str) -> IResult<&str, Arg> {
138    alt((
139        map(
140            tuple((identifier, preceded(char('='), parse_value))),
141            |(ident, value)| Kwarg(ident, value),
142        ),
143        map(parse_value, Arg::Arg),
144    ))(input)
145}
146
147pub fn parse_constructor(input: &str) -> IResult<&str, Value> {
148    map(
149        tuple((
150            identifier,
151            delimited(char('('), separated_list(comma_space, parse_arg), char(')')),
152        )),
153        |(name, kwargs)| Value::Constructor(name, kwargs),
154    )(input)
155}
156
157pub fn parse_value(input: &str) -> IResult<&str, Value> {
158    alt((
159        parse_int,
160        parse_float, // Appears after int parser because f64 is superset of i64
161        parse_bool,
162        parse_str,
163        parse_list,
164        parse_tuple,
165        parse_dict,
166        parse_set, // Appears after dict parser because `{}` is a dict, not a set.
167        parse_constructor,
168        parse_symbol,
169    ))(input)
170}
171
172impl<'a> TryFrom<&'a str> for Value<'a> {
173    type Error = nom::Err<(&'a str, nom::error::ErrorKind)>;
174
175    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
176        match parse_value(input) {
177            Ok((_rest, value)) => Ok(value),
178            Err(err) => Err(err),
179        }
180    }
181}