diff --git a/jsonpath-ast/src/ast.rs b/jsonpath-ast/src/ast.rs index af316b7..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 { @@ -61,33 +74,38 @@ 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; 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, @@ -100,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(), ))) } @@ -108,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; @@ -123,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())) } } @@ -152,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, } @@ -174,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))] @@ -191,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, ), } @@ -202,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 { @@ -382,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, @@ -392,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, ); @@ -415,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, } @@ -423,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; @@ -496,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, } @@ -610,36 +628,37 @@ impl<'pest> FromPest<'pest> for CompOp { } } -#[derive(Debug, new, PartialEq)] +#[derive(Debug, new, PartialEq, FromPest)] #[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, +#[pest_ast(rule(Rule::function_expr))] +pub enum FunctionExpr { + ReturnsValue(ReturnsValue), + ReturnsLogical(ReturnsLogical), + ReturnsNodes(ReturnsNodes) } -impl<'pest> from_pest::FromPest<'pest> for FunctionExpr { +#[derive(Debug, new, PartialEq)] +pub struct FnCallOneArg +where + NameToken: Default, +{ + pub(crate) name: PestLiteral, + pub(crate) arg: Arg, +} + +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 { @@ -649,29 +668,31 @@ 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 struct FnCallTwoArg { + pub(crate) name: PestLiteral, + pub(crate) arg1: Arg1, + pub(crate) c: PestLiteral, + pub(crate) arg2: Arg2, } -impl<'pest> FromPest<'pest> for FunctionName { +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> { + 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_one_arg | Rule::function_name_two_arg - ) { - 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()), + 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) @@ -683,24 +704,157 @@ impl<'pest> FromPest<'pest> for FunctionName { #[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::returns_value_type))] +pub enum ReturnsValue { + #[cfg_attr(feature = "compiled-path", parse(peek = kw::length))] + Length(FnCallOneArg), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::value))] + Value(FnCallOneArg), + #[cfg_attr(feature = "compiled-path", parse(peek = 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(FnCallTwoArg), + #[cfg_attr(feature = "compiled-path", parse(peek = Token![match]))] + Match(FnCallTwoArg), + #[cfg_attr(feature = "compiled-path", parse(peek = Token![in]))] + In(FnCallTwoArg), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::nin))] + Nin(FnCallTwoArg), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::none_of))] + NoneOf(FnCallTwoArg), + #[cfg_attr(feature = "compiled-path", parse(peek = kw::any_of))] + AnyOf(FnCallTwoArg), + #[cfg_attr(feature = "compiled-path", parse(peek = 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> from_pest::FromPest<'pest> for ReturnsNodes { + type Rule = Rule; + type FatalError = Void; + + 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::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, } @@ -718,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, } @@ -726,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, } @@ -782,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, } @@ -798,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, } @@ -885,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 bda5ce0..1dae47f 100644 --- a/jsonpath-ast/src/syn_parse.rs +++ b/jsonpath-ast/src/syn_parse.rs @@ -1,24 +1,24 @@ #[cfg(feature = "compiled-path")] pub(crate) mod parse_impl { use crate::ast::parse::{JSPathParser, Rule}; + 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, 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, 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 crate::ast::{CompOp, IndexSelector, Main, NameSelector, kw}; - use pest::Parser; + use pest::Parser as PestParser; 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; @@ -39,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 @@ -55,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) @@ -258,7 +258,7 @@ pub(crate) mod parse_impl { } } - impl ToTokens for PestIgnoredPunctuated + impl ToTokens for PestWithIgnoredPunctuation where T: ToTokens, P: ToTokens, @@ -627,29 +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) { + let variant = match self { + 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::new( - ::proc_macro2::Ident::new("function_name", ::proc_macro2::Span::call_site()) - ) + ::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 }); } } @@ -792,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) { @@ -840,9 +917,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 ) }); @@ -887,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 { @@ -992,7 +1073,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,13 +1169,28 @@ pub(crate) mod parse_impl { Ok(num) } - impl ParseUtilsExt for FunctionExpr { - fn peek(input: ParseStream) -> bool { - FunctionName::peek(input) - } - } + // 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 FunctionName { + impl ParseUtilsExt for FunctionExpr { fn peek(input: ParseStream) -> bool { input.peek(kw::length) || input.peek(kw::value) @@ -1105,53 +1205,42 @@ 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())); + impl ParseUtilsExt for ReturnsValue { + fn peek(input: ParseStream) -> bool { + input.peek(kw::value) || input.peek(kw::length) || input.peek(kw::count) } - if input.peek(kw::subset_of) { - input.parse::()?; - return Ok(Ident::new("subset_of", input.span())); + } + + 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 ) } - 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 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/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.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 33e5f17..914a904 100644 --- a/src/parser/grammar/json_path_9535.pest +++ b/src/parser/grammar/json_path_9535.pest @@ -16,22 +16,65 @@ 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 = { 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" ~ "(" ~ 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. +// - 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(or is coercible to one) + // | 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 } + + +// 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"}