diff --git a/src/jsonpath/parser.rs b/src/jsonpath/parser.rs index e4ea1be..3a889af 100644 --- a/src/jsonpath/parser.rs +++ b/src/jsonpath/parser.rs @@ -319,6 +319,23 @@ fn op(input: &[u8]) -> IResult<&[u8], BinaryOperator> { ))(input) } +fn unary_arith_op(input: &[u8]) -> IResult<&[u8], UnaryArithmeticOperator> { + alt(( + value(UnaryArithmeticOperator::Add, char('+')), + value(UnaryArithmeticOperator::Subtract, char('-')), + ))(input) +} + +fn binary_arith_op(input: &[u8]) -> IResult<&[u8], BinaryArithmeticOperator> { + alt(( + value(BinaryArithmeticOperator::Add, char('+')), + value(BinaryArithmeticOperator::Subtract, char('-')), + value(BinaryArithmeticOperator::Multiply, char('*')), + value(BinaryArithmeticOperator::Divide, char('/')), + value(BinaryArithmeticOperator::Modulus, char('%')), + ))(input) +} + fn path_value(input: &[u8]) -> IResult<&[u8], PathValue<'_>> { alt(( value(PathValue::Null, tag("null")), @@ -339,8 +356,33 @@ fn inner_expr(input: &[u8], root_predicate: bool) -> IResult<&[u8], Expr<'_>> { } fn expr_atom(input: &[u8], root_predicate: bool) -> IResult<&[u8], Expr<'_>> { - // TODO, support arithmetic expressions. alt(( + map( + tuple(( + delimited(multispace0, |i| inner_expr(i, root_predicate), multispace0), + binary_arith_op, + delimited(multispace0, |i| inner_expr(i, root_predicate), multispace0), + )), + |(left, op, right)| { + Expr::ArithmeticFunc(ArithmeticFunc::Binary { + op, + left: Box::new(left), + right: Box::new(right), + }) + }, + ), + map( + tuple(( + unary_arith_op, + delimited(multispace0, |i| inner_expr(i, root_predicate), multispace0), + )), + |(op, operand)| { + Expr::ArithmeticFunc(ArithmeticFunc::Unary { + op, + operand: Box::new(operand), + }) + }, + ), map( tuple(( delimited(multispace0, |i| inner_expr(i, root_predicate), multispace0), diff --git a/src/jsonpath/path.rs b/src/jsonpath/path.rs index ac9861f..dcc0784 100644 --- a/src/jsonpath/path.rs +++ b/src/jsonpath/path.rs @@ -62,6 +62,8 @@ pub enum Path<'a> { /// There can be more than one index, e.g. `$[0, last-1 to last, 5]` represents the first, /// the last two, and the sixth element in an Array. ArrayIndices(Vec), + /// `` standalone unary or binary arithmetic expression, like '-$.a[*]' or '$.a + 3' + ArithmeticExpr(Box>), /// `?()` represents selecting all elements in an object or array that match the filter expression, like `$.book[?(@.price < 10)]`. FilterExpr(Box>), /// `` standalone filter expression, like `$.book[*].price > 10`. @@ -120,6 +122,41 @@ pub enum BinaryOperator { Gte, } +#[derive(Debug, Clone, PartialEq)] +pub enum UnaryArithmeticOperator { + /// `Add` represents unary arithmetic + operation. + Add, + /// `Subtract` represents unary arithmetic - operation. + Subtract, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BinaryArithmeticOperator { + /// `Add` represents binary arithmetic + operation. + Add, + /// `Subtract` represents binary arithmetic - operation. + Subtract, + /// `Multiply` represents binary arithmetic * operation. + Multiply, + /// `Divide` represents binary arithmetic / operation. + Divide, + /// `Modulus` represents binary arithmetic % operation. + Modulus, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ArithmeticFunc<'a> { + Unary { + op: UnaryArithmeticOperator, + operand: Box>, + }, + Binary { + op: BinaryArithmeticOperator, + left: Box>, + right: Box>, + }, +} + /// Represents a filter expression used to filter Array or Object. #[derive(Debug, Clone, PartialEq)] pub enum Expr<'a> { @@ -133,6 +170,8 @@ pub enum Expr<'a> { left: Box>, right: Box>, }, + /// Arithmetic expression that performs an arithmetic operation, returns a number value. + ArithmeticFunc(ArithmeticFunc<'a>), /// Filter function, returns a boolean value. FilterFunc(FilterFunc<'a>), } @@ -223,6 +262,9 @@ impl<'a> Display for Path<'a> { } write!(f, "]")?; } + Path::ArithmeticExpr(expr) => { + write!(f, "?({expr})")?; + } Path::FilterExpr(expr) => { write!(f, "?({expr})")?; } @@ -288,6 +330,29 @@ impl Display for BinaryOperator { } } +impl Display for UnaryArithmeticOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let symbol = match self { + UnaryArithmeticOperator::Add => "+", + UnaryArithmeticOperator::Subtract => "-", + }; + write!(f, "{}", symbol) + } +} + +impl Display for BinaryArithmeticOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let symbol = match self { + BinaryArithmeticOperator::Add => "+", + BinaryArithmeticOperator::Subtract => "-", + BinaryArithmeticOperator::Multiply => "*", + BinaryArithmeticOperator::Divide => "/", + BinaryArithmeticOperator::Modulus => "%", + }; + write!(f, "{}", symbol) + } +} + impl<'a> Display for Expr<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -320,6 +385,14 @@ impl<'a> Display for Expr<'a> { write!(f, "{right}")?; } } + Expr::ArithmeticFunc(expr) => match expr { + ArithmeticFunc::Unary { op, operand } => { + write!(f, "{}{}", op, operand)?; + } + ArithmeticFunc::Binary { op, left, right } => { + write!(f, "{} {} {}", left, op, right)?; + } + }, Expr::FilterFunc(func) => match func { FilterFunc::Exists(paths) => { f.write_str("exists(")?; diff --git a/tests/it/jsonpath_parser.rs b/tests/it/jsonpath_parser.rs index bb07b2c..928fe1a 100644 --- a/tests/it/jsonpath_parser.rs +++ b/tests/it/jsonpath_parser.rs @@ -25,8 +25,17 @@ fn test_json_path() { r#"$"#, r#"$.*"#, r#"$[*]"#, + r#"5 + 5"#, + r#"10 - 5"#, + r#"10 * 5"#, + r#"10 / 5"#, + r#"10 % 5"#, r#"$.store.book[*].*"#, + // r#"$.store.book[*].* + 5"#, r#"$.store.book[0].price"#, + r#"+$.store.book[0].price"#, + r#"-$.store.book[0].price"#, + r#"$.store.book[0].price + 5"#, r#"$.store.book[last].isbn"#, r"$.store.book[last].test_key\uD83D\uDC8E测试", r#"$.store.book[0,1, last - 2].price"#, diff --git a/tests/it/testdata/json_path.txt b/tests/it/testdata/json_path.txt index 1938b14..4a5b223 100644 --- a/tests/it/testdata/json_path.txt +++ b/tests/it/testdata/json_path.txt @@ -36,6 +36,166 @@ JsonPath { } +---------- Input ---------- +5 + 5 +---------- Output --------- +5 + 5 +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Binary { + op: Add, + left: Value( + Number( + UInt64( + 5, + ), + ), + ), + right: Value( + Number( + UInt64( + 5, + ), + ), + ), + }, + ), + ), + ], +} + + +---------- Input ---------- +10 - 5 +---------- Output --------- +10 - 5 +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Binary { + op: Subtract, + left: Value( + Number( + UInt64( + 10, + ), + ), + ), + right: Value( + Number( + UInt64( + 5, + ), + ), + ), + }, + ), + ), + ], +} + + +---------- Input ---------- +10 * 5 +---------- Output --------- +10 * 5 +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Binary { + op: Multiply, + left: Value( + Number( + UInt64( + 10, + ), + ), + ), + right: Value( + Number( + UInt64( + 5, + ), + ), + ), + }, + ), + ), + ], +} + + +---------- Input ---------- +10 / 5 +---------- Output --------- +10 / 5 +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Binary { + op: Divide, + left: Value( + Number( + UInt64( + 10, + ), + ), + ), + right: Value( + Number( + UInt64( + 5, + ), + ), + ), + }, + ), + ), + ], +} + + +---------- Input ---------- +10 % 5 +---------- Output --------- +10 % 5 +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Binary { + op: Modulus, + left: Value( + Number( + UInt64( + 10, + ), + ), + ), + right: Value( + Number( + UInt64( + 5, + ), + ), + ), + }, + ), + ), + ], +} + + ---------- Input ---------- $.store.book[*].* ---------- Output --------- @@ -86,6 +246,136 @@ JsonPath { } +---------- Input ---------- ++$.store.book[0].price +---------- Output --------- ++$.store.book[0].price +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Unary { + op: Add, + operand: Paths( + [ + Root, + DotField( + "store", + ), + DotField( + "book", + ), + ArrayIndices( + [ + Index( + Index( + 0, + ), + ), + ], + ), + DotField( + "price", + ), + ], + ), + }, + ), + ), + ], +} + + +---------- Input ---------- +-$.store.book[0].price +---------- Output --------- +-$.store.book[0].price +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Unary { + op: Subtract, + operand: Paths( + [ + Root, + DotField( + "store", + ), + DotField( + "book", + ), + ArrayIndices( + [ + Index( + Index( + 0, + ), + ), + ], + ), + DotField( + "price", + ), + ], + ), + }, + ), + ), + ], +} + + +---------- Input ---------- +$.store.book[0].price + 5 +---------- Output --------- +$.store.book[0].price + 5 +---------- AST ------------ +JsonPath { + paths: [ + Predicate( + ArithmeticFunc( + Binary { + op: Add, + left: Paths( + [ + Root, + DotField( + "store", + ), + DotField( + "book", + ), + ArrayIndices( + [ + Index( + Index( + 0, + ), + ), + ], + ), + DotField( + "price", + ), + ], + ), + right: Value( + Number( + UInt64( + 5, + ), + ), + ), + }, + ), + ), + ], +} + + ---------- Input ---------- $.store.book[last].isbn ---------- Output ---------