Add range filtering

This commit is contained in:
NGnius (Graham) 2022-01-12 16:23:56 -05:00
parent ca17313b7b
commit d23495a5ef
5 changed files with 216 additions and 2 deletions

View file

@ -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())
.add(crate::lang::vocabulary::filters::field_filter_maybe()) .add(crate::lang::vocabulary::filters::field_filter_maybe())
.add(crate::lang::vocabulary::filters::index_filter()) .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::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)

View file

@ -311,15 +311,16 @@ impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMps
} else { } else {
// <some other op>.(predicate) // <some other op>.(predicate)
//let mut new_tokens = tokens.range(0..start_of_op).map(|x| x.to_owned()).collect(); //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)?; let inner_op = dict.try_build_statement(tokens)?;
tokens.extend(end_tokens); tokens.extend(end_tokens);
op = VariableOrOp::Op(inner_op.into()); op = VariableOrOp::Op(inner_op.into());
} }
assert_token_raw(MpsToken::Dot, tokens)?; assert_token_raw(MpsToken::Dot, tokens)?;
assert_token_raw(MpsToken::OpenBracket, 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)?; 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 { Ok(Box::new(MpsFilterStatement {
predicate: filter, predicate: filter,
iterable: op, iterable: op,

View file

@ -2,6 +2,7 @@ mod empty_filter;
mod field_filter; mod field_filter;
mod field_filter_maybe; mod field_filter_maybe;
mod index_filter; mod index_filter;
mod range_filter;
pub(crate) mod utility; pub(crate) mod utility;
pub use empty_filter::{ pub use empty_filter::{
@ -12,3 +13,4 @@ pub use field_filter::{
}; };
pub use field_filter_maybe::{field_filter_maybe, FieldFilterMaybeFactory, FieldFilterMaybeStatementFactory}; pub use field_filter_maybe::{field_filter_maybe, FieldFilterMaybeFactory, FieldFilterMaybeStatementFactory};
pub use index_filter::{index_filter, IndexFilter, IndexFilterFactory, IndexFilterStatementFactory}; pub use index_filter::{index_filter, IndexFilter, IndexFilterFactory, IndexFilterStatementFactory};
pub use range_filter::{range_filter, RangeFilter, RangeFilterFactory, RangeFilterStatementFactory};

View file

@ -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<Lookup>,
end: Option<Lookup>,
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<bool, RuntimeError> {
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<u64, RuntimeError> {
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<RangeFilter> 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<MpsToken>,
_dict: &MpsLanguageDictionary,
) -> Result<RangeFilter, SyntaxError> {
// 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<RangeFilter, RangeFilterFactory>;
#[inline(always)]
pub fn range_filter() -> RangeFilterStatementFactory {
RangeFilterStatementFactory::new(RangeFilterFactory)
}

View file

@ -216,3 +216,27 @@ fn execute_indexfilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
true, true,
) )
} }
#[test]
fn execute_rangefilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
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,
)
}