From a635e6ac4fd5c8f29ebbda652fc0771f626c2fec Mon Sep 17 00:00:00 2001 From: thehiddenwaffle Date: Mon, 2 Jun 2025 13:05:10 -0400 Subject: [PATCH 1/3] Progress on the test functions --- jsonpath-ast/src/ast.rs | 61 ++++++--- jsonpath-ast/src/syn_parse.rs | 127 +++++++++--------- .../functions/count/compile_and_passes.rs | 28 ++++ .../functions/count/compile_but_expect_err.rs | 63 +++++++++ .../functions/count/does_not_compile.rs | 47 +++++++ .../functions/count/mod.rs | 2 + .../rfc9535_compile_tests/functions/mod.rs | 1 + .../tests/rfc9535_compile_tests/mod.rs | 1 + jsonpath-rust-impl/tests/test.rs | 4 +- 9 files changed, 251 insertions(+), 83 deletions(-) create mode 100644 jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_and_passes.rs create mode 100644 jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_but_expect_err.rs create mode 100644 jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/does_not_compile.rs create mode 100644 jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/mod.rs create mode 100644 jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/mod.rs diff --git a/jsonpath-ast/src/ast.rs b/jsonpath-ast/src/ast.rs index af316b7..dffb381 100644 --- a/jsonpath-ast/src/ast.rs +++ b/jsonpath-ast/src/ast.rs @@ -61,22 +61,23 @@ macro_rules! terminating_from_pest { use super::parse::{JSPathParser, Rule}; #[cfg(feature = "compiled-path")] use crate::syn_parse::parse_impl::{ - ParseUtilsExt, parse_bool, parse_float, validate_function_name, validate_js_int, - validate_js_str, validate_member_name_shorthand, + parse_bool, parse_float, validate_js_int, validate_js_str, + validate_member_name_shorthand, ParseUtilsExt, }; use derive_new::new; use from_pest::{ConversionError, FromPest, Void}; -use pest::Parser; use pest::iterators::{Pair, Pairs}; +use pest::Parser; use pest_ast::FromPest; use proc_macro2::Span; -#[allow(unused_imports)] -use syn::LitBool; +use quote::ToTokens; #[cfg(feature = "compiled-path")] use syn::parse::ParseStream; use syn::punctuated::Punctuated; use syn::token::Bracket; -use syn::{Ident, Token, token}; +#[allow(unused_imports)] +use syn::LitBool; +use syn::{token, Token}; #[cfg(feature = "compiled-path")] use syn_derive::Parse; @@ -611,12 +612,12 @@ impl<'pest> FromPest<'pest> for CompOp { } #[derive(Debug, new, PartialEq)] -#[cfg_attr(feature = "compiled-path", derive(Parse))] +// #[cfg_attr(feature = "compiled-path", derive(Parse))] pub struct FunctionExpr { pub(crate) name: FunctionName, - #[cfg_attr(feature = "compiled-path", syn(parenthesized))] + // #[cfg_attr(feature = "compiled-path", syn(parenthesized))] pub(crate) paren: token::Paren, - #[cfg_attr(feature = "compiled-path", syn(in = paren))] + // #[cfg_attr(feature = "compiled-path", syn(in = paren))] // #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestIgnoredPunctuated::parse_terminated(i)))] pub(crate) args: PestIgnoredPunctuated, } @@ -650,9 +651,27 @@ impl<'pest> from_pest::FromPest<'pest> for FunctionExpr { #[derive(Debug, new, PartialEq)] #[cfg_attr(feature = "compiled-path", derive(Parse))] -pub struct FunctionName { - #[cfg_attr(feature = "compiled-path", parse(validate_function_name))] - name: Ident, +pub enum FunctionName { + #[cfg_attr(feature = "compiled-path", parse(peek = kw::length))] + Length(kw::length), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::value))] + Value(kw::value), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::count))] + Count(kw::count), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::search))] + Search(kw::search), + #[cfg_attr(feature = "compiled-path", parse(peek = Token![match]))] + Match(Token![match]), + #[cfg_attr(feature = "compiled-path", parse(peek = Token![in]))] + In(Token![in]), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::nin))] + Nin(kw::nin), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::none_of))] + NoneOf(kw::none_of), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::any_of))] + AnyOf(kw::any_of), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::subset_of))] + SubsetOf(kw::subset_of), } impl<'pest> FromPest<'pest> for FunctionName { @@ -666,12 +685,20 @@ impl<'pest> FromPest<'pest> for FunctionName { let pair = clone.next().ok_or(ConversionError::NoMatch)?; if matches!( pair.as_rule(), - Rule::function_name_one_arg | Rule::function_name_two_arg + Rule::function_name ) { - let mut inner = pair.into_inner(); - let inner = &mut inner; - let this = FunctionName { - name: Ident::new(inner.to_string().as_str().trim(), Span::call_site()), + let this = match pair.as_str().trim() { + "length" => Self::Length(Default::default()), + "value" => Self::Value(Default::default()), + "count" => Self::Count(Default::default()), + "search" => Self::Search(Default::default()), + "match" => Self::Match(Default::default()), + "in" => Self::In(Default::default()), + "nin" => Self::Nin(Default::default()), + "none_of" => Self::NoneOf(Default::default()), + "any_of" => Self::AnyOf(Default::default()), + "subset_of" => Self::SubsetOf(Default::default()), + _ => unreachable!("Invalid function name should be impossible, error in pest grammar") }; *pest = clone; Ok(this) diff --git a/jsonpath-ast/src/syn_parse.rs b/jsonpath-ast/src/syn_parse.rs index bda5ce0..2c87549 100644 --- a/jsonpath-ast/src/syn_parse.rs +++ b/jsonpath-ast/src/syn_parse.rs @@ -1,24 +1,25 @@ #[cfg(feature = "compiled-path")] pub(crate) mod parse_impl { use crate::ast::parse::{JSPathParser, Rule}; + use crate::ast::{kw, CompOp, IndexSelector, Main, NameSelector}; use crate::ast::{ AbsSingularQuery, AtomExpr, Bool, BracketName, BracketedSelection, ChildSegment, CompExpr, - Comparable, DescendantSegment, EOI, FilterSelector, FunctionArgument, FunctionExpr, - FunctionName, IndexSegment, JPQuery, JSInt, JSString, Literal, LogicalExpr, LogicalExprAnd, - MemberNameShorthand, NameSegment, NotOp, Null, Number, ParenExpr, PestIgnoredPunctuated, - PestLiteralWithoutRule, RelQuery, RelSingularQuery, Root, Segment, Segments, Selector, - SingularQuery, SingularQuerySegment, SingularQuerySegments, SliceEnd, SliceSelector, - SliceStart, SliceStep, Test, TestExpr, WildcardSelector, - WildcardSelectorOrMemberNameShorthand, + Comparable, DescendantSegment, FilterSelector, FunctionArgument, FunctionExpr, FunctionName, + IndexSegment, JPQuery, JSInt, JSString, Literal, LogicalExpr, LogicalExprAnd, MemberNameShorthand, + NameSegment, NotOp, Null, Number, ParenExpr, PestIgnoredPunctuated, PestLiteralWithoutRule, + RelQuery, RelSingularQuery, Root, Segment, Segments, Selector, SingularQuery, + SingularQuerySegment, SingularQuerySegments, SliceEnd, SliceSelector, SliceStart, + SliceStep, Test, TestExpr, WildcardSelector, WildcardSelectorOrMemberNameShorthand, + EOI, }; - use crate::ast::{CompOp, IndexSelector, Main, NameSelector, kw}; use pest::Parser; use proc_macro2::{Ident, TokenStream}; - use quote::{ToTokens, quote}; + use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; + use syn::spanned::Spanned; use syn::token::Token; - use syn::{LitBool, LitInt, LitStr, Token, token}; + use syn::{token, LitBool, LitInt, LitStr, Token}; pub trait ParseUtilsExt: Parse { fn peek(input: ParseStream) -> bool; @@ -629,11 +630,27 @@ pub(crate) mod parse_impl { impl ToTokens for FunctionName { fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(quote! { - ::jsonpath_ast::ast::FunctionName::new( - ::proc_macro2::Ident::new("function_name", ::proc_macro2::Span::call_site()) - ) - }); + // tokens.extend(quote! { + // ::jsonpath_ast::ast::FunctionName::new( + // ::proc_macro2::Ident::new("function_name", ::proc_macro2::Span::call_site()) + // ) + // }); + let variant = match self { + // Literal::Number(inner) => { + // quote!(new_number(#inner)) + // } + FunctionName::Length(_) => { quote!(new_length(Default::default())) } + FunctionName::Value(_) => { quote!(new_value(Default::default())) } + FunctionName::Count(_) => { quote!(new_count(Default::default())) } + FunctionName::Search(_) => { quote!(new_search(Default::default())) } + FunctionName::Match(_) => { quote!(new_match(Default::default())) } + FunctionName::In(_) => { quote!(new_in(Default::default())) } + FunctionName::Nin(_) => { quote!(new_nin(Default::default())) } + FunctionName::NoneOf(_) => { quote!(new_none_of(Default::default())) } + FunctionName::AnyOf(_) => { quote!(new_any_of(Default::default())) } + FunctionName::SubsetOf(_) => { quote!(new_subset_of(Default::default())) } + }; + tokens.extend(quote!(::jsonpath_ast::ast::FunctionName::#variant)) } } @@ -840,9 +857,13 @@ pub(crate) mod parse_impl { impl ToTokens for TestExpr { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { not_op, test } = self; + let repr_not = match not_op { + Some(not_op) => quote! {Some(#not_op)}, + None => quote! {None}, + }; tokens.extend(quote! { ::jsonpath_ast::ast::TestExpr::new( - #not_op, + #repr_not, #test ) }); @@ -992,7 +1013,11 @@ pub(crate) mod parse_impl { impl ParseUtilsExt for CompExpr { fn peek(input: ParseStream) -> bool { - Comparable::peek(input) + let fork = input.fork(); + // This is very suboptimal but the only option because at this point in the stream a comp_expr and a test_expr + // look identical if they're both functions, IE: $[?match(@, $.regex)] is a test_exp while $[?match(@, $.regex) == true] + // is a comp_exp + fork.parse::().is_ok() && fork.parse::().is_ok() } } impl ParseUtilsExt for TestExpr { @@ -1084,6 +1109,27 @@ pub(crate) mod parse_impl { Ok(num) } + fn function_name_expected_args(name: &FunctionName) -> (String, usize) { + (format!("{:?}", name), match name { + FunctionName::Length(_) | FunctionName::Value(_) | FunctionName::Count(_) => { 1 }, + FunctionName::Search(_) | FunctionName::Match(_) + | FunctionName::In(_) | FunctionName::Nin(_) + | FunctionName::NoneOf(_) | FunctionName::AnyOf(_) | FunctionName::SubsetOf(_) => { 2 }, + }) + } + impl Parse for FunctionExpr { + fn parse(__input: ParseStream) -> ::syn::Result { + let paren; + let ret = Self { name: __input.parse()?, paren: syn::parenthesized!(paren in __input ), args: PestIgnoredPunctuated::parse_separated_nonempty(&paren)? }; + let (func_name, expected_num_args) = function_name_expected_args(&ret.name); + if expected_num_args == ret.args.0.len() { + Ok(ret) + } else { + Err(syn::Error::new(ret.args.span(), format!("Invalid number of arguments for function {}, expected {}", func_name, expected_num_args))) + } + } + } + impl ParseUtilsExt for FunctionExpr { fn peek(input: ParseStream) -> bool { FunctionName::peek(input) @@ -1105,53 +1151,6 @@ pub(crate) mod parse_impl { } } - pub fn validate_function_name(input: ParseStream) -> Result { - if input.peek(kw::length) { - input.parse::()?; - return Ok(Ident::new("length", input.span())); - } - if input.peek(kw::value) { - input.parse::()?; - return Ok(Ident::new("value", input.span())); - } - if input.peek(kw::count) { - input.parse::()?; - return Ok(Ident::new("count", input.span())); - } - if input.peek(kw::search) { - input.parse::()?; - return Ok(Ident::new("search", input.span())); - } - if input.peek(Token![match]) { - input.parse::()?; - return Ok(Ident::new("match", input.span())); - } - if input.peek(Token![in]) { - input.parse::()?; - return Ok(Ident::new("in", input.span())); - } - if input.peek(kw::nin) { - input.parse::()?; - return Ok(Ident::new("nin", input.span())); - } - if input.peek(kw::none_of) { - input.parse::()?; - return Ok(Ident::new("none_of", input.span())); - } - if input.peek(kw::any_of) { - input.parse::()?; - return Ok(Ident::new("any_of", input.span())); - } - if input.peek(kw::subset_of) { - input.parse::()?; - return Ok(Ident::new("subset_of", input.span())); - } - Err(syn::Error::new( - input.span(), - "invalid function name, expected one of: length, value, count, search, match, in, nin, none_of, any_of, subset_of", - )) - } - impl ParseUtilsExt for RelQuery { fn peek(input: ParseStream) -> bool { input.peek(Token![@]) diff --git a/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_and_passes.rs b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_and_passes.rs new file mode 100644 index 0000000..b529b39 --- /dev/null +++ b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_and_passes.rs @@ -0,0 +1,28 @@ +// Test case: 00_count_function +// Tags: function, count +#[test] +fn test_00_count_function() { + let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@..*)>2]); + let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@..*)>2]"#).expect("failed to parse"); + assert_eq!(q_pest, q_ast); +} + +// Test case: 01_single_node_arg +// Tags: function, count +#[test] +fn test_01_single_node_arg() { + let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@.a)>1]); + let q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@.a)>1]"#).expect("failed to parse"); + assert_eq!(q_pest, q_ast); +} + +// Test case: 02_multiple_selector_arg +// Tags: function, count +#[test] +fn test_02_multiple_selector_arg() { + let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@["a","d"])>1]); + let q_pest_double = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@["a","d"])>1]"#).expect("failed to parse"); + let q_pest_single = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@['a','d'])>1]"#).expect("failed to parse"); + assert_eq!(q_pest_double, q_ast); + assert_eq!(q_pest_single, q_ast); +} diff --git a/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_but_expect_err.rs b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_but_expect_err.rs new file mode 100644 index 0000000..dffda3e --- /dev/null +++ b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/compile_but_expect_err.rs @@ -0,0 +1,63 @@ +// Test case: 03_non_query_arg_number +// Tags: function, count +#[test] +fn test_03_non_query_arg_number() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count(1)>2]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(1)>2]"#).expect_err("should not parse"); +} + +// Test case: 04_non_query_arg_string +// Tags: function, count +#[test] +fn test_04_non_query_arg_string() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count('string')>2]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count('string')>2]"#).expect_err("should not parse"); +} + +// Test case: 05_non_query_arg_true +// Tags: function, count +#[test] +fn test_05_non_query_arg_true() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count(true)>2]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(true)>2]"#).expect_err("should not parse"); +} + +// Test case: 06_non_query_arg_false +// Tags: function, count +#[test] +fn test_06_non_query_arg_false() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count(false)>2]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(false)>2]"#).expect_err("should not parse"); +} + +// Test case: 07_non_query_arg_null +// Tags: function, count +#[test] +fn test_07_non_query_arg_null() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count(null)>2]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(null)>2]"#).expect_err("should not parse"); +} + +// Test case: 08_result_must_be_compared +// Tags: function, count +#[test] +fn test_08_result_must_be_compared() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@..*)]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@..*)]"#).expect_err("should not parse"); +} + +// Test case: 09_no_params +// Tags: function, count +#[test] +fn test_09_no_params() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count()==1]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count()==1]"#).expect_err("should not parse"); +} + +// Test case: 10_too_many_params +// Tags: function, count +#[test] +fn test_10_too_many_params() { + // let q_ast = ::jsonpath_rust_impl::json_query!($[?count(@.a,@.b)==1]); + let _q_pest = ::jsonpath_ast::ast::Main::try_from_pest_parse(r#"$[?count(@.a,@.b)==1]"#).expect_err("should not parse"); +} diff --git a/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/does_not_compile.rs b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/does_not_compile.rs new file mode 100644 index 0000000..81f4a4a --- /dev/null +++ b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/does_not_compile.rs @@ -0,0 +1,47 @@ +// Test case: 03_non_query_arg_number +// Tags: function, count +fn test_03_non_query_arg_number() { + ::jsonpath_rust_impl::json_query!($[?count(1)>2]); +} + +// Test case: 04_non_query_arg_string +// Tags: function, count +fn test_04_non_query_arg_string() { + ::jsonpath_rust_impl::json_query!($[?count('string')>2]); +} + +// Test case: 05_non_query_arg_true +// Tags: function, count +fn test_05_non_query_arg_true() { + ::jsonpath_rust_impl::json_query!($[?count(true)>2]); +} + +// Test case: 06_non_query_arg_false +// Tags: function, count +fn test_06_non_query_arg_false() { + ::jsonpath_rust_impl::json_query!($[?count(false)>2]); +} + +// Test case: 07_non_query_arg_null +// Tags: function, count +fn test_07_non_query_arg_null() { + ::jsonpath_rust_impl::json_query!($[?count(null)>2]); +} + +// Test case: 08_result_must_be_compared +// Tags: function, count +fn test_08_result_must_be_compared() { + ::jsonpath_rust_impl::json_query!($[?count(@..*)]); +} + +// Test case: 09_no_params +// Tags: function, count +fn test_09_no_params() { + ::jsonpath_rust_impl::json_query!($[?count()==1]); +} + +// Test case: 10_too_many_params +// Tags: function, count +fn test_10_too_many_params() { + ::jsonpath_rust_impl::json_query!($[?count(@.a,@.b)==1]); +} diff --git a/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/mod.rs b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/mod.rs new file mode 100644 index 0000000..31def23 --- /dev/null +++ b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/count/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod compile_and_passes; +pub(crate) mod compile_but_expect_err; diff --git a/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/mod.rs b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/mod.rs new file mode 100644 index 0000000..39f712c --- /dev/null +++ b/jsonpath-rust-impl/tests/rfc9535_compile_tests/functions/mod.rs @@ -0,0 +1 @@ +pub(crate) mod count; \ No newline at end of file diff --git a/jsonpath-rust-impl/tests/rfc9535_compile_tests/mod.rs b/jsonpath-rust-impl/tests/rfc9535_compile_tests/mod.rs index f641bc5..e3c36a2 100644 --- a/jsonpath-rust-impl/tests/rfc9535_compile_tests/mod.rs +++ b/jsonpath-rust-impl/tests/rfc9535_compile_tests/mod.rs @@ -1 +1,2 @@ pub(crate) mod basic; +pub(crate) mod functions; diff --git a/jsonpath-rust-impl/tests/test.rs b/jsonpath-rust-impl/tests/test.rs index 6a418ed..c0b228d 100644 --- a/jsonpath-rust-impl/tests/test.rs +++ b/jsonpath-rust-impl/tests/test.rs @@ -9,7 +9,7 @@ mod tests { #[test] fn scratch() { - let q_ast = json_query!($.values[?match(@, $.regex)]).into(); + let q_ast = json_query!($.values[?match(@, $.regex)]); json_query!( $..[1] ); json_query!( $[1,::] ); @@ -57,7 +57,7 @@ mod tests { /// Common function to run trybuild for all in suite dir fn trybuild(dir: &str) { - let t = ::trybuild::TestCases::new(); + let t = trybuild::TestCases::new(); let fail_path = format!("tests/rfc9535_compile_tests/{}/does_not_compile.rs", dir); t.compile_fail(fail_path); } From 8c09cb9949d516137493fdc9dab419307e5f461d Mon Sep 17 00:00:00 2001 From: thehiddenwaffle Date: Mon, 2 Jun 2025 13:05:52 -0400 Subject: [PATCH 2/3] Refactor grammar to improve function parsing with type-sensitive function signatures. Updated the grammar to handle function expressions more explicitly and aligned them with RFC standards for LogicalType, NodesType, and ValueType. --- src/parser/grammar/json_path_9535.pest | 66 ++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/src/parser/grammar/json_path_9535.pest b/src/parser/grammar/json_path_9535.pest index 33e5f17..4bbe107 100644 --- a/src/parser/grammar/json_path_9535.pest +++ b/src/parser/grammar/json_path_9535.pest @@ -16,22 +16,68 @@ step = {":" ~ S ~ int?} start = {int} end = {int} slice_selector = { start? ~ S ~ ":" ~ S ~ end? ~ S ~ step? } -filter_selector = {"?"~ S ~ logical_expr} +filter_selector = {"?" ~ S ~ logical_expr} logical_expr = {logical_expr_and ~ S ~ ("||" ~ S ~ logical_expr_and)*} logical_expr_and = {atom_expr ~ S ~ ("&&" ~ S ~ atom_expr)*} atom_expr = {paren_expr | comp_expr| test_expr} paren_expr = {not_op? ~ S ~ "(" ~ S ~ logical_expr ~ S ~ ")"} comp_expr = { comparable ~ S ~ comp_op ~ S ~ comparable } test_expr = {not_op? ~ S ~ test} -test = {rel_query | jp_query | function_expr} -rel_query = {curr ~ S ~ segments} -function_expr = { ( function_name_one_arg ~ one_arg ) | ( function_name_two_arg ~ two_arg ) } -function_name_one_arg = { "length" | "value" | "count" } -function_name_two_arg = { "search" | "match" | "in" | "nin" | "none_of" | "any_of" | "subset_of" } -function_argument = { literal | test | logical_expr } -one_arg = _{ "(" ~ S ~ function_argument ~ S ~ ")" } -two_arg = _{ "(" ~ S ~ function_argument ~ S ~ "," ~ S ~ function_argument ~ S ~ ")" } -comparable = { literal | singular_query | function_expr } +test = { + filter_query // existence/non-existence + // Per RFC: [function expressions may be used] As a test-expr in a logical expression: + // The function's declared result type is LogicalType or (giving rise to conversion as per Section 2.4.2) NodesType. + | ( &( returns_logical_type | returns_nodes_type ) ~ function_expr ) // LogicalType or NodesType +} +filter_query = _{ rel_query | jp_query } +rel_query = {curr ~ segments} + + +function_expr = { length_func_call | search_func_call | match_func_call | in_func_call | nin_func_call | none_of_func_call | any_of_func_call | subset_of_func_call } +// https://github.com/pest-parser/pest/issues/333 would be awesome for this but it doesn't exist yet and it's been 7 years +// Lookahead to peek the names and then homogenize them into the same rule till we refine the parser code +length_func_call = _{ &"length" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ ")" } +value_func_call = _{ &"value" ~ function_name ~ "(" ~ S ~ nodes_type ~ S ~ ")" } +count_func_call = _{ &"count" ~ function_name ~ "(" ~ S ~ nodes_type ~ S ~ ")" } +search_func_call = _{ &"search" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +match_func_call = _{ &"match" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } + +in_func_call = _{ &"in" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +nin_func_call = _{ &"nin" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +none_of_func_call = _{ &"none_of" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +any_of_func_call = _{ &"any_of" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +subset_of_func_call = _{ &"subset_of" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } + +// When the declared type of the parameter is ValueType and the argument is one of the following: +// - A value expressed as a literal. +// - A singular query. In this case: +// - If the query results in a nodelist consisting of a single node, the argument is the value of the node. +// - If the query results in an empty nodelist, the argument is the special result Nothing. +value_type = { literal | singular_query | returns_value_type } +// When the declared type of the parameter is LogicalType and the argument is one of the following: +// - A function expression with declared result type NodesType. In this case, the argument is converted to LogicalType as per Section 2.4.2. +// - A logical-expr that is not a function expression. +logical_type = { + logical_expr // TODO why is this not allowed to be a function_expr? we guarantee it's return is a logical type + // | returns_logical_type // this case is actually covered as a subset of logical_expr + | nodes_type +} +// When the declared type of the parameter is NodesType and the argument is a query (which includes singular query). +nodes_type = { jp_query | returns_nodes_type } + + +returns_value_type = _{ length_func_call | value_func_call | count_func_call } +returns_logical_type = _{ search_func_call | match_func_call | in_func_call | nin_func_call | none_of_func_call | any_of_func_call | subset_of_func_call } +// Currently no functions return this, so never match for now. To add a node which returns NodesType, replace !ANY +returns_nodes_type = _{ !ANY } + +function_name = { "length" | "value" | "count" | "search" | "match" | "in" | "nin" | "none_of" | "any_of" | "subset_of" } +// Removed, a literal is a ValueType, and a logical_expr is just a test with more rules around it, both are LogicalType +// function_argument = { literal | test | logical_expr } + +// Per RFC: As a comparable in a comparison: +// The function's declared result type is ValueType. +comparable = { literal | singular_query | ( &returns_value_type ~ function_expr ) } literal = { number | string | bool | null } bool = {"true" | "false"} null = {"null"} From 0b7c34c2a781adcb2de67a2bb953ab705576141a Mon Sep 17 00:00:00 2001 From: thehiddenwaffle Date: Fri, 8 Aug 2025 08:21:11 -0400 Subject: [PATCH 3/3] Refactor function parsing to support type-sensitive signatures. Improved grammar rules and parsing logic to better align with function return types, distinguishing between LogicalType, NodesType, and ValueType. Introduced utilities for clearer type handling and refactored related structures for consistency. --- jsonpath-ast/src/ast.rs | 335 +++++++++++++++++-------- jsonpath-ast/src/syn_parse.rs | 292 +++++++++++++-------- src/parser.rs | 17 +- src/parser/grammar/json_path_9535.pest | 37 ++- 4 files changed, 449 insertions(+), 232 deletions(-) diff --git a/jsonpath-ast/src/ast.rs b/jsonpath-ast/src/ast.rs index dffb381..6afc61c 100644 --- a/jsonpath-ast/src/ast.rs +++ b/jsonpath-ast/src/ast.rs @@ -6,12 +6,9 @@ pub mod parse { } pub(crate) mod kw { - // syn::custom_keyword!(in); - syn::custom_keyword!(nin); - syn::custom_keyword!(size); - syn::custom_keyword!(none_of); - syn::custom_keyword!(any_of); - syn::custom_keyword!(subset_of); + use crate::ast::KnowsRule; + use crate::parse::Rule; + use syn::Token; syn::custom_keyword!(length); syn::custom_keyword!(value); @@ -19,8 +16,24 @@ pub(crate) mod kw { syn::custom_keyword!(search); // reserved // syn::custom_keyword!(match); + // syn::custom_keyword!(in); + syn::custom_keyword!(nin); + syn::custom_keyword!(none_of); + syn::custom_keyword!(any_of); + syn::custom_keyword!(subset_of); syn::custom_keyword!(null); + + impl KnowsRule for length { const RULE: Rule = Rule::length_func_call; } + impl KnowsRule for value { const RULE: Rule = Rule::value_func_call; } + impl KnowsRule for count { const RULE: Rule = Rule::count_func_call; } + impl KnowsRule for search { const RULE: Rule = Rule::search_func_call; } + impl KnowsRule for Token![match] { const RULE: Rule = Rule::match_func_call; } + impl KnowsRule for Token![in] { const RULE: Rule = Rule::in_func_call; } + impl KnowsRule for nin { const RULE: Rule = Rule::nin_func_call; } + impl KnowsRule for none_of { const RULE: Rule = Rule::none_of_func_call; } + impl KnowsRule for any_of { const RULE: Rule = Rule::any_of_func_call; } + impl KnowsRule for subset_of { const RULE: Rule = Rule::subset_of_func_call; } } macro_rules! terminating_from_pest { @@ -83,12 +96,16 @@ use syn_derive::Parse; pub trait KnowsRule { const RULE: Rule; + + fn matches_rule(other: Rule) -> bool { + other == Self::RULE + } } #[derive(Debug, new, PartialEq)] -pub struct PestIgnoredPunctuated(pub(crate) Punctuated); +pub struct PestWithIgnoredPunctuation(pub(crate) Punctuated); -impl<'pest, T, P> FromPest<'pest> for PestIgnoredPunctuated +impl<'pest, T, P> FromPest<'pest> for PestWithIgnoredPunctuation where T: FromPest<'pest, Rule = Rule, FatalError = Void> + KnowsRule + std::fmt::Debug, P: Default, @@ -101,7 +118,7 @@ where ) -> Result> { let parsed_items = Vec::::from_pest(pest)?; - Ok(PestIgnoredPunctuated(Punctuated::from_iter( + Ok(PestWithIgnoredPunctuation(Punctuated::from_iter( parsed_items.into_iter(), ))) } @@ -109,14 +126,14 @@ where /// Allows for syn to parse things that pest checks but does not store as rules #[derive(Debug, Default, new, PartialEq)] -pub struct PestLiteralWithoutRule(pub(crate) T); +pub struct PestLiteral(pub(crate) T); -impl From for PestLiteralWithoutRule { +impl From for PestLiteral { fn from(value: T) -> Self { Self(value) } } -impl<'pest, T: Default> FromPest<'pest> for PestLiteralWithoutRule { +impl<'pest, T: Default> FromPest<'pest> for PestLiteral { type Rule = Rule; type FatalError = Void; @@ -124,7 +141,7 @@ impl<'pest, T: Default> FromPest<'pest> for PestLiteralWithoutRule { fn from_pest( _pest: &mut Pairs<'pest, Self::Rule>, ) -> Result> { - Ok(PestLiteralWithoutRule(T::default())) + Ok(PestLiteral(T::default())) } } @@ -153,7 +170,7 @@ pub struct EOI; #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::jp_query))] pub struct JPQuery { - pub(crate) root: PestLiteralWithoutRule, + pub(crate) root: PestLiteral, pub(crate) segments: Segments, } @@ -175,8 +192,8 @@ pub enum Segment { // THIS MUST BE FIRST #[cfg_attr(feature = "compiled-path", parse(peek_func = DescendantSegment::peek))] Descendant( - PestLiteralWithoutRule, - PestLiteralWithoutRule, + PestLiteral, + PestLiteral, DescendantSegment, ), #[cfg_attr(feature = "compiled-path", parse(peek_func = ChildSegment::peek))] @@ -192,7 +209,7 @@ pub enum ChildSegment { // search for `[` or `.`(must NOT be `..` because that is a descendant segment but syn will parse that as `..` not 2 periods) #[cfg_attr(feature = "compiled-path", parse(peek = Token![.]))] WildcardOrShorthand( - PestLiteralWithoutRule, + PestLiteral, WildcardSelectorOrMemberNameShorthand, ), } @@ -203,9 +220,9 @@ pub struct BracketedSelection { #[cfg_attr(feature = "compiled-path", syn(bracketed))] pub(crate) arg_bracket: token::Bracket, #[cfg_attr(feature = "compiled-path", syn(in = arg_bracket))] - #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestIgnoredPunctuated::parse_separated_nonempty(i) + #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestWithIgnoredPunctuation::parse_separated_nonempty(i) ))] - pub(crate) selectors: PestIgnoredPunctuated, + pub(crate) selectors: PestWithIgnoredPunctuation, } impl<'pest> from_pest::FromPest<'pest> for BracketedSelection { @@ -383,7 +400,7 @@ impl KnowsRule for Selector { pub struct SliceSelector( #[cfg_attr(feature = "compiled-path", parse(SliceStart::maybe_parse))] pub(crate) Option, - pub(crate) PestLiteralWithoutRule, + pub(crate) PestLiteral, #[cfg_attr(feature = "compiled-path", parse(SliceEnd::maybe_parse))] pub(crate) Option, #[cfg_attr(feature = "compiled-path", parse(SliceStep::maybe_parse))] pub(crate) Option, @@ -393,7 +410,7 @@ pub struct SliceSelector( #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::step))] pub struct SliceStep( - pub(crate) PestLiteralWithoutRule, + pub(crate) PestLiteral, pub(crate) JSInt, ); @@ -416,7 +433,7 @@ pub struct IndexSelector(pub(crate) JSInt); #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::filter_selector))] pub struct FilterSelector { - pub q: PestLiteralWithoutRule, + pub q: PestLiteral, pub expr: LogicalExpr, } @@ -424,18 +441,18 @@ pub struct FilterSelector { #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::logical_expr))] pub struct LogicalExpr { - #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestIgnoredPunctuated::parse_separated_nonempty(i) + #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestWithIgnoredPunctuation::parse_separated_nonempty(i) ))] - pub ands: PestIgnoredPunctuated, + pub ands: PestWithIgnoredPunctuation, } #[derive(Debug, new, PartialEq, FromPest)] #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::logical_expr_and))] pub struct LogicalExprAnd { - #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestIgnoredPunctuated::parse_separated_nonempty(i) + #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestWithIgnoredPunctuation::parse_separated_nonempty(i) ))] - pub atoms: PestIgnoredPunctuated, + pub atoms: PestWithIgnoredPunctuation, } impl KnowsRule for LogicalExprAnd { const RULE: Rule = Rule::logical_expr_and; @@ -497,7 +514,7 @@ pub struct ParenExpr { // #[cfg_attr(feature = "compiled-path", parse(peek_func = NotOp::peek))] pub(crate) not_op: Option, // #[paren] - pub(crate) paren: PestLiteralWithoutRule, + pub(crate) paren: PestLiteral, // #[inside(paren)] pub(crate) expr: LogicalExpr, } @@ -611,36 +628,37 @@ impl<'pest> FromPest<'pest> for CompOp { } } +#[derive(Debug, new, PartialEq, FromPest)] +#[cfg_attr(feature = "compiled-path", derive(Parse))] +#[pest_ast(rule(Rule::function_expr))] +pub enum FunctionExpr { + ReturnsValue(ReturnsValue), + ReturnsLogical(ReturnsLogical), + ReturnsNodes(ReturnsNodes) +} + #[derive(Debug, new, PartialEq)] -// #[cfg_attr(feature = "compiled-path", derive(Parse))] -pub struct FunctionExpr { - pub(crate) name: FunctionName, - // #[cfg_attr(feature = "compiled-path", syn(parenthesized))] - pub(crate) paren: token::Paren, - // #[cfg_attr(feature = "compiled-path", syn(in = paren))] - // #[cfg_attr(feature = "compiled-path", parse(|i: ParseStream| PestIgnoredPunctuated::parse_terminated(i)))] - pub(crate) args: PestIgnoredPunctuated, +pub struct FnCallOneArg +where + NameToken: Default, +{ + pub(crate) name: PestLiteral, + pub(crate) arg: Arg, } -impl<'pest> from_pest::FromPest<'pest> for FunctionExpr { +impl<'pest, N, Arg> FromPest<'pest> for FnCallOneArg +where + N: Default + KnowsRule, + Arg: FromPest<'pest, Rule = Rule, FatalError = Void>, +{ type Rule = Rule; type FatalError = Void; - fn from_pest(pest: &mut Pairs<'pest, Rule>) -> Result> { + + fn from_pest(pest: &mut Pairs<'pest, Self::Rule>) -> Result> { let mut clone = pest.clone(); let pair = clone.next().ok_or(ConversionError::NoMatch)?; - if pair.as_rule() == Rule::function_expr { - let mut inner = pair.into_inner(); - let inner = &mut inner; - let this = FunctionExpr { - name: ::from_pest::FromPest::from_pest(inner)?, - paren: Default::default(), - args: FromPest::from_pest(inner)?, - }; - if inner.clone().next().is_some() { - Err(ConversionError::Extraneous { - current_node: "FunctionExpr", - })?; - } + if N::matches_rule(pair.as_rule()) { + let this = Self { name: Default::default(), arg: Arg::from_pest(pest)? }; *pest = clone; Ok(this) } else { @@ -650,84 +668,193 @@ impl<'pest> from_pest::FromPest<'pest> for FunctionExpr { } #[derive(Debug, new, PartialEq)] +pub struct FnCallTwoArg { + pub(crate) name: PestLiteral, + pub(crate) arg1: Arg1, + pub(crate) c: PestLiteral, + pub(crate) arg2: Arg2, +} + +impl<'pest, N, Arg1, Arg2> FromPest<'pest> for FnCallTwoArg +where + N: Default + KnowsRule, + Arg1: FromPest<'pest, Rule = Rule, FatalError = Void>, + Arg2: FromPest<'pest, Rule = Rule, FatalError = Void>, +{ + type Rule = Rule; + type FatalError = Void; + + fn from_pest(pest: &mut Pairs<'pest, Self::Rule>) -> Result> { + let mut clone = pest.clone(); + let pair = clone.next().ok_or(ConversionError::NoMatch)?; + if N::matches_rule(pair.as_rule()) { + let this = Self { + name: Default::default(), + arg1: Arg1::from_pest(pest)?, + c: Default::default(), + arg2: Arg2::from_pest(pest)? + }; + *pest = clone; + Ok(this) + } else { + Err(ConversionError::NoMatch) + } + } +} + +#[derive(Debug, new, PartialEq, FromPest)] #[cfg_attr(feature = "compiled-path", derive(Parse))] -pub enum FunctionName { +#[pest_ast(rule(Rule::returns_value_type))] +pub enum ReturnsValue { #[cfg_attr(feature = "compiled-path", parse(peek = kw::length))] - Length(kw::length), + Length(FnCallOneArg), #[cfg_attr(feature = "compiled-path", parse(peek = kw::value))] - Value(kw::value), + Value(FnCallOneArg), #[cfg_attr(feature = "compiled-path", parse(peek = kw::count))] - Count(kw::count), + Count(FnCallOneArg), +} + +#[derive(Debug, new, PartialEq, FromPest)] +#[cfg_attr(feature = "compiled-path", derive(Parse))] +#[pest_ast(rule(Rule::returns_logical_type))] +pub enum ReturnsLogical { #[cfg_attr(feature = "compiled-path", parse(peek = kw::search))] - Search(kw::search), + Search(FnCallTwoArg), #[cfg_attr(feature = "compiled-path", parse(peek = Token![match]))] - Match(Token![match]), + Match(FnCallTwoArg), #[cfg_attr(feature = "compiled-path", parse(peek = Token![in]))] - In(Token![in]), + In(FnCallTwoArg), #[cfg_attr(feature = "compiled-path", parse(peek = kw::nin))] - Nin(kw::nin), + Nin(FnCallTwoArg), #[cfg_attr(feature = "compiled-path", parse(peek = kw::none_of))] - NoneOf(kw::none_of), + NoneOf(FnCallTwoArg), #[cfg_attr(feature = "compiled-path", parse(peek = kw::any_of))] - AnyOf(kw::any_of), + AnyOf(FnCallTwoArg), #[cfg_attr(feature = "compiled-path", parse(peek = kw::subset_of))] - SubsetOf(kw::subset_of), + SubsetOf(FnCallTwoArg), +} + + +#[derive(Debug, PartialEq/*, FromPest*/)] +#[cfg_attr(feature = "compiled-path", derive(Parse))] +// #[pest_ast(rule(Rule::returns_nodes_type))] +#[allow(private_bounds)] +pub(crate) enum ReturnsNodes { + // IF YOU PUT SOMETHING HERE YOU MUST REMOVE CURRENT IMPL OF `FromPest for ReturnsNodes`(currently just unreachable!()) } -impl<'pest> FromPest<'pest> for FunctionName { +impl<'pest> from_pest::FromPest<'pest> for ReturnsNodes { type Rule = Rule; type FatalError = Void; - fn from_pest( - pest: &mut Pairs<'pest, Self::Rule>, - ) -> Result> { - let mut clone = pest.clone(); - let pair = clone.next().ok_or(ConversionError::NoMatch)?; - if matches!( - pair.as_rule(), - Rule::function_name - ) { - let this = match pair.as_str().trim() { - "length" => Self::Length(Default::default()), - "value" => Self::Value(Default::default()), - "count" => Self::Count(Default::default()), - "search" => Self::Search(Default::default()), - "match" => Self::Match(Default::default()), - "in" => Self::In(Default::default()), - "nin" => Self::Nin(Default::default()), - "none_of" => Self::NoneOf(Default::default()), - "any_of" => Self::AnyOf(Default::default()), - "subset_of" => Self::SubsetOf(Default::default()), - _ => unreachable!("Invalid function name should be impossible, error in pest grammar") - }; - *pest = clone; - Ok(this) - } else { - Err(ConversionError::NoMatch) - } + fn from_pest(_pest: &mut Pairs<'pest, Self::Rule>) -> Result> { + unreachable!() } } + +// #[derive(Debug, new, PartialEq)] +// #[cfg_attr(feature = "compiled-path", derive(Parse))] +// pub enum FunctionName { +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::length))] +// Length(kw::length), +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::value))] +// Value(kw::value), +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::count))] +// Count(kw::count), +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::search))] +// Search(kw::search), +// #[cfg_attr(feature = "compiled-path", parse(peek = Token![match]))] +// Match(Token![match]), +// #[cfg_attr(feature = "compiled-path", parse(peek = Token![in]))] +// In(Token![in]), +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::nin))] +// Nin(kw::nin), +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::none_of))] +// NoneOf(kw::none_of), +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::any_of))] +// AnyOf(kw::any_of), +// #[cfg_attr(feature = "compiled-path", parse(peek = kw::subset_of))] +// SubsetOf(kw::subset_of), +// } + +// impl<'pest> FromPest<'pest> for FunctionName { +// type Rule = Rule; +// type FatalError = Void; +// +// fn from_pest( +// pest: &mut Pairs<'pest, Self::Rule>, +// ) -> Result> { +// let mut clone = pest.clone(); +// let pair = clone.next().ok_or(ConversionError::NoMatch)?; +// if matches!( +// pair.as_rule(), +// Rule::function_name +// ) { +// let this = match pair.as_str().trim() { +// "length" => Self::Length(Default::default()), +// "value" => Self::Value(Default::default()), +// "count" => Self::Count(Default::default()), +// "search" => Self::Search(Default::default()), +// "match" => Self::Match(Default::default()), +// "in" => Self::In(Default::default()), +// "nin" => Self::Nin(Default::default()), +// "none_of" => Self::NoneOf(Default::default()), +// "any_of" => Self::AnyOf(Default::default()), +// "subset_of" => Self::SubsetOf(Default::default()), +// _ => unreachable!("Invalid function name should be impossible, error in pest grammar") +// }; +// *pest = clone; +// Ok(this) +// } else { +// Err(ConversionError::NoMatch) +// } +// } +// } + +// #[derive(Debug, new, PartialEq, FromPest)] +// #[cfg_attr(feature = "compiled-path", derive(Parse))] +// #[pest_ast(rule(Rule::function_argument))] +// pub enum FunctionArgument { +// #[cfg_attr(feature = "compiled-path", parse(peek_func = Literal::peek))] +// Literal(Literal), +// #[cfg_attr(feature = "compiled-path", parse(peek_func = Test::peek))] +// Test(Test), +// #[cfg_attr(feature = "compiled-path", parse(peek_func = LogicalExpr::peek))] +// LogicalExpr(LogicalExpr), +// } +// impl KnowsRule for FunctionArgument { +// const RULE: Rule = Rule::function_argument; +// } + #[derive(Debug, new, PartialEq, FromPest)] #[cfg_attr(feature = "compiled-path", derive(Parse))] -#[pest_ast(rule(Rule::function_argument))] -pub enum FunctionArgument { +#[pest_ast(rule(Rule::value_type))] +pub enum ValueType { #[cfg_attr(feature = "compiled-path", parse(peek_func = Literal::peek))] Literal(Literal), - #[cfg_attr(feature = "compiled-path", parse(peek_func = Test::peek))] - Test(Test), - #[cfg_attr(feature = "compiled-path", parse(peek_func = LogicalExpr::peek))] - LogicalExpr(LogicalExpr), + #[cfg_attr(feature = "compiled-path", parse(peek_func = SingularQuery::peek))] + SingularQuery(SingularQuery), + #[cfg_attr(feature = "compiled-path", parse(peek_func = ReturnsValue::peek))] + ValueFunction(Box) } -impl KnowsRule for FunctionArgument { - const RULE: Rule = Rule::function_argument; + + +#[derive(Debug, new, PartialEq, FromPest)] +#[cfg_attr(feature = "compiled-path", derive(Parse))] +#[pest_ast(rule(Rule::nodes_type))] +pub enum NodesType { + #[cfg_attr(feature = "compiled-path", parse(peek_func = JPQuery::peek))] + SubQuery(JPQuery), + NodesFunction(Box), } + #[derive(Debug, new, PartialEq, FromPest)] #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::rel_query))] pub struct RelQuery { - pub(crate) curr: PestLiteralWithoutRule, + pub(crate) curr: PestLiteral, pub(crate) segments: Segments, } @@ -745,7 +872,7 @@ pub enum SingularQuery { #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::rel_singular_query))] pub struct RelSingularQuery { - pub(crate) curr: PestLiteralWithoutRule, + pub(crate) curr: PestLiteral, pub(crate) segments: SingularQuerySegments, } @@ -753,7 +880,7 @@ pub struct RelSingularQuery { #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::abs_singular_query))] pub struct AbsSingularQuery { - pub(crate) root: PestLiteralWithoutRule, + pub(crate) root: PestLiteral, pub(crate) segments: SingularQuerySegments, } @@ -809,14 +936,14 @@ pub enum NameSegment { #[cfg_attr(feature = "compiled-path", parse(peek = token::Bracket))] BracketedName(BracketName), #[cfg_attr(feature = "compiled-path", parse(peek = Token![.]))] - DotName(PestLiteralWithoutRule, MemberNameShorthand), + DotName(PestLiteral, MemberNameShorthand), } #[derive(Debug, new, PartialEq, FromPest)] #[pest_ast(rule(Rule::name_selector))] pub struct BracketName { // #[cfg_attr(feature = "compiled-path", syn(bracketed))] - pub(crate) bracket: PestLiteralWithoutRule, + pub(crate) bracket: PestLiteral, // #[cfg_attr(feature = "compiled-path", syn(in = bracket))] pub(crate) name: JSString, } @@ -825,7 +952,7 @@ pub struct BracketName { #[pest_ast(rule(Rule::index_segment))] pub struct IndexSegment { // #[cfg_attr(feature = "compiled-path", syn(bracketed))] - pub(crate) bracket: PestLiteralWithoutRule, + pub(crate) bracket: PestLiteral, // #[cfg_attr(feature = "compiled-path", syn(in = bracket))] pub(crate) index: JSInt, } @@ -912,4 +1039,4 @@ terminating_from_pest!(Bool, Rule::bool, |inner: Pair| Bool( #[derive(Debug, new, PartialEq, FromPest)] #[cfg_attr(feature = "compiled-path", derive(Parse))] #[pest_ast(rule(Rule::null))] -pub struct Null(pub(crate) PestLiteralWithoutRule); +pub struct Null(pub(crate) PestLiteral); diff --git a/jsonpath-ast/src/syn_parse.rs b/jsonpath-ast/src/syn_parse.rs index 2c87549..1dae47f 100644 --- a/jsonpath-ast/src/syn_parse.rs +++ b/jsonpath-ast/src/syn_parse.rs @@ -1,18 +1,17 @@ #[cfg(feature = "compiled-path")] pub(crate) mod parse_impl { use crate::ast::parse::{JSPathParser, Rule}; - use crate::ast::{kw, CompOp, IndexSelector, Main, NameSelector}; + use crate::ast::{kw, CompOp, FnCallOneArg, FnCallTwoArg, IndexSelector, Main, NameSelector, NodesType, ReturnsLogical, ReturnsNodes, ReturnsValue, ValueType}; use crate::ast::{ AbsSingularQuery, AtomExpr, Bool, BracketName, BracketedSelection, ChildSegment, CompExpr, - Comparable, DescendantSegment, FilterSelector, FunctionArgument, FunctionExpr, FunctionName, - IndexSegment, JPQuery, JSInt, JSString, Literal, LogicalExpr, LogicalExprAnd, MemberNameShorthand, - NameSegment, NotOp, Null, Number, ParenExpr, PestIgnoredPunctuated, PestLiteralWithoutRule, + Comparable, DescendantSegment, FilterSelector, FunctionExpr, IndexSegment, JPQuery, JSInt, JSString, Literal, LogicalExpr, LogicalExprAnd, MemberNameShorthand, + NameSegment, NotOp, Null, Number, ParenExpr, PestLiteral, PestWithIgnoredPunctuation, RelQuery, RelSingularQuery, Root, Segment, Segments, Selector, SingularQuery, SingularQuerySegment, SingularQuerySegments, SliceEnd, SliceSelector, SliceStart, SliceStep, Test, TestExpr, WildcardSelector, WildcardSelectorOrMemberNameShorthand, EOI, }; - use pest::Parser; + use pest::Parser as PestParser; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; @@ -40,9 +39,9 @@ pub(crate) mod parse_impl { } } - impl PestIgnoredPunctuated { + impl PestWithIgnoredPunctuation { pub(crate) fn parse_terminated(input: ParseStream) -> syn::Result { - Ok(PestIgnoredPunctuated(Punctuated::parse_terminated(input)?)) + Ok(PestWithIgnoredPunctuation(Punctuated::parse_terminated(input)?)) } pub(crate) fn parse_separated_nonempty(input: ParseStream) -> syn::Result @@ -56,32 +55,32 @@ pub(crate) mod parse_impl { std::any::type_name::() ))) } else { - Ok(PestIgnoredPunctuated(res)) + Ok(PestWithIgnoredPunctuation(res)) } } } - impl Parse for PestIgnoredPunctuated { + impl Parse for PestWithIgnoredPunctuation { fn parse(input: ParseStream) -> syn::Result { Self::parse_terminated(input) } } - impl Parse for PestLiteralWithoutRule { + impl Parse for PestLiteral { fn parse(input: ParseStream) -> syn::Result { - Ok(PestLiteralWithoutRule(input.parse::()?)) + Ok(PestLiteral(input.parse::()?)) } } - impl ToTokens for PestLiteralWithoutRule { - fn to_tokens(&self, tokens: &mut ::proc_macro2::TokenStream) { + impl ToTokens for PestLiteral { + fn to_tokens(&self, tokens: &mut TokenStream) { let Self { 0: __0 } = self; { { - let __expr: fn(&mut ::proc_macro2::TokenStream, _) = |tokens, val: &T| { + let __expr: fn(&mut TokenStream, _) = |tokens, val: &T| { let mut sub = TokenStream::new(); val.to_tokens(&mut sub); tokens.extend( - quote! { ::jsonpath_ast::ast::PestLiteralWithoutRule::new(Default::default()) }, + quote! { ::jsonpath_ast::ast::PestLiteral::new(Default::default()) }, ); }; __expr(tokens, __0) @@ -259,7 +258,7 @@ pub(crate) mod parse_impl { } } - impl ToTokens for PestIgnoredPunctuated + impl ToTokens for PestWithIgnoredPunctuation where T: ToTokens, P: ToTokens, @@ -628,45 +627,106 @@ pub(crate) mod parse_impl { } } - impl ToTokens for FunctionName { + // impl ToTokens for FunctionName { + // fn to_tokens(&self, tokens: &mut TokenStream) { + // // tokens.extend(quote! { + // // ::jsonpath_ast::ast::FunctionName::new( + // // ::proc_macro2::Ident::new("function_name", ::proc_macro2::Span::call_site()) + // // ) + // // }); + // let variant = match self { + // // Literal::Number(inner) => { + // // quote!(new_number(#inner)) + // // } + // FunctionName::Length(_) => { quote!(new_length(Default::default())) } + // FunctionName::Value(_) => { quote!(new_value(Default::default())) } + // FunctionName::Count(_) => { quote!(new_count(Default::default())) } + // FunctionName::Search(_) => { quote!(new_search(Default::default())) } + // FunctionName::Match(_) => { quote!(new_match(Default::default())) } + // FunctionName::In(_) => { quote!(new_in(Default::default())) } + // FunctionName::Nin(_) => { quote!(new_nin(Default::default())) } + // FunctionName::NoneOf(_) => { quote!(new_none_of(Default::default())) } + // FunctionName::AnyOf(_) => { quote!(new_any_of(Default::default())) } + // FunctionName::SubsetOf(_) => { quote!(new_subset_of(Default::default())) } + // }; + // tokens.extend(quote!(::jsonpath_ast::ast::FunctionName::#variant)) + // } + // } + + impl ToTokens for FunctionExpr { fn to_tokens(&self, tokens: &mut TokenStream) { - // tokens.extend(quote! { - // ::jsonpath_ast::ast::FunctionName::new( - // ::proc_macro2::Ident::new("function_name", ::proc_macro2::Span::call_site()) - // ) - // }); let variant = match self { - // Literal::Number(inner) => { - // quote!(new_number(#inner)) - // } - FunctionName::Length(_) => { quote!(new_length(Default::default())) } - FunctionName::Value(_) => { quote!(new_value(Default::default())) } - FunctionName::Count(_) => { quote!(new_count(Default::default())) } - FunctionName::Search(_) => { quote!(new_search(Default::default())) } - FunctionName::Match(_) => { quote!(new_match(Default::default())) } - FunctionName::In(_) => { quote!(new_in(Default::default())) } - FunctionName::Nin(_) => { quote!(new_nin(Default::default())) } - FunctionName::NoneOf(_) => { quote!(new_none_of(Default::default())) } - FunctionName::AnyOf(_) => { quote!(new_any_of(Default::default())) } - FunctionName::SubsetOf(_) => { quote!(new_subset_of(Default::default())) } + FunctionExpr::ReturnsValue(v) => { quote!(new_returns_value(#v)) } + FunctionExpr::ReturnsLogical(v) => { quote!(new_returns_logical(#v)) } + FunctionExpr::ReturnsNodes(v) => { quote!(new_returns_nodes(#v)) } }; - tokens.extend(quote!(::jsonpath_ast::ast::FunctionName::#variant)) + tokens.extend(quote! { + ::jsonpath_ast::ast::FunctionExpr::#variant + }); } } - impl ToTokens for FunctionExpr { + impl ToTokens for ReturnsValue { fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - name, - paren: _, - args, - } = self; + let variant = match self { + ReturnsValue::Length(FnCallOneArg{arg, ..}) => { quote!(new_length(Default::default(), #arg)) } + ReturnsValue::Value(FnCallOneArg{arg, ..}) => { quote!(new_value(Default::default(), #arg)) } + ReturnsValue::Count(FnCallOneArg{arg, ..}) => { quote!(new_count(Default::default(), #arg)) } + }; tokens.extend(quote! { - ::jsonpath_ast::ast::FunctionExpr::new( - #name, - Default::default(), - #args - ) + ::jsonpath_ast::ast::ReturnsValue::#variant + }); + } + } + + impl ToTokens for ReturnsLogical { + fn to_tokens(&self, tokens: &mut TokenStream) { + let variant = match self { + ReturnsLogical::Search(FnCallTwoArg{arg1, arg2, ..}) => {quote!(new_search(Default::default(), #arg1, Default::default(), #arg2))} + ReturnsLogical::Match(FnCallTwoArg{arg1, arg2, ..}) => {quote!(new_match(Default::default(), #arg1, Default::default(), #arg2))} + ReturnsLogical::In(FnCallTwoArg{arg1, arg2, ..}) => {quote!(new_in(Default::default(), #arg1, Default::default(), #arg2))} + ReturnsLogical::Nin(FnCallTwoArg{arg1, arg2, ..}) => {quote!(new_nin(Default::default(), #arg1, Default::default(), #arg2))} + ReturnsLogical::NoneOf(FnCallTwoArg{arg1, arg2, ..}) => {quote!(new_none_of(Default::default(), #arg1, Default::default(), #arg2))} + ReturnsLogical::AnyOf(FnCallTwoArg{arg1, arg2, ..}) => {quote!(new_any_of(Default::default(), #arg1, Default::default(), #arg2))} + ReturnsLogical::SubsetOf(FnCallTwoArg{arg1, arg2, ..}) => {quote!(new_subset_of(Default::default(), #arg1, Default::default(), #arg2))} + }; + tokens.extend(quote! { + ::jsonpath_ast::ast::ReturnsLogical::#variant + }); + } + } + + impl ToTokens for ReturnsNodes { + fn to_tokens(&self, tokens: &mut TokenStream) { + // let variant = match self { + // }; + tokens.extend(quote! { + compile_error!("No functions return nodes type yet, if this is no longer true update the ToTokens impl for ReturnsNodes") + }); + } + } + + impl ToTokens for ValueType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let variant = match self { + ValueType::Literal(v) => {quote!(new_literal(#v))} + ValueType::SingularQuery(v) => {quote!(new_singular_query(#v))} + ValueType::ValueFunction(v) => {quote!(new_value_function(#v))} + }; + tokens.extend(quote! { + ::jsonpath_ast::ast::ValueType::#variant + }); + } + } + + impl ToTokens for NodesType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let variant = match self { + NodesType::SubQuery(v) => {quote!(new_sub_query(#v))} + NodesType::NodesFunction(v) => {quote!(new_nodes_function(#v))} + }; + tokens.extend(quote! { + ::jsonpath_ast::ast::NodesType::#variant }); } } @@ -809,33 +869,33 @@ pub(crate) mod parse_impl { } } - impl ToTokens for FunctionArgument { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Literal(literal) => { - let mut literal_tokens = TokenStream::new(); - literal.to_tokens(&mut literal_tokens); - tokens.extend(quote! { - ::jsonpath_ast::ast::FunctionArgument::Literal(#literal_tokens) - }); - } - Self::Test(test) => { - let mut test_tokens = TokenStream::new(); - test.to_tokens(&mut test_tokens); - tokens.extend(quote! { - ::jsonpath_ast::ast::FunctionArgument::Test(#test_tokens) - }); - } - Self::LogicalExpr(expr) => { - let mut expr_tokens = TokenStream::new(); - expr.to_tokens(&mut expr_tokens); - tokens.extend(quote! { - ::jsonpath_ast::ast::FunctionArgument::LogicalExpr(#expr_tokens) - }); - } - } - } - } + // impl ToTokens for FunctionArgument { + // fn to_tokens(&self, tokens: &mut TokenStream) { + // match self { + // Self::Literal(literal) => { + // let mut literal_tokens = TokenStream::new(); + // literal.to_tokens(&mut literal_tokens); + // tokens.extend(quote! { + // ::jsonpath_ast::ast::FunctionArgument::Literal(#literal_tokens) + // }); + // } + // Self::Test(test) => { + // let mut test_tokens = TokenStream::new(); + // test.to_tokens(&mut test_tokens); + // tokens.extend(quote! { + // ::jsonpath_ast::ast::FunctionArgument::Test(#test_tokens) + // }); + // } + // Self::LogicalExpr(expr) => { + // let mut expr_tokens = TokenStream::new(); + // expr.to_tokens(&mut expr_tokens); + // tokens.extend(quote! { + // ::jsonpath_ast::ast::FunctionArgument::LogicalExpr(#expr_tokens) + // }); + // } + // } + // } + // } impl ToTokens for Test { fn to_tokens(&self, tokens: &mut TokenStream) { @@ -908,7 +968,7 @@ pub(crate) mod parse_impl { None }; let __paren_backing_token_stream; - let paren: PestLiteralWithoutRule = + let paren: PestLiteral = syn::parenthesized!(__paren_backing_token_stream in input ).into(); let expr: LogicalExpr = __paren_backing_token_stream.parse()?; Ok(ParenExpr { @@ -1109,34 +1169,28 @@ pub(crate) mod parse_impl { Ok(num) } - fn function_name_expected_args(name: &FunctionName) -> (String, usize) { - (format!("{:?}", name), match name { - FunctionName::Length(_) | FunctionName::Value(_) | FunctionName::Count(_) => { 1 }, - FunctionName::Search(_) | FunctionName::Match(_) - | FunctionName::In(_) | FunctionName::Nin(_) - | FunctionName::NoneOf(_) | FunctionName::AnyOf(_) | FunctionName::SubsetOf(_) => { 2 }, - }) - } - impl Parse for FunctionExpr { - fn parse(__input: ParseStream) -> ::syn::Result { - let paren; - let ret = Self { name: __input.parse()?, paren: syn::parenthesized!(paren in __input ), args: PestIgnoredPunctuated::parse_separated_nonempty(&paren)? }; - let (func_name, expected_num_args) = function_name_expected_args(&ret.name); - if expected_num_args == ret.args.0.len() { - Ok(ret) - } else { - Err(syn::Error::new(ret.args.span(), format!("Invalid number of arguments for function {}, expected {}", func_name, expected_num_args))) - } - } - } + // fn function_name_expected_args(name: &FunctionName) -> (String, usize) { + // (format!("{:?}", name), match name { + // FunctionName::Length(_) | FunctionName::Value(_) | FunctionName::Count(_) => { 1 }, + // FunctionName::Search(_) | FunctionName::Match(_) + // | FunctionName::In(_) | FunctionName::Nin(_) + // | FunctionName::NoneOf(_) | FunctionName::AnyOf(_) | FunctionName::SubsetOf(_) => { 2 }, + // }) + // } + // impl Parse for FunctionExpr { + // fn parse(__input: ParseStream) -> ::syn::Result { + // let paren; + // let ret = Self { name: __input.parse()?, paren: syn::parenthesized!(paren in __input ), args: PestWithIgnoredPunctuation::parse_separated_nonempty(&paren)? }; + // let (func_name, expected_num_args) = function_name_expected_args(&ret.name); + // if expected_num_args == ret.args.0.len() { + // Ok(ret) + // } else { + // Err(syn::Error::new(ret.args.span(), format!("Invalid number of arguments for function {}, expected {}", func_name, expected_num_args))) + // } + // } + // } impl ParseUtilsExt for FunctionExpr { - fn peek(input: ParseStream) -> bool { - FunctionName::peek(input) - } - } - - impl ParseUtilsExt for FunctionName { fn peek(input: ParseStream) -> bool { input.peek(kw::length) || input.peek(kw::value) @@ -1151,6 +1205,42 @@ pub(crate) mod parse_impl { } } + impl ParseUtilsExt for ReturnsValue { + fn peek(input: ParseStream) -> bool { + input.peek(kw::value) || input.peek(kw::length) || input.peek(kw::count) + } + } + + impl ParseUtilsExt for ReturnsLogical { + fn peek(input: ParseStream) -> bool { + input.peek(kw::search) + | input.peek(Token![match]) + | input.peek(Token![in]) + | input.peek(kw::nin) + | input.peek(kw::none_of) + | input.peek(kw::any_of) + | input.peek(kw::subset_of ) + } + } + + impl Parse for FnCallOneArg + where + NameToken: Default + Parse, + Arg: Parse, + { + fn parse(__input: ParseStream) -> syn::Result { Ok(Self { name: __input.parse()?, arg: __input.parse()? }) } + } + + impl Parse for FnCallTwoArg + where + NameToken: Default + Parse, + Arg1: Parse, + Arg2: Parse, + { + fn parse(__input: ParseStream) -> syn::Result { Ok(Self { name: __input.parse()?, arg1: __input.parse()?, c: __input.parse()?, arg2: __input.parse()? }) } + } + + impl ParseUtilsExt for RelQuery { fn peek(input: ParseStream) -> bool { input.peek(Token![@]) diff --git a/src/parser.rs b/src/parser.rs index 6b8fa32..625d637 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -134,11 +134,11 @@ pub fn selector(rule: Pair) -> Parsed { pub fn function_expr(rule: Pair) -> Parsed { let fn_str = rule.as_str(); - let mut elems = rule.into_inner(); - let name = elems - .next() - .map(|e| e.as_str()) - .ok_or(JsonPathError::empty("function expression"))?; + let mut elems = rule.into_inner().next().ok_or(JsonPathError::empty("function rule was empty"))?; + match elems.as_rule() { + Rule::returns_logical_type => + } + let name = todo!(); // Check if the function name is valid namely nothing between the name and the opening parenthesis if fn_str @@ -153,7 +153,7 @@ pub fn function_expr(rule: Pair) -> Parsed { ))) } else { let mut args = vec![]; - for arg in elems { + for arg in elems.into_inner() { let next = next_down(arg)?; match next.as_rule() { Rule::literal => args.push(FnArg::Literal(literal(next)?)), @@ -173,7 +173,10 @@ pub fn test(rule: Pair) -> Parsed { match child.as_rule() { Rule::jp_query => Ok(Test::AbsQuery(jp_query(child)?)), Rule::rel_query => Ok(Test::RelQuery(rel_query(child)?)), - Rule::function_expr => Ok(Test::Function(Box::new(function_expr(child)?))), + Rule::function_expr => { + let func = function_expr(child).map(|f| f)?; + Ok(Test::Function(Box::new(func))) + }, _ => Err(child.into()), } } diff --git a/src/parser/grammar/json_path_9535.pest b/src/parser/grammar/json_path_9535.pest index 4bbe107..914a904 100644 --- a/src/parser/grammar/json_path_9535.pest +++ b/src/parser/grammar/json_path_9535.pest @@ -32,21 +32,24 @@ test = { filter_query = _{ rel_query | jp_query } rel_query = {curr ~ segments} - -function_expr = { length_func_call | search_func_call | match_func_call | in_func_call | nin_func_call | none_of_func_call | any_of_func_call | subset_of_func_call } +function_expr = { returns_value_type | returns_logical_type | returns_nodes_type } // https://github.com/pest-parser/pest/issues/333 would be awesome for this but it doesn't exist yet and it's been 7 years // Lookahead to peek the names and then homogenize them into the same rule till we refine the parser code -length_func_call = _{ &"length" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ ")" } -value_func_call = _{ &"value" ~ function_name ~ "(" ~ S ~ nodes_type ~ S ~ ")" } -count_func_call = _{ &"count" ~ function_name ~ "(" ~ S ~ nodes_type ~ S ~ ")" } -search_func_call = _{ &"search" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } -match_func_call = _{ &"match" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } - -in_func_call = _{ &"in" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } -nin_func_call = _{ &"nin" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } -none_of_func_call = _{ &"none_of" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } -any_of_func_call = _{ &"any_of" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } -subset_of_func_call = _{ &"subset_of" ~ function_name ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +length_func_call = { "length" ~ "(" ~ S ~ value_type ~ S ~ ")" } +value_func_call = { "value" ~ "(" ~ S ~ nodes_type ~ S ~ ")" } +count_func_call = { "count" ~ "(" ~ S ~ nodes_type ~ S ~ ")" } +search_func_call = { "search" ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +match_func_call = { "match" ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +in_func_call = { "in" ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +nin_func_call = { "nin" ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +none_of_func_call = { "none_of" ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +any_of_func_call = { "any_of" ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } +subset_of_func_call = { "subset_of" ~ "(" ~ S ~ value_type ~ S ~ "," ~ S ~ value_type ~ S ~ ")" } + +returns_value_type = { length_func_call | value_func_call | count_func_call } +returns_logical_type = { search_func_call | match_func_call | in_func_call | nin_func_call | none_of_func_call | any_of_func_call | subset_of_func_call } +// Currently no functions return this, so never match for now. To add a node which returns NodesType, replace !ANY +returns_nodes_type = { !ANY } // When the declared type of the parameter is ValueType and the argument is one of the following: // - A value expressed as a literal. @@ -58,7 +61,7 @@ value_type = { literal | singular_query | returns_value_type } // - A function expression with declared result type NodesType. In this case, the argument is converted to LogicalType as per Section 2.4.2. // - A logical-expr that is not a function expression. logical_type = { - logical_expr // TODO why is this not allowed to be a function_expr? we guarantee it's return is a logical type + logical_expr // TODO why is this not allowed to be a function_expr? we guarantee it's return is a logical type(or is coercible to one) // | returns_logical_type // this case is actually covered as a subset of logical_expr | nodes_type } @@ -66,12 +69,6 @@ logical_type = { nodes_type = { jp_query | returns_nodes_type } -returns_value_type = _{ length_func_call | value_func_call | count_func_call } -returns_logical_type = _{ search_func_call | match_func_call | in_func_call | nin_func_call | none_of_func_call | any_of_func_call | subset_of_func_call } -// Currently no functions return this, so never match for now. To add a node which returns NodesType, replace !ANY -returns_nodes_type = _{ !ANY } - -function_name = { "length" | "value" | "count" | "search" | "match" | "in" | "nin" | "none_of" | "any_of" | "subset_of" } // Removed, a literal is a ValueType, and a logical_expr is just a test with more rules around it, both are LogicalType // function_argument = { literal | test | logical_expr }