diff options
| author | Daniel <kingdread@gmx.de> | 2020-04-26 11:45:37 +0200 | 
|---|---|---|
| committer | Daniel <kingdread@gmx.de> | 2020-04-26 11:45:37 +0200 | 
| commit | 13053073e3336b8e6ffefd6a056d159239550be7 (patch) | |
| tree | 63544e8764b55563a48d3f9fd530b0381f42a277 /src/fexpr | |
| parent | 3c429432382dfad6d4ac97349c96e4a4eb292089 (diff) | |
| parent | 9bbd5db2a6caae10f0ab2cf2625fbc34485a4ce9 (diff) | |
| download | raidgrep-13053073e3336b8e6ffefd6a056d159239550be7.tar.gz raidgrep-13053073e3336b8e6ffefd6a056d159239550be7.tar.bz2 raidgrep-13053073e3336b8e6ffefd6a056d159239550be7.zip | |
Merge branch 'new-filters'
The new filter system (includes both the internal rewrite and the
command line parsing) is now being included in master. This gives a lot
more flexibility.
Diffstat (limited to 'src/fexpr')
| -rw-r--r-- | src/fexpr/grammar.lalrpop | 171 | ||||
| -rw-r--r-- | src/fexpr/mod.rs | 66 | 
2 files changed, 237 insertions, 0 deletions
| diff --git a/src/fexpr/grammar.lalrpop b/src/fexpr/grammar.lalrpop new file mode 100644 index 0000000..58ec052 --- /dev/null +++ b/src/fexpr/grammar.lalrpop @@ -0,0 +1,171 @@ +use super::{ +    FError, +    FErrorKind, +    FightOutcome, +    filters, +    SearchField, +    Weekday, +}; +use evtclib::statistics::gamedata::Boss; +use std::collections::HashSet; +use lalrpop_util::ParseError; + +use chrono::NaiveDateTime; +use regex::Regex; + +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::log::before(<>), +    "-after" <Date> => filters::log::after(<>), + +    "-boss" <Comma<Boss>> => filters::log::boss(<>), + +    "-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(<>), + +    "(" <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(<>), + +    "(" <PlayerFilter> ")", +} + +Regex: Regex = { +    <l:@L> <s:regex> =>? Regex::new(&s[1..s.len() - 1]).map_err(|error| ParseError::User { +        error: FError { +            location: l, +            data: s.to_string(), +            kind: error.into(), +        } +    }), +    <l:@L> <s:word> =>? Regex::new(s).map_err(|error| ParseError::User { +        error: FError { +            location: l, +            data: s.to_string(), +            kind: error.into(), +        } +    }), +} + +FightOutcome: FightOutcome = { +    <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { +        error: FError { +            location: l, +            data: w.into(), +            kind: FErrorKind::InvalidFightOutcome, +        } +    }), +} + +Weekday: Weekday = { +    <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { +        error: FError { +            location: l, +            data: w.into(), +            kind: FErrorKind::InvalidWeekday, +        } +    }), +} + +Boss: Boss = { +    <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { +        error: FError { +            location: l, +            data: w.into(), +            kind: FErrorKind::InvalidBoss, +        } +    }), +} + +Date: NaiveDateTime = { +    <l:@L> <d:datetime> =>? NaiveDateTime::parse_from_str(d, "%Y-%m-%d %H:%M:%S") +        .map_err(|error| ParseError::User { +            error: FError { +                location: l, +                data: d.into(), +                kind: error.into(), +            } +        }), +    <l:@L> <d:date> =>? NaiveDateTime::parse_from_str(&format!("{} 00:00:00", d), "%Y-%m-%d %H:%M:%S") +        .map_err(|error| ParseError::User { +            error: FError { +                location: l, +                data: d.into(), +                kind: error.into(), +            } +        }), +} + +Comma<T>: HashSet<T> = { +    <v:(<T> ",")*> <e:T> => { +        let mut result = v.into_iter().collect::<HashSet<_>>(); +        result.insert(e); +        result +    }, +} + +match { +    "player" => "player", +    "not" => "not", +    "or" => "or", +    "and" => "and", +    "any" => "any", +    "all" => "all", +    "exists" => "exists", + +    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"\w+" => word, +    r#""[^"]*""# => regex, + +    _ +} diff --git a/src/fexpr/mod.rs b/src/fexpr/mod.rs new file mode 100644 index 0000000..5610aba --- /dev/null +++ b/src/fexpr/mod.rs @@ -0,0 +1,66 @@ +//! Filter expression language. +//! +//! This module contains methods to parse a given string into an abstract filter tree, check its +//! type and convert it to a [`Filter`][super::filters::Filter]. +// Make it available in the grammar mod. +use super::{filters, FightOutcome, SearchField, Weekday}; + +use std::{error, fmt}; + +use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError}; +use thiserror::Error; + +lalrpop_mod!(#[allow(clippy::all)] pub grammar, "/fexpr/grammar.rs"); + +#[derive(Debug)] +pub struct FError { +    location: usize, +    data: String, +    kind: FErrorKind, +} + +impl fmt::Display for FError { +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +        write!(f, "{} (at {})", self.kind, self.location) +    } +} + +impl error::Error for FError { +    fn source(&self) -> Option<&(dyn error::Error + 'static)> { +        Some(&self.kind) +    } +} + +#[derive(Debug, Error)] +pub enum FErrorKind { +    #[error("invalid regular expression: {0}")] +    InvalidRegex(#[from] regex::Error), +    #[error("invalid fight outcome")] +    InvalidFightOutcome, +    #[error("invalid weekday")] +    InvalidWeekday, +    #[error("invalid timestamp: {0}")] +    InvalidTimestamp(#[from] chrono::format::ParseError), +    #[error("invalid boss name")] +    InvalidBoss, +} + +/// Shortcut to create a new parser and parse the given input. +pub fn parse_logfilter<'a>( +    input: &'a str, +) -> Result<Box<dyn filters::log::LogFilter>, ParseError<usize, Token<'a>, FError>> { +    grammar::LogFilterParser::new().parse(input) +} + +/// Extract the location from the given error. +pub fn location<T>(err: &ParseError<usize, T, FError>) -> usize { +    match *err { +        ParseError::InvalidToken { location } => location, +        ParseError::UnrecognizedEOF { location, .. } => location, +        ParseError::UnrecognizedToken { +            token: (l, _, _), .. +        } => l, +        ParseError::ExtraToken { token: (l, _, _) } => l, +        ParseError::User { ref error } => error.location, +    } +} | 
