2023-01-27 15:48:07 +01:00
use std ::{
fmt ::{ self , Display } ,
str ::FromStr ,
} ;
2023-01-26 23:07:16 +01:00
2023-01-27 15:48:07 +01:00
use crate ::filter ::{ build_filter , fallback_filter , CardFilter } ;
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-01-27 15:48:07 +01:00
combinator ::{ complete , map , map_res , rest , verify } ,
2023-01-26 23:07:16 +01:00
multi ::many_m_n ,
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-01-27 15:48:07 +01:00
v . sort_unstable_by_key ( | RawCardFilter ( f , _ , _ ) | * f as u8 ) ;
2023-01-31 11:47:59 +01:00
Ok ( ( v . clone ( ) , v . 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 )
}
fn parse_raw_filter ( input : & str ) -> IResult < & str , RawCardFilter > {
2023-01-27 15:48:07 +01:00
preceded (
multispace0 ,
alt ( ( map ( complete ( tuple ( ( field , operator , value ) ) ) , | ( f , o , v ) | RawCardFilter ( f , o , v ) ) , map_res ( word_non_empty , fallback_filter ) ) ) ,
) ( 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 )
}
fn value ( input : & str ) -> IResult < & str , Value > {
2023-01-27 00:03:00 +01:00
map_res ( alt ( ( delimited ( char ( '"' ) , take_until1 ( " \" " ) , char ( '"' ) ) , take_until1 ( " " ) , rest ) ) , | i : & str | match i . parse ( ) {
2023-01-26 23:07:16 +01:00
Ok ( n ) = > Ok ( Value ::Numerical ( n ) ) ,
Err ( _ ) if i . is_empty ( ) = > Err ( " empty filter argument " ) ,
Err ( _ ) = > Ok ( Value ::String ( i . to_lowercase ( ) ) ) ,
} ) ( 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-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-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 " ) ]
#[ test_case( " l===10 " ) ]
#[ test_case( " t= " ) ]
#[ test_case( " =100 " ) ]
#[ test_case( " a " ) ]
fn unsuccessful_parsing_test ( input : & str ) {
assert! ( parse_filters ( input ) . is_err ( ) ) ;
}
#[ 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
]
) )
) ;
}
#[ 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
}
}