use super::{ FError, FErrorKind, FightOutcome, filters, PlayerClass, DateProducer, DurationProducer, CountProducer, }; use evtclib::Encounter; use std::collections::HashSet; use lalrpop_util::ParseError; use chrono::{DateTime, Local, TimeZone, Utc, Weekday, Duration}; use regex::{Regex, RegexBuilder}; grammar; extern { type Error = FError; } pub LogFilter: Box<dyn filters::log::LogFilter> = { Disjunction<LogPredicate>, } PlayerFilter: Box<dyn filters::player::PlayerFilter> = { Disjunction<PlayerPredicate>, } Disjunction<T>: T = { <a:Disjunction<T>> "or" <b:Conjunction<T>> => a | b, Conjunction<T>, } Conjunction<T>: T = { <a:Conjunction<T>> "and"? <b:Negation<T>> => a & b, Negation<T>, } Negation<T>: T = { "not" <Negation<T>> => ! <>, "!" <Negation<T>> => ! <>, T, } LogPredicate: Box<dyn filters::log::LogFilter> = { "-success" => filters::log::success(), "-wipe" => filters::log::wipe(), "-outcome" <Comma<FightOutcome>> => filters::log::outcome(<>), "-weekday" <Comma<Weekday>> => filters::log::weekday(<>), "-before" <Date> => filters::values::comparison( filters::values::time(), filters::values::CompOp::Less, filters::values::constant(<>) ), "-after" <Date> => filters::values::comparison( filters::values::time(), filters::values::CompOp::Greater, filters::values::constant(<>) ), "-log-before" <Date> => filters::log::log_before(<>), "-log-after" <Date> => filters::log::log_after(<>), "-boss" <Comma<Encounter>> => filters::log::encounter(<>), "-cm" => filters::log::challenge_mote(), "-include" => filters::constant(true), "-exclude" => filters::constant(false), "-player" <Regex> => filters::player::any( filters::player::character(<>.clone()) | filters::player::account(<>) ), "all" "(" "player" ":" <PlayerFilter> ")" => filters::player::all(<>), "any" "(" "player" ":" <PlayerFilter> ")" => filters::player::any(<>), "exists" "(" "player" ":" <PlayerFilter> ")" => filters::player::any(<>), <Comparison<DateProducer>>, <Comparison<DurationProducer>>, <Comparison<CountProducer>>, "(" <LogFilter> ")", } PlayerPredicate: Box<dyn filters::player::PlayerFilter> = { "-character" <Regex> => filters::player::character(<>), "-account" <Regex> => filters::player::account(<>), "-name" <Regex> => filters::player::account(<>.clone()) | filters::player::character(<>), "-class" <Comma<PlayerClass>> => filters::player::class(<>), "(" <PlayerFilter> ")", } Regex: Regex = { <l:@L> <s:string> =>? RegexBuilder::new(&s[1..s.len() - 1]) .case_insensitive(true) .build() .map_err(|error| ParseError::User { error: FError { location: l, kind: error.into(), } }), <l:@L> <s:word> =>? RegexBuilder::new(s) .case_insensitive(true) .build() .map_err(|error| ParseError::User { error: FError { location: l, kind: error.into(), } }), } FightOutcome: FightOutcome = { <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { error: FError { location: l, kind: FErrorKind::FightOutcome, } }), } Weekday: Weekday = { <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { error: FError { location: l, kind: FErrorKind::Weekday, } }), } Encounter: Encounter = { <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { error: FError { location: l, kind: FErrorKind::Boss, } }), <l:@L> <s:string> =>? s[1..s.len() -1].parse().map_err(|_| ParseError::User { error: FError { location: l, kind: FErrorKind::Boss, } }), } PlayerClass: PlayerClass = { <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { error: FError { location: l, kind: FErrorKind::Class, } }), } Date: DateTime<Utc> = { <l:@L> <d:datetime> =>? Local.datetime_from_str(d, "%Y-%m-%d %H:%M:%S") .map_err(|error| ParseError::User { error: FError { location: l, kind: error.into(), } }) .map(|d| d.with_timezone(&Utc)), <l:@L> <d:date> =>? Local.datetime_from_str(&format!("{} 00:00:00", d), "%Y-%m-%d %H:%M:%S") .map_err(|error| ParseError::User { error: FError { location: l, kind: error.into(), } }) .map(|d| d.with_timezone(&Utc)), } Duration: Duration = { duration => Duration::from_std(humantime::parse_duration(<>).unwrap()).unwrap(), } CompOp: filters::values::CompOp = { "<" => filters::values::CompOp::Less, "<=" => filters::values::CompOp::LessEqual, "=" => filters::values::CompOp::Equal, ">=" => filters::values::CompOp::GreaterEqual, ">" => filters::values::CompOp::Greater, } Comparison<T>: Box<dyn filters::log::LogFilter> = { <lhs:T> <op:CompOp> <rhs:T> => filters::values::comparison(lhs, op, rhs), } Comma<T>: HashSet<T> = { <v:(<T> ",")*> <e:T> => { let mut result = v.into_iter().collect::<HashSet<_>>(); result.insert(e); result }, } DateProducer: Box<dyn DateProducer> = { <Date> => filters::values::constant(<>), "-time" => filters::values::time(), } DurationProducer: Box<dyn DurationProducer> = { <Duration> => filters::values::constant(<>), "-duration" => filters::values::duration(), } CountProducer: Box<dyn CountProducer> = { <integer> => filters::values::constant(<>.parse().unwrap()), "count" "(" "player" ":" <PlayerFilter> ")" => filters::values::player_count(<>), "count" "(" "player" ")" => filters::values::player_count(filters::constant(true)), } match { "player" => "player", "not" => "not", "or" => "or", "and" => "and", "any" => "any", "all" => "all", "exists" => "exists", "count" => "count", r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d" => datetime, r"\d\d\d\d-\d\d-\d\d" => date, r"((\d+m ?)?\d+s)|(\d+m)" => duration, r"\d+" => integer, r"[[:alpha:]][\w]*" => word, r#""[^"]*""# => string, _ }