Add variant of field filter which ignores missing field and comparison errors
This commit is contained in:
parent
29c880d3e8
commit
9df28eb69b
6 changed files with 179 additions and 25 deletions
|
@ -158,6 +158,7 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
|
|||
vocabulary
|
||||
.add(crate::lang::vocabulary::filters::empty_filter())
|
||||
.add(crate::lang::vocabulary::filters::field_filter())
|
||||
.add(crate::lang::vocabulary::filters::field_filter_maybe())
|
||||
.add(crate::lang::vocabulary::sql_function_factory())
|
||||
.add(crate::lang::vocabulary::simple_sql_function_factory())
|
||||
.add(crate::lang::vocabulary::CommentStatementFactory)
|
||||
|
|
|
@ -14,24 +14,46 @@ use crate::MpsContext;
|
|||
use crate::MpsMusicItem;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum VariableOrValue {
|
||||
pub(super) enum VariableOrValue {
|
||||
Variable(String),
|
||||
Value(MpsTypePrimitive),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FieldFilter {
|
||||
field_name: String,
|
||||
val: VariableOrValue,
|
||||
comparison: [i8; 2],
|
||||
pub(super) field_name: String,
|
||||
pub(super) field_errors: FieldFilterErrorHandling,
|
||||
pub(super) comparison_errors: FieldFilterErrorHandling,
|
||||
pub(super) val: VariableOrValue,
|
||||
pub(super) comparison: [i8; 2],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FieldFilterErrorHandling {
|
||||
Error, // return error
|
||||
Ignore, // return Ok(false) when error encountered
|
||||
Include, // return Ok(true) when error encountered
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn comparison_op(c: &[i8; 2]) -> &str {
|
||||
match c {
|
||||
[-1, -1] => "<",
|
||||
[0, 0] => "==",
|
||||
[1, 1] => ">",
|
||||
[0, -1] => "<=",
|
||||
[0, 1] => ">=",
|
||||
[-1, 1] => "!=",
|
||||
_ => "??"
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FieldFilter {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
// TODO display other comparison operators correctly
|
||||
let comp_op = comparison_op(&self.comparison);
|
||||
match &self.val {
|
||||
VariableOrValue::Variable(name) => write!(f, "{} == {}", self.field_name, name),
|
||||
VariableOrValue::Value(t) => write!(f, "{} == {}", self.field_name, t),
|
||||
VariableOrValue::Variable(name) => write!(f, "{} {} {}", self.field_name, comp_op, name),
|
||||
VariableOrValue::Value(t) => write!(f, "{} {} {}", self.field_name, comp_op, t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,25 +78,38 @@ impl MpsFilterPredicate for FieldFilter {
|
|||
VariableOrValue::Value(val) => Ok(val),
|
||||
}?;
|
||||
if let Some(field) = music_item_lut.get(&self.field_name) {
|
||||
let compare = field.compare(variable).map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: e,
|
||||
})?;
|
||||
let mut is_match = false;
|
||||
for comparator in self.comparison {
|
||||
if comparator == compare {
|
||||
is_match = true;
|
||||
break;
|
||||
let compare_res = field.compare(variable);
|
||||
if let Err(e) = compare_res {
|
||||
match self.comparison_errors {
|
||||
FieldFilterErrorHandling::Error => Err(RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: e,
|
||||
}),
|
||||
FieldFilterErrorHandling::Ignore => Ok(false),
|
||||
FieldFilterErrorHandling::Include => Ok(true),
|
||||
}
|
||||
} else {
|
||||
let compare = compare_res.unwrap();
|
||||
let mut is_match = false;
|
||||
for comparator in self.comparison {
|
||||
if comparator == compare {
|
||||
is_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(is_match)
|
||||
}
|
||||
Ok(is_match)
|
||||
} else {
|
||||
Err(RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Field {} does not exist", &self.field_name),
|
||||
})
|
||||
match self.field_errors {
|
||||
FieldFilterErrorHandling::Error => Err(RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Field {} does not exist", &self.field_name),
|
||||
}),
|
||||
FieldFilterErrorHandling::Ignore => Ok(false),
|
||||
FieldFilterErrorHandling::Include => Ok(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,6 +156,8 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
|
|||
let value = VariableOrValue::Value(assert_type(tokens)?);
|
||||
Ok(FieldFilter {
|
||||
field_name: field,
|
||||
field_errors: FieldFilterErrorHandling::Error,
|
||||
comparison_errors: FieldFilterErrorHandling::Error,
|
||||
val: value,
|
||||
comparison: compare_operator,
|
||||
})
|
||||
|
@ -135,6 +172,8 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
|
|||
)?);
|
||||
Ok(FieldFilter {
|
||||
field_name: field,
|
||||
field_errors: FieldFilterErrorHandling::Error,
|
||||
comparison_errors: FieldFilterErrorHandling::Error,
|
||||
val: variable,
|
||||
comparison: compare_operator,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use super::utility::assert_comparison_operator;
|
||||
use super::{FieldFilter, FieldFilterErrorHandling, field_filter::VariableOrValue};
|
||||
use crate::lang::utility::{assert_token, assert_type, check_is_type, assert_token_raw};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::{MpsFilterFactory, MpsFilterStatementFactory};
|
||||
use crate::lang::SyntaxError;
|
||||
use crate::tokens::MpsToken;
|
||||
|
||||
pub struct FieldFilterMaybeFactory;
|
||||
|
||||
impl MpsFilterFactory<FieldFilter> for FieldFilterMaybeFactory {
|
||||
fn is_filter(&self, tokens: &VecDeque<&MpsToken>) -> bool {
|
||||
let tokens_len = tokens.len();
|
||||
(tokens_len == 4 // field > variable OR field < variable
|
||||
&& tokens[0].is_name()
|
||||
&& (tokens[1].is_interrogation() || tokens[1].is_exclamation())
|
||||
&& (tokens[2].is_open_angle_bracket() || tokens[2].is_close_angle_bracket())
|
||||
&& (tokens[3].is_name() || check_is_type(&tokens[3])))
|
||||
|| (tokens_len == 5 // field >= variable OR field <= variable OR field != variable
|
||||
&& tokens[0].is_name()
|
||||
&& (tokens[1].is_interrogation() || tokens[1].is_exclamation())
|
||||
&& (tokens[2].is_open_angle_bracket() || tokens[2].is_close_angle_bracket() || tokens[2].is_equals() || tokens[2].is_exclamation())
|
||||
&& tokens[3].is_equals()
|
||||
&& (tokens[4].is_name() || check_is_type(&tokens[4])))
|
||||
}
|
||||
|
||||
fn build_filter(
|
||||
&self,
|
||||
tokens: &mut VecDeque<MpsToken>,
|
||||
_dict: &MpsLanguageDictionary,
|
||||
) -> Result<FieldFilter, SyntaxError> {
|
||||
let field = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(n) => Some(n),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("field_name".into()),
|
||||
tokens,
|
||||
)?;
|
||||
let error_f;
|
||||
let error_c;
|
||||
if tokens[0].is_interrogation() {
|
||||
error_f = FieldFilterErrorHandling::Ignore;
|
||||
error_c = FieldFilterErrorHandling::Ignore;
|
||||
assert_token_raw(MpsToken::Interrogation, tokens)?;
|
||||
} else {
|
||||
error_f = FieldFilterErrorHandling::Include;
|
||||
error_c = FieldFilterErrorHandling::Include;
|
||||
assert_token_raw(MpsToken::Exclamation, tokens)?;
|
||||
}
|
||||
let compare_operator = assert_comparison_operator(tokens)?;
|
||||
if check_is_type(&tokens[0]) {
|
||||
let value = VariableOrValue::Value(assert_type(tokens)?);
|
||||
Ok(FieldFilter {
|
||||
field_name: field,
|
||||
field_errors: error_f,
|
||||
comparison_errors: error_c,
|
||||
val: value,
|
||||
comparison: compare_operator,
|
||||
})
|
||||
} else {
|
||||
let variable = VariableOrValue::Variable(assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(n) => Some(n),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("variable_name".into()),
|
||||
tokens,
|
||||
)?);
|
||||
Ok(FieldFilter {
|
||||
field_name: field,
|
||||
field_errors: error_f,
|
||||
comparison_errors: error_c,
|
||||
val: variable,
|
||||
comparison: compare_operator,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type FieldFilterMaybeStatementFactory = MpsFilterStatementFactory<FieldFilter, FieldFilterMaybeFactory>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn field_filter_maybe() -> FieldFilterMaybeStatementFactory {
|
||||
FieldFilterMaybeStatementFactory::new(FieldFilterMaybeFactory)
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
mod empty_filter;
|
||||
mod field_filter;
|
||||
mod field_filter_maybe;
|
||||
pub(crate) mod utility;
|
||||
|
||||
pub use empty_filter::{
|
||||
empty_filter, EmptyFilter, EmptyFilterFactory, EmptyFilterStatementFactory,
|
||||
};
|
||||
pub use field_filter::{
|
||||
field_filter, FieldFilter, FieldFilterFactory, FieldFilterStatementFactory,
|
||||
field_filter, FieldFilter, FieldFilterFactory, FieldFilterStatementFactory, FieldFilterErrorHandling,
|
||||
};
|
||||
pub use field_filter_maybe::{field_filter_maybe, FieldFilterMaybeFactory, FieldFilterMaybeStatementFactory};
|
||||
|
|
|
@ -260,7 +260,7 @@ impl ReaderStateMachine {
|
|||
' ' => Self::EndToken {},
|
||||
'\n' | ';' => Self::EndStatement {},
|
||||
'\0' => Self::EndOfFile {},
|
||||
'(' | ')' | ',' | '=' | '<' | '>' | '.' => Self::SingleCharToken { out: input },
|
||||
'(' | ')' | ',' | '=' | '<' | '>' | '.' | '!' | '?' => Self::SingleCharToken { out: input },
|
||||
_ => Self::Regular { out: input },
|
||||
},
|
||||
Self::Escaped { inside } => match inside {
|
||||
|
|
|
@ -159,6 +159,30 @@ fn execute_fieldfilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_fieldfiltermaybe_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||
execute_single_line(
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(year? >= 2000)",
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
execute_single_line(
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(year? <= 2020)",
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
execute_single_line(
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(year! == 2016)",
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
execute_single_line(
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(year! != `test`)",
|
||||
false,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_files_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||
execute_single_line(
|
||||
|
|
Loading…
Reference in a new issue