diff --git a/mps-interpreter/src/interpretor.rs b/mps-interpreter/src/interpretor.rs index b679825..c4c3166 100644 --- a/mps-interpreter/src/interpretor.rs +++ b/mps-interpreter/src/interpretor.rs @@ -151,12 +151,14 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) { vocabulary // filters .add(crate::lang::vocabulary::filters::empty_filter()) - .add(crate::lang::vocabulary::filters::field_filter()) + .add(crate::lang::vocabulary::filters::unique_filter()) // accepts .(unique) + .add(crate::lang::vocabulary::filters::field_filter()) // accepts any .(something) .add(crate::lang::vocabulary::filters::field_filter_maybe()) .add(crate::lang::vocabulary::filters::index_filter()) .add(crate::lang::vocabulary::filters::range_filter()) .add(crate::lang::vocabulary::filters::field_like_filter()) .add(crate::lang::vocabulary::filters::field_re_filter()) + .add(crate::lang::vocabulary::filters::unique_field_filter()) // sorters .add(crate::lang::vocabulary::sorters::empty_sort()) .add(crate::lang::vocabulary::sorters::shuffle_sort()) // accepts ~(shuffle) diff --git a/mps-interpreter/src/lang/type_primitives.rs b/mps-interpreter/src/lang/type_primitives.rs index 09e9fa3..b7fa369 100644 --- a/mps-interpreter/src/lang/type_primitives.rs +++ b/mps-interpreter/src/lang/type_primitives.rs @@ -1,9 +1,9 @@ //! Basic types for MPS -use std::cmp::{Ord, Ordering}; +use std::cmp::{Eq, Ord, Ordering, PartialEq}; use std::fmt::{Debug, Display, Error, Formatter}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum MpsTypePrimitive { String(String), Int(i64), @@ -173,6 +173,15 @@ impl Display for MpsTypePrimitive { } } +impl PartialEq for MpsTypePrimitive { + fn eq(&self, other: &Self) -> bool { + let cmp = self.partial_cmp(other); + cmp.is_some() && cmp.unwrap() == Ordering::Equal + } +} + +impl Eq for MpsTypePrimitive {} + impl PartialOrd for MpsTypePrimitive { fn partial_cmp(&self, other: &Self) -> Option { match self { diff --git a/mps-interpreter/src/lang/vocabulary/filters/mod.rs b/mps-interpreter/src/lang/vocabulary/filters/mod.rs index e88817f..0d48710 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/mod.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/mod.rs @@ -5,6 +5,7 @@ mod field_like_filter; mod field_match_filter; mod index_filter; mod range_filter; +mod unique; pub(crate) mod utility; pub use empty_filter::{ @@ -29,3 +30,7 @@ pub use index_filter::{ pub use range_filter::{ range_filter, RangeFilter, RangeFilterFactory, RangeFilterStatementFactory, }; +pub use unique::{ + unique_field_filter, unique_filter, UniqueFieldFilter, UniqueFieldFilterStatementFactory, + UniqueFilter, UniqueFilterFactory, UniqueFilterStatementFactory, +}; diff --git a/mps-interpreter/src/lang/vocabulary/filters/unique.rs b/mps-interpreter/src/lang/vocabulary/filters/unique.rs new file mode 100644 index 0000000..3e3ea23 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/filters/unique.rs @@ -0,0 +1,162 @@ +use std::collections::{HashSet, VecDeque}; +use std::fmt::{Debug, Display, Error, Formatter}; + +use super::field_filter::FieldFilterErrorHandling; +use crate::lang::utility::{assert_name, assert_token, assert_token_raw, check_name}; +use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory}; +use crate::lang::{MpsLanguageDictionary, MpsTypePrimitive}; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::tokens::MpsToken; +use crate::MpsContext; +use crate::MpsItem; + +#[derive(Debug, Clone)] +pub struct UniqueFieldFilter { + field: String, + field_errors: FieldFilterErrorHandling, + // state + seen: HashSet, +} + +impl Display for UniqueFieldFilter { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "unique {}", &self.field) + } +} + +impl MpsFilterPredicate for UniqueFieldFilter { + fn matches(&mut self, item: &MpsItem, _ctx: &mut MpsContext) -> Result { + if let Some(field) = item.field(&self.field) { + if self.seen.contains(field) { + Ok(false) + } else { + self.seen.insert(field.to_owned()); + Ok(true) + } + } else { + match self.field_errors { + FieldFilterErrorHandling::Error => { + Err(RuntimeMsg(format!("Field {} does not exist", &self.field))) + } + FieldFilterErrorHandling::Ignore => Ok(false), + FieldFilterErrorHandling::Include => Ok(true), + } + } + } + + fn is_complete(&self) -> bool { + false + } + + fn reset(&mut self) -> Result<(), RuntimeMsg> { + self.seen.clear(); + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct UniqueFilter { + // state + seen: HashSet, +} + +impl Display for UniqueFilter { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "unique") + } +} + +impl MpsFilterPredicate for UniqueFilter { + fn matches(&mut self, item: &MpsItem, _ctx: &mut MpsContext) -> Result { + if self.seen.contains(item) { + Ok(false) + } else { + self.seen.insert(item.clone()); + Ok(true) + } + } + + fn is_complete(&self) -> bool { + false + } + + fn reset(&mut self) -> Result<(), RuntimeMsg> { + self.seen.clear(); + Ok(()) + } +} + +pub struct UniqueFilterFactory; + +impl MpsFilterFactory for UniqueFilterFactory { + fn is_filter(&self, tokens: &VecDeque<&MpsToken>) -> bool { + (tokens.len() == 2 || tokens.len() == 3) + && check_name("unique", &tokens[0]) + && tokens[1].is_name() + } + + fn build_filter( + &self, + tokens: &mut VecDeque, + _dict: &MpsLanguageDictionary, + ) -> Result { + assert_name("unique", tokens)?; + let field_name = assert_token( + |t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, + MpsToken::Name("field_name".into()), + tokens, + )?; + let error_handling = if !tokens.is_empty() { + if tokens[0].is_exclamation() { + assert_token_raw(MpsToken::Exclamation, tokens)?; + FieldFilterErrorHandling::Ignore + } else { + assert_token_raw(MpsToken::Interrogation, tokens)?; + FieldFilterErrorHandling::Include + } + } else { + FieldFilterErrorHandling::Error + }; + Ok(UniqueFieldFilter { + field: field_name, + field_errors: error_handling, + seen: HashSet::new(), + }) + } +} + +impl MpsFilterFactory for UniqueFilterFactory { + fn is_filter(&self, tokens: &VecDeque<&MpsToken>) -> bool { + tokens.len() == 1 && check_name("unique", &tokens[0]) + } + + fn build_filter( + &self, + tokens: &mut VecDeque, + _dict: &MpsLanguageDictionary, + ) -> Result { + assert_name("unique", tokens)?; + Ok(UniqueFilter { + seen: HashSet::new(), + }) + } +} + +pub type UniqueFieldFilterStatementFactory = + MpsFilterStatementFactory; + +#[inline(always)] +pub fn unique_field_filter() -> UniqueFieldFilterStatementFactory { + UniqueFieldFilterStatementFactory::new(UniqueFilterFactory) +} + +pub type UniqueFilterStatementFactory = + MpsFilterStatementFactory; + +#[inline(always)] +pub fn unique_filter() -> UniqueFilterStatementFactory { + UniqueFilterStatementFactory::new(UniqueFilterFactory) +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs b/mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs index df82991..bc4d9a4 100644 --- a/mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs +++ b/mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs @@ -60,8 +60,7 @@ impl MpsItemOp for InterpolateStringItemOp { MpsType::Op(op) => { let result = self.format.replace("{}", &format!("{}", op)); Ok(MpsType::Primitive(MpsTypePrimitive::String(result))) - } - //val => Err(RuntimeMsg(format!("Cannot insert {} ({}) into format string", self.inner_op, val))) + } //val => Err(RuntimeMsg(format!("Cannot insert {} ({}) into format string", self.inner_op, val))) } //Ok(MpsType::empty()) } diff --git a/mps-interpreter/tests/single_line.rs b/mps-interpreter/tests/single_line.rs index 2089eb5..6395243 100644 --- a/mps-interpreter/tests/single_line.rs +++ b/mps-interpreter/tests/single_line.rs @@ -722,3 +722,31 @@ fn execute_iteritemop_line() -> Result<(), Box> { true, ) } + +#[test] +fn execute_uniquefieldfilter_line() -> Result<(), Box> { + execute_single_line( + "repeat(files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`), 3).(unique title?)", + false, + true, + )?; + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(unique album!)", + false, + true, + )?; + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(unique album)", + false, + true, + ) +} + +#[test] +fn execute_uniquefilter_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(unique)", + false, + true, + ) +}