netidx_value/
parser.rs

1use crate::{pbuf::PBytes, Value};
2use arcstr::ArcStr;
3use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
4use bytes::Bytes;
5use combine::{
6    attempt, between, choice, from_str, many1, none_of, not_followed_by, one_of,
7    optional, parser,
8    parser::{
9        char::{alpha_num, digit, spaces, string},
10        combinator::recognize,
11        range::{take_while, take_while1},
12        repeat::escaped,
13    },
14    sep_by,
15    stream::{position, Range},
16    token, unexpected_any, EasyParser, ParseError, Parser, RangeStream,
17};
18use compact_str::CompactString;
19use netidx_core::utils;
20use rust_decimal::Decimal;
21use std::{borrow::Cow, result::Result, str::FromStr, time::Duration};
22
23pub static VAL_ESC: [char; 2] = ['\\', '"'];
24
25pub fn escaped_string<I>(esc: &'static [char]) -> impl Parser<I, Output = String>
26where
27    I: RangeStream<Token = char>,
28    I::Error: ParseError<I::Token, I::Range, I::Position>,
29    I::Range: Range,
30{
31    recognize(escaped(
32        take_while1(move |c| !esc.contains(&c)),
33        '\\',
34        one_of(esc.iter().copied()),
35    ))
36    .map(|s| match utils::unescape(&s, '\\') {
37        Cow::Borrowed(_) => s, // it didn't need unescaping, so just return it
38        Cow::Owned(s) => s,
39    })
40}
41
42fn quoted<I>(esc: &'static [char]) -> impl Parser<I, Output = String>
43where
44    I: RangeStream<Token = char>,
45    I::Error: ParseError<I::Token, I::Range, I::Position>,
46    I::Range: Range,
47{
48    spaces().with(between(token('"'), token('"'), escaped_string(esc)))
49}
50
51fn uint<I, T: FromStr + Clone + Copy>() -> impl Parser<I, Output = T>
52where
53    I: RangeStream<Token = char>,
54    I::Error: ParseError<I::Token, I::Range, I::Position>,
55    I::Range: Range,
56{
57    many1(digit()).then(|s: CompactString| match s.parse::<T>() {
58        Ok(i) => combine::value(i).right(),
59        Err(_) => unexpected_any("invalid unsigned integer").left(),
60    })
61}
62
63pub fn int<I, T: FromStr + Clone + Copy>() -> impl Parser<I, Output = T>
64where
65    I: RangeStream<Token = char>,
66    I::Error: ParseError<I::Token, I::Range, I::Position>,
67    I::Range: Range,
68{
69    recognize((optional(token('-')), take_while1(|c: char| c.is_digit(10)))).then(
70        |s: CompactString| match s.parse::<T>() {
71            Ok(i) => combine::value(i).right(),
72            Err(_) => unexpected_any("invalid signed integer").left(),
73        },
74    )
75}
76
77fn flt<I, T: FromStr + Clone + Copy>() -> impl Parser<I, Output = T>
78where
79    I: RangeStream<Token = char>,
80    I::Error: ParseError<I::Token, I::Range, I::Position>,
81    I::Range: Range,
82{
83    choice((
84        attempt(recognize((
85            optional(token('-')),
86            take_while1(|c: char| c.is_digit(10)),
87            optional(token('.')),
88            take_while(|c: char| c.is_digit(10)),
89            token('e'),
90            optional(token('-')),
91            take_while1(|c: char| c.is_digit(10)),
92        ))),
93        attempt(recognize((
94            optional(token('-')),
95            take_while1(|c: char| c.is_digit(10)),
96            token('.'),
97            take_while(|c: char| c.is_digit(10)),
98        ))),
99    ))
100    .then(|s: CompactString| match s.parse::<T>() {
101        Ok(i) => combine::value(i).right(),
102        Err(_) => unexpected_any("invalid float").left(),
103    })
104}
105
106fn dcml<I>() -> impl Parser<I, Output = Decimal>
107where
108    I: RangeStream<Token = char>,
109    I::Error: ParseError<I::Token, I::Range, I::Position>,
110    I::Range: Range,
111{
112    recognize((
113        optional(token('-')),
114        take_while1(|c: char| c.is_digit(10)),
115        optional(token('.')),
116        take_while(|c: char| c.is_digit(10)),
117    ))
118    .then(|s: CompactString| match s.parse::<Decimal>() {
119        Ok(i) => combine::value(i).right(),
120        Err(_) => unexpected_any("invalid decimal").left(),
121    })
122}
123
124struct Base64Encoded(Vec<u8>);
125
126impl FromStr for Base64Encoded {
127    type Err = base64::DecodeError;
128
129    fn from_str(s: &str) -> Result<Self, Self::Err> {
130        BASE64.decode(s).map(Base64Encoded)
131    }
132}
133
134fn base64str<I>() -> impl Parser<I, Output = String>
135where
136    I: RangeStream<Token = char>,
137    I::Error: ParseError<I::Token, I::Range, I::Position>,
138    I::Range: Range,
139{
140    recognize((
141        take_while(|c: char| c.is_ascii_alphanumeric() || c == '+' || c == '/'),
142        take_while(|c: char| c == '='),
143    ))
144}
145
146fn constant<I>(typ: &'static str) -> impl Parser<I, Output = char>
147where
148    I: RangeStream<Token = char>,
149    I::Error: ParseError<I::Token, I::Range, I::Position>,
150    I::Range: Range,
151{
152    string(typ).with(token(':'))
153}
154
155pub fn close_expr<I>() -> impl Parser<I, Output = ()>
156where
157    I: RangeStream<Token = char>,
158    I::Error: ParseError<I::Token, I::Range, I::Position>,
159    I::Range: Range,
160{
161    not_followed_by(none_of([' ', '\n', '\t', ';', ')', ',', ']', '}', '"']))
162}
163
164fn value_<I>(esc: &'static [char]) -> impl Parser<I, Output = Value>
165where
166    I: RangeStream<Token = char>,
167    I::Error: ParseError<I::Token, I::Range, I::Position>,
168    I::Range: Range,
169{
170    spaces().with(choice((
171        attempt(
172            between(token('['), token(']'), sep_by(value(esc), token(',')))
173                .map(|vals: Vec<Value>| Value::Array(vals.into())),
174        ),
175        attempt(quoted(esc)).map(|s| Value::String(ArcStr::from(s))),
176        attempt(flt::<_, f64>()).map(Value::F64),
177        attempt(int::<_, i64>()).map(Value::I64),
178        attempt(
179            string("true").skip(not_followed_by(alpha_num())).map(|_| Value::Bool(true)),
180        ),
181        attempt(
182            string("false")
183                .skip(not_followed_by(alpha_num()))
184                .map(|_| Value::Bool(false)),
185        ),
186        attempt(string("null").skip(not_followed_by(alpha_num())).map(|_| Value::Null)),
187        attempt(constant("decimal").with(dcml()).map(Value::Decimal)),
188        attempt(constant("u32").with(uint::<_, u32>()).map(Value::U32)),
189        attempt(constant("v32").with(uint::<_, u32>()).map(Value::V32)),
190        attempt(constant("i32").with(int::<_, i32>()).map(Value::I32)),
191        attempt(constant("z32").with(int::<_, i32>()).map(Value::Z32)),
192        attempt(constant("u64").with(uint::<_, u64>()).map(Value::U64)),
193        attempt(constant("v64").with(uint::<_, u64>()).map(Value::V64)),
194        attempt(constant("i64").with(int::<_, i64>()).map(Value::I64)),
195        attempt(constant("z64").with(int::<_, i64>()).map(Value::Z64)),
196        attempt(constant("f32").with(flt::<_, f32>()).map(Value::F32)),
197        attempt(constant("f64").with(flt::<_, f64>()).map(Value::F64)),
198        attempt(
199            constant("bytes")
200                .with(from_str(base64str()))
201                .map(|Base64Encoded(v)| Value::Bytes(PBytes::new(Bytes::from(v)))),
202        ),
203        attempt(
204            constant("error").with(quoted(esc)).map(|s| Value::Error(ArcStr::from(s))),
205        ),
206        attempt(
207            constant("datetime").with(from_str(quoted(esc))).map(|d| Value::DateTime(d)),
208        ),
209        attempt(
210            constant("duration")
211                .with(flt::<_, f64>().and(choice((
212                    string("ns"),
213                    string("us"),
214                    string("ms"),
215                    string("s"),
216                ))))
217                .map(|(n, suffix)| {
218                    let d = match suffix {
219                        "ns" => Duration::from_secs_f64(n / 1e9),
220                        "us" => Duration::from_secs_f64(n / 1e6),
221                        "ms" => Duration::from_secs_f64(n / 1e3),
222                        "s" => Duration::from_secs_f64(n),
223                        _ => unreachable!(),
224                    };
225                    Value::Duration(d)
226                }),
227        ),
228    )))
229}
230
231parser! {
232    pub fn value[I](escaped: &'static [char])(I) -> Value
233    where [I: RangeStream<Token = char>, I::Range: Range]
234    {
235        value_(escaped)
236    }
237}
238
239pub fn parse_value(s: &str) -> anyhow::Result<Value> {
240    value(&VAL_ESC)
241        .easy_parse(position::Stream::new(s))
242        .map(|(r, _)| r)
243        .map_err(|e| anyhow::anyhow!(format!("{}", e)))
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn parse() {
252        assert_eq!(Value::U32(23), parse_value("u32:23").unwrap());
253        assert_eq!(Value::V32(42), parse_value("v32:42").unwrap());
254        assert_eq!(Value::I32(-10), parse_value("i32:-10").unwrap());
255        assert_eq!(Value::I32(12321), parse_value("i32:12321").unwrap());
256        assert_eq!(Value::Z32(-99), parse_value("z32:-99").unwrap());
257        assert_eq!(Value::U64(100), parse_value("u64:100").unwrap());
258        assert_eq!(Value::V64(100), parse_value("v64:100").unwrap());
259        assert_eq!(Value::I64(-100), parse_value("i64:-100").unwrap());
260        assert_eq!(Value::I64(-100), parse_value("-100").unwrap());
261        assert_eq!(Value::I64(100), parse_value("i64:100").unwrap());
262        assert_eq!(Value::I64(100), parse_value("100").unwrap());
263        assert_eq!(Value::Z64(-100), parse_value("z64:-100").unwrap());
264        assert_eq!(Value::Z64(100), parse_value("z64:100").unwrap());
265        assert_eq!(Value::F32(3.1415), parse_value("f32:3.1415").unwrap());
266        assert_eq!(Value::F32(675.6), parse_value("f32:675.6").unwrap());
267        assert_eq!(Value::F32(42.3435), parse_value("f32:42.3435").unwrap());
268        assert_eq!(Value::F32(1.123e9), parse_value("f32:1.123e9").unwrap());
269        assert_eq!(Value::F32(1e9), parse_value("f32:1e9").unwrap());
270        assert_eq!(Value::F32(21.2443e-6), parse_value("f32:21.2443e-6").unwrap());
271        assert_eq!(Value::F32(3.), parse_value("f32:3.").unwrap());
272        assert_eq!(Value::F64(3.1415), parse_value("f64:3.1415").unwrap());
273        assert_eq!(Value::F64(3.1415), parse_value("3.1415").unwrap());
274        assert_eq!(Value::F64(1.123e9), parse_value("1.123e9").unwrap());
275        assert_eq!(Value::F64(1e9), parse_value("1e9").unwrap());
276        assert_eq!(Value::F64(21.2443e-6), parse_value("21.2443e-6").unwrap());
277        assert_eq!(Value::F64(3.), parse_value("f64:3.").unwrap());
278        assert_eq!(Value::F64(3.), parse_value("3.").unwrap());
279        let c = ArcStr::from(r#"I've got a lovely "bunch" of (coconuts)"#);
280        let s = r#""I've got a lovely \"bunch\" of (coconuts)""#;
281        assert_eq!(Value::String(c), parse_value(s).unwrap());
282        let c = ArcStr::new();
283        assert_eq!(Value::String(c), parse_value(r#""""#).unwrap());
284        let c = ArcStr::from(r#"""#);
285        let s = r#""\"""#;
286        assert_eq!(Value::String(c), parse_value(s).unwrap());
287        assert_eq!(Value::Bool(true), parse_value("true").unwrap());
288        assert_eq!(Value::Bool(true), parse_value("true ").unwrap());
289        assert_eq!(Value::Bool(false), parse_value("false").unwrap());
290        assert_eq!(Value::Null, parse_value("null").unwrap());
291        assert_eq!(
292            Value::Error(ArcStr::from("error")),
293            parse_value(r#"error:"error""#).unwrap()
294        );
295    }
296}