Add sort by field name
This commit is contained in:
parent
9ac5dd4570
commit
8dc571521f
8 changed files with 361 additions and 59 deletions
|
@ -162,8 +162,10 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
|
||||||
.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::filters::range_filter())
|
||||||
|
.add(crate::lang::vocabulary::filters::field_like_filter())
|
||||||
// sorters
|
// sorters
|
||||||
.add(crate::lang::vocabulary::sorters::empty_sort())
|
.add(crate::lang::vocabulary::sorters::empty_sort())
|
||||||
|
.add(crate::lang::vocabulary::sorters::field_sort())
|
||||||
// functions and misc
|
// functions and misc
|
||||||
.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())
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
//! Basic types for MPS
|
//! Basic types for MPS
|
||||||
|
|
||||||
use std::fmt::{Debug, Display, Error, Formatter};
|
use std::fmt::{Debug, Display, Error, Formatter};
|
||||||
|
use std::cmp::{Ordering, Ord};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum MpsTypePrimitive {
|
pub enum MpsTypePrimitive {
|
||||||
String(String),
|
String(String),
|
||||||
Int(i64),
|
Int(i64),
|
||||||
|
@ -14,60 +15,9 @@ pub enum MpsTypePrimitive {
|
||||||
impl MpsTypePrimitive {
|
impl MpsTypePrimitive {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn compare(&self, other: &Self) -> Result<i8, String> {
|
pub fn compare(&self, other: &Self) -> Result<i8, String> {
|
||||||
let result = match self {
|
let result = self.partial_cmp(other);
|
||||||
Self::String(s1) => match other {
|
|
||||||
Self::String(s2) => Some(map_ordering(s1.cmp(s2))),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Self::Int(i1) => match other {
|
|
||||||
Self::Int(i2) => Some(map_ordering(i1.cmp(i2))),
|
|
||||||
Self::UInt(i2) => Some(map_ordering((*i1 as i128).cmp(&(*i2 as i128)))),
|
|
||||||
Self::Float(i2) => Some(map_ordering(
|
|
||||||
(*i1 as f64)
|
|
||||||
.partial_cmp(&(*i2 as f64))
|
|
||||||
.unwrap_or(std::cmp::Ordering::Less),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Self::UInt(u1) => match other {
|
|
||||||
Self::UInt(u2) => Some(map_ordering(u1.cmp(u2))),
|
|
||||||
Self::Int(u2) => Some(map_ordering((*u1 as i128).cmp(&(*u2 as i128)))),
|
|
||||||
Self::Float(u2) => Some(map_ordering(
|
|
||||||
(*u1 as f64)
|
|
||||||
.partial_cmp(&(*u2 as f64))
|
|
||||||
.unwrap_or(std::cmp::Ordering::Less),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Self::Float(f1) => match other {
|
|
||||||
Self::Float(f2) => Some(map_ordering(
|
|
||||||
f1.partial_cmp(f2).unwrap_or(std::cmp::Ordering::Less),
|
|
||||||
)),
|
|
||||||
Self::Int(f2) => Some(map_ordering(
|
|
||||||
f1.partial_cmp(&(*f2 as f64))
|
|
||||||
.unwrap_or(std::cmp::Ordering::Less),
|
|
||||||
)),
|
|
||||||
Self::UInt(f2) => Some(map_ordering(
|
|
||||||
f1.partial_cmp(&(*f2 as f64))
|
|
||||||
.unwrap_or(std::cmp::Ordering::Less),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Self::Bool(b1) => match other {
|
|
||||||
Self::Bool(b2) => {
|
|
||||||
if *b2 == *b1 {
|
|
||||||
Some(0)
|
|
||||||
} else if *b1 {
|
|
||||||
Some(1)
|
|
||||||
} else {
|
|
||||||
Some(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
match result {
|
match result {
|
||||||
Some(x) => Ok(x),
|
Some(x) => Ok(map_ordering(x)),
|
||||||
None => Err(format!(
|
None => Err(format!(
|
||||||
"Cannot compare {} to {}: incompatible types",
|
"Cannot compare {} to {}: incompatible types",
|
||||||
self, other
|
self, other
|
||||||
|
@ -82,6 +32,16 @@ impl MpsTypePrimitive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::String(s) => s.clone(),
|
||||||
|
Self::UInt(x) => format!("{}", x),
|
||||||
|
Self::Int(x) => format!("{}", x),
|
||||||
|
Self::Float(x) => format!("{}", x),
|
||||||
|
Self::Bool(x) => format!("{}", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_u64(self) -> Option<u64> {
|
pub fn to_u64(self) -> Option<u64> {
|
||||||
match self {
|
match self {
|
||||||
Self::UInt(x) => Some(x),
|
Self::UInt(x) => Some(x),
|
||||||
|
@ -120,11 +80,68 @@ impl MpsTypePrimitive {
|
||||||
impl Display for MpsTypePrimitive {
|
impl Display for MpsTypePrimitive {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
match self {
|
match self {
|
||||||
Self::String(s) => write!(f, "(String) {}", s),
|
Self::String(s) => write!(f, "String[`{}`]", s),
|
||||||
Self::Int(i) => write!(f, "(Int) {}", *i),
|
Self::Int(i) => write!(f, "Int[{}]", *i),
|
||||||
Self::UInt(u) => write!(f, "(UInt) {}", *u),
|
Self::UInt(u) => write!(f, "UInt[{}]", *u),
|
||||||
Self::Float(f_) => write!(f, "(Float) {}", *f_),
|
Self::Float(f_) => write!(f, "Float[{}]", *f_),
|
||||||
Self::Bool(b) => write!(f, "(Bool) {}", *b),
|
Self::Bool(b) => write!(f, "Bool[{}]", *b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for MpsTypePrimitive {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
match self {
|
||||||
|
Self::String(s1) => match other {
|
||||||
|
Self::String(s2) => Some(s1.cmp(s2)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Self::Int(i1) => match other {
|
||||||
|
Self::Int(i2) => Some(i1.cmp(i2)),
|
||||||
|
Self::UInt(i2) => Some((*i1 as i128).cmp(&(*i2 as i128))),
|
||||||
|
Self::Float(i2) => Some(
|
||||||
|
(*i1 as f64)
|
||||||
|
.partial_cmp(&(*i2 as f64))
|
||||||
|
.unwrap_or(std::cmp::Ordering::Less),
|
||||||
|
),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Self::UInt(u1) => match other {
|
||||||
|
Self::UInt(u2) => Some(u1.cmp(u2)),
|
||||||
|
Self::Int(u2) => Some((*u1 as i128).cmp(&(*u2 as i128))),
|
||||||
|
Self::Float(u2) => Some(
|
||||||
|
(*u1 as f64)
|
||||||
|
.partial_cmp(&(*u2 as f64))
|
||||||
|
.unwrap_or(std::cmp::Ordering::Less),
|
||||||
|
),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Self::Float(f1) => match other {
|
||||||
|
Self::Float(f2) => Some(
|
||||||
|
f1.partial_cmp(f2).unwrap_or(std::cmp::Ordering::Less),
|
||||||
|
),
|
||||||
|
Self::Int(f2) => Some(
|
||||||
|
f1.partial_cmp(&(*f2 as f64))
|
||||||
|
.unwrap_or(std::cmp::Ordering::Less),
|
||||||
|
),
|
||||||
|
Self::UInt(f2) => Some(
|
||||||
|
f1.partial_cmp(&(*f2 as f64))
|
||||||
|
.unwrap_or(std::cmp::Ordering::Less),
|
||||||
|
),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Self::Bool(b1) => match other {
|
||||||
|
Self::Bool(b2) => {
|
||||||
|
if *b2 == *b1 {
|
||||||
|
Some(std::cmp::Ordering::Equal)
|
||||||
|
} else if *b1 {
|
||||||
|
Some(std::cmp::Ordering::Greater)
|
||||||
|
} else {
|
||||||
|
Some(std::cmp::Ordering::Less)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
158
mps-interpreter/src/lang/vocabulary/filters/field_like_filter.rs
Normal file
158
mps-interpreter/src/lang/vocabulary/filters/field_like_filter.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt::{Debug, Display, Error, Formatter};
|
||||||
|
|
||||||
|
use super::field_filter::{VariableOrValue, FieldFilterErrorHandling};
|
||||||
|
use crate::lang::utility::{assert_token, check_name, assert_name, assert_token_raw};
|
||||||
|
use crate::lang::MpsLanguageDictionary;
|
||||||
|
use crate::lang::MpsTypePrimitive;
|
||||||
|
use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory};
|
||||||
|
use crate::lang::{RuntimeError, SyntaxError};
|
||||||
|
use crate::processing::general::MpsType;
|
||||||
|
use crate::processing::OpGetter;
|
||||||
|
use crate::tokens::MpsToken;
|
||||||
|
use crate::MpsContext;
|
||||||
|
use crate::MpsItem;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FieldLikeFilter {
|
||||||
|
field_name: String,
|
||||||
|
field_errors: FieldFilterErrorHandling,
|
||||||
|
val: VariableOrValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FieldLikeFilter {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
match &self.val {
|
||||||
|
VariableOrValue::Variable(name) => write!(f, "{} like {}", self.field_name, name),
|
||||||
|
VariableOrValue::Value(t) => write!(f, "{} like {}", self.field_name, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MpsFilterPredicate for FieldLikeFilter {
|
||||||
|
fn matches(
|
||||||
|
&mut self,
|
||||||
|
music_item_lut: &MpsItem,
|
||||||
|
ctx: &mut MpsContext,
|
||||||
|
op: &mut OpGetter,
|
||||||
|
) -> Result<bool, RuntimeError> {
|
||||||
|
let variable = match &self.val {
|
||||||
|
VariableOrValue::Variable(name) => match ctx.variables.get(&name, op)? {
|
||||||
|
MpsType::Primitive(MpsTypePrimitive::String(s)) => Ok(s),
|
||||||
|
_ => Err(RuntimeError {
|
||||||
|
line: 0,
|
||||||
|
op: op(),
|
||||||
|
msg: format!("Variable {} is not comparable", name),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
VariableOrValue::Value(MpsTypePrimitive::String(s)) => Ok(s),
|
||||||
|
// non-string values will be stopped at parse-time, so this should never occur
|
||||||
|
_ => Err(RuntimeError {
|
||||||
|
line: 0,
|
||||||
|
op: op(),
|
||||||
|
msg: format!("Value is not type String"),
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
if let Some(field) = music_item_lut.field(&self.field_name) {
|
||||||
|
let field_str = field.as_str().to_lowercase();
|
||||||
|
Ok(field_str.contains(&variable.to_lowercase()))
|
||||||
|
} else {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_complete(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FieldLikeFilterFactory;
|
||||||
|
|
||||||
|
impl MpsFilterFactory<FieldLikeFilter> for FieldLikeFilterFactory {
|
||||||
|
fn is_filter(&self, tokens: &VecDeque<&MpsToken>) -> bool {
|
||||||
|
let tokens_len = tokens.len();
|
||||||
|
(tokens_len == 3 // field like variable
|
||||||
|
&& tokens[0].is_name()
|
||||||
|
&& check_name("like", &tokens[1])
|
||||||
|
&& (tokens[2].is_name() || tokens[2].is_literal()))
|
||||||
|
|| (tokens_len == 4 // field? like variable OR field! like variable
|
||||||
|
&& tokens[0].is_name()
|
||||||
|
&& (tokens[1].is_interrogation() || tokens[1].is_exclamation())
|
||||||
|
&& check_name("like", &tokens[2])
|
||||||
|
&& (tokens[3].is_name() || tokens[3].is_literal()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_filter(
|
||||||
|
&self,
|
||||||
|
tokens: &mut VecDeque<MpsToken>,
|
||||||
|
_dict: &MpsLanguageDictionary,
|
||||||
|
) -> Result<FieldLikeFilter, SyntaxError> {
|
||||||
|
let field = assert_token(
|
||||||
|
|t| match t {
|
||||||
|
MpsToken::Name(n) => Some(n),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
MpsToken::Name("field_name".into()),
|
||||||
|
tokens,
|
||||||
|
)?;
|
||||||
|
let error_handling = if tokens[0].is_interrogation() {
|
||||||
|
assert_token_raw(MpsToken::Interrogation, tokens)?;
|
||||||
|
FieldFilterErrorHandling::Ignore
|
||||||
|
} else if tokens[0].is_exclamation() {
|
||||||
|
assert_token_raw(MpsToken::Exclamation, tokens)?;
|
||||||
|
FieldFilterErrorHandling::Include
|
||||||
|
} else {
|
||||||
|
FieldFilterErrorHandling::Error
|
||||||
|
};
|
||||||
|
assert_name("like", tokens)?;
|
||||||
|
if tokens[0].is_literal() {
|
||||||
|
let literal = assert_token(
|
||||||
|
|t| match t {
|
||||||
|
MpsToken::Literal(n) => Some(n),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
MpsToken::Literal("like_string".into()),
|
||||||
|
tokens,
|
||||||
|
)?;
|
||||||
|
let value = VariableOrValue::Value(MpsTypePrimitive::String(literal));
|
||||||
|
Ok(FieldLikeFilter {
|
||||||
|
field_name: field,
|
||||||
|
field_errors: error_handling,
|
||||||
|
val: value,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let variable = VariableOrValue::Variable(assert_token(
|
||||||
|
|t| match t {
|
||||||
|
MpsToken::Name(n) => Some(n),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
MpsToken::Name("variable_name".into()),
|
||||||
|
tokens,
|
||||||
|
)?);
|
||||||
|
Ok(FieldLikeFilter {
|
||||||
|
field_name: field,
|
||||||
|
field_errors: FieldFilterErrorHandling::Error,
|
||||||
|
val: variable,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type FieldLikeFilterStatementFactory = MpsFilterStatementFactory<FieldLikeFilter, FieldLikeFilterFactory>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn field_like_filter() -> FieldLikeFilterStatementFactory {
|
||||||
|
FieldLikeFilterStatementFactory::new(FieldLikeFilterFactory)
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
mod empty_filter;
|
mod empty_filter;
|
||||||
mod field_filter;
|
mod field_filter;
|
||||||
mod field_filter_maybe;
|
mod field_filter_maybe;
|
||||||
|
mod field_like_filter;
|
||||||
mod index_filter;
|
mod index_filter;
|
||||||
mod range_filter;
|
mod range_filter;
|
||||||
pub(crate) mod utility;
|
pub(crate) mod utility;
|
||||||
|
@ -12,5 +13,6 @@ pub use field_filter::{
|
||||||
field_filter, FieldFilter, FieldFilterFactory, FieldFilterStatementFactory, FieldFilterErrorHandling,
|
field_filter, FieldFilter, FieldFilterFactory, FieldFilterStatementFactory, FieldFilterErrorHandling,
|
||||||
};
|
};
|
||||||
pub use field_filter_maybe::{field_filter_maybe, FieldFilterMaybeFactory, FieldFilterMaybeStatementFactory};
|
pub use field_filter_maybe::{field_filter_maybe, FieldFilterMaybeFactory, FieldFilterMaybeStatementFactory};
|
||||||
|
pub use field_like_filter::{field_like_filter, FieldLikeFilterFactory, FieldLikeFilterStatementFactory};
|
||||||
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};
|
pub use range_filter::{range_filter, RangeFilter, RangeFilterFactory, RangeFilterStatementFactory};
|
||||||
|
|
87
mps-interpreter/src/lang/vocabulary/sorters/field_sorter.rs
Normal file
87
mps-interpreter/src/lang/vocabulary/sorters/field_sorter.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt::{Debug, Display, Error, Formatter};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use crate::lang::{MpsSorter, MpsSorterFactory, MpsSortStatementFactory};
|
||||||
|
use crate::lang::{MpsLanguageDictionary, MpsIteratorItem, MpsOp};
|
||||||
|
use crate::lang::{RuntimeError, SyntaxError};
|
||||||
|
use crate::lang::utility::assert_token;
|
||||||
|
use crate::tokens::MpsToken;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FieldSorter {
|
||||||
|
field_name: String,
|
||||||
|
up_to: usize,
|
||||||
|
default_order: Ordering,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MpsSorter for FieldSorter {
|
||||||
|
fn sort(&mut self, iterator: &mut dyn MpsOp, item_buf: &mut VecDeque<MpsIteratorItem>) -> Result<(), RuntimeError> {
|
||||||
|
let buf_len_old = item_buf.len(); // save buffer length before modifying buffer
|
||||||
|
if item_buf.len() < self.up_to {
|
||||||
|
for item in iterator {
|
||||||
|
item_buf.push_back(item);
|
||||||
|
if item_buf.len() >= self.up_to {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf_len_old != item_buf.len() {
|
||||||
|
// when buf_len_old == item_buf.len(), iterator was already complete
|
||||||
|
// no need to sort in that case, since buffer was sorted in last call to sort or buffer never had any items to sort
|
||||||
|
item_buf.make_contiguous().sort_by(
|
||||||
|
|a, b| {
|
||||||
|
if let Ok(a) = a {
|
||||||
|
if let Some(a_field) = a.field(&self.field_name) {
|
||||||
|
if let Ok(b) = b {
|
||||||
|
if let Some(b_field) = b.field(&self.field_name) {
|
||||||
|
return a_field.partial_cmp(b_field).unwrap_or(self.default_order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.default_order
|
||||||
|
}
|
||||||
|
);
|
||||||
|
println!("Field-sorted item_buf: {:?}", item_buf);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FieldSorter {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
write!(f, "{}", self.field_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FieldSorterFactory;
|
||||||
|
|
||||||
|
impl MpsSorterFactory<FieldSorter> for FieldSorterFactory {
|
||||||
|
fn is_sorter(&self, tokens: &VecDeque<&MpsToken>) -> bool {
|
||||||
|
tokens.len() == 1 && tokens[0].is_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_sorter(
|
||||||
|
&self,
|
||||||
|
tokens: &mut VecDeque<MpsToken>,
|
||||||
|
_dict: &MpsLanguageDictionary,
|
||||||
|
) -> Result<FieldSorter, SyntaxError> {
|
||||||
|
let name = assert_token(|t| match t {
|
||||||
|
MpsToken::Name(s) => Some(s),
|
||||||
|
_ => None
|
||||||
|
}, MpsToken::Name("field_name".into()), tokens)?;
|
||||||
|
Ok(FieldSorter {
|
||||||
|
field_name: name,
|
||||||
|
up_to: usize::MAX,
|
||||||
|
default_order: Ordering::Equal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type FieldSorterStatementFactory = MpsSortStatementFactory<FieldSorter, FieldSorterFactory>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn field_sort() -> FieldSorterStatementFactory {
|
||||||
|
FieldSorterStatementFactory::new(FieldSorterFactory)
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
mod empty_sorter;
|
mod empty_sorter;
|
||||||
|
mod field_sorter;
|
||||||
|
|
||||||
pub use empty_sorter::{empty_sort, EmptySorter, EmptySorterFactory, EmptySorterStatementFactory};
|
pub use empty_sorter::{empty_sort, EmptySorter, EmptySorterFactory, EmptySorterStatementFactory};
|
||||||
|
pub use field_sorter::{field_sort, FieldSorter, FieldSorterFactory, FieldSorterStatementFactory};
|
||||||
|
|
|
@ -289,6 +289,7 @@ impl Iterator for FileIter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.is_complete = true;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,3 +300,36 @@ fn execute_emptysort_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_likefilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||||
|
execute_single_line(
|
||||||
|
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(not_a_field? like `24K Magic`)",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
execute_single_line(
|
||||||
|
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(not_a_field! like `24K Magic`)",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
execute_single_line(
|
||||||
|
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(album like `24K Magic`)",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_fieldsort_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||||
|
execute_single_line(
|
||||||
|
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)~(title)",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
execute_single_line(
|
||||||
|
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).sort(not_a_field)",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue