aboutsummaryrefslogtreecommitdiff
path: root/src/fexpr/grammar.lalrpop
blob: 4e6ac898e02a752be9f678e44f84ae77003f534a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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:Conjunction<T>> "or" <b:Conjunction<T>> => a | b,
    Conjunction<T>,
}

Conjunction<T>: T = {
    <a:Negation<T>> "and"? <b:Negation<T>> => a & b,
    Negation<T>,
}

Negation<T>: T = {
    "not" <T> => ! <>,
    "!" <T> => ! <>,
    T,
}

LogPredicate: Box<dyn filters::log::LogFilter> = {
    "-success" => filters::log::OutcomeFilter::success(),
    "-wipe" => filters::log::OutcomeFilter::wipe(),
    "-outcome" <Comma<FightOutcome>> => filters::log::OutcomeFilter::new(<>),

    "-weekday" <Comma<Weekday>> => filters::log::WeekdayFilter::new(<>),
    "-before" <Date> => filters::log::TimeFilter::new(None, Some(<>)),
    "-after" <Date> => filters::log::TimeFilter::new(Some(<>), None),

    "-boss" <Comma<Boss>> => filters::log::BossFilter::new(<>),

    "-player" <Regex> => filters::player::any(
            filters::player::NameFilter::new(SearchField::Character, <>.clone())
            | filters::player::NameFilter::new(SearchField::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::NameFilter::new(SearchField::Character, <>),
    "-account" <Regex> => filters::player::NameFilter::new(SearchField::Account, <>),
    "-name" <Regex> =>
        filters::player::NameFilter::new(SearchField::Account, <>.clone())
        | filters::player::NameFilter::new(SearchField::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",
    "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,

    _
}