Add variant of field filter which ignores missing field and comparison errors

This commit is contained in:
NGnius (Graham) 2022-01-12 09:59:30 -05:00
parent 29c880d3e8
commit 9df28eb69b
6 changed files with 179 additions and 25 deletions

View file

@ -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)

View file

@ -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,
})

View file

@ -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)
}

View file

@ -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};

View file

@ -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 {

View file

@ -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(