2023-01-27 15:48:07 +01:00
use std ::{
fmt ::{ self , Display } ,
str ::FromStr ,
} ;
2023-01-26 23:07:16 +01:00
2023-04-17 23:45:59 +02:00
use crate ::filter ::{ build_filter , CardFilter } ;
2023-02-03 17:27:01 +01:00
use itertools ::Itertools ;
2023-01-26 23:07:16 +01:00
use nom ::{
branch ::alt ,
bytes ::complete ::{ take_until1 , take_while , take_while_m_n } ,
2023-01-27 00:03:00 +01:00
character ::complete ::{ char , multispace0 } ,
2023-04-17 23:45:59 +02:00
combinator ::{ complete , map , map_res , recognize , rest , verify } ,
multi ::{ many_m_n , separated_list1 } ,
2023-01-27 00:03:00 +01:00
sequence ::{ delimited , preceded , tuple } ,
2023-01-26 23:07:16 +01:00
IResult ,
} ;
2023-01-31 11:47:59 +01:00
pub fn parse_filters ( input : & str ) -> Result < ( Vec < RawCardFilter > , Vec < CardFilter > ) , String > {
2023-01-27 00:03:00 +01:00
parse_raw_filters ( input ) . map_err ( | e | format! ( " Error while parsing filters “ {input} ”: {e:?} " ) ) . and_then ( | ( rest , mut v ) | {
2023-01-26 23:07:16 +01:00
if rest . is_empty ( ) {
2023-02-03 17:27:01 +01:00
// Sorting must be stable or we can’t combine multiple name filters into one.
v . sort_by_key ( | RawCardFilter ( f , _ , _ ) | * f as u8 ) ;
// Combine multiple names searches into one search filter. This makes the readable query nicer
// (“Showing 21 results where name is ally and name is of and name is justice” becomes
// “Showing 21 results where name is ‘ally of justice’”)
// and improves search performance by only performing one String::contains.
// This could be done without allocating two vectors, but coalesce is just so much nicer.
v = v
. into_iter ( )
. coalesce ( | a , b | match ( & a , & b ) {
(
RawCardFilter ( Field ::Name , Operator ::Equal , Value ::String ( s1 ) ) ,
RawCardFilter ( Field ::Name , Operator ::Equal , Value ::String ( s2 ) ) ,
) = > Ok ( RawCardFilter ( Field ::Name , Operator ::Equal , Value ::String ( format! ( " {s1} {s2} " ) ) ) ) ,
_ = > Err ( ( a , b ) ) ,
} )
. collect ( ) ;
Ok ( ( v . clone ( ) , v . clone ( ) . into_iter ( ) . map ( | r | build_filter ( r ) ) . collect ::< Result < Vec < _ > , _ > > ( ) ? ) )
2023-01-26 23:07:16 +01:00
} else {
Err ( format! ( " Input was not fully parsed. Left over: “ {rest} ” " ) )
}
} )
}
fn parse_raw_filters ( input : & str ) -> IResult < & str , Vec < RawCardFilter > > {
many_m_n ( 1 , 32 , parse_raw_filter ) ( input )
}
fn word_non_empty ( input : & str ) -> IResult < & str , & str > {
verify ( alt ( ( take_until1 ( " " ) , rest ) ) , | s : & str | s . len ( ) > = 2 ) ( input )
}
2023-04-17 23:45:59 +02:00
fn sanitize ( query : & str ) -> Result < String , String > {
2023-04-18 00:27:31 +02:00
if query . contains ( OPERATOR_CHARS ) | | query . is_empty ( ) {
2023-04-17 23:45:59 +02:00
Err ( format! ( " Invalid query: {query} " ) )
} else {
Ok ( query . to_lowercase ( ) )
}
}
fn fallback_filter ( query : & str ) -> Result < RawCardFilter , String > {
let q = sanitize ( query ) ? ;
Ok ( RawCardFilter ( Field ::Name , Operator ::Equal , Value ::String ( q ) ) )
}
2023-01-26 23:07:16 +01:00
fn parse_raw_filter ( input : & str ) -> IResult < & str , RawCardFilter > {
2023-01-27 15:48:07 +01:00
preceded (
multispace0 ,
2023-04-17 23:45:59 +02:00
alt ( (
map ( complete ( tuple ( ( field , operator , values ) ) ) , | ( f , o , v ) | RawCardFilter ( f , o , v ) ) ,
map_res ( word_non_empty , fallback_filter ) ,
) ) ,
2023-01-27 15:48:07 +01:00
) ( input )
2023-01-26 23:07:16 +01:00
}
fn field ( input : & str ) -> IResult < & str , Field > {
map_res ( take_while ( char ::is_alphabetic ) , str ::parse ) ( input )
}
2023-01-27 14:25:06 +01:00
pub const OPERATOR_CHARS : & [ char ] = & [ '=' , '<' , '>' , ':' , '!' ] ;
2023-01-26 23:07:16 +01:00
fn operator ( input : & str ) -> IResult < & str , Operator > {
map_res ( take_while_m_n ( 1 , 2 , | c | OPERATOR_CHARS . contains ( & c ) ) , str ::parse ) ( input )
}
2023-04-17 23:45:59 +02:00
fn values ( input : & str ) -> IResult < & str , Value > {
map_res (
alt ( (
delimited ( char ( '"' ) , take_until1 ( " \" " ) , char ( '"' ) ) ,
recognize ( separated_list1 ( char ( '|' ) , take_until1 ( " | " ) ) ) ,
take_until1 ( " " ) ,
rest ,
) ) ,
2023-04-18 00:27:31 +02:00
parse_values ,
2023-04-17 23:45:59 +02:00
) ( input )
2023-01-26 23:07:16 +01:00
}
2023-04-18 00:27:31 +02:00
fn parse_values ( input : & str ) -> Result < Value , String > {
let values = input . split ( '|' ) . map ( parse_single_value ) . collect ::< Result < Vec < Value > , String > > ( ) ? ;
Ok ( match values . as_slice ( ) {
[ v ] = > v . clone ( ) ,
_ = > Value ::Multiple ( values ) ,
} )
}
fn parse_single_value ( input : & str ) -> Result < Value , String > {
Ok ( match input . parse ( ) {
Ok ( n ) = > Value ::Numerical ( n ) ,
Err ( _ ) = > Value ::String ( sanitize ( input ) ? ) ,
} )
}
2023-01-27 00:03:00 +01:00
/// Ordinals are given highest = fastest to filter.
/// This is used to sort filters before applying them.
2023-01-26 23:07:16 +01:00
#[ derive(Debug, PartialEq, Eq, Clone, Copy) ]
pub enum Field {
2023-01-27 15:48:07 +01:00
Atk = 1 ,
Def = 2 ,
2023-02-03 16:19:26 +01:00
Legal = 3 ,
2023-02-02 11:34:58 +01:00
Level = 4 ,
LinkRating = 6 ,
Year = 8 ,
Set = 10 ,
Type = 12 ,
Attribute = 14 ,
Class = 16 ,
Name = 18 ,
Text = 20 ,
2023-01-27 15:48:07 +01:00
}
impl Display for Field {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
f . write_str ( match self {
Self ::Text = > " text " ,
Self ::Name = > " name " ,
Self ::Class = > " card type " ,
Self ::Attribute = > " attribute " ,
Self ::Type = > " type " ,
Self ::Level = > " level/rank " ,
Self ::Atk = > " ATK " ,
Self ::Def = > " DEF " ,
2023-01-30 11:51:36 +01:00
Self ::LinkRating = > " link rating " ,
2023-02-01 18:47:27 +01:00
Self ::Set = > " set " ,
2023-02-02 11:34:58 +01:00
Self ::Year = > " year " ,
2023-02-03 16:19:26 +01:00
Self ::Legal = > " allowed copies " ,
2023-01-27 15:48:07 +01:00
} )
}
2023-01-26 23:07:16 +01:00
}
impl FromStr for Field {
type Err = String ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
Ok ( match s . to_lowercase ( ) . as_ref ( ) {
" atk " = > Self ::Atk ,
" def " = > Self ::Def ,
" level " | " l " = > Self ::Level ,
" type " | " t " = > Self ::Type ,
" attribute " | " attr " | " a " = > Self ::Attribute ,
" c " | " class " = > Self ::Class ,
" o " | " eff " | " text " | " effect " | " e " = > Self ::Text ,
2023-01-30 11:51:36 +01:00
" lr " | " linkrating " = > Self ::LinkRating ,
2023-01-30 17:27:44 +01:00
" name " = > Self ::Name ,
2023-02-01 18:47:27 +01:00
" set " | " s " = > Self ::Set ,
2023-02-02 11:34:58 +01:00
" year " | " y " = > Self ::Year ,
2023-02-03 16:19:26 +01:00
" legal " | " copies " = > Self ::Legal ,
2023-01-26 23:07:16 +01:00
_ = > Err ( s . to_string ( ) ) ? ,
} )
}
}
#[ derive(Debug, PartialEq, Eq, Clone, Copy) ]
pub enum Operator {
2023-01-27 14:25:06 +01:00
Equal ,
NotEqual ,
2023-01-26 23:07:16 +01:00
Less ,
LessEqual ,
Greater ,
GreaterEqual ,
}
impl Operator {
pub fn filter_number ( & self , a : Option < i32 > , b : i32 ) -> bool {
if let Some ( a ) = a {
match self {
2023-01-27 14:25:06 +01:00
Self ::Equal = > a = = b ,
2023-01-26 23:07:16 +01:00
Self ::Less = > a < b ,
Self ::LessEqual = > a < = b ,
Self ::Greater = > a > b ,
Self ::GreaterEqual = > a > = b ,
2023-01-27 14:25:06 +01:00
Self ::NotEqual = > a ! = b ,
2023-01-26 23:07:16 +01:00
}
} else {
2023-01-27 14:25:06 +01:00
self = = & Self ::NotEqual
2023-01-26 23:07:16 +01:00
}
}
}
impl FromStr for Operator {
type Err = String ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
Ok ( match s {
2023-01-27 14:25:06 +01:00
" = " | " == " | " : " = > Self ::Equal ,
2023-01-26 23:07:16 +01:00
" >= " | " => " = > Self ::GreaterEqual ,
" <= " | " =< " = > Self ::LessEqual ,
" > " = > Self ::Greater ,
" < " = > Self ::Less ,
2023-01-27 14:25:06 +01:00
" != " = > Self ::NotEqual ,
2023-01-26 23:07:16 +01:00
_ = > Err ( s . to_owned ( ) ) ? ,
} )
}
}
2023-01-27 15:48:07 +01:00
impl Display for Operator {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
f . write_str ( match self {
Self ::Equal = > " is " ,
Self ::NotEqual = > " is not " ,
Self ::Less = > " < " ,
Self ::LessEqual = > " <= " ,
Self ::Greater = > " > " ,
Self ::GreaterEqual = > " >= " ,
} )
}
}
#[ derive(Debug, PartialEq, Eq, Clone) ]
pub struct RawCardFilter ( pub Field , pub Operator , pub Value ) ;
impl Display for RawCardFilter {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
write! ( f , " {} {} {} " , self . 0 , self . 1 , self . 2 )
}
}
2023-01-26 23:07:16 +01:00
#[ derive(Debug, PartialEq, Eq, Clone) ]
pub enum Value {
String ( String ) ,
Numerical ( i32 ) ,
2023-04-17 23:45:59 +02:00
Multiple ( Vec < Value > ) ,
2023-01-26 23:07:16 +01:00
}
2023-01-27 15:48:07 +01:00
impl Display for Value {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
match & self {
2023-01-31 11:47:59 +01:00
Self ::String ( s ) = > {
if s . contains ( ' ' ) {
write! ( f , " \" {s} \" " )
} else {
f . write_str ( s )
}
}
Self ::Numerical ( n ) = > write! ( f , " {n} " ) ,
2023-04-17 23:45:59 +02:00
Self ::Multiple ( m ) = > {
2023-04-18 00:43:15 +02:00
write! ( f , " one of [{}] " , m . iter ( ) . map ( Value ::to_string ) . join ( " , " ) )
2023-04-17 23:45:59 +02:00
}
2023-01-27 15:48:07 +01:00
}
}
}
2023-01-26 23:07:16 +01:00
#[ cfg(test) ]
mod tests {
use super ::* ;
use test_case ::test_case ;
2023-01-27 15:48:07 +01:00
#[ test_case( " t=pyro " => Ok(( " " , RawCardFilter(Field::Type, Operator::Equal, Value::String( " pyro " .into()))))) ]
#[ test_case( " t:PYro " => Ok(( " " , RawCardFilter(Field::Type, Operator::Equal, Value::String( " pyro " .into())))); " input is lowercased " ) ]
#[ test_case( " t==warrior " => Ok(( " " , RawCardFilter(Field::Type, Operator::Equal, Value::String( " warrior " .into()))))) ]
#[ test_case( " atk>=100 " => Ok(( " " , RawCardFilter(Field::Atk, Operator::GreaterEqual, Value::Numerical(100))))) ]
#[ test_case( " Necrovalley " => Ok(( " " , RawCardFilter(Field::Name, Operator::Equal, Value::String( " necrovalley " .into()))))) ]
#[ test_case( " l=10 " => Ok(( " " , RawCardFilter(Field::Level, Operator::Equal, Value::Numerical(10))))) ]
#[ test_case( " Ib " => Ok(( " " , RawCardFilter(Field::Name, Operator::Equal, Value::String( " ib " .to_owned()))))) ]
#[ test_case( " c!=synchro " => Ok(( " " , RawCardFilter(Field::Class, Operator::NotEqual, Value::String( " synchro " .to_owned()))))) ]
2023-01-26 23:07:16 +01:00
fn successful_parsing_test ( input : & str ) -> IResult < & str , RawCardFilter > {
parse_raw_filter ( input )
}
#[ test_case( " atk<=>1 " ) ]
2023-04-18 00:27:31 +02:00
#[ test_case( " atk=50| " ) ]
#[ test_case( " def=| " ) ]
2023-01-26 23:07:16 +01:00
#[ test_case( " l===10 " ) ]
#[ test_case( " t= " ) ]
#[ test_case( " =100 " ) ]
#[ test_case( " a " ) ]
fn unsuccessful_parsing_test ( input : & str ) {
2023-04-17 23:45:59 +02:00
if let Ok ( ( filters , _ ) ) = parse_filters ( input ) {
assert! ( false , " Should have failed, but parsed as {filters:?} " ) ;
}
2023-01-26 23:07:16 +01:00
}
#[ test ]
fn sequential_parsing_test ( ) {
let ( rest , filter ) = parse_raw_filter ( " atk>=100 l:4 " ) . unwrap ( ) ;
2023-01-27 15:48:07 +01:00
assert_eq! ( filter , RawCardFilter ( Field ::Atk , Operator ::GreaterEqual , Value ::Numerical ( 100 ) ) ) ;
assert_eq! ( parse_raw_filter ( rest ) , Ok ( ( " " , RawCardFilter ( Field ::Level , Operator ::Equal , Value ::Numerical ( 4 ) ) ) ) ) ;
2023-01-26 23:07:16 +01:00
assert_eq! (
2023-01-27 00:03:00 +01:00
parse_raw_filters ( " atk>=100 l=4 " ) ,
2023-01-26 23:07:16 +01:00
Ok ( (
" " ,
2023-01-27 15:48:07 +01:00
vec! [
RawCardFilter ( Field ::Atk , Operator ::GreaterEqual , Value ::Numerical ( 100 ) ) ,
RawCardFilter ( Field ::Level , Operator ::Equal , Value ::Numerical ( 4 ) )
]
2023-01-26 23:07:16 +01:00
) )
) ;
2023-01-27 00:03:00 +01:00
assert_eq! (
parse_raw_filters ( r # "t:counter c:trap o:"negate the summon""# ) ,
Ok ( (
" " ,
vec! [
2023-01-27 15:48:07 +01:00
RawCardFilter ( Field ::Type , Operator ::Equal , Value ::String ( " counter " . into ( ) ) ) ,
RawCardFilter ( Field ::Class , Operator ::Equal , Value ::String ( " trap " . into ( ) ) ) ,
RawCardFilter ( Field ::Text , Operator ::Equal , Value ::String ( " negate the summon " . into ( ) ) ) ,
2023-01-27 00:03:00 +01:00
]
) )
) ;
}
2023-04-17 23:45:59 +02:00
#[ test ]
2023-04-18 00:27:31 +02:00
fn parse_multiple_values ( ) {
2023-04-17 23:45:59 +02:00
let input = " level=4|5|6 " ;
let expected_output = vec! [ RawCardFilter (
Field ::Level ,
Operator ::Equal ,
Value ::Multiple ( vec! [ Value ::Numerical ( 4 ) , Value ::Numerical ( 5 ) , Value ::Numerical ( 6 ) ] ) ,
) ] ;
assert_eq! ( parse_raw_filters ( input ) , Ok ( ( " " , expected_output ) ) ) ;
}
2023-01-27 00:03:00 +01:00
#[ test ]
fn quoted_value_test ( ) {
let ( rest , filter ) = parse_raw_filter ( r # "o:"destroy that target""# ) . unwrap ( ) ;
assert_eq! ( rest , " " ) ;
2023-01-27 15:48:07 +01:00
assert_eq! ( filter , RawCardFilter ( Field ::Text , Operator ::Equal , Value ::String ( " destroy that target " . into ( ) ) ) ) ;
2023-01-26 23:07:16 +01:00
}
}