balena-temen 0.5.7

Templating engine for (not just) JSON
Documentation
//! An expression Abstract Syntax Tree
//!
//! [The Elegant Parser] is used to parse an expression. Full [grammar].
//!
//! # Examples
//!
//! ```rust
//! use balena_temen::ast::*;
//!
//! let parsed: Expression = "1 + 2".parse().unwrap();
//! let manual = Expression::new(
//!     ExpressionValue::Math(
//!         MathExpression::new(
//!             Expression::new(ExpressionValue::Integer(1)),
//!             Expression::new(ExpressionValue::Integer(2)),
//!             MathOperator::Addition
//!         )
//!     )
//! );
//! assert_eq!(parsed, manual);
//! ```
//!
//! [The Elegant Parser]: https://github.com/pest-parser/pest
//! [grammar]: https://github.com/balena-io-modules/balena-temen/blob/master/src/parser/grammar.pest
use std::str::FromStr;

use crate::{error::*, parser::parse};

/// Math operator
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MathOperator {
    /// `+`
    Addition,
    /// `-`
    Subtraction,
    /// `*`
    Multiplication,
    /// `/`
    Division,
    /// `%`
    Modulo,
}

/// Logical operator
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum LogicalOperator {
    /// `==`
    Equal,
    /// `!=`
    NotEqual,
    /// `>`
    GreaterThan,
    /// `>=`
    GreaterThanOrEqual,
    /// `<`
    LowerThan,
    /// `<=`
    LowerThanOrEqual,
    /// `and`
    And,
    /// `or`
    Or,
}

/// A function call
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionCall {
    /// A function name
    pub name: String,
    /// Positional arguments
    pub args: Vec<Expression>,
}

impl FunctionCall {
    /// Creates new function call
    ///
    /// # Arguments
    ///
    /// * `name` - A function name
    /// * `args` - Positional arguments
    pub fn new<S>(name: S, args: Vec<Expression>) -> FunctionCall
    where
        S: Into<String>,
    {
        FunctionCall {
            name: name.into(),
            args,
        }
    }
}

/// Math expression
#[derive(Clone, Debug, PartialEq)]
pub struct MathExpression {
    /// A left-hand side
    pub lhs: Box<Expression>,
    /// A right-hand side
    pub rhs: Box<Expression>,
    /// An operator
    pub operator: MathOperator,
}

impl MathExpression {
    /// Creates new mathematical expression
    ///
    /// # Arguments
    ///
    /// * `lhs` - A left-hand side
    /// * `rhs` - A right-hand side
    /// * `operator` - An operator
    pub fn new(lhs: Expression, rhs: Expression, operator: MathOperator) -> MathExpression {
        MathExpression {
            lhs: Box::new(lhs),
            rhs: Box::new(rhs),
            operator,
        }
    }
}

/// Math expression
#[derive(Clone, Debug, PartialEq)]
pub struct TernaryExpression {
    pub condition: Box<Expression>,
    pub truthy: Box<Expression>,
    pub falsy: Box<Expression>,
}

impl TernaryExpression {
    pub fn new(condition: Expression, truthy: Expression, falsy: Expression) -> TernaryExpression {
        TernaryExpression {
            condition: Box::new(condition),
            truthy: Box::new(truthy),
            falsy: Box::new(falsy),
        }
    }
}

/// Logical expression
#[derive(Clone, Debug, PartialEq)]
pub struct LogicalExpression {
    /// A left-hand side
    pub lhs: Box<Expression>,
    /// A right-hand side
    pub rhs: Box<Expression>,
    /// An operator
    pub operator: LogicalOperator,
}

impl LogicalExpression {
    /// Creates new logical expression
    ///
    /// # Arguments
    ///
    /// * `lhs` - A left-hand side
    /// * `rhs` - A right-hand side
    /// * `operator` - An operator
    pub fn new(lhs: Expression, rhs: Expression, operator: LogicalOperator) -> LogicalExpression {
        LogicalExpression {
            lhs: Box::new(lhs),
            rhs: Box::new(rhs),
            operator,
        }
    }
}

