From 9df28eb69bae211dcf557788e482607abd32596d Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Wed, 12 Jan 2022 09:59:30 -0500 Subject: [PATCH] Add variant of field filter which ignores missing field and comparison errors --- mps-interpreter/src/interpretor.rs | 1 + .../lang/vocabulary/filters/field_filter.rs | 85 +++++++++++++----- .../vocabulary/filters/field_filter_maybe.rs | 88 +++++++++++++++++++ .../src/lang/vocabulary/filters/mod.rs | 4 +- mps-interpreter/src/tokens/tokenizer.rs | 2 +- mps-interpreter/tests/single_line.rs | 24 +++++ 6 files changed, 179 insertions(+), 25 deletions(-) create mode 100644 mps-interpreter/src/lang/vocabulary/filters/field_filter_maybe.rs diff --git a/mps-interpreter/src/interpretor.rs b/mps-interpreter/src/interpretor.rs index 76726af..1361867 100644 --- a/mps-interpreter/src/interpretor.rs +++ b/mps-interpreter/src/interpretor.rs @@ -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) diff --git a/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs b/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs index 8da2351..3291d46 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs @@ -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 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 for FieldFilterFactory { )?); Ok(FieldFilter { field_name: field, + field_errors: FieldFilterErrorHandling::Error, + comparison_errors: FieldFilterErrorHandling::Error, val: variable, comparison: compare_operator, }) diff --git a/mps-interpreter/src/lang/vocabulary/filters/field_filter_maybe.rs b/mps-interpreter/src/lang/vocabulary/filters/field_filter_maybe.rs new file mode 100644 index 0000000..f4b8894 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/filters/field_filter_maybe.rs @@ -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 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, + _dict: &MpsLanguageDictionary, + ) -> Result { + 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; + +#[inline(always)] +pub fn field_filter_maybe() -> FieldFilterMaybeStatementFactory { + FieldFilterMaybeStatementFactory::new(FieldFilterMaybeFactory) +} diff --git a/mps-interpreter/src/lang/vocabulary/filters/mod.rs b/mps-interpreter/src/lang/vocabulary/filters/mod.rs index 5cc6410..f02416d 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/mod.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/mod.rs @@ -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}; diff --git a/mps-interpreter/src/tokens/tokenizer.rs b/mps-interpreter/src/tokens/tokenizer.rs index 5c3676b..49eb6da 100644 --- a/mps-interpreter/src/tokens/tokenizer.rs +++ b/mps-interpreter/src/tokens/tokenizer.rs @@ -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 { diff --git a/mps-interpreter/tests/single_line.rs b/mps-interpreter/tests/single_line.rs index 4350ba4..d297505 100644 --- a/mps-interpreter/tests/single_line.rs +++ b/mps-interpreter/tests/single_line.rs @@ -159,6 +159,30 @@ fn execute_fieldfilter_line() -> Result<(), Box> { ) } +#[test] +fn execute_fieldfiltermaybe_line() -> Result<(), Box> { + 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> { execute_single_line(