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
|
vocabulary
|
||||||
.add(crate::lang::vocabulary::filters::empty_filter())
|
.add(crate::lang::vocabulary::filters::empty_filter())
|
||||||
.add(crate::lang::vocabulary::filters::field_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::sql_function_factory())
|
||||||
.add(crate::lang::vocabulary::simple_sql_function_factory())
|
.add(crate::lang::vocabulary::simple_sql_function_factory())
|
||||||
.add(crate::lang::vocabulary::CommentStatementFactory)
|
.add(crate::lang::vocabulary::CommentStatementFactory)
|
||||||
|
|
|
@ -14,24 +14,46 @@ use crate::MpsContext;
|
||||||
use crate::MpsMusicItem;
|
use crate::MpsMusicItem;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum VariableOrValue {
|
pub(super) enum VariableOrValue {
|
||||||
Variable(String),
|
Variable(String),
|
||||||
Value(MpsTypePrimitive),
|
Value(MpsTypePrimitive),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FieldFilter {
|
pub struct FieldFilter {
|
||||||
field_name: String,
|
pub(super) field_name: String,
|
||||||
val: VariableOrValue,
|
pub(super) field_errors: FieldFilterErrorHandling,
|
||||||
comparison: [i8; 2],
|
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 {
|
impl Display for FieldFilter {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
// TODO display other comparison operators correctly
|
let comp_op = comparison_op(&self.comparison);
|
||||||
match &self.val {
|
match &self.val {
|
||||||
VariableOrValue::Variable(name) => write!(f, "{} == {}", self.field_name, name),
|
VariableOrValue::Variable(name) => write!(f, "{} {} {}", self.field_name, comp_op, name),
|
||||||
VariableOrValue::Value(t) => write!(f, "{} == {}", self.field_name, t),
|
VariableOrValue::Value(t) => write!(f, "{} {} {}", self.field_name, comp_op, t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,25 +78,38 @@ impl MpsFilterPredicate for FieldFilter {
|
||||||
VariableOrValue::Value(val) => Ok(val),
|
VariableOrValue::Value(val) => Ok(val),
|
||||||
}?;
|
}?;
|
||||||
if let Some(field) = music_item_lut.get(&self.field_name) {
|
if let Some(field) = music_item_lut.get(&self.field_name) {
|
||||||
let compare = field.compare(variable).map_err(|e| RuntimeError {
|
let compare_res = field.compare(variable);
|
||||||
line: 0,
|
if let Err(e) = compare_res {
|
||||||
op: op(),
|
match self.comparison_errors {
|
||||||
msg: e,
|
FieldFilterErrorHandling::Error => Err(RuntimeError {
|
||||||
})?;
|
line: 0,
|
||||||
let mut is_match = false;
|
op: op(),
|
||||||
for comparator in self.comparison {
|
msg: e,
|
||||||
if comparator == compare {
|
}),
|
||||||
is_match = true;
|
FieldFilterErrorHandling::Ignore => Ok(false),
|
||||||
break;
|
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 {
|
} else {
|
||||||
Err(RuntimeError {
|
match self.field_errors {
|
||||||
line: 0,
|
FieldFilterErrorHandling::Error => Err(RuntimeError {
|
||||||
op: op(),
|
line: 0,
|
||||||
msg: format!("Field {} does not exist", &self.field_name),
|
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)?);
|
let value = VariableOrValue::Value(assert_type(tokens)?);
|
||||||
Ok(FieldFilter {
|
Ok(FieldFilter {
|
||||||
field_name: field,
|
field_name: field,
|
||||||
|
field_errors: FieldFilterErrorHandling::Error,
|
||||||
|
comparison_errors: FieldFilterErrorHandling::Error,
|
||||||
val: value,
|
val: value,
|
||||||
comparison: compare_operator,
|
comparison: compare_operator,
|
||||||
})
|
})
|
||||||
|
@ -135,6 +172,8 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
|
||||||
)?);
|
)?);
|
||||||
Ok(FieldFilter {
|
Ok(FieldFilter {
|
||||||
field_name: field,
|
field_name: field,
|
||||||
|
field_errors: FieldFilterErrorHandling::Error,
|
||||||
|
comparison_errors: FieldFilterErrorHandling::Error,
|
||||||
val: variable,
|
val: variable,
|
||||||
comparison: compare_operator,
|
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 empty_filter;
|
||||||
mod field_filter;
|
mod field_filter;
|
||||||
|
mod field_filter_maybe;
|
||||||
pub(crate) mod utility;
|
pub(crate) mod utility;
|
||||||
|
|
||||||
pub use empty_filter::{
|
pub use empty_filter::{
|
||||||
empty_filter, EmptyFilter, EmptyFilterFactory, EmptyFilterStatementFactory,
|
empty_filter, EmptyFilter, EmptyFilterFactory, EmptyFilterStatementFactory,
|
||||||
};
|
};
|
||||||
pub use field_filter::{
|
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 {},
|
' ' => Self::EndToken {},
|
||||||
'\n' | ';' => Self::EndStatement {},
|
'\n' | ';' => Self::EndStatement {},
|
||||||
'\0' => Self::EndOfFile {},
|
'\0' => Self::EndOfFile {},
|
||||||
'(' | ')' | ',' | '=' | '<' | '>' | '.' => Self::SingleCharToken { out: input },
|
'(' | ')' | ',' | '=' | '<' | '>' | '.' | '!' | '?' => Self::SingleCharToken { out: input },
|
||||||
_ => Self::Regular { out: input },
|
_ => Self::Regular { out: input },
|
||||||
},
|
},
|
||||||
Self::Escaped { inside } => match inside {
|
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]
|
#[test]
|
||||||
fn execute_files_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
fn execute_files_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||||
execute_single_line(
|
execute_single_line(
|
||||||
|
|
Loading…
Reference in a new issue