use std::fmt;
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{arbitrary::ParamsFor, prelude::*};
use crate::{
containers::ListOf,
error::{ParseError, ParseResult},
parser::{
debug_construction, impl_case_insensitive_str_primitive, impl_from_str, next_into_or,
rule_mismatch, ParserRule, TokenPair,
},
};
#[cfg(any(test, feature = "arbitrary"))]
use crate::primitive::arbitrary::impl_rpsl_name_arbitrary;
#[allow(clippy::module_name_repetitions)]
pub type ActionExpr = Expr;
impl_from_str!(ParserRule::just_action_expr => Expr);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Expr(Vec<Stmt>);
impl TryFrom<TokenPair<'_>> for Expr {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => Expr);
match pair.as_rule() {
ParserRule::action_expr => Ok(Self(
pair.into_inner()
.map(Stmt::try_from)
.collect::<ParseResult<_>>()?,
)),
_ => Err(rule_mismatch!(pair => "action expression")),
}
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = self
.0
.iter()
.map(Stmt::to_string)
.collect::<Vec<_>>()
.join("; ");
s.push(';');
s.fmt(f)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl Arbitrary for Expr {
type Parameters = ParamsFor<Stmt>;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
prop::collection::vec(any_with::<Stmt>(params), 1..8)
.prop_map(Self)
.boxed()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum Stmt {
Operator(OperatorStmt),
Method(MethodStmt),
}
impl TryFrom<TokenPair<'_>> for Stmt {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => Stmt);
match pair.as_rule() {
ParserRule::action_stmt_oper => Ok(Self::Operator(pair.try_into()?)),
ParserRule::action_stmt_meth => Ok(Self::Method(pair.try_into()?)),
_ => Err(rule_mismatch!(pair => "action expression statement")),
}
}
}
impl fmt::Display for Stmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Operator(stmt) => stmt.fmt(f),
Self::Method(stmt) => stmt.fmt(f),
}
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl Arbitrary for Stmt {
type Parameters = ParamsFor<MethodStmt>;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
prop_oneof![
any::<OperatorStmt>().prop_map(Self::Operator),
any_with::<MethodStmt>(params).prop_map(Self::Method),
]
.boxed()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct OperatorStmt {
prop: Property,
op: Operator,
val: Value,
}
impl TryFrom<TokenPair<'_>> for OperatorStmt {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => OperatorStmt);
match pair.as_rule() {
ParserRule::action_stmt_oper => {
let mut pairs = pair.into_inner();
Ok(Self {
prop: next_into_or!(pairs => "failed to get action property")?,
op: next_into_or!(pairs => "failed to get action operator")?,
val: next_into_or!(pairs => "failed to get action operand")?,
})
}
_ => Err(rule_mismatch!(pair => "action expression operator statement")),
}
}
}
impl fmt::Display for OperatorStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.prop, self.op, self.val)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl Arbitrary for OperatorStmt {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
(any::<Property>(), any::<Operator>(), any::<Value>())
.prop_map(|(prop, op, val)| Self { prop, op, val })
.boxed()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct MethodStmt {
prop: Property,
method: Option<Method>,
vals: ListOf<Value>,
}
impl TryFrom<TokenPair<'_>> for MethodStmt {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => MethodStmt);
match pair.as_rule() {
ParserRule::action_stmt_meth => {
let mut pairs = pair.into_inner().peekable();
let prop = next_into_or!(pairs => "failed to get action property")?;
let method =
if pairs.peek().map(TokenPair::as_rule) == Some(ParserRule::action_meth) {
Some(next_into_or!(pairs => "failed to get action method")?)
} else {
None
};
let vals = next_into_or!(pairs => "failed to get action operands")?;
Ok(Self { prop, method, vals })
}
_ => Err(rule_mismatch!(pair => "action expression method statement")),
}
}
}
impl fmt::Display for MethodStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.prop)?;
if let Some(method) = &self.method {
write!(f, ".{method}")?;
}
write!(f, "({})", self.vals)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl Arbitrary for MethodStmt {
type Parameters = ParamsFor<Option<Method>>;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
(
any::<Property>(),
any_with::<Option<Method>>(params),
any::<ListOf<Value>>(),
)
.prop_map(|(prop, method, vals)| Self { prop, method, vals })
.boxed()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) enum Property {
Pref,
Med,
Dpa,
AsPath,
Community,
NextHop,
Cost,
Unknown(UnknownProperty),
}
impl TryFrom<TokenPair<'_>> for Property {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => Property);
match pair.as_rule() {
ParserRule::rp_pref => Ok(Self::Pref),
ParserRule::rp_med => Ok(Self::Med),
ParserRule::rp_dpa => Ok(Self::Dpa),
ParserRule::rp_aspath => Ok(Self::AsPath),
ParserRule::rp_community => Ok(Self::Community),
ParserRule::rp_next_hop => Ok(Self::NextHop),
ParserRule::rp_cost => Ok(Self::Cost),
ParserRule::rp_unknown => Ok(Self::Unknown(pair.try_into()?)),
_ => Err(rule_mismatch!(pair => "action property name")),
}
}
}
impl fmt::Display for Property {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pref => write!(f, "pref"),
Self::Med => write!(f, "med"),
Self::Dpa => write!(f, "dpa"),
Self::AsPath => write!(f, "aspath"),
Self::Community => write!(f, "community"),
Self::NextHop => write!(f, "next-hop"),
Self::Cost => write!(f, "cost"),
Self::Unknown(property) => property.fmt(f),
}
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl Arbitrary for Property {
type Parameters = ParamsFor<UnknownProperty>;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(Self::Pref),
Just(Self::Med),
Just(Self::Dpa),
Just(Self::AsPath),
Just(Self::Community),
Just(Self::NextHop),
Just(Self::Cost),
any_with::<UnknownProperty>(params).prop_map(Self::Unknown),
]
.boxed()
}
}
#[derive(Clone, Debug)]
pub(crate) struct UnknownProperty(String);
impl_case_insensitive_str_primitive!(ParserRule::rp_unknown => UnknownProperty);
#[cfg(any(test, feature = "arbitrary"))]
impl_rpsl_name_arbitrary!(UnknownProperty);
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub(crate) enum Operator {
Assign,
Append,
LshAssign,
RshAssign,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
Eq,
Ne,
Le,
Ge,
Lt,
Gt,
}
impl TryFrom<TokenPair<'_>> for Operator {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => Operator);
match pair.as_rule() {
ParserRule::action_op_assign => Ok(Self::Assign),
ParserRule::action_op_append => Ok(Self::Append),
ParserRule::action_op_lsh_assign => Ok(Self::LshAssign),
ParserRule::action_op_rsh_assign => Ok(Self::RshAssign),
ParserRule::action_op_add_assign => Ok(Self::AddAssign),
ParserRule::action_op_sub_assign => Ok(Self::SubAssign),
ParserRule::action_op_mul_assign => Ok(Self::MulAssign),
ParserRule::action_op_div_assign => Ok(Self::DivAssign),
ParserRule::action_op_eq => Ok(Self::Eq),
ParserRule::action_op_ne => Ok(Self::Ne),
ParserRule::action_op_le => Ok(Self::Le),
ParserRule::action_op_ge => Ok(Self::Ge),
ParserRule::action_op_lt => Ok(Self::Lt),
ParserRule::action_op_gt => Ok(Self::Gt),
_ => Err(rule_mismatch!(pair => "action expression operator")),
}
}
}
impl fmt::Display for Operator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Assign => write!(f, "="),
Self::Append => write!(f, ".="),
Self::LshAssign => write!(f, "<<="),
Self::RshAssign => write!(f, ">>="),
Self::AddAssign => write!(f, "+="),
Self::SubAssign => write!(f, "-="),
Self::MulAssign => write!(f, "*="),
Self::DivAssign => write!(f, "/="),
Self::Eq => write!(f, "=="),
Self::Ne => write!(f, "!="),
Self::Le => write!(f, "<="),
Self::Ge => write!(f, ">="),
Self::Lt => write!(f, "<"),
Self::Gt => write!(f, ">"),
}
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl Arbitrary for Operator {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(Self::Assign),
Just(Self::Append),
Just(Self::LshAssign),
Just(Self::RshAssign),
Just(Self::AddAssign),
Just(Self::SubAssign),
Just(Self::MulAssign),
Just(Self::DivAssign),
Just(Self::Eq),
Just(Self::Ne),
Just(Self::Le),
Just(Self::Ge),
Just(Self::Lt),
Just(Self::Gt),
]
.boxed()
}
}
#[derive(Clone, Debug)]
pub struct Method(String);
impl_case_insensitive_str_primitive!(ParserRule::action_meth => Method);
#[cfg(any(test, feature = "arbitrary"))]
impl_rpsl_name_arbitrary!(Method);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) enum Value {
Unit(String),
List(Box<ListOf<Value>>),
}
impl TryFrom<TokenPair<'_>> for Value {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => Value);
match pair.as_rule() {
ParserRule::action_val_nested => Ok(Self::List(Box::new(
next_into_or!(pair.into_inner() => "failed to get inner action operand list")?,
))),
ParserRule::action_val_unit => Ok(Self::Unit(pair.as_str().to_string())),
_ => Err(rule_mismatch!(pair => "action operand")),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::List(val_list) => write!(f, "{{{val_list}}}"),
Self::Unit(val) => val.fmt(f),
}
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl Arbitrary for Value {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
let leaf = r"[0-9A-Za-z]+".prop_map(Self::Unit);
leaf.prop_recursive(1, 4, 4, |unit| {
proptest::collection::vec(unit, 0..4)
.prop_map(ListOf::from_iter)
.prop_map(Box::new)
.prop_map(Self::List)
})
.boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{compare_ast, display_fmt_parses};
display_fmt_parses! {
ActionExpr,
}
compare_ast! {
ActionExpr {
rfc2622_sect6_autnum_example1: "pref = 1;" => {
Expr(vec![Stmt::Operator(OperatorStmt {
prop: Property::Pref,
op: Operator::Assign,
val: Value::Unit("1".into()),
})])
}
rfc2622_sect6_autnum_example2: "\
pref = 10; med = 0; community.append(10250, 3561:10);" => {
Expr(vec![
Stmt::Operator(OperatorStmt {
prop: Property::Pref,
op: Operator::Assign,
val: Value::Unit("10".into()),
}),
Stmt::Operator(OperatorStmt {
prop: Property::Med,
op: Operator::Assign,
val: Value::Unit("0".into()),
}),
Stmt::Method(MethodStmt {
prop: Property::Community,
method: Some("append".into()),
vals: vec![
Value::Unit("10250".into()),
Value::Unit("3561:10".into()),
].into_iter().collect()
})
])
}
regression1: "pref.RS-a(a);" => {
Expr(vec![Stmt::Method(MethodStmt {
prop: Property::Pref,
method: Some("RS-a".into()),
vals: vec![Value::Unit("a".into())].into_iter().collect(),
})])
}
regression2: "meD0 = a;" => {
Expr(vec![Stmt::Operator(OperatorStmt {
prop: Property::Unknown("meD0".into()),
op: Operator::Assign,
val: Value::Unit("a".into()),
})])
}
regression3: "meD0(a);" => {
Expr(vec![Stmt::Method(MethodStmt {
prop: Property::Unknown("meD0".into()),
method: None,
vals: vec![Value::Unit("a".into())].into_iter().collect(),
})])
}
regression4: "community .= { 70 };" => {
Expr(vec![Stmt::Operator(OperatorStmt {
prop: Property::Community,
op: Operator::Append,
val: Value::List(Box::new(vec![Value::Unit("70".into())].into_iter().collect())),
})])
}
regression5: "pref = { };" => {
Expr(vec![Stmt::Operator(OperatorStmt {
prop: Property::Pref,
op: Operator::Assign,
val: Value::List(Box::new(vec![].into_iter().collect())),
})])
}
}
}
}