/// String concatenation
#[derive(Clone, Debug, PartialEq)]
pub struct StringConcat {
    /// List of values to concatenate
    pub values: Vec<ExpressionValue>,
}

impl StringConcat {
    /// Creates new concatenation expression
    ///
    /// # Arguments
    ///
    /// * `values` - List of values to concatenate
    pub fn new(values: Vec<ExpressionValue>) -> StringConcat {
        StringConcat { values }
    }
}

/// An identifier
///
/// # Examples
///
/// ```text
/// networks[0].name
///   |      │   |
///   |      |   â”” IdentifierValue::Name("name")
///   |      |
///   |      â”” IdentifierValue::Index(0)
///   |
///   â”” IdentifierValue::Name("networks")
/// ```
///
/// ```text
/// persons[boss.id]["name"]
///   |      │         |
///   |      |         â”” IdentifierValue::Name("name")
///   |      |
///   |      â”” IdentifierValue::Identifier(boss.id)
///   |                                     |   |
///   |                                     |   â”” IdentifierValue::Name("id")
///   |                                     |
///   |                                     â”” IdentifierValue::Name("boss")
///   |
///   â”” IdentifierValue::Name("persons")
/// ```
///
/// ```text
/// this.id
///   |  |
///   |  â”” IdentifierValue::Name("id")
///   |
///   â”” IdentifierValue::This
/// ```
#[derive(Clone, Debug, PartialEq)]
pub struct Identifier {
    /// List of identifier values (components)
    pub values: Vec<IdentifierValue>,
}

impl Identifier {
    /// Creates new identifier
    ///
    /// # Arguments
    ///
    /// * `values` - List of identifier values (components)
    pub fn new(values: Vec<IdentifierValue>) -> Identifier {
        Identifier { values }
    }

    /// Check if an identifier is canonical
    ///
    /// An identifier is considered as canonical if none relative identifier values
    /// (`IdentifierValue::This`, `IdentifierValue::Super`) are present.
    ///
    /// It affects (checks) nested identifiers as well.
    ///
    /// # Examples
    ///
    /// Canonical identifiers.
    ///
    /// ```rust
    /// use balena_temen::ast::*;
    ///
    /// let identifier: Identifier = "names.wifi".parse().unwrap();
    /// assert!(identifier.is_canonical());
    ///
    /// let identifier: Identifier = "names.wifi[first].id".parse().unwrap();
    /// assert!(identifier.is_canonical());
    /// ```
    ///
    /// Not canonical identifiers.
    ///
    /// ```rust
    /// use balena_temen::ast::*;
    ///
    /// let identifier: Identifier = "names.this".parse().unwrap();
    /// assert!(!identifier.is_canonical());
    ///
    /// let identifier: Identifier = "names[this.index]".parse().unwrap();
    /// assert!(!identifier.is_canonical());
    /// ```
    pub fn is_canonical(&self) -> bool {
        for v in &self.values {
            match v {
                IdentifierValue::This | IdentifierValue::Super => return false,
                IdentifierValue::Identifier(ref identifier) => {
                    if !identifier.is_canonical() {
                        return false;
                    }
                }
                _ => {}
            };
        }

        true
    }

    fn is_relative(&self) -> bool {
        if let Some(first) = self.values.first() {
            match first {
                IdentifierValue::This | IdentifierValue::Super => true,
                _ => false,
            }
        } else {
            false
        }
    }

