diff --git a/mps-interpreter/src/interpretor.rs b/mps-interpreter/src/interpretor.rs index 7e59c60..68505dd 100644 --- a/mps-interpreter/src/interpretor.rs +++ b/mps-interpreter/src/interpretor.rs @@ -160,6 +160,7 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) { .add(crate::lang::vocabulary::filters::field_filter()) .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::sql_function_factory()) .add(crate::lang::vocabulary::simple_sql_function_factory()) .add(crate::lang::vocabulary::CommentStatementFactory) diff --git a/mps-interpreter/src/lang/filter.rs b/mps-interpreter/src/lang/filter.rs index cdbf419..c6a0044 100644 --- a/mps-interpreter/src/lang/filter.rs +++ b/mps-interpreter/src/lang/filter.rs @@ -311,15 +311,16 @@ impl + 'static> BoxedMps } else { // .(predicate) //let mut new_tokens = tokens.range(0..start_of_op).map(|x| x.to_owned()).collect(); - let end_tokens = tokens.split_off(start_of_op); + let end_tokens = tokens.split_off(start_of_op); // don't parse filter in inner statement let inner_op = dict.try_build_statement(tokens)?; tokens.extend(end_tokens); op = VariableOrOp::Op(inner_op.into()); } assert_token_raw(MpsToken::Dot, tokens)?; assert_token_raw(MpsToken::OpenBracket, tokens)?; + let mut end_tokens = tokens.split_off(tokens.len()-1); // don't parse closing bracket in filter let filter = self.filter_factory.build_filter(tokens, dict)?; - assert_token_raw(MpsToken::CloseBracket, tokens)?; + assert_token_raw(MpsToken::CloseBracket, &mut end_tokens)?; Ok(Box::new(MpsFilterStatement { predicate: filter, iterable: op, diff --git a/mps-interpreter/src/lang/vocabulary/filters/mod.rs b/mps-interpreter/src/lang/vocabulary/filters/mod.rs index 7a343fc..101fbc5 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/mod.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/mod.rs @@ -2,6 +2,7 @@ mod empty_filter; mod field_filter; mod field_filter_maybe; mod index_filter; +mod range_filter; pub(crate) mod utility; pub use empty_filter::{ @@ -12,3 +13,4 @@ pub use field_filter::{ }; pub use field_filter_maybe::{field_filter_maybe, FieldFilterMaybeFactory, FieldFilterMaybeStatementFactory}; pub use index_filter::{index_filter, IndexFilter, IndexFilterFactory, IndexFilterStatementFactory}; +pub use range_filter::{range_filter, RangeFilter, RangeFilterFactory, RangeFilterStatementFactory}; diff --git a/mps-interpreter/src/lang/vocabulary/filters/range_filter.rs b/mps-interpreter/src/lang/vocabulary/filters/range_filter.rs new file mode 100644 index 0000000..ea10c4e --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/filters/range_filter.rs @@ -0,0 +1,186 @@ +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::{MpsLanguageDictionary, MpsTypePrimitive}; +use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory}; +use crate::lang::{RuntimeError, SyntaxError}; +use crate::lang::Lookup; +use crate::lang::utility::assert_token_raw; +use crate::processing::{OpGetter, general::MpsType}; +use crate::tokens::MpsToken; +use crate::MpsContext; +use crate::MpsMusicItem; + +#[derive(Debug, Clone)] +pub struct RangeFilter { + start: Option, + end: Option, + inclusive_end: bool, + // state + current: u64, + complete: bool, +} + +impl Display for RangeFilter { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{}{}{}", + if let Some(start) = &self.start {format!("{}", start)} else {"".into()}, + if self.inclusive_end {"="} else {""}, + if let Some(end) = &self.end {format!("{}", end)} else {"".into()},) + } +} + +impl MpsFilterPredicate for RangeFilter { + fn matches( + &mut self, + _item: &MpsMusicItem, + ctx: &mut MpsContext, + op: &mut OpGetter, + ) -> Result { + let start_index = if let Some(start) = &self.start { + lookup_to_index(start.get(ctx, op)?, op)? + } else {0}; + let current = self.current; + self.current += 1; + if current >= start_index { + if let Some(end) = &self.end { + let end_index = lookup_to_index(end.get(ctx, op)?, op)?; + if self.inclusive_end && current <= end_index { + if current == end_index { + self.complete = true; + } + Ok(true) + } else if !self.inclusive_end && current < end_index { + if self.current == end_index { + self.complete = true; + } + Ok(true) + } else { + Ok(false) + } + } else { + Ok(true) + } + } else { + Ok(false) + } + } + + fn is_complete(&self) -> bool { + self.complete + } + + fn reset(&mut self) -> Result<(), RuntimeError> { + self.current = 0; + self.complete = false; + Ok(()) + } +} + +fn lookup_to_index(item: &MpsType, op: &mut OpGetter) -> Result { + match item { + MpsType::Primitive(val) => match val { + MpsTypePrimitive::Int(i) => Ok(*i as u64), + MpsTypePrimitive::UInt(u) => Ok(*u), + MpsTypePrimitive::Float(f) => Ok(*f as u64), + val => Err(RuntimeError { + line: 0, + op: op(), + msg: format!("Cannot use {} as index", val), + }) + }, + val => Err(RuntimeError { + line: 0, + op: op(), + msg: format!("Cannot use {} as index", val), + }) + } +} + +pub struct RangeFilterFactory; + +impl MpsFilterFactory for RangeFilterFactory { + fn is_filter(&self, tokens: &VecDeque<&MpsToken>) -> bool { + ( + // .. + tokens.len() == 2 + && tokens[0].is_dot() + && tokens[1].is_dot() + ) || ( + tokens.len() == 3 + && (( + // ..number + tokens[0].is_dot() + && tokens[1].is_dot() + && Lookup::check_is(&tokens[2]) + ) || ( + // number.. + Lookup::check_is(&tokens[0]) + && tokens[1].is_dot() + && tokens[2].is_dot() + )) + ) || ( + tokens.len() == 4 + && (( // number..number + Lookup::check_is(&tokens[0]) + && tokens[1].is_dot() + && tokens[2].is_dot() + && Lookup::check_is(&tokens[3]) + ) || ( // ..=number + tokens[0].is_dot() + && tokens[1].is_dot() + && tokens[2].is_equals() + && Lookup::check_is(&tokens[3]) + )) + ) || ( + // number..=number + tokens.len() == 5 + && Lookup::check_is(&tokens[0]) + && tokens[1].is_dot() + && tokens[2].is_dot() + && tokens[3].is_equals() + && Lookup::check_is(&tokens[4]) + ) + } + + fn build_filter( + &self, + tokens: &mut VecDeque, + _dict: &MpsLanguageDictionary, + ) -> Result { + // start index + let start = if Lookup::check_is(&tokens[0]) { + Some(Lookup::parse(tokens)?) + } else {None}; + // .. + assert_token_raw(MpsToken::Dot, tokens)?; + assert_token_raw(MpsToken::Dot, tokens)?; + // tokens VecDeque might now be empty (guaranteed to have tokens up to this point) + // = (optional) + let equals_at_end = if !tokens.is_empty() && tokens[0].is_equals() { + assert_token_raw(MpsToken::Equals, tokens)?; + true + } else { + false + }; + // end index + let end = if !tokens.is_empty() { + Some(Lookup::parse(tokens)?) + } else {None}; + + Ok(RangeFilter { + start: start, + end: end, + inclusive_end: equals_at_end, + current: 0, + complete: false, + }) + } +} + +pub type RangeFilterStatementFactory = MpsFilterStatementFactory; + +#[inline(always)] +pub fn range_filter() -> RangeFilterStatementFactory { + RangeFilterStatementFactory::new(RangeFilterFactory) +} diff --git a/mps-interpreter/tests/single_line.rs b/mps-interpreter/tests/single_line.rs index f974b11..d522578 100644 --- a/mps-interpreter/tests/single_line.rs +++ b/mps-interpreter/tests/single_line.rs @@ -216,3 +216,27 @@ fn execute_indexfilter_line() -> Result<(), Box> { true, ) } + +#[test] +fn execute_rangefilter_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(..)", + false, + true, + )?; + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(0..=4)", + false, + true, + )?; + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(..=4)", + false, + true, + )?; + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(0..5)", + false, + true, + ) +}