diff --git a/mps-interpreter/README.md b/mps-interpreter/README.md index 20abe9a..23cb344 100644 --- a/mps-interpreter/README.md +++ b/mps-interpreter/README.md @@ -154,6 +154,10 @@ Combine multiple iterables such that only items that exist in iterable1 and iter Empty iterator. Useful for deleting items using replacement filters. +#### empties(count); + +Iterate over count empty items. The items in this iterator have no fields (i.e. are empty). + ### Sorters Operations to sort the items in an iterable: `iterable~(sorter)` OR `iterable.sort(sorter)`. @@ -231,5 +235,9 @@ Various algebraic operations: brackets (order of operations), negation, subtract Format a value into a string. This behaves differently depending on the value's type: When the value is an Item, the item's corresponding field will replace all `{field}` instances in the format string. When the value is a primitive type (String, Int, Bool, etc.), the value's text equivalent will replace all `{}` instances in the format string. When the value is an iterable operation (Op), the operation's script equivalent will replace all `{}` instances in the format string. +#### file(filepath) -- e.g. `file("~/Music/Romantic Traffic.flac"),` + +Load a item from file, populating the item with the song's tags. + License: LGPL-2.1-only OR GPL-3.0-only diff --git a/mps-interpreter/src/interpretor.rs b/mps-interpreter/src/interpretor.rs index da20ee4..4bebef0 100644 --- a/mps-interpreter/src/interpretor.rs +++ b/mps-interpreter/src/interpretor.rs @@ -200,6 +200,7 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) { .add(crate::lang::vocabulary::sql_init_function_factory()) .add(crate::lang::vocabulary::files_function_factory()) .add(crate::lang::vocabulary::empty_function_factory()) + .add(crate::lang::vocabulary::empties_function_factory()) .add(crate::lang::vocabulary::reset_function_factory()) .add(crate::lang::vocabulary::union_function_factory()) .add(crate::lang::vocabulary::intersection_function_factory()); diff --git a/mps-interpreter/src/lang/lookup.rs b/mps-interpreter/src/lang/lookup.rs index b6a9626..e5f6b8d 100644 --- a/mps-interpreter/src/lang/lookup.rs +++ b/mps-interpreter/src/lang/lookup.rs @@ -18,6 +18,7 @@ impl Lookup { pub fn check_is(token: &MpsToken) -> bool { token.is_name() || check_is_type(token) } + pub fn parse(tokens: &mut VecDeque) -> Result { if tokens.is_empty() { Err(SyntaxError { diff --git a/mps-interpreter/src/lang/vocabulary/empties.rs b/mps-interpreter/src/lang/vocabulary/empties.rs new file mode 100644 index 0000000..8590a6e --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/empties.rs @@ -0,0 +1,152 @@ +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::Iterator; + +use crate::tokens::MpsToken; +use crate::MpsContext; + +use crate::lang::{Lookup, MpsLanguageDictionary, PseudoOp}; +use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsOp}; +use crate::lang::{RuntimeError, RuntimeOp, SyntaxError}; +use crate::processing::general::MpsType; +use crate::MpsItem; + +#[derive(Debug)] +pub struct EmptiesStatement { + count: Lookup, + context: Option, + // state + current_i: u64, + is_errored: bool, +} + +impl Display for EmptiesStatement { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "empties({})", self.count) + } +} + +impl std::clone::Clone for EmptiesStatement { + fn clone(&self) -> Self { + Self { + count: self.count.clone(), + context: None, + current_i: self.current_i, + is_errored: self.is_errored, + } + } +} + +impl Iterator for EmptiesStatement { + type Item = MpsIteratorItem; + + fn next(&mut self) -> Option { + let val = self.count.get(self.context.as_ref().unwrap()); + match val { + Ok(val) => { + if let MpsType::Primitive(val) = val { + if let Some(val) = val.clone().to_u64() { + if self.current_i < val { + self.current_i += 1; + Some(Ok(MpsItem::new())) + } else { + None + } + } else { + self.is_errored = true; + Some(Err(RuntimeError { + line: 0, + op: PseudoOp::from_printable(self), + msg: format!( + "Cannot use primitive {} ({}) as count (should be UInt)", + self.count, val + ), + })) + } + } else { + self.is_errored = true; + Some(Err(RuntimeError { + line: 0, + op: PseudoOp::from_printable(self), + msg: format!( + "Cannot use non-primitive {} ({}) as count (should be UInt)", + self.count, val + ), + })) + } + } + Err(e) => { + if self.is_errored { + None + } else { + self.is_errored = true; + Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))) + } + } + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(0)) + } +} + +impl MpsOp for EmptiesStatement { + fn enter(&mut self, ctx: MpsContext) { + self.context = Some(ctx) + } + + fn escape(&mut self) -> MpsContext { + self.context.take().unwrap() + } + + fn is_resetable(&self) -> bool { + true + } + + fn reset(&mut self) -> Result<(), RuntimeError> { + self.current_i = 0; + Ok(()) + } + + fn dup(&self) -> Box { + Box::new(Self { + count: self.count.clone(), + context: None, + current_i: 0, + is_errored: false, + }) + } +} + +pub struct EmptiesFunctionFactory; + +impl MpsFunctionFactory for EmptiesFunctionFactory { + fn is_function(&self, name: &str) -> bool { + name == "empties" + } + + fn build_function_params( + &self, + _name: String, + tokens: &mut VecDeque, + _dict: &MpsLanguageDictionary, + ) -> Result { + // empties(count) + let count_lookup = Lookup::parse(tokens)?; + Ok(EmptiesStatement { + count: count_lookup, + context: None, + current_i: 0, + is_errored: false, + }) + } +} + +pub type EmptiesStatementFactory = + MpsFunctionStatementFactory; + +#[inline(always)] +pub fn empties_function_factory() -> EmptiesStatementFactory { + EmptiesStatementFactory::new(EmptiesFunctionFactory) +} diff --git a/mps-interpreter/src/lang/vocabulary/mod.rs b/mps-interpreter/src/lang/vocabulary/mod.rs index 70d9ffe..1e2a4ca 100644 --- a/mps-interpreter/src/lang/vocabulary/mod.rs +++ b/mps-interpreter/src/lang/vocabulary/mod.rs @@ -1,4 +1,5 @@ mod comment; +mod empties; mod empty; mod files; mod intersection; @@ -11,6 +12,7 @@ mod union; mod variable_assign; pub use comment::{CommentStatement, CommentStatementFactory}; +pub use empties::{empties_function_factory, EmptiesStatementFactory}; pub use empty::{empty_function_factory, EmptyStatementFactory}; pub use files::{files_function_factory, FilesStatementFactory}; pub use intersection::{intersection_function_factory, IntersectionStatementFactory}; diff --git a/mps-interpreter/src/lib.rs b/mps-interpreter/src/lib.rs index ef0a728..2c67b8c 100644 --- a/mps-interpreter/src/lib.rs +++ b/mps-interpreter/src/lib.rs @@ -152,6 +152,10 @@ //! //! Empty iterator. Useful for deleting items using replacement filters. //! +//! ### empties(count); +//! +//! Iterate over count empty items. The items in this iterator have no fields (i.e. are empty). +//! //! ## Sorters //! Operations to sort the items in an iterable: `iterable~(sorter)` OR `iterable.sort(sorter)`. //! diff --git a/mps-interpreter/tests/single_line.rs b/mps-interpreter/tests/single_line.rs index 1828fd0..42a25fe 100644 --- a/mps-interpreter/tests/single_line.rs +++ b/mps-interpreter/tests/single_line.rs @@ -754,9 +754,25 @@ fn execute_uniquefilter_line() -> Result<(), Box> { fn execute_fileitemop_line() -> Result<(), Box> { execute_single_line( "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.title = `something else`, item = file(item.filename), }", false, true, ) } + +#[test] +fn execute_emptiesop_line() -> Result<(), Box> { + execute_single_line( + "empties(1).{let count = 0, item.title = ~`title #{}` count+1, item.filename = ~`filename_{}` count, count = count + 1}", + false, + true, + )?; + execute_single_line( + "empties(42).{let count = 0, item.title = ~`title #{}` count+1, item.filename = ~`filename_{}` count, count = count + 1}", + false, + true, + )?; + execute_single_line("empties(0)", true, true) +} diff --git a/src/help.rs b/src/help.rs index 826a8b5..b509574 100644 --- a/src/help.rs +++ b/src/help.rs @@ -46,7 +46,10 @@ These always return an iterable which can be manipulated. Combine multiple iterables such that only items that exist in iterable1 and iterable2 and ... are returned. The order of items from iterable1 is maintained. There is no limit on the amount of iterables which can be provided as parameters. empty() - Empty iterator. Useful for deleting items using replacement filters."; + Empty iterator. Useful for deleting items using replacement filters. + + empties(count); + Iterate over count empty items. The items in this iterator have no fields (i.e. are empty)."; pub const FILTERS: &str = "FILTERS (?filters)