    fn initial_position_identifier_values<'a>(
        &self,
        position: &'a Identifier,
    ) -> Result<Option<&'a [IdentifierValue]>> {
        if self.is_relative() {
            // Identifier is relative, it must start somewhere
            if position.is_relative() {
                // Position must not be relative
                return Err(Error::with_message("unable to canonicalize identifier")
                    .context("reason", "identifier and position are relative identifiers")
                    .context("identifier", format!("{:?}", self))
                    .context("position", format!("{:?}", position)));
            }

            if position.values.is_empty() {
                // Position must not be empty
                return Err(Error::with_message("unable to canonicalize identifier")
                    .context("reason", "identifier is relative and position is empty")
                    .context("identifier", format!("{:?}", self))
                    .context("position", format!("{:?}", position)));
            }
            Ok(Some(&position.values))
        } else {
            // Identifier is not relative, no initial position values
            Ok(None)
        }
    }

    /// Returns the canonical, absolute, identifier with all intermediate
    /// components normalized and nested identifiers canonicalized.
    ///
    /// Nested identifiers (`IdentifierValue::Identifier`) are canonicalized
    /// too.
    ///
    /// # Arguments
    ///
    /// * `position` - An identifier position
    ///
    /// # Examples
    ///
    /// ```rust
    /// use balena_temen::ast::*;
    ///
    /// let identifier: Identifier = "names".parse().unwrap();
    /// assert_eq!(identifier.canonicalize(&Identifier::default()).unwrap(), identifier);
    ///
    /// let identifier: Identifier = "names.this.id.this.super".parse().unwrap();
    /// let canonicalized: Identifier = "names".parse().unwrap();
    /// assert_eq!(identifier.canonicalize(&Identifier::default()).unwrap(), canonicalized);
    ///
    /// let identifier: Identifier = "super.id".parse().unwrap();
    /// let position: Identifier = "wifi[`zrzka`].ssid".parse().unwrap();
    /// let canonicalized: Identifier = "wifi[`zrzka`].id".parse().unwrap();
    /// assert_eq!(identifier.canonicalize(&position).unwrap(), canonicalized);
    /// ```
    pub fn canonicalize(&self, position: &Identifier) -> Result<Identifier> {
        let values = self
            .initial_position_identifier_values(position)?
            .into_iter()
            .flatten()
            .chain(self.values.iter());

        let mut result = vec![];
        for value in values {
            match value {
                IdentifierValue::This => {
                    // This resolves to self, we can remove it
                }
                IdentifierValue::Super => {
                    // Super should resolve to parent, pop the latest identifier
                    // from result
                    result.pop().ok_or_else(|| {
                        Error::with_message("unable to canonicalize identifier")
                            .context("reason", "`super` can not be resolved")
                    })?;
                }
                IdentifierValue::Identifier(ref identifier) => {
                    // Canonicalize nested identifiers
                    result.push(IdentifierValue::Identifier(identifier.canonicalize(position)?));
                }
                _ => {
                    // Rest is just cloned
                    result.push(value.clone());
                }
            }
        }

        Ok(Identifier::new(result))
    }

    /// Appends `IdentifierValue::Name` to the identifier
    ///
    /// # Arguments
    ///
    /// * `name` - A name (object field, string index)
    ///
    /// # Examples
    ///
    /// ```rust
    /// use balena_temen::ast::*;
    ///
    /// let identifier = Identifier::default()
    ///     .name("wifi")
    ///     .name("ssid");
    ///
    /// let parsed = "wifi.ssid".parse().unwrap();
    ///
    /// assert_eq!(identifier, parsed);
    /// ```
    pub fn name<S>(self, name: S) -> Identifier
    where
        S: Into<String>,
    {
        let mut values = self.values;
        values.push(IdentifierValue::Name(name.into()));
        Identifier { values }
    }

    /// Appends `IdentifierValue::Index` to the identifier
    ///
    /// # Arguments
    ///
    /// * `index` - An array index
    ///
    /// ```rust
    /// use balena_temen::ast::*;
    ///
    /// let identifier = Identifier::default()
    ///     .name("networks")
    ///     .index(0);
    ///
    /// let parsed = "networks[0]".parse().unwrap();
    ///
    /// assert_eq!(identifier, parsed);
    /// ```
    pub fn index(self, index: isize) -> Identifier {
        let mut values = self.values;
        values.push(IdentifierValue::Index(index));
        Identifier { values }
    }

    /// Appends `IdentifierValue::Identifier` to the identifier
    ///
    /// # Arguments
    ///
    /// * `identifier` - An identifier index
    ///
    /// ```rust
    /// use balena_temen::ast::*;
    ///
    /// let identifier = Identifier::default()
    ///     .name("wifi")
    ///     .identifier(Identifier::default().name("first_wifi_id"));
    ///
    /// let parsed = "wifi[first_wifi_id]".parse().unwrap();
    ///
    /// assert_eq!(identifier, parsed);
    /// ```
    pub fn identifier(self, identifier: Identifier) -> Identifier {
        let mut values = self.values;
        values.push(IdentifierValue::Identifier(identifier));
        Identifier { values }
    }

    /// Returns `Identifier` with the last identifier value removed
    pub fn pop(self) -> Result<Identifier> {
        let mut values = self.values;
        match values.pop() {
            Some(_) => Ok(Identifier { values }),
            None => Err(Error::with_message("unable to pop identifier")),
        }
    }
}

