jexl-parser 0.4.0

A JEXL parser written in Rust
Documentation
use crate::ast::{Expression, OpCode};
use crate::lexer::Token;

grammar<'input>();

extern {
    type Location = usize;
    type Error = crate::lexer::LexError;
    
    enum Token<'input> {
        // Literals
        Number => Token::Number(<f64>),
        DoubleQuotedString => Token::DoubleQuotedString(<&'input str>),
        SingleQuotedString => Token::SingleQuotedString(<&'input str>),
        Boolean => Token::Boolean(<bool>),
        Null => Token::Null,
        Identifier => Token::Identifier(<&'input str>),
        
        // Operators
        "+" => Token::Plus,
        "-" => Token::Minus,
        "*" => Token::Multiply,
        "/" => Token::Divide,
        "//" => Token::FloorDivide,
        "%" => Token::Modulus,
        "^" => Token::Exponent,
        
        // Comparison
        "==" => Token::Equal,
        "!=" => Token::NotEqual,
        ">" => Token::Greater,
        ">=" => Token::GreaterEqual,
        "<" => Token::Less,
        "<=" => Token::LessEqual,
        "in" => Token::In,
        
        // Logical
        "&&" => Token::And,
        "||" => Token::Or,
        
        // Punctuation
        "(" => Token::LeftParen,
        ")" => Token::RightParen,
        "[" => Token::LeftBracket,
        "]" => Token::RightBracket,
        "{" => Token::LeftBrace,
        "}" => Token::RightBrace,
        "," => Token::Comma,
        "." => Token::Dot,
        ":" => Token::Colon,
        "?" => Token::Question,
        "|" => Token::Pipe,
    }
}

pub Expression: Box<Expression> = Expr00;

Expr00: Box<Expression> = {
    <left: Expression> <operation: Op10> <right: Expr10> => Box::new(Expression::BinaryOperation { left, right, operation }),
    Expr10,
};

Expr10: Box<Expression> = {
    <left: Expr10> <operation: Op20> <right: Expr20> => Box::new(Expression::BinaryOperation { left, right, operation }),
    Expr20,
};

Expr20: Box<Expression> = {
    <left: Expr20> <operation: Op30> <right: Expr30> => Box::new(Expression::BinaryOperation { left, right, operation }),
    Expr30,
};

Expr30: Box<Expression> = {
    <left: Expr30> <operation: Op40> <right: Expr40> => Box::new(Expression::BinaryOperation { left, right, operation }),
    Expr40,
};

Expr40: Box<Expression> = {
    <left: Expr40> <operation: Op50> <right: Expr50> => Box::new(Expression::BinaryOperation { left, right, operation }),
    Expr50,
};

Expr50: Box<Expression> = {
    <left: Expr50> "?" <truthy: Expr60> ":" <falsy: Expr60> => Box::new(Expression::Conditional {left, truthy, falsy}),
    Expr60,
}

Expr60: Box<Expression> = {
    <subject: Expr60> "|" <name: Identifier> <args: Args?> => Box::new(Expression::Transform{name: name.to_string(), subject, args}),
    Expr70
};


/// Expression for dereferencing.
/// Used for dereferencing object literals, array literals, and the context
/// There are two types of operations here:
/// - Either a `dot` operation, taking an expression on the left hand side, and an identifier on the right hand side (a string without the quotations)
/// - Or an `index` operation, taking an expression on the left hand side, and another expression inside square ("[]") brackets.
///
/// # Examples:
/// 
/// Assume our context is the following
/// ```
///{
///  "foo":
///  {
///     "bar": [{"baz": 1}, {"bobo": [13, 12]}]
//   }
// }
/// ```
///
/// `foo.bar == [{"baz": 1}, {"bobo": [13, 12]]`
/// `foo.bar[0] == {"baz": 1}`
/// `foo.bar[1].bobo[0] == 13`
/// `[1, 2, 3][1] == 2`
Expr70: Box<Expression> = {
    <subject: Expr70> <index: Index> => Box::new(Expression::IndexOperation{subject, index}),
    <subject: Expr70> "." <ident: Identifier>  => Box::new(Expression::DotOperation{subject, ident: ident.to_string()}),
    Expr80
};

Expr80: Box<Expression> = {
    Number => Box::new(Expression::Number(<>)),
    Boolean => Box::new(Expression::Boolean(<>)),
    String => Box::new(Expression::String(<>)),
    Array => Box::new(Expression::Array(<>)),
    Object => Box::new(Expression::Object(<>)),
    Null => Box::new(Expression::Null),
    Identifier => Box::new(Expression::Identifier(<>.to_string())),
    "(" <Expression> ")",
};

Args: Vec<Box<Expression>> = {
    "(" <Comma<Expression>> ")"
};

Op10: OpCode = {
    "&&" => OpCode::And,
    "||" => OpCode::Or,
};

Op20: OpCode = {
    "==" => OpCode::Equal,
    "!=" => OpCode::NotEqual,
    ">=" => OpCode::GreaterEqual,
    "<=" => OpCode::LessEqual,
    ">" => OpCode::Greater,
    "<" => OpCode::Less,
    "in" => OpCode::In,
};

Op30: OpCode = {
    "+" => OpCode::Add,
    "-" => OpCode::Subtract,
};

Op40: OpCode = {
    "*" => OpCode::Multiply,
    "//" => OpCode::FloorDivide,
    "/" => OpCode::Divide,
};

Op50: OpCode = {
    "%" => OpCode::Modulus,
    "^" => OpCode::Exponent,
};

// The weird string literal handling here to preserve the weird semantics of the previous lexer implementation:
//  String: String = {
//     <s: r#""([^"\\]*(\\")?)*""#> => s[1..s.len() - 1].to_string().replace("\\\"", "\""),
//     <s: r#"'([^'\\]*(\\')?)*'"#> => s[1..s.len() - 1].to_string().replace("\\'", "'"),
//  };
String: String = {
    <s: DoubleQuotedString> => s.replace("\\\"", "\""),
    <s: SingleQuotedString> => s.replace("\\'", "'"),
};

Index: Box<Expression> = {
    "[" "." <ident: Identifier> <op: Op20> <right: Expr80> "]" => Box::new(Expression::Filter {ident: ident.to_string(), op, right}),
    "[" <Expression> "]",
}


// Boolean tokens are now handled by the external lexer

Comma<T>: Vec<T> = {
    <v: (<T> ",")*> <e:T?> => match e {
        None => v,
        Some(e) => {
            let mut v = v;
            v.push(e);
            v
        }
    }
};

Array: Vec<Box<Expression>> = {
    "[" <Comma<Expression>> "]"
}

Object: Vec<(String, Box<Expression>)> = {
    "{" <Comma<(<ObjectIdentifier> ":" <Expression>)>> "}",
}



ObjectIdentifier: String = {
    String,
    Identifier => <>.to_string()
}