netidx_bscript/
expr.rs

1use crate::parser;
2use netidx::{subscriber::Value, utils};
3use regex::Regex;
4use serde::{
5    de::{self, Visitor},
6    Deserialize, Deserializer, Serialize, Serializer,
7};
8use std::{
9    cmp::{Ordering, PartialEq, PartialOrd},
10    fmt::{self, Write},
11    result,
12    str::FromStr,
13};
14
15lazy_static! {
16    pub static ref VNAME: Regex = Regex::new("^[a-z][a-z0-9_]*$").unwrap();
17}
18
19atomic_id!(ExprId);
20
21#[derive(Debug, Clone, PartialOrd, PartialEq)]
22pub enum ExprKind {
23    Constant(Value),
24    Apply { args: Vec<Expr>, function: String },
25}
26
27impl ExprKind {
28    pub fn to_expr(self) -> Expr {
29        Expr { id: ExprId::new(), kind: self }
30    }
31
32    pub fn to_string_pretty(&self, col_limit: usize) -> String {
33        let mut buf = String::new();
34        self.pretty_print(0, col_limit, &mut buf).unwrap();
35        buf
36    }
37
38    fn pretty_print(&self, indent: usize, limit: usize, buf: &mut String) -> fmt::Result {
39        fn push_indent(indent: usize, buf: &mut String) {
40            buf.extend((0..indent).into_iter().map(|_| ' '));
41        }
42        match self {
43            ExprKind::Constant(_) => {
44                push_indent(indent, buf);
45                write!(buf, "{}", self)
46            }
47            ExprKind::Apply { function, args } => {
48                let mut tmp = String::new();
49                push_indent(indent, &mut tmp);
50                write!(tmp, "{}", self)?;
51                if tmp.len() < limit {
52                    buf.push_str(&*tmp);
53                    Ok(())
54                } else {
55                    if function == "string_concat" {
56                        buf.push_str(&*tmp);
57                        Ok(())
58                    } else if function == "get" && args.len() == 1 && args[0].is_fn() {
59                        buf.push_str(&*tmp);
60                        Ok(())
61                    } else if (function == "set" || function == "let")
62                        && args.len() == 2
63                        && args[0].is_fn()
64                    {
65                        match &args[0].kind {
66                            ExprKind::Constant(Value::String(c)) => {
67                                push_indent(indent, buf);
68                                let local = if function == "let" { "let " } else { "" };
69                                writeln!(buf, "{}{} <-", local, c)?;
70                                args[1].kind.pretty_print(indent + 2, limit, buf)
71                            }
72                            _ => unreachable!(),
73                        }
74                    } else if function == "do" {
75                        push_indent(indent, buf);
76                        writeln!(buf, "{}", "{")?;
77                        for i in 0..args.len() {
78                            args[i].kind.pretty_print(indent + 2, limit, buf)?;
79                            if i < args.len() - 1 {
80                                writeln!(buf, ";")?
81                            } else {
82                                writeln!(buf, "")?
83                            }
84                        }
85                        push_indent(indent, buf);
86                        writeln!(buf, "{}", "}")
87                    } else if function == "array" {
88                        push_indent(indent, buf);
89                        writeln!(buf, "{}", "[")?;
90                        for i in 0..args.len() {
91                            args[i].kind.pretty_print(indent + 2, limit, buf)?;
92                            if i < args.len() - 1 {
93                                writeln!(buf, ",")?
94                            } else {
95                                writeln!(buf, "")?
96                            }
97                        }
98                        push_indent(indent, buf);
99                        writeln!(buf, "{}", "]")
100                    } else {
101                        push_indent(indent, buf);
102                        writeln!(buf, "{}(", function)?;
103                        for i in 0..args.len() {
104                            args[i].kind.pretty_print(indent + 2, limit, buf)?;
105                            if i < args.len() - 1 {
106                                writeln!(buf, ", ")?;
107                            } else {
108                                writeln!(buf, "")?;
109                            }
110                        }
111                        push_indent(indent, buf);
112                        writeln!(buf, ")")
113                    }
114                }
115            }
116        }
117    }
118}
119
120impl fmt::Display for ExprKind {
121    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122        match self {
123            ExprKind::Constant(v) => v.fmt_ext(f, &parser::BSCRIPT_ESC, true),
124            ExprKind::Apply { args, function } => {
125                if function == "string_concat" && args.len() > 0 {
126                    // interpolation
127                    write!(f, "\"")?;
128                    for s in args {
129                        match &s.kind {
130                            ExprKind::Constant(Value::String(s)) if s.len() > 0 => {
131                                write!(
132                                    f,
133                                    "{}",
134                                    utils::escape(&*s, '\\', &parser::BSCRIPT_ESC)
135                                )?;
136                            }
137                            s => {
138                                write!(f, "[{}]", s)?;
139                            }
140                        }
141                    }
142                    write!(f, "\"")
143                } else if function == "get" && args.len() == 1 && args[0].is_fn() {
144                    // constant variable load
145                    match &args[0].kind {
146                        ExprKind::Constant(Value::String(c)) => write!(f, "{}", c),
147                        _ => unreachable!(),
148                    }
149                } else if (function == "set" || function == "let")
150                    && args.len() == 2
151                    && args[0].is_fn()
152                {
153                    // constant variable store
154                    match &args[0].kind {
155                        ExprKind::Constant(Value::String(c)) => {
156                            let local = if function == "let" { "let " } else { "" };
157                            write!(f, "{}{} <- {}", local, c, &args[1])
158                        }
159                        _ => unreachable!(),
160                    }
161                } else if function == "do" {
162                    // do block
163                    write!(f, "{}", '{')?;
164                    for i in 0..args.len() {
165                        if i < args.len() - 1 {
166                            write!(f, "{};", &args[i])?
167                        } else {
168                            write!(f, "{}", &args[i])?
169                        }
170                    }
171                    write!(f, "{}", '}')
172                } else if function == "array" {
173                    write!(f, "{}", '[')?;
174                    for i in 0..args.len() {
175                        if i < args.len() - 1 {
176                            write!(f, "{},", &args[i])?
177                        } else {
178                            write!(f, "{}", &args[i])?
179                        }
180                    }
181                    write!(f, "{}", ']')
182                } else {
183                    // it's a normal function
184                    write!(f, "{}(", function)?;
185                    for i in 0..args.len() {
186                        write!(f, "{}", &args[i])?;
187                        if i < args.len() - 1 {
188                            write!(f, ", ")?;
189                        }
190                    }
191                    write!(f, ")")
192                }
193            }
194        }
195    }
196}
197
198#[derive(Debug, Clone)]
199pub struct Expr {
200    pub id: ExprId,
201    pub kind: ExprKind,
202}
203
204impl PartialOrd for Expr {
205    fn partial_cmp(&self, rhs: &Expr) -> Option<Ordering> {
206        self.kind.partial_cmp(&rhs.kind)
207    }
208}
209
210impl PartialEq for Expr {
211    fn eq(&self, rhs: &Expr) -> bool {
212        self.kind.eq(&rhs.kind)
213    }
214}
215
216impl Eq for Expr {}
217
218impl Serialize for Expr {
219    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220    where
221        S: Serializer,
222    {
223        serializer.serialize_str(&self.to_string())
224    }
225}
226
227impl Default for Expr {
228    fn default() -> Self {
229        ExprKind::Constant(Value::Null).to_expr()
230    }
231}
232
233#[derive(Clone, Copy)]
234struct ExprVisitor;
235
236impl<'de> Visitor<'de> for ExprVisitor {
237    type Value = Expr;
238
239    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
240        write!(formatter, "expected expression")
241    }
242
243    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
244    where
245        E: de::Error,
246    {
247        Expr::from_str(s).map_err(de::Error::custom)
248    }
249
250    fn visit_borrowed_str<E>(self, s: &'de str) -> Result<Self::Value, E>
251    where
252        E: de::Error,
253    {
254        Expr::from_str(s).map_err(de::Error::custom)
255    }
256
257    fn visit_string<E>(self, s: String) -> Result<Self::Value, E>
258    where
259        E: de::Error,
260    {
261        Expr::from_str(&s).map_err(de::Error::custom)
262    }
263}
264
265impl<'de> Deserialize<'de> for Expr {
266    fn deserialize<D>(de: D) -> Result<Self, D::Error>
267    where
268        D: Deserializer<'de>,
269    {
270        de.deserialize_str(ExprVisitor)
271    }
272}
273
274impl Expr {
275    pub fn new(kind: ExprKind) -> Self {
276        Expr { id: ExprId::new(), kind }
277    }
278
279    pub fn is_fn(&self) -> bool {
280        match &self.kind {
281            ExprKind::Constant(Value::String(c)) => VNAME.is_match(&*c),
282            ExprKind::Constant(_) | ExprKind::Apply { .. } => false,
283        }
284    }
285
286    pub fn to_string_pretty(&self, col_limit: usize) -> String {
287        self.kind.to_string_pretty(col_limit)
288    }
289}
290
291impl fmt::Display for Expr {
292    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293        write!(f, "{}", self.kind)
294    }
295}
296
297impl FromStr for Expr {
298    type Err = anyhow::Error;
299
300    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
301        parser::parse_expr(s)
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308    use bytes::Bytes;
309    use chrono::prelude::*;
310    use netidx_core::chars::Chars;
311    use proptest::{collection, prelude::*};
312    use std::time::Duration;
313
314    fn datetime() -> impl Strategy<Value = DateTime<Utc>> {
315        (
316            DateTime::<Utc>::MIN_UTC.timestamp()..DateTime::<Utc>::MAX_UTC.timestamp(),
317            0..1_000_000_000u32,
318        )
319            .prop_map(|(s, ns)| Utc.timestamp_opt(s, ns).unwrap())
320    }
321
322    fn duration() -> impl Strategy<Value = Duration> {
323        (any::<u64>(), 0..1_000_000_000u32).prop_map(|(s, ns)| Duration::new(s, ns))
324    }
325
326    fn bytes() -> impl Strategy<Value = Bytes> {
327        any::<Vec<u8>>().prop_map(Bytes::from)
328    }
329
330    fn chars() -> impl Strategy<Value = Chars> {
331        any::<String>().prop_map(Chars::from)
332    }
333
334    fn value() -> impl Strategy<Value = Value> {
335        prop_oneof![
336            any::<u32>().prop_map(Value::U32),
337            any::<u32>().prop_map(Value::V32),
338            any::<i32>().prop_map(Value::I32),
339            any::<i32>().prop_map(Value::Z32),
340            any::<u64>().prop_map(Value::U64),
341            any::<u64>().prop_map(Value::V64),
342            any::<i64>().prop_map(Value::I64),
343            any::<i64>().prop_map(Value::Z64),
344            any::<f32>().prop_map(Value::F32),
345            any::<f64>().prop_map(Value::F64),
346            datetime().prop_map(Value::DateTime),
347            duration().prop_map(Value::Duration),
348            chars().prop_map(Value::String),
349            bytes().prop_map(Value::Bytes),
350            Just(Value::True),
351            Just(Value::False),
352            Just(Value::Null),
353            Just(Value::Ok),
354            chars().prop_map(Value::Error),
355        ]
356    }
357
358    prop_compose! {
359        fn random_fname()(s in "[a-z][a-z0-9_]*".prop_filter("Filter reserved words", |s| {
360            s != "ok"
361                && s != "true"
362                && s != "false"
363                && s != "null"
364                && s != "load"
365                && s != "store"
366                && s != "load_var"
367                && s != "store_var"
368        })) -> String {
369            s
370        }
371    }
372
373    fn valid_fname() -> impl Strategy<Value = String> {
374        prop_oneof![
375            Just(String::from("any")),
376            Just(String::from("array")),
377            Just(String::from("all")),
378            Just(String::from("sum")),
379            Just(String::from("product")),
380            Just(String::from("divide")),
381            Just(String::from("mean")),
382            Just(String::from("min")),
383            Just(String::from("max")),
384            Just(String::from("and")),
385            Just(String::from("or")),
386            Just(String::from("not")),
387            Just(String::from("cmp")),
388            Just(String::from("if")),
389            Just(String::from("filter")),
390            Just(String::from("cast")),
391            Just(String::from("isa")),
392            Just(String::from("eval")),
393            Just(String::from("count")),
394            Just(String::from("sample")),
395            Just(String::from("string_join")),
396            Just(String::from("string_concat")),
397            Just(String::from("navigate")),
398            Just(String::from("confirm")),
399            Just(String::from("load")),
400            Just(String::from("get")),
401            Just(String::from("store")),
402            Just(String::from("set")),
403            Just(String::from("let")),
404        ]
405    }
406
407    fn fname() -> impl Strategy<Value = String> {
408        prop_oneof![random_fname(), valid_fname(),]
409    }
410
411    fn expr() -> impl Strategy<Value = Expr> {
412        let leaf = value().prop_map(|v| ExprKind::Constant(v).to_expr());
413        leaf.prop_recursive(100, 1000000, 10, |inner| {
414            prop_oneof![(collection::vec(inner, (0, 10)), fname()).prop_map(|(s, f)| {
415                ExprKind::Apply { function: f, args: s }.to_expr()
416            })]
417        })
418    }
419
420    fn acc_strings(args: &Vec<Expr>) -> Vec<Expr> {
421        let mut v: Vec<Expr> = Vec::new();
422        for s in args {
423            let s = s.clone();
424            match s.kind {
425                ExprKind::Constant(Value::String(ref c1)) => match v.last_mut() {
426                    None => v.push(s),
427                    Some(e0) => match &mut e0.kind {
428                        ExprKind::Constant(Value::String(c0))
429                            if c1.len() > 0 && c0.len() > 0 =>
430                        {
431                            let mut st = String::new();
432                            st.push_str(&*c0);
433                            st.push_str(&*c1);
434                            *c0 = Chars::from(st);
435                        }
436                        _ => v.push(s),
437                    },
438                },
439                _ => v.push(s),
440            }
441        }
442        v
443    }
444
445    fn check(s0: &Expr, s1: &Expr) -> bool {
446        match (&s0.kind, &s1.kind) {
447            (ExprKind::Constant(v0), ExprKind::Constant(v1)) => match (v0, v1) {
448                (Value::Duration(d0), Value::Duration(d1)) => {
449                    let f0 = d0.as_secs_f64();
450                    let f1 = d1.as_secs_f64();
451                    f0 == f1 || (f0 != 0. && f1 != 0. && ((f0 - f1).abs() / f0) < 1e-8)
452                }
453                (Value::F32(v0), Value::F32(v1)) => v0 == v1 || (v0 - v1).abs() < 1e-7,
454                (Value::F64(v0), Value::F64(v1)) => v0 == v1 || (v0 - v1).abs() < 1e-8,
455                (v0, v1) => dbg!(dbg!(v0) == dbg!(v1)),
456            },
457            (
458                ExprKind::Apply { args: srs0, function: fn0 },
459                ExprKind::Constant(Value::String(c1)),
460            ) if fn0 == "string_concat" => match &acc_strings(srs0)[..] {
461                [Expr { kind: ExprKind::Constant(Value::String(c0)), .. }] => c0 == c1,
462                _ => false,
463            },
464            (
465                ExprKind::Apply { args: srs0, function: fn0 },
466                ExprKind::Apply { args: srs1, function: fn1 },
467            ) if fn0 == fn1 && fn0.as_str() == "string_concat" => {
468                let srs0 = acc_strings(srs0);
469                srs0.iter().zip(srs1.iter()).fold(true, |r, (s0, s1)| r && check(s0, s1))
470            }
471            (
472                ExprKind::Apply { args: srs0, function: f0 },
473                ExprKind::Apply { args: srs1, function: f1 },
474            ) if f0 == f1 && srs0.len() == srs1.len() => {
475                srs0.iter().zip(srs1.iter()).fold(true, |r, (s0, s1)| r && check(s0, s1))
476            }
477            (_, _) => false,
478        }
479    }
480
481    proptest! {
482        #[test]
483        fn expr_round_trip(s in expr()) {
484            assert!(check(dbg!(&s), &dbg!(dbg!(s.to_string()).parse::<Expr>().unwrap())))
485        }
486
487        #[test]
488        fn expr_pp_round_trip(s in expr()) {
489            assert!(check(dbg!(&s), &dbg!(dbg!(s.to_string_pretty(80)).parse::<Expr>().unwrap())))
490        }
491    }
492}