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); } 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"}