// Copyright (C) 2020-2023 Michael Herstine <sp1ff@pobox.com> -*- mode: rust; rust-format-on-save: nil -*-
//
// This file is part of mpdpopm.
//
// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along with mpdpopm. If not,
// see <http://www.gnu.org/licenses/>.
use lalrpop_util::ParseError;
use crate::filters_ast::{Conjunction, Disjunction, Expression, OpCode, Selector, Term, Value,
expect_quoted, parse_iso_8601};
grammar;
pub ExprOp: OpCode = {
"==" => OpCode::Equality,
"!=" => OpCode::Inequality,
"contains" => OpCode::Contains,
"=~" => OpCode::RegexMatch,
"!~" => OpCode::RegexExclude,
">" => OpCode::GreaterThan,
"<" => OpCode::LessThan,
">=" => OpCode::GreaterThanEqual,
"<=" => OpCode::LessThanEqual,
};
pub ExprSel: Selector = {
r"(?i)artist" => Selector::Artist,
r"(?i)album" => Selector::Album,
r"(?i)albumartist" => Selector::AlbumArtist,
r"(?i)titile" => Selector::Title,
r"(?i)track" => Selector::Track,
r"(?i)name" => Selector::Name,
r"(?i)genre" => Selector::Genre,
r"(?i)date" => Selector::Date,
r"(?i)originaldate" => Selector::OriginalDate,
r"(?i)composer" => Selector::Composer,
r"(?i)performer" => Selector::Performer,
r"(?i)conductor" => Selector::Conductor,
r"(?i)work" => Selector::Work,
r"(?i)grouping" => Selector::Grouping,
r"(?i)comment" => Selector::Comment,
r"(?i)disc" => Selector::Disc,
r"(?i)label" => Selector::Label,
r"(?i)musicbrainz_aristid" => Selector::MusicbrainzAristID,
r"(?i)musicbrainz_albumid" => Selector::MusicbrainzAlbumID,
r"(?i)musicbrainz_albumartistid" => Selector::MusicbrainzAlbumArtistID,
r"(?i)musicbrainz_trackid" => Selector::MusicbrainzTrackID,
r"(?i)musicbrainz_releasetrackid" => Selector::MusicbrainzReleaseTrackID,
r"(?i)musicbrainz_workid" => Selector::MusicbrainzWorkID,
r"(?i)file" => Selector::File,
r"(?i)base" => Selector::Base,
r"(?i)modified-since" => Selector::ModifiedSince,
r"(?i)audioformat" => Selector::AudioFormat,
r"(?i)rating" => Selector::Rating,
r"(?i)playcount" => Selector::PlayCount,
r"(?i)lastplayed" => Selector::LastPlayed,
};
pub Token: Value = {
<s:r"[0-9]+"> =>? {
eprintln!("matched token: ``{}''.", s);
// We need to yield a Result<Value, ParseError>
match s.parse::<usize>() {
Ok(n) => Ok(Value::Uint(n)),
Err(_) => Err(ParseError::User {
error: "Internal parse error while parsing unsigned int" })
}
},
<s:r#""([ \t'a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\"|\\')+""#> => {
eprintln!("matched token: ``{}''.", s);
let s = expect_quoted(s).unwrap();
match parse_iso_8601(&mut s.as_bytes()) {
Ok(x) => Value::UnixEpoch(x),
Err(_) => Value::Text(s),
}
},
<s:r#"'([ \t"a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\'|\\")+'"#> => {
eprintln!("matched token: ``{}''.", s);
let s = expect_quoted(s).unwrap();
match parse_iso_8601(&mut s.as_bytes()) {
Ok(x) => Value::UnixEpoch(x),
Err(_) => Value::Text(s),
}
},
};
pub Term: Box<Term> = {
<t:ExprSel> <u:Token> => {
eprintln!("matched unary condition: ``({}, {:#?})''", t, u);
Box::new(Term::UnaryCondition(t, u))
},
<t:ExprSel> <o:ExprOp> <u:Token> => {
eprintln!("matched binary condition: ``({}, {:#?}, {:#?})''", t, o, u);
Box::new(Term::BinaryCondition(t, o, u))
},
}
pub Conjunction: Box<Conjunction> = {
<e1:Expression> "AND" <e2:Expression> => {
eprintln!("matched conjunction: ``({:#?}, {:#?})''", e1, e2);
Box::new(Conjunction::Simple(e1, e2))
},
<c:Conjunction> "AND" <e:Expression> => {
eprintln!("matched conjunction: ``({:#?}, {:#?})''", c, e);
Box::new(Conjunction::Compound(c, e))
},
}
pub Disjunction: Box<Disjunction> = {
<e1:Expression> "OR" <e2:Expression> => {
eprintln!("matched disjunction: ``({:#?}, {:#?})''", e1, e2);
Box::new(Disjunction::Simple(e1, e2))
},
<c:Disjunction> "OR" <e:Expression> => {
eprintln!("matched disjunction: ``({:#?}, {:#?})''", c, e);
Box::new(Disjunction::Compound(c, e))
},
}
pub Expression: Box<Expression> = {
"(" <t:Term> ")" => {
eprintln!("matched parenthesized term: ``({:#?})''", t);
Box::new(Expression::Simple(t))
},
"(" "!" <e:Expression> ")" => Box::new(Expression::Negation(e)),
"(" <c:Conjunction> ")" => {
eprintln!("matched parenthesized conjunction: ``({:#?})''", c);
Box::new(Expression::Conjunction(c))
},
"(" <c:Disjunction> ")" => {
eprintln!("matched parenthesized disjunction: ``({:#?})''", c);
Box::new(Expression::Disjunction(c))
},
}