Implement reset functionality on existing statements

This commit is contained in:
NGnius (Graham) 2022-01-11 10:22:05 -05:00
parent 90a310bbbe
commit a8eb390ab8
15 changed files with 190 additions and 38 deletions

View file

@ -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,12 +119,12 @@ 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(),
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();

View file

@ -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,

View file

@ -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 {

View file

@ -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;

View file

@ -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;

View file

@ -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])))
}

View file

@ -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 =

View file

@ -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,
};
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);
}
if real_op.is_resetable() {
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
}
).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),
})
}
}

View file

@ -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)

View file

@ -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)

View file

@ -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 {
vec.push(
root_path
.read_dir()
.map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: format!("Directory read error: {}", e),
})?.into());
})?
.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();
}

View file

@ -69,7 +69,6 @@ pub struct MpsOpStorage {
}
impl MpsVariableStorer for MpsOpStorage {
fn get_opt(&self, key: &str) -> Option<&MpsType> {
self.storage.get(key)
}

View file

@ -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, "?"),
}
}
}

View file

@ -119,7 +119,10 @@ where
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
))),
};
}
_ => {}

View file

@ -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)
}