impl Default for Identifier {
    /// Creates new, empty, identifier
    ///
    /// This identifier can be used to refer to the root.
    fn default() -> Identifier {
        Identifier::new(vec![])
    }
}

/// An identifier value (component)
#[derive(Clone, Debug, PartialEq)]
pub enum IdentifierValue {
    /// A string index (dictionaries)
    Name(String),
    /// An integer index (arrays)
    Index(isize),
    /// An indirect index (value of another identifier)
    Identifier(Identifier),
    /// Current object
    This,
    /// Parent object
    Super,
}

/// An expression value
#[derive(Clone, Debug, PartialEq)]
pub enum ExpressionValue {
    /// An integer
    Integer(i64),
    /// A floating point
    Float(f64),
    /// A boolean
    Boolean(bool),
    /// A string
    String(String),
    /// An identifier (variable name, array index, ...)
    Identifier(Identifier),
    /// A mathematical expression
    Math(MathExpression),
    /// A logical expression
    Logical(LogicalExpression),
    /// A function call
    FunctionCall(FunctionCall),
    /// String concatenation
    StringConcat(StringConcat),
    /// Ternary expression
    Ternary(TernaryExpression),
}

/// An expression
#[derive(Clone, Debug, PartialEq)]
pub struct Expression {
    /// An expression value
    pub value: ExpressionValue,
    /// Is expression negated?
    pub negated: bool,
    /// List of filters to apply
    pub filters: Vec<FunctionCall>,
}

impl Expression {
    /// Creates new expression
    ///
    /// Expression is not negated and no filters are applied.
    ///
    /// # Arguments
    ///
    /// * `value` - An expression value
    pub fn new(value: ExpressionValue) -> Expression {
        Expression {
            value,
            negated: false,
            filters: vec![],
        }
    }

    /// Creates new negated expression
    ///
    /// Expression is negated and no filters are applied.
    ///
    /// # Arguments
    ///
    /// * `value` - An expression value
    pub fn new_negated(value: ExpressionValue) -> Expression {
        Expression {
            value,
            negated: true,
            filters: vec![],
        }
    }

    /// Creates new expression
    ///
    /// Expression is not negated and filters are applied.
    ///
    /// # Arguments
    ///
    /// * `value` - An expression value
    /// * `filters` - List of filters to apply
    pub fn new_with_filters(value: ExpressionValue, filters: Vec<FunctionCall>) -> Expression {
        Expression {
            value,
            negated: false,
            filters,
        }
    }

    /// Converts self into negated expression
    pub fn into_negated(self) -> Expression {
        Expression {
            value: self.value,
            negated: !self.negated,
            filters: self.filters,
        }
    }

    /// Returns identifier from an expression value
    pub fn identifier(&self) -> Option<&Identifier> {
        match &self.value {
            ExpressionValue::Identifier(ref identifier) => Some(identifier),
            _ => None,
        }
    }

    /// Converts self into [`Identifier`]
    ///
    /// [`Identifier`]: struct.Identifier.html
    pub fn into_identifier(self) -> Result<Identifier> {
        match self.value {
            ExpressionValue::Identifier(identifier) => Ok(identifier),
            _ => Err(Error::with_message("expression does not contain an identifier")
                .context("expression", format!("{:?}", self))),
        }
    }
}

impl FromStr for Expression {
    type Err = Error;

    fn from_str(s: &str) -> Result<Expression> {
        parse(s)
    }
}

impl FromStr for Identifier {
    type Err = Error;

    fn from_str(s: &str) -> Result<Identifier> {
        parse(s)?.into_identifier()
    }
}