rpsl/expr/
action.rs

1use std::fmt;
2
3#[cfg(any(test, feature = "arbitrary"))]
4use proptest::{arbitrary::ParamsFor, prelude::*};
5
6use crate::{
7    containers::ListOf,
8    error::{ParseError, ParseResult},
9    parser::{
10        debug_construction, impl_case_insensitive_str_primitive, impl_from_str, next_into_or,
11        rule_mismatch, ParserRule, TokenPair,
12    },
13};
14
15#[cfg(any(test, feature = "arbitrary"))]
16use crate::primitive::arbitrary::impl_rpsl_name_arbitrary;
17
18/// RPSL `action` expression. See [RFC2622].
19///
20/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-6.1.1
21#[allow(clippy::module_name_repetitions)]
22pub type ActionExpr = Expr;
23impl_from_str!(ParserRule::just_action_expr => Expr);
24
25#[derive(Clone, Debug, Hash, PartialEq, Eq)]
26pub struct Expr(Vec<Stmt>);
27
28impl TryFrom<TokenPair<'_>> for Expr {
29    type Error = ParseError;
30
31    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
32        debug_construction!(pair => Expr);
33        match pair.as_rule() {
34            ParserRule::action_expr => Ok(Self(
35                pair.into_inner()
36                    .map(Stmt::try_from)
37                    .collect::<ParseResult<_>>()?,
38            )),
39            _ => Err(rule_mismatch!(pair => "action expression")),
40        }
41    }
42}
43
44impl fmt::Display for Expr {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        let mut s = self
47            .0
48            .iter()
49            .map(Stmt::to_string)
50            .collect::<Vec<_>>()
51            .join("; ");
52        s.push(';');
53        s.fmt(f)
54    }
55}
56
57#[cfg(any(test, feature = "arbitrary"))]
58impl Arbitrary for Expr {
59    type Parameters = ParamsFor<Stmt>;
60    type Strategy = BoxedStrategy<Self>;
61    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
62        prop::collection::vec(any_with::<Stmt>(params), 1..8)
63            .prop_map(Self)
64            .boxed()
65    }
66}
67
68#[derive(Clone, Debug, Hash, PartialEq, Eq)]
69pub enum Stmt {
70    Operator(OperatorStmt),
71    Method(MethodStmt),
72}
73
74impl TryFrom<TokenPair<'_>> for Stmt {
75    type Error = ParseError;
76
77    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
78        debug_construction!(pair => Stmt);
79        match pair.as_rule() {
80            ParserRule::action_stmt_oper => Ok(Self::Operator(pair.try_into()?)),
81            ParserRule::action_stmt_meth => Ok(Self::Method(pair.try_into()?)),
82            _ => Err(rule_mismatch!(pair => "action expression statement")),
83        }
84    }
85}
86
87impl fmt::Display for Stmt {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        match self {
90            Self::Operator(stmt) => stmt.fmt(f),
91            Self::Method(stmt) => stmt.fmt(f),
92        }
93    }
94}
95
96#[cfg(any(test, feature = "arbitrary"))]
97impl Arbitrary for Stmt {
98    type Parameters = ParamsFor<MethodStmt>;
99    type Strategy = BoxedStrategy<Self>;
100    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
101        prop_oneof![
102            any::<OperatorStmt>().prop_map(Self::Operator),
103            any_with::<MethodStmt>(params).prop_map(Self::Method),
104        ]
105        .boxed()
106    }
107}
108
109#[derive(Clone, Debug, Hash, PartialEq, Eq)]
110pub struct OperatorStmt {
111    prop: Property,
112    op: Operator,
113    val: Value,
114}
115
116impl TryFrom<TokenPair<'_>> for OperatorStmt {
117    type Error = ParseError;
118
119    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
120        debug_construction!(pair => OperatorStmt);
121        match pair.as_rule() {
122            ParserRule::action_stmt_oper => {
123                let mut pairs = pair.into_inner();
124                Ok(Self {
125                    prop: next_into_or!(pairs => "failed to get action property")?,
126                    op: next_into_or!(pairs => "failed to get action operator")?,
127                    val: next_into_or!(pairs => "failed to get action operand")?,
128                })
129            }
130            _ => Err(rule_mismatch!(pair => "action expression operator statement")),
131        }
132    }
133}
134
135impl fmt::Display for OperatorStmt {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        write!(f, "{} {} {}", self.prop, self.op, self.val)
138    }
139}
140
141#[cfg(any(test, feature = "arbitrary"))]
142impl Arbitrary for OperatorStmt {
143    type Parameters = ();
144    type Strategy = BoxedStrategy<Self>;
145    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
146        (any::<Property>(), any::<Operator>(), any::<Value>())
147            .prop_map(|(prop, op, val)| Self { prop, op, val })
148            .boxed()
149    }
150}
151
152#[derive(Clone, Debug, Hash, PartialEq, Eq)]
153pub struct MethodStmt {
154    prop: Property,
155    method: Option<Method>,
156    vals: ListOf<Value>,
157}
158
159impl TryFrom<TokenPair<'_>> for MethodStmt {
160    type Error = ParseError;
161
162    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
163        debug_construction!(pair => MethodStmt);
164        match pair.as_rule() {
165            ParserRule::action_stmt_meth => {
166                let mut pairs = pair.into_inner().peekable();
167                let prop = next_into_or!(pairs => "failed to get action property")?;
168                let method =
169                    if pairs.peek().map(TokenPair::as_rule) == Some(ParserRule::action_meth) {
170                        Some(next_into_or!(pairs => "failed to get action method")?)
171                    } else {
172                        None
173                    };
174                let vals = next_into_or!(pairs => "failed to get action operands")?;
175                Ok(Self { prop, method, vals })
176            }
177            _ => Err(rule_mismatch!(pair => "action expression method statement")),
178        }
179    }
180}
181
182impl fmt::Display for MethodStmt {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        write!(f, "{}", self.prop)?;
185        if let Some(method) = &self.method {
186            write!(f, ".{method}")?;
187        }
188        write!(f, "({})", self.vals)
189    }
190}
191
192#[cfg(any(test, feature = "arbitrary"))]
193impl Arbitrary for MethodStmt {
194    type Parameters = ParamsFor<Option<Method>>;
195    type Strategy = BoxedStrategy<Self>;
196    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
197        (
198            any::<Property>(),
199            any_with::<Option<Method>>(params),
200            any::<ListOf<Value>>(),
201        )
202            .prop_map(|(prop, method, vals)| Self { prop, method, vals })
203            .boxed()
204    }
205}
206
207#[derive(Clone, Debug, Hash, PartialEq, Eq)]
208pub(crate) enum Property {
209    Pref,
210    Med,
211    Dpa,
212    AsPath,
213    Community,
214    NextHop,
215    Cost,
216    Unknown(UnknownProperty),
217}
218
219impl TryFrom<TokenPair<'_>> for Property {
220    type Error = ParseError;
221
222    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
223        debug_construction!(pair => Property);
224        match pair.as_rule() {
225            ParserRule::rp_pref => Ok(Self::Pref),
226            ParserRule::rp_med => Ok(Self::Med),
227            ParserRule::rp_dpa => Ok(Self::Dpa),
228            ParserRule::rp_aspath => Ok(Self::AsPath),
229            ParserRule::rp_community => Ok(Self::Community),
230            ParserRule::rp_next_hop => Ok(Self::NextHop),
231            ParserRule::rp_cost => Ok(Self::Cost),
232            ParserRule::rp_unknown => Ok(Self::Unknown(pair.try_into()?)),
233            _ => Err(rule_mismatch!(pair => "action property name")),
234        }
235    }
236}
237
238impl fmt::Display for Property {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        match self {
241            Self::Pref => write!(f, "pref"),
242            Self::Med => write!(f, "med"),
243            Self::Dpa => write!(f, "dpa"),
244            Self::AsPath => write!(f, "aspath"),
245            Self::Community => write!(f, "community"),
246            Self::NextHop => write!(f, "next-hop"),
247            Self::Cost => write!(f, "cost"),
248            Self::Unknown(property) => property.fmt(f),
249        }
250    }
251}
252
253#[cfg(any(test, feature = "arbitrary"))]
254impl Arbitrary for Property {
255    type Parameters = ParamsFor<UnknownProperty>;
256    type Strategy = BoxedStrategy<Self>;
257    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
258        prop_oneof![
259            Just(Self::Pref),
260            Just(Self::Med),
261            Just(Self::Dpa),
262            Just(Self::AsPath),
263            Just(Self::Community),
264            Just(Self::NextHop),
265            Just(Self::Cost),
266            any_with::<UnknownProperty>(params).prop_map(Self::Unknown),
267        ]
268        .boxed()
269    }
270}
271
272#[derive(Clone, Debug)]
273pub(crate) struct UnknownProperty(String);
274impl_case_insensitive_str_primitive!(ParserRule::rp_unknown => UnknownProperty);
275#[cfg(any(test, feature = "arbitrary"))]
276impl_rpsl_name_arbitrary!(UnknownProperty);
277
278#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
279pub(crate) enum Operator {
280    Assign,
281    Append,
282    LshAssign,
283    RshAssign,
284    AddAssign,
285    SubAssign,
286    MulAssign,
287    DivAssign,
288    Eq,
289    Ne,
290    Le,
291    Ge,
292    Lt,
293    Gt,
294}
295
296impl TryFrom<TokenPair<'_>> for Operator {
297    type Error = ParseError;
298
299    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
300        debug_construction!(pair => Operator);
301        match pair.as_rule() {
302            ParserRule::action_op_assign => Ok(Self::Assign),
303            ParserRule::action_op_append => Ok(Self::Append),
304            ParserRule::action_op_lsh_assign => Ok(Self::LshAssign),
305            ParserRule::action_op_rsh_assign => Ok(Self::RshAssign),
306            ParserRule::action_op_add_assign => Ok(Self::AddAssign),
307            ParserRule::action_op_sub_assign => Ok(Self::SubAssign),
308            ParserRule::action_op_mul_assign => Ok(Self::MulAssign),
309            ParserRule::action_op_div_assign => Ok(Self::DivAssign),
310            ParserRule::action_op_eq => Ok(Self::Eq),
311            ParserRule::action_op_ne => Ok(Self::Ne),
312            ParserRule::action_op_le => Ok(Self::Le),
313            ParserRule::action_op_ge => Ok(Self::Ge),
314            ParserRule::action_op_lt => Ok(Self::Lt),
315            ParserRule::action_op_gt => Ok(Self::Gt),
316            _ => Err(rule_mismatch!(pair => "action expression operator")),
317        }
318    }
319}
320
321impl fmt::Display for Operator {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        match self {
324            Self::Assign => write!(f, "="),
325            Self::Append => write!(f, ".="),
326            Self::LshAssign => write!(f, "<<="),
327            Self::RshAssign => write!(f, ">>="),
328            Self::AddAssign => write!(f, "+="),
329            Self::SubAssign => write!(f, "-="),
330            Self::MulAssign => write!(f, "*="),
331            Self::DivAssign => write!(f, "/="),
332            Self::Eq => write!(f, "=="),
333            Self::Ne => write!(f, "!="),
334            Self::Le => write!(f, "<="),
335            Self::Ge => write!(f, ">="),
336            Self::Lt => write!(f, "<"),
337            Self::Gt => write!(f, ">"),
338        }
339    }
340}
341
342#[cfg(any(test, feature = "arbitrary"))]
343impl Arbitrary for Operator {
344    type Parameters = ();
345    type Strategy = BoxedStrategy<Self>;
346    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
347        prop_oneof![
348            Just(Self::Assign),
349            Just(Self::Append),
350            Just(Self::LshAssign),
351            Just(Self::RshAssign),
352            Just(Self::AddAssign),
353            Just(Self::SubAssign),
354            Just(Self::MulAssign),
355            Just(Self::DivAssign),
356            Just(Self::Eq),
357            Just(Self::Ne),
358            Just(Self::Le),
359            Just(Self::Ge),
360            Just(Self::Lt),
361            Just(Self::Gt),
362        ]
363        .boxed()
364    }
365}
366
367#[derive(Clone, Debug)]
368pub struct Method(String);
369impl_case_insensitive_str_primitive!(ParserRule::action_meth => Method);
370#[cfg(any(test, feature = "arbitrary"))]
371impl_rpsl_name_arbitrary!(Method);
372
373#[derive(Clone, Debug, Hash, PartialEq, Eq)]
374pub(crate) enum Value {
375    Unit(String),
376    List(Box<ListOf<Value>>),
377}
378
379impl TryFrom<TokenPair<'_>> for Value {
380    type Error = ParseError;
381
382    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
383        debug_construction!(pair => Value);
384        match pair.as_rule() {
385            ParserRule::action_val_nested => Ok(Self::List(Box::new(
386                next_into_or!(pair.into_inner() => "failed to get inner action operand list")?,
387            ))),
388            ParserRule::action_val_unit => Ok(Self::Unit(pair.as_str().to_string())),
389            _ => Err(rule_mismatch!(pair => "action operand")),
390        }
391    }
392}
393
394impl fmt::Display for Value {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        match self {
397            Self::List(val_list) => write!(f, "{{{val_list}}}"),
398            Self::Unit(val) => val.fmt(f),
399        }
400    }
401}
402
403#[cfg(any(test, feature = "arbitrary"))]
404impl Arbitrary for Value {
405    type Parameters = ();
406    type Strategy = BoxedStrategy<Self>;
407    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
408        let leaf = r"[0-9A-Za-z]+".prop_map(Self::Unit);
409        leaf.prop_recursive(1, 4, 4, |unit| {
410            proptest::collection::vec(unit, 0..4)
411                .prop_map(ListOf::from_iter)
412                .prop_map(Box::new)
413                .prop_map(Self::List)
414        })
415        .boxed()
416    }
417}
418
419// TODO
420// #[derive(Clone, Debug, Hash, PartialEq, Eq)]
421// pub enum Value {
422//     EmailAddr(EmailAddress),
423//     AutNum(AutNum),
424//     Ipv4Addr(Ipv4Addr),
425//     Ipv4Prefix(Ipv4Net),
426//     Ipv4PrefixRange(PrefixRange<Ipv4>),
427//     Ipv6Addr(Ipv6Addr),
428//     Ipv6Prefix(Ipv6Net),
429//     Ipv6PrefixRange(PrefixRange<Ipv6>),
430//     DnsName(DnsName),
431//     Filter(MpFilterExpr),
432//     AsSet(AsSet),
433//     RouteSet(RouteSet),
434//     RtrSet(RtrSet),
435//     FilterSet(FilterSet),
436//     PeeringSet(PeeringSet),
437//     Num(u64),
438//     Unknown(String),
439// }
440
441// impl TryFrom<TokenPair<'_>> for Value {
442//     type Error = ParseError;
443
444//     fn try_from(pair: TokenPair) -> ParseResult<Self> {
445//         debug_construction!(pair => Value);
446//         match pair.as_rule() {
447//             ParserRule::email_addr => Ok(Self::EmailAddr(pair.try_into()?)),
448//             ParserRule::aut_num => Ok(Self::AutNum(pair.try_into()?)),
449//             ParserRule::ipv4_addr => Ok(Self::Ipv4Addr(pair.as_str().parse()?)),
450//             ParserRule::ipv4_prefix => Ok(Self::Ipv4Prefix(pair.as_str().parse()?)),
451//             ParserRule::ipv4_prefix_range => Ok(Self::Ipv4PrefixRange(pair.try_into()?)),
452//             ParserRule::ipv6_addr => Ok(Self::Ipv6Addr(pair.as_str().parse()?)),
453//             ParserRule::ipv6_prefix => Ok(Self::Ipv6Prefix(pair.as_str().parse()?)),
454//             ParserRule::ipv6_prefix_range => Ok(Self::Ipv6PrefixRange(pair.try_into()?)),
455//             ParserRule::dns_name => Ok(Self::DnsName(pair.try_into()?)),
456//             ParserRule::mp_filter_expr_unit
457//             | ParserRule::mp_filter_expr_and
458//             | ParserRule::mp_filter_expr_or
459//             | ParserRule::mp_filter_expr_not => Ok(Self::Filter(pair.try_into()?)),
460//             ParserRule::as_set => Ok(Self::AsSet(pair.try_into()?)),
461//             ParserRule::route_set => Ok(Self::RouteSet(pair.try_into()?)),
462//             ParserRule::rtr_set => Ok(Self::RtrSet(pair.try_into()?)),
463//             ParserRule::filter_set => Ok(Self::FilterSet(pair.try_into()?)),
464//             ParserRule::peering_set => Ok(Self::PeeringSet(pair.try_into()?)),
465//             ParserRule::num => Ok(Self::Num(pair.as_str().parse()?)),
466//             ParserRule::action_val_unknown => Ok(Self::Unknown(pair.as_str().to_owned())),
467//             _ => Err(rule_mismatch!(pair => "action expression operand value")),
468//         }
469//     }
470// }
471
472// impl fmt::Display for Value {
473//     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
474//         match self {
475//             Self::EmailAddr(val) => val.fmt(f),
476//             Self::AutNum(val) => val.fmt(f),
477//             Self::Ipv4Addr(val) => val.fmt(f),
478//             Self::Ipv4Prefix(val) => val.fmt(f),
479//             Self::Ipv4PrefixRange(val) => val.fmt(f),
480//             Self::Ipv6Addr(val) => val.fmt(f),
481//             Self::Ipv6Prefix(val) => val.fmt(f),
482//             Self::Ipv6PrefixRange(val) => val.fmt(f),
483//             Self::DnsName(val) => val.fmt(f),
484//             Self::Filter(val) => val.fmt(f),
485//             Self::AsSet(val) => val.fmt(f),
486//             Self::RouteSet(val) => val.fmt(f),
487//             Self::RtrSet(val) => val.fmt(f),
488//             Self::FilterSet(val) => val.fmt(f),
489//             Self::PeeringSet(val) => val.fmt(f),
490//             Self::Num(val) => val.fmt(f),
491//             Self::Unknown(val) => val.fmt(f),
492//         }
493//     }
494// }
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499    use crate::tests::{compare_ast, display_fmt_parses};
500
501    display_fmt_parses! {
502        ActionExpr,
503    }
504
505    compare_ast! {
506        ActionExpr {
507            rfc2622_sect6_autnum_example1: "pref = 1;" => {
508                Expr(vec![Stmt::Operator(OperatorStmt {
509                    prop: Property::Pref,
510                    op: Operator::Assign,
511                    val: Value::Unit("1".into()),
512                })])
513            }
514            rfc2622_sect6_autnum_example2: "\
515            pref = 10; med = 0; community.append(10250, 3561:10);" => {
516                Expr(vec![
517                    Stmt::Operator(OperatorStmt {
518                        prop: Property::Pref,
519                        op: Operator::Assign,
520                        val: Value::Unit("10".into()),
521                    }),
522                    Stmt::Operator(OperatorStmt {
523                        prop: Property::Med,
524                        op: Operator::Assign,
525                        val: Value::Unit("0".into()),
526                    }),
527                    Stmt::Method(MethodStmt {
528                        prop: Property::Community,
529                        method: Some("append".into()),
530                        vals: vec![
531                            Value::Unit("10250".into()),
532                            Value::Unit("3561:10".into()),
533                        ].into_iter().collect()
534                    })
535                ])
536            }
537            regression1: "pref.RS-a(a);" => {
538                Expr(vec![Stmt::Method(MethodStmt {
539                    prop: Property::Pref,
540                    method: Some("RS-a".into()),
541                    vals: vec![Value::Unit("a".into())].into_iter().collect(),
542                })])
543            }
544            regression2: "meD0 = a;" => {
545                Expr(vec![Stmt::Operator(OperatorStmt {
546                    prop: Property::Unknown("meD0".into()),
547                    op: Operator::Assign,
548                    val: Value::Unit("a".into()),
549                })])
550            }
551            regression3: "meD0(a);" => {
552                Expr(vec![Stmt::Method(MethodStmt {
553                    prop: Property::Unknown("meD0".into()),
554                    method: None,
555                    vals: vec![Value::Unit("a".into())].into_iter().collect(),
556                })])
557            }
558            regression4: "community .= { 70 };" => {
559                Expr(vec![Stmt::Operator(OperatorStmt {
560                    prop: Property::Community,
561                    op: Operator::Append,
562                    val: Value::List(Box::new(vec![Value::Unit("70".into())].into_iter().collect())),
563                })])
564            }
565            regression5: "pref = { };" => {
566                Expr(vec![Stmt::Operator(OperatorStmt {
567                    prop: Property::Pref,
568                    op: Operator::Assign,
569                    val: Value::List(Box::new(vec![].into_iter().collect())),
570                })])
571            }
572        }
573    }
574}