Implement reset functionality on existing statements
This commit is contained in:
parent
90a310bbbe
commit
a8eb390ab8
15 changed files with 190 additions and 38 deletions
|
@ -20,6 +20,10 @@ pub trait MpsFilterPredicate: Clone + Debug + Display {
|
|||
ctx: &mut MpsContext,
|
||||
op: &mut OpGetter,
|
||||
) -> Result<bool, RuntimeError>;
|
||||
|
||||
fn is_complete(&self) -> bool;
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError>;
|
||||
}
|
||||
|
||||
pub trait MpsFilterFactory<P: MpsFilterPredicate + 'static> {
|
||||
|
@ -88,7 +92,7 @@ impl<P: MpsFilterPredicate + 'static> MpsOp for MpsFilterStatement<P> {
|
|||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
VariableOrOp::Op(PseudoOp::Real(op)) => op.is_resetable(),
|
||||
VariableOrOp::Op(PseudoOp::Fake(_)) => false,
|
||||
}
|
||||
|
@ -96,10 +100,17 @@ impl<P: MpsFilterPredicate + 'static> MpsOp for MpsFilterStatement<P> {
|
|||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
let fake = PseudoOp::Fake(format!("{}", self));
|
||||
self.predicate.reset()?;
|
||||
match &mut self.iterable {
|
||||
VariableOrOp::Variable(s) => {
|
||||
let fake_getter = &mut move || fake.clone();
|
||||
if let MpsType::Op(var) = self.context.as_mut().unwrap().variables.get_mut(s, fake_getter)? {
|
||||
if let MpsType::Op(var) = self
|
||||
.context
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.variables
|
||||
.get_mut(s, fake_getter)?
|
||||
{
|
||||
var.reset()
|
||||
} else {
|
||||
Err(RuntimeError {
|
||||
|
@ -108,13 +119,13 @@ impl<P: MpsFilterPredicate + 'static> MpsOp for MpsFilterStatement<P> {
|
|||
msg: "Cannot reset non-iterable filter variable".to_string(),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
VariableOrOp::Op(PseudoOp::Real(op)) => op.reset(),
|
||||
VariableOrOp::Op(PseudoOp::Fake(_)) => Err(RuntimeError {
|
||||
line: 0,
|
||||
op: fake,
|
||||
msg: "Cannot reset fake filter".to_string(),
|
||||
}),
|
||||
line: 0,
|
||||
op: fake,
|
||||
msg: "Cannot reset PseudoOp::Fake filter".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +134,9 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
|
|||
type Item = Result<MpsMusicItem, RuntimeError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.predicate.is_complete() {
|
||||
return None;
|
||||
}
|
||||
let self_clone = self.clone();
|
||||
let self_clone2 = self_clone.clone();
|
||||
let mut op_getter = move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into();
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::fmt::{Debug, Display};
|
|||
use std::iter::Iterator;
|
||||
|
||||
use super::MpsLanguageDictionary;
|
||||
use super::{RuntimeError, SyntaxError};
|
||||
use super::PseudoOp;
|
||||
use super::{RuntimeError, SyntaxError};
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
|
@ -63,12 +63,17 @@ pub trait MpsOp: Iterator<Item = Result<MpsMusicItem, RuntimeError>> + Debug + D
|
|||
|
||||
fn escape(&mut self) -> MpsContext;
|
||||
|
||||
fn is_resetable(&self) -> bool {false}
|
||||
fn is_resetable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
#[cfg(debug_assertions)]
|
||||
if self.is_resetable() {
|
||||
panic!("MpsOp reported that it can be reset but did not implement reset (op: {})", self)
|
||||
panic!(
|
||||
"MpsOp reported that it can be reset but did not implement reset (op: {})",
|
||||
self
|
||||
)
|
||||
}
|
||||
Err(RuntimeError {
|
||||
line: 0,
|
||||
|
|
|
@ -51,6 +51,10 @@ impl PseudoOp {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_printable<D: Display + Debug>(item: &D) -> Self {
|
||||
Self::Fake(format!("{}", item))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for PseudoOp {
|
||||
|
|
|
@ -106,6 +106,16 @@ impl MpsOp for FilesStatement {
|
|||
fn escape(&mut self) -> MpsContext {
|
||||
self.context.take().unwrap()
|
||||
}
|
||||
|
||||
fn is_resetable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
self.has_tried = false;
|
||||
self.file_iter = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FilesFunctionFactory;
|
||||
|
|
|
@ -27,6 +27,14 @@ impl MpsFilterPredicate for EmptyFilter {
|
|||
) -> Result<bool, RuntimeError> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmptyFilterFactory;
|
||||
|
|
|
@ -77,6 +77,14 @@ impl MpsFilterPredicate for FieldFilter {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldFilterFactory;
|
||||
|
@ -88,9 +96,9 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
|
|||
&& tokens[0].is_name()
|
||||
&& (tokens[1].is_open_angle_bracket() || tokens[1].is_close_angle_bracket())
|
||||
&& (tokens[2].is_name() || check_is_type(&tokens[2])))
|
||||
|| (tokens_len == 4 // field >= variable OR field <= variable
|
||||
|| (tokens_len == 4 // field >= variable OR field <= variable OR field != variable
|
||||
&& tokens[0].is_name()
|
||||
&& (tokens[1].is_open_angle_bracket() || tokens[1].is_close_angle_bracket() || tokens[1].is_equals())
|
||||
&& (tokens[1].is_open_angle_bracket() || tokens[1].is_close_angle_bracket() || tokens[1].is_equals() || tokens[1].is_exclamation())
|
||||
&& tokens[2].is_equals()
|
||||
&& (tokens[3].is_name() || check_is_type(&tokens[3])))
|
||||
}
|
||||
|
|
|
@ -74,6 +74,10 @@ pub fn assert_comparison_operator(tokens: &mut VecDeque<MpsToken>) -> Result<[i8
|
|||
Ok([1, 1])
|
||||
}
|
||||
}
|
||||
MpsToken::Exclamation => {
|
||||
assert_token_raw(MpsToken::Equals, tokens)?;
|
||||
Ok([-1, 1])
|
||||
}
|
||||
_ => Err(SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Equals, // TODO this can be < > or =
|
||||
|
|
|
@ -20,6 +20,7 @@ pub struct RepeatStatement {
|
|||
cache_position: usize,
|
||||
repetitions: usize,
|
||||
loop_forever: bool,
|
||||
original_repetitions: usize,
|
||||
}
|
||||
|
||||
impl Display for RepeatStatement {
|
||||
|
@ -38,6 +39,7 @@ impl std::clone::Clone for RepeatStatement {
|
|||
cache_position: self.cache_position,
|
||||
repetitions: self.repetitions,
|
||||
loop_forever: self.loop_forever,
|
||||
original_repetitions: self.original_repetitions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,12 +52,12 @@ impl Iterator for RepeatStatement {
|
|||
Err(e) => return Some(Err(e)),
|
||||
Ok(real) => real,
|
||||
};
|
||||
// give context to inner (should only occur on first run)
|
||||
if self.context.is_some() {
|
||||
let ctx = self.context.take().unwrap();
|
||||
real_op.enter(ctx);
|
||||
}
|
||||
if real_op.is_resetable() {
|
||||
// give context to inner (should only occur on first run)
|
||||
if self.context.is_some() {
|
||||
let ctx = self.context.take().unwrap();
|
||||
real_op.enter(ctx);
|
||||
}
|
||||
while self.loop_forever || !self.inner_done {
|
||||
while let Some(item) = real_op.next() {
|
||||
return Some(item);
|
||||
|
@ -69,14 +71,14 @@ impl Iterator for RepeatStatement {
|
|||
self.repetitions -= 1;
|
||||
match real_op.reset() {
|
||||
Err(e) => return Some(Err(e)),
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// always reset in infinite loop mode
|
||||
match real_op.reset() {
|
||||
Err(e) => return Some(Err(e)),
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +153,42 @@ impl MpsOp for RepeatStatement {
|
|||
self.inner_statement.try_real().unwrap().escape()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_resetable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
let real_op = self.inner_statement.try_real()?;
|
||||
if self.context.is_some() {
|
||||
let ctx = self.context.take().unwrap();
|
||||
real_op.enter(ctx);
|
||||
}
|
||||
if real_op.is_resetable() {
|
||||
real_op.reset()?;
|
||||
if self.original_repetitions == 0 {
|
||||
self.repetitions = 0;
|
||||
self.inner_done = true;
|
||||
} else {
|
||||
self.repetitions = self.original_repetitions - 1;
|
||||
self.inner_done = false;
|
||||
}
|
||||
} else {
|
||||
if self.inner_done {
|
||||
self.repetitions = self.original_repetitions;
|
||||
self.cache_position = 0;
|
||||
} else {
|
||||
return Err(RuntimeError {
|
||||
line: 0,
|
||||
op: PseudoOp::from_printable(self),
|
||||
msg:
|
||||
"Cannot reset part way through repeat when inner statement is not resetable"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RepeatFunctionFactory;
|
||||
|
@ -177,14 +215,17 @@ impl MpsFunctionFactory<RepeatStatement> for RepeatFunctionFactory {
|
|||
assert_token_raw(MpsToken::Comma, tokens)?;
|
||||
count = Some(assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(n) => n.parse::<usize>().map(
|
||||
|d| if d == 0 {
|
||||
MpsToken::Name(n) => n
|
||||
.parse::<usize>()
|
||||
.map(|d| {
|
||||
if d == 0 {
|
||||
inner_done = true;
|
||||
0
|
||||
} else {
|
||||
d-1
|
||||
d - 1
|
||||
}
|
||||
).ok(),
|
||||
})
|
||||
.ok(),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("usize".into()),
|
||||
|
@ -199,6 +240,7 @@ impl MpsFunctionFactory<RepeatStatement> for RepeatFunctionFactory {
|
|||
cache_position: 0,
|
||||
repetitions: count.unwrap_or(0),
|
||||
loop_forever: count.is_none(),
|
||||
original_repetitions: count.and_then(|c| Some(c + 1)).unwrap_or(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,16 @@ impl MpsOp for SqlStatement {
|
|||
fn escape(&mut self) -> MpsContext {
|
||||
self.context.take().unwrap()
|
||||
}
|
||||
|
||||
fn is_resetable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
self.rows = None;
|
||||
self.current = 0;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::clone::Clone for SqlStatement {
|
||||
|
@ -88,7 +98,7 @@ impl Iterator for SqlStatement {
|
|||
Err(e) => {
|
||||
self.rows = Some(Vec::with_capacity(0));
|
||||
return Some(Err(e));
|
||||
},
|
||||
}
|
||||
Ok(rows) => {
|
||||
self.rows = Some(rows);
|
||||
self.get_item(false)
|
||||
|
|
|
@ -107,6 +107,16 @@ impl MpsOp for SimpleSqlStatement {
|
|||
fn escape(&mut self) -> MpsContext {
|
||||
self.context.take().unwrap()
|
||||
}
|
||||
|
||||
fn is_resetable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<(), RuntimeError> {
|
||||
self.rows = None;
|
||||
self.current = 0;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::clone::Clone for SimpleSqlStatement {
|
||||
|
@ -150,7 +160,7 @@ impl Iterator for SimpleSqlStatement {
|
|||
Err(e) => {
|
||||
self.rows = Some(Vec::with_capacity(0));
|
||||
return Some(Err(e));
|
||||
},
|
||||
}
|
||||
Ok(rows) => {
|
||||
self.rows = Some(rows);
|
||||
self.get_item(false)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::fs::{ReadDir, DirEntry};
|
||||
use std::fs::{DirEntry, ReadDir};
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
@ -86,11 +86,16 @@ impl FileIter {
|
|||
};
|
||||
let dir_vec = if root_path.is_dir() {
|
||||
let mut vec = Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE);
|
||||
vec.push(root_path.read_dir().map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Directory read error: {}", e),
|
||||
})?.into());
|
||||
vec.push(
|
||||
root_path
|
||||
.read_dir()
|
||||
.map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Directory read error: {}", e),
|
||||
})?
|
||||
.into(),
|
||||
);
|
||||
vec
|
||||
} else {
|
||||
Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE)
|
||||
|
@ -257,7 +262,7 @@ impl Iterator for FileIter {
|
|||
Err(e) => {
|
||||
self.is_complete = true;
|
||||
return Some(Err(format!("Directory read error: {}", e)));
|
||||
},
|
||||
}
|
||||
});
|
||||
return self.next();
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ pub struct MpsOpStorage {
|
|||
}
|
||||
|
||||
impl MpsVariableStorer for MpsOpStorage {
|
||||
|
||||
fn get_opt(&self, key: &str) -> Option<&MpsType> {
|
||||
self.storage.get(key)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ pub enum MpsToken {
|
|||
OpenAngleBracket,
|
||||
CloseAngleBracket,
|
||||
Dot,
|
||||
Exclamation,
|
||||
Interrogation,
|
||||
}
|
||||
|
||||
impl MpsToken {
|
||||
|
@ -30,6 +32,8 @@ impl MpsToken {
|
|||
"<" => Ok(Self::OpenAngleBracket),
|
||||
">" => Ok(Self::CloseAngleBracket),
|
||||
"." => Ok(Self::Dot),
|
||||
"!" => Ok(Self::Exclamation),
|
||||
"?" => Ok(Self::Interrogation),
|
||||
_ => {
|
||||
// name validation
|
||||
let mut ok = true;
|
||||
|
@ -138,6 +142,20 @@ impl MpsToken {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_exclamation(&self) -> bool {
|
||||
match self {
|
||||
Self::Exclamation => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_interrogation(&self) -> bool {
|
||||
match self {
|
||||
Self::Interrogation => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MpsToken {
|
||||
|
@ -156,6 +174,8 @@ impl Display for MpsToken {
|
|||
Self::OpenAngleBracket => write!(f, "<"),
|
||||
Self::CloseAngleBracket => write!(f, ">"),
|
||||
Self::Dot => write!(f, "."),
|
||||
Self::Exclamation => write!(f, "!"),
|
||||
Self::Interrogation => write!(f, "?"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,12 +114,15 @@ where
|
|||
}
|
||||
ReaderStateMachine::Invalid { .. } => {
|
||||
let invalid_char = bigger_buf.pop().unwrap(); // invalid single char
|
||||
// clear everything, to avoid further errors
|
||||
// clear everything, to avoid further errors
|
||||
bigger_buf.clear();
|
||||
buf.clear();
|
||||
return match invalid_char {
|
||||
0 => Err(self.error(format!("EOF"))),
|
||||
_ => Err(self.error(format!("character {:?} ({})", invalid_char as char, invalid_char)))
|
||||
_ => Err(self.error(format!(
|
||||
"character {:?} ({})",
|
||||
invalid_char as char, invalid_char
|
||||
))),
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -136,14 +136,24 @@ fn execute_emptyfilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
|||
|
||||
#[test]
|
||||
fn execute_fieldfilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||
execute_single_line("song(`lov`).(year >= 2020)", false, true)
|
||||
execute_single_line(
|
||||
"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).(year >= 2000)",
|
||||
false,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_files_line() -> Result<(), Box<dyn MpsLanguageError>> {
|
||||
execute_single_line(
|
||||
r"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`, re=``, recursive=false)",
|
||||
r"files(folder=`~/Music/MusicFlac/Bruno Mars/24K Magic/`, re=``, recursive=false)",
|
||||
false,
|
||||
true,
|
||||
)
|
||||
)?;
|
||||
execute_single_line(
|
||||
r"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`)",
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
execute_single_line(r"files()", false, true)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue