Document iter block functionality and cargo fmt

This commit is contained in:
NGnius (Graham) 2022-02-23 11:33:45 -05:00
parent a915cbd029
commit bb492dcd77
41 changed files with 762 additions and 354 deletions

View file

@ -169,5 +169,62 @@ Sort by the distance (similarity) from the first song in the iterator. Songs whi
Sort by the distance (similarity) between the last played song in the iterator. Similar to bliss_first. The song which is the most similar (lower distance) to the previous song in the iterator will be placed next to it, then the process is repeated. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
### Procedures
Operations to apply to each item in an iterable: iterable.{step1, step2, ...}
License: LGPL-2.1-only OR GPL-2.0-or-later
Comma-separated procedure steps will be executed sequentially (like a for loop in regular programming languages). The variable item contains the current item of the iterable.
#### let variable = something -- e.g. let my_var = 42
Declare the variable and (optionally) set the initial value to something. The assignment will only be performed when the variable has not yet been declared. When the initial value (and equals sign) is omitted, the variable is initialized as empty().
#### variable = something -- e.g. my_var = 42
Assign something to the variable. The variable must have already been declared.
#### empty() -- e.g. empty()
The empty or null constant.
#### if condition { something } else { something_else } -- e.g.
```mps
if item.title == `Romantic Traffic` {
} else {
remove item
}
```
Branch based on a boolean condition. Multiple comma-separated procedure steps may be supplied in the if and else branches. This does not currently support if else chains, but they can be nested to accomplish similar behaviour.
#### something1 == something2
#### something1 != something2
#### something1 >= something2
#### something1 > something2
#### something1 <= something2
#### something1 < something2 -- e.g. item.filename != item.title
Compare something1 to something2. The result is a boolean which is useful for branch conditions.
#### op iterable_operation -- e.g. op files().(0..=42)~(shuffle)
An iterable operation inside of the procedure. When assigned to item, this can be used to replace item with multiple others. Note that iterable operations are never executed inside the procedure; when item is iterable, it will be executed immediately after the end of the procedure for the current item.
#### (something1)
#### -something1
#### something1 - something2
#### something1 + something2
#### something1 || something2
#### something1 && something2 -- e.g. 42 + (128 - 64)
Various algebraic operations: brackets (order of operations), negation, subtraction, addition, logical OR, logical AND; respectively.
#### Item(field1 = something1, field2 = something2, ...) - e.g. item = Item(title = item.title, filename = `/dev/null`)
Constructor for a new item. Each function parameter defines a new field and it's value.
#### ~`string_format` something -- e.g. ~`{filename}` item
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.
License: LGPL-2.1-only OR GPL-3.0-only

View file

@ -1,11 +1,9 @@
#[cfg(feature = "advanced")]
use super::processing::advanced::{MpsDefaultAnalyzer, MpsMusicAnalyzer};
use super::processing::database::{MpsDatabaseQuerier, MpsSQLiteExecutor};
use super::processing::general::{
MpsFilesystemExecutor, MpsFilesystemQuerier, MpsOpStorage, MpsVariableStorer,
};
#[cfg(feature = "advanced")]
use super::processing::advanced::{
MpsMusicAnalyzer, MpsDefaultAnalyzer
};
use std::fmt::{Debug, Display, Error, Formatter};
#[derive(Debug)]

View file

@ -184,8 +184,8 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
.add(crate::lang::vocabulary::item_ops::SubtractItemOpFactory)
.add(crate::lang::vocabulary::item_ops::OrItemOpFactory)
.add(crate::lang::vocabulary::item_ops::AndItemOpFactory)
.add(crate::lang::vocabulary::item_ops::BracketsItemOpFactory)
)
.add(crate::lang::vocabulary::item_ops::BracketsItemOpFactory),
)
// functions and misc
// functions don't enforce bracket coherence
// -- function().() is valid despite the ).( in between brackets

View file

@ -33,7 +33,7 @@ impl MpsItem {
self.fields.remove(name)
}
pub fn iter(&self) -> impl Iterator<Item=&String> {
pub fn iter(&self) -> impl Iterator<Item = &String> {
self.fields.keys()
}

View file

@ -182,10 +182,13 @@ impl<P: MpsFilterPredicate + 'static> MpsOp for MpsFilterStatement<P> {
predicate: self.predicate.clone(),
iterable: match &self.iterable {
VariableOrOp::Variable(s) => VariableOrOp::Variable(s.clone()),
VariableOrOp::Op(op) => VariableOrOp::Op(op.try_real_ref().unwrap().dup().into())
VariableOrOp::Op(op) => VariableOrOp::Op(op.try_real_ref().unwrap().dup().into()),
},
context: None,
other_filters: self.other_filters.as_ref().map(|x| PseudoOp::from(x.try_real_ref().unwrap().dup())),
other_filters: self
.other_filters
.as_ref()
.map(|x| PseudoOp::from(x.try_real_ref().unwrap().dup())),
})
}
}
@ -350,15 +353,14 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
// handle other filters
// make fake inner item
let single_op = SingleItem::new_ok(item.clone());
match ctx.variables.declare(
INNER_VARIABLE_NAME,
MpsType::Op(Box::new(single_op)),
) {
match ctx
.variables
.declare(INNER_VARIABLE_NAME, MpsType::Op(Box::new(single_op)))
{
Ok(x) => x,
Err(e) => {
//self.context = Some(op.escape());
maybe_result =
Some(Err(e.with(RuntimeOp(fake.clone()))));
maybe_result = Some(Err(e.with(RuntimeOp(fake.clone()))));
self.context = Some(ctx);
break;
}
@ -381,9 +383,8 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
Ok(_) => {}
Err(e) => match maybe_result {
Some(Ok(_)) => {
maybe_result = Some(Err(
e.with(RuntimeOp(fake.clone()))
))
maybe_result =
Some(Err(e.with(RuntimeOp(fake.clone()))))
}
Some(Err(e2)) => maybe_result = Some(Err(e2)), // already failing, do not replace error,
None => {} // impossible

View file

@ -145,11 +145,14 @@ impl<P: MpsFilterPredicate + 'static> MpsOp for MpsFilterReplaceStatement<P> {
predicate: self.predicate.clone(),
iterable: match &self.iterable {
VariableOrOp::Variable(s) => VariableOrOp::Variable(s.clone()),
VariableOrOp::Op(op) => VariableOrOp::Op(op.try_real_ref().unwrap().dup().into())
VariableOrOp::Op(op) => VariableOrOp::Op(op.try_real_ref().unwrap().dup().into()),
},
context: None,
op_if: PseudoOp::from(self.op_if.try_real_ref().unwrap().dup()),
op_else: self.op_else.as_ref().map(|x| PseudoOp::from(x.try_real_ref().unwrap().dup())),
op_else: self
.op_else
.as_ref()
.map(|x| PseudoOp::from(x.try_real_ref().unwrap().dup())),
item_cache: VecDeque::new(),
})
}

View file

@ -1,9 +1,9 @@
use core::ops::Deref;
use std::sync::Arc;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use std::marker::PhantomData;
use std::sync::Arc;
use crate::lang::utility::{assert_token_raw, assert_token_raw_back};
use crate::lang::MpsLanguageDictionary;
@ -31,13 +31,16 @@ pub trait MpsItemOpFactory<T: Deref<Target = dyn MpsItemOp> + 'static> {
) -> Result<T, SyntaxError>;
}
pub struct MpsItemOpBoxer<X: MpsItemOpFactory<Y> + 'static, Y: Deref<Target = dyn MpsItemOp> + MpsItemOp + 'static> {
pub struct MpsItemOpBoxer<
X: MpsItemOpFactory<Y> + 'static,
Y: Deref<Target = dyn MpsItemOp> + MpsItemOp + 'static,
> {
idc: PhantomData<Y>,
factory: X,
}
impl<X: MpsItemOpFactory<Y> + 'static, Y: Deref<Target = dyn MpsItemOp> + MpsItemOp + 'static> MpsItemOpFactory<Box<dyn MpsItemOp>>
for MpsItemOpBoxer<X, Y>
impl<X: MpsItemOpFactory<Y> + 'static, Y: Deref<Target = dyn MpsItemOp> + MpsItemOp + 'static>
MpsItemOpFactory<Box<dyn MpsItemOp>> for MpsItemOpBoxer<X, Y>
{
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
self.factory.is_item_op(tokens)
@ -84,7 +87,6 @@ impl std::clone::Clone for MpsItemBlockStatement {
}
}
impl MpsOp for MpsItemBlockStatement {
fn enter(&mut self, ctx: MpsContext) {
self.iterable.try_real().unwrap().enter(ctx)
@ -151,10 +153,12 @@ impl Iterator for MpsItemBlockStatement {
let old_var = replace_item_var(&mut ctx, MpsType::Item(item));
for op in self.statements.iter_mut() {
match op.execute(&mut ctx) {
Ok(_) => {},
Ok(_) => {}
Err(e) => {
#[allow(unused_must_use)]
{restore_item_var(&mut ctx, old_var);}
{
restore_item_var(&mut ctx, old_var);
}
real_op.enter(ctx);
return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self)))));
}
@ -169,9 +173,7 @@ impl Iterator for MpsItemBlockStatement {
};
real_op.enter(ctx);
match item {
Some(MpsType::Item(item)) => {
return Some(Ok(item))
},
Some(MpsType::Item(item)) => return Some(Ok(item)),
Some(MpsType::Op(mut op)) => {
op.enter(real_op.escape());
if let Some(item) = op.next() {
@ -181,12 +183,17 @@ impl Iterator for MpsItemBlockStatement {
} else {
real_op.enter(op.escape());
}
},
Some(x) => return Some(Err(RuntimeError {
line: 0,
op: PseudoOp::from_printable(self),
msg: format!("Expected `item` like MpsType::Item(MpsItem[...]), got {}", x),
})),
}
Some(x) => {
return Some(Err(RuntimeError {
line: 0,
op: PseudoOp::from_printable(self),
msg: format!(
"Expected `item` like MpsType::Item(MpsItem[...]), got {}",
x
),
}))
}
None => {}
}
}
@ -206,9 +213,12 @@ pub struct MpsItemBlockFactory {
}
impl MpsItemBlockFactory {
pub fn add<T: MpsItemOpFactory<Y> + 'static, Y: Deref<Target=dyn MpsItemOp> + MpsItemOp + 'static>(
pub fn add<
T: MpsItemOpFactory<Y> + 'static,
Y: Deref<Target = dyn MpsItemOp> + MpsItemOp + 'static,
>(
mut self,
factory: T
factory: T,
) -> Self {
self.vocabulary.push(Box::new(MpsItemOpBoxer {
factory: factory,
@ -250,7 +260,7 @@ impl MpsItemBlockFactory {
impl BoxedMpsOpFactory for MpsItemBlockFactory {
fn is_op_boxed(&self, tokens: &VecDeque<MpsToken>) -> bool {
tokens[tokens.len()-1].is_close_curly()
tokens[tokens.len() - 1].is_close_curly()
}
fn build_op_boxed(
@ -303,7 +313,10 @@ fn replace_item_var(ctx: &mut MpsContext, item: MpsType) -> Option<MpsType> {
old_var
}
fn restore_item_var(ctx: &mut MpsContext, old_var: Option<MpsType>) -> Result<Option<MpsType>, RuntimeMsg> {
fn restore_item_var(
ctx: &mut MpsContext,
old_var: Option<MpsType>,
) -> Result<Option<MpsType>, RuntimeMsg> {
let new_var;
if ctx.variables.exists(ITEM_VARIABLE_NAME) {
new_var = Some(ctx.variables.remove(ITEM_VARIABLE_NAME)?);
@ -326,16 +339,16 @@ fn find_last_open_curly(tokens: &VecDeque<MpsToken>) -> Option<usize> {
if bracket_depth != 0 {
bracket_depth -= 1;
}
},
}
MpsToken::CloseCurly => {
bracket_depth += 1;
},
}
MpsToken::Dot => {
if bracket_depth == 0 && curly_found {
return Some(i+1);
return Some(i + 1);
}
}
_ => {},
_ => {}
}
if token.is_open_curly() {
curly_found = true;

View file

@ -85,28 +85,46 @@ impl MpsTypePrimitive {
match self {
Self::String(s) => match other {
Self::String(other_s) => Ok(Self::String(s.to_owned() + other_s)),
other => Err(format!("Cannot add {} and {}: incompatible types", self, other)),
other => Err(format!(
"Cannot add {} and {}: incompatible types",
self, other
)),
},
Self::Int(i) => match other {
Self::Int(other_i) => Ok(Self::Int(i + other_i)),
Self::UInt(u) => Ok(Self::Int(i + *u as i64)),
Self::Float(f) => Ok(Self::Float(*i as f64 + f)),
other => Err(format!("Cannot add {} and {}: incompatible types", self, other)),
other => Err(format!(
"Cannot add {} and {}: incompatible types",
self, other
)),
},
Self::UInt(u) => match other {
Self::UInt(other_u) => Ok(Self::UInt(u + other_u)),
Self::Int(i) => Ok(Self::UInt(u + *i as u64)),
Self::Float(f) => Ok(Self::Float(*u as f64 + f)),
other => Err(format!("Cannot add {} and {}: incompatible types", self, other)),
other => Err(format!(
"Cannot add {} and {}: incompatible types",
self, other
)),
},
Self::Float(f) => match other {
Self::Float(other_f) => Ok(Self::Float(f + other_f)),
Self::Int(i) => Ok(Self::Float(f + *i as f64)),
Self::UInt(u) => Ok(Self::Float(f + *u as f64)),
other => Err(format!("Cannot add {} and {}: incompatible types", self, other)),
other => Err(format!(
"Cannot add {} and {}: incompatible types",
self, other
)),
},
Self::Bool(_) => Err(format!("Cannot add {} and {}: incompatible types", self, other)),
Self::Empty => Err(format!("Cannot add {} and {}: incompatible types", self, other)),
Self::Bool(_) => Err(format!(
"Cannot add {} and {}: incompatible types",
self, other
)),
Self::Empty => Err(format!(
"Cannot add {} and {}: incompatible types",
self, other
)),
}
}
@ -134,7 +152,10 @@ impl MpsTypePrimitive {
pub fn try_not(&self) -> Result<Self, String> {
match self {
Self::Bool(b) => Ok(Self::Bool(!*b)),
_ => Err(format!("Cannot apply logical NOT to {}: incompatible type", self)),
_ => Err(format!(
"Cannot apply logical NOT to {}: incompatible type",
self
)),
}
}
}
@ -206,7 +227,7 @@ impl PartialOrd for MpsTypePrimitive {
Self::Empty => match other {
Self::Empty => Some(std::cmp::Ordering::Equal),
_ => None,
}
},
}
}
}
@ -222,7 +243,7 @@ impl std::hash::Hash for MpsTypePrimitive {
Self::UInt(u) => u.hash(state),
Self::Float(f_) => (*f_ as u64).hash(state),
Self::Bool(b) => b.hash(state),
Self::Empty => {},
Self::Empty => {}
}
}
}

View file

@ -133,7 +133,9 @@ impl MpsOp for IntersectionStatement {
init_needed: true,
};
for op in self.ops.iter() {
clone.ops.push(PseudoOp::from(op.try_real_ref().unwrap().dup()));
clone
.ops
.push(PseudoOp::from(op.try_real_ref().unwrap().dup()));
}
Box::new(clone)
}

View file

@ -4,8 +4,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -35,12 +35,20 @@ impl MpsItemOp for AddItemOp {
if let MpsType::Primitive(lhs) = &lhs {
let rhs = self.rhs.execute(context)?;
if let MpsType::Primitive(rhs) = &rhs {
Ok(MpsType::Primitive(lhs.try_add(rhs).map_err(|e| RuntimeMsg(e))?))
Ok(MpsType::Primitive(
lhs.try_add(rhs).map_err(|e| RuntimeMsg(e))?,
))
} else {
Err(RuntimeMsg(format!("Cannot add right-hand side `{}` ({}): not primitive type", self.rhs, rhs)))
Err(RuntimeMsg(format!(
"Cannot add right-hand side `{}` ({}): not primitive type",
self.rhs, rhs
)))
}
} else {
Err(RuntimeMsg(format!("Cannot add left-hand side `{}` ({}): not primitive type", self.lhs, lhs)))
Err(RuntimeMsg(format!(
"Cannot add left-hand side `{}` ({}): not primitive type",
self.lhs, lhs
)))
}
}
}

View file

@ -1,10 +1,10 @@
use std::convert::AsRef;
use std::collections::VecDeque;
use std::convert::AsRef;
use crate::lang::utility::{assert_token_raw, assert_token_raw_back};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -13,7 +13,9 @@ pub struct BracketsItemOpFactory;
impl MpsItemOpFactory<Box<dyn MpsItemOp>> for BracketsItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
tokens.len() >= 2 && tokens[0].is_open_bracket() && tokens[tokens.len()-1].is_close_bracket()
tokens.len() >= 2
&& tokens[0].is_open_bracket()
&& tokens[tokens.len() - 1].is_close_bracket()
}
fn build_item_op(

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, check_name, assert_name};
use crate::lang::utility::{assert_name, assert_token_raw, check_name};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -76,7 +76,10 @@ impl MpsItemOp for BranchItemOp {
Ok(MpsType::empty())
}
} else {
Err(RuntimeMsg(format!("Cannot use {} ({}) as if branch condition (should be Bool)", self.condition, condition_val)))
Err(RuntimeMsg(format!(
"Cannot use {} ({}) as if branch condition (should be Bool)",
self.condition, condition_val
)))
}
}
}
@ -183,13 +186,17 @@ fn next_curly_open_bracket(tokens: &VecDeque<MpsToken>) -> Option<usize> {
for i in 0..tokens.len() {
match &tokens[i] {
MpsToken::OpenBracket => bracket_depth += 1,
MpsToken::CloseBracket => if bracket_depth != 0 {
bracket_depth -= 1;
},
MpsToken::OpenCurly => if bracket_depth == 0 {
return Some(i);
},
_ => {},
MpsToken::CloseBracket => {
if bracket_depth != 0 {
bracket_depth -= 1;
}
}
MpsToken::OpenCurly => {
if bracket_depth == 0 {
return Some(i);
}
}
_ => {}
}
}
None
@ -201,16 +208,20 @@ fn next_curly_close_bracket(tokens: &VecDeque<MpsToken>) -> Option<usize> {
for i in 0..tokens.len() {
match &tokens[i] {
MpsToken::OpenBracket => bracket_depth += 1,
MpsToken::CloseBracket => if bracket_depth != 0 {
bracket_depth -= 1;
},
MpsToken::CloseBracket => {
if bracket_depth != 0 {
bracket_depth -= 1;
}
}
MpsToken::OpenCurly => curly_depth += 1,
MpsToken::CloseCurly => if bracket_depth == 0 && curly_depth == 0 {
return Some(i);
} else if curly_depth != 0 {
curly_depth -= 1;
},
_ => {},
MpsToken::CloseCurly => {
if bracket_depth == 0 && curly_depth == 0 {
return Some(i);
} else if curly_depth != 0 {
curly_depth -= 1;
}
}
_ => {}
}
}
None

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::vocabulary::filters::utility::{assert_comparison_operator, comparison_op};
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{MpsLanguageDictionary, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::lang::vocabulary::filters::utility::{assert_comparison_operator, comparison_op};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -26,7 +26,13 @@ impl Deref for CompareItemOp {
impl Display for CompareItemOp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{} {} {}", self.lhs, comparison_op(&self.comparison), self.rhs)
write!(
f,
"{} {} {}",
self.lhs,
comparison_op(&self.comparison),
self.rhs
)
}
}
@ -46,10 +52,16 @@ impl MpsItemOp for CompareItemOp {
}
Ok(MpsType::Primitive(MpsTypePrimitive::Bool(is_match)))
} else {
Err(RuntimeMsg(format!("Cannot compare non-primitive right-hand side {} ({})", self.rhs, rhs_val)))
Err(RuntimeMsg(format!(
"Cannot compare non-primitive right-hand side {} ({})",
self.rhs, rhs_val
)))
}
} else {
Err(RuntimeMsg(format!("Cannot compare non-primitive left-hand side {} ({})", self.lhs, lhs_val)))
Err(RuntimeMsg(format!(
"Cannot compare non-primitive left-hand side {} ({})",
self.lhs, lhs_val
)))
}
}
}
@ -87,18 +99,30 @@ fn find_first_comparison(tokens: &VecDeque<MpsToken>) -> Option<usize> {
for i in 0..tokens.len() {
match &tokens[i] {
MpsToken::OpenCurly => curly_depth += 1,
MpsToken::CloseCurly => if curly_depth != 0 {
curly_depth -= 1;
},
MpsToken::CloseCurly => {
if curly_depth != 0 {
curly_depth -= 1;
}
}
MpsToken::OpenBracket => bracket_depth += 1,
MpsToken::CloseBracket => if bracket_depth != 0 {
curly_depth -= 1;
},
MpsToken::OpenAngleBracket | MpsToken::CloseAngleBracket => if curly_depth == 0 && bracket_depth == 0 {
MpsToken::CloseBracket => {
if bracket_depth != 0 {
curly_depth -= 1;
}
}
MpsToken::OpenAngleBracket | MpsToken::CloseAngleBracket => {
if curly_depth == 0 && bracket_depth == 0 {
return Some(i);
},
MpsToken::Equals | MpsToken::Exclamation => if curly_depth == 0 && bracket_depth == 0 && i+1 != tokens.len() && tokens[i+1].is_equals() {
return Some(i);
}
}
MpsToken::Equals | MpsToken::Exclamation => {
if curly_depth == 0
&& bracket_depth == 0
&& i + 1 != tokens.len()
&& tokens[i + 1].is_equals()
{
return Some(i);
}
}
_ => {}
}

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{check_is_type, assert_type};
use crate::lang::utility::{assert_type, check_is_type};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -38,8 +38,7 @@ pub struct ConstantItemOpFactory;
impl MpsItemOpFactory<ConstantItemOp> for ConstantItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
tokens.len() == 1
&& check_is_type(&tokens[0])
tokens.len() == 1 && check_is_type(&tokens[0])
}
fn build_item_op(
@ -49,8 +48,6 @@ impl MpsItemOpFactory<ConstantItemOp> for ConstantItemOpFactory {
_dict: &MpsLanguageDictionary,
) -> Result<ConstantItemOp, SyntaxError> {
let const_value = assert_type(tokens)?;
Ok(ConstantItemOp {
value: const_value,
})
Ok(ConstantItemOp { value: const_value })
}
}

View file

@ -2,10 +2,12 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, assert_token_raw_back, check_name, assert_name, assert_token};
use crate::lang::utility::{
assert_name, assert_token, assert_token_raw, assert_token_raw_back, check_name,
};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -34,11 +36,11 @@ impl Display for ConstructorItemOp {
write!(f, "Item(")?;
if self.fields.len() > 1 {
write!(f, "\n")?;
for i in 0..self.fields.len()-1 {
for i in 0..self.fields.len() - 1 {
let field = &self.fields[i];
write!(f, "{}: {}, ", field.name, field.value)?;
}
let field = &self.fields[self.fields.len()-1];
let field = &self.fields[self.fields.len() - 1];
write!(f, "{}: {}", field.name, field.value)?;
} else if !self.fields.is_empty() {
let field = &self.fields[0];
@ -56,7 +58,10 @@ impl MpsItemOp for ConstructorItemOp {
if let MpsType::Primitive(value) = value {
result.set_field(&field.name, value);
} else {
return Err(RuntimeMsg(format!("Cannot assign non-primitive {} to Item field `{}`", value, &field.name)));
return Err(RuntimeMsg(format!(
"Cannot assign non-primitive {} to Item field `{}`",
value, &field.name
)));
}
}
Ok(MpsType::Item(result))
@ -67,9 +72,7 @@ pub struct ConstructorItemOpFactory;
impl MpsItemOpFactory<ConstructorItemOp> for ConstructorItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
tokens.len() > 2
&& check_name("Item", &tokens[0])
&& tokens[1].is_open_bracket()
tokens.len() > 2 && check_name("Item", &tokens[0]) && tokens[1].is_open_bracket()
}
fn build_item_op(
@ -83,10 +86,14 @@ impl MpsItemOpFactory<ConstructorItemOp> for ConstructorItemOpFactory {
assert_token_raw_back(MpsToken::CloseBracket, tokens)?;
let mut field_descriptors = Vec::new();
while !tokens.is_empty() {
let field_name = assert_token(|t| match t {
MpsToken::Name(n) => Some(n),
_ => None,
}, MpsToken::Name("field_name".into()), tokens)?;
let field_name = assert_token(
|t| match t {
MpsToken::Name(n) => Some(n),
_ => None,
},
MpsToken::Name("field_name".into()),
tokens,
)?;
assert_token_raw(MpsToken::Equals, tokens)?;
let field_val;
if let Some(comma_pos) = find_next_comma(tokens) {
@ -97,12 +104,10 @@ impl MpsItemOpFactory<ConstructorItemOp> for ConstructorItemOpFactory {
} else {
field_val = factory.try_build_item_statement(tokens, dict)?;
}
field_descriptors.push(
FieldAssignment {
name: field_name,
value: field_val,
}
);
field_descriptors.push(FieldAssignment {
name: field_name,
value: field_val,
});
}
Ok(ConstructorItemOp {
fields: field_descriptors,

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, check_name, assert_name};
use crate::lang::utility::{assert_name, assert_token_raw, check_name};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -37,9 +37,9 @@ pub struct EmptyItemOpFactory;
impl MpsItemOpFactory<EmptyItemOp> for EmptyItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
tokens.len() == 3
&& check_name("empty", &tokens[0])
&& tokens[1].is_open_bracket()
&& tokens[2].is_close_bracket()
&& check_name("empty", &tokens[0])
&& tokens[1].is_open_bracket()
&& tokens[2].is_close_bracket()
}
fn build_item_op(

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -26,7 +26,11 @@ impl Deref for FieldAssignItemOp {
impl Display for FieldAssignItemOp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}.{} = {}", &self.variable_name, &self.field_name, &self.inner)
write!(
f,
"{}.{} = {}",
&self.variable_name, &self.field_name, &self.inner
)
}
}
@ -39,10 +43,16 @@ impl MpsItemOp for FieldAssignItemOp {
var.set_field(&self.field_name, val);
Ok(MpsType::empty())
} else {
Err(RuntimeMsg(format!("Cannot assign non-primitive {} to variable field `{}.{}`", mps_type, &self.variable_name, &self.field_name)))
Err(RuntimeMsg(format!(
"Cannot assign non-primitive {} to variable field `{}.{}`",
mps_type, &self.variable_name, &self.field_name
)))
}
} else {
Err(RuntimeMsg(format!("Cannot access field `{}` on variable `{}` ({} is not Item)", &self.field_name, &self.variable_name, var)))
Err(RuntimeMsg(format!(
"Cannot access field `{}` on variable `{}` ({} is not Item)",
&self.field_name, &self.variable_name, var
)))
}
}
}
@ -51,9 +61,15 @@ pub struct FieldAssignItemOpFactory;
impl MpsItemOpFactory<FieldAssignItemOp> for FieldAssignItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
(tokens.len() > 4 && tokens[0].is_name() && tokens[1].is_dot() && tokens[2].is_name() && tokens[3].is_equals())
||
(tokens.len() > 3 && tokens[0].is_dot() && tokens[1].is_name() && tokens[2].is_equals())
(tokens.len() > 4
&& tokens[0].is_name()
&& tokens[1].is_dot()
&& tokens[2].is_name()
&& tokens[3].is_equals())
|| (tokens.len() > 3
&& tokens[0].is_dot()
&& tokens[1].is_name()
&& tokens[2].is_equals())
}
fn build_item_op(
@ -66,16 +82,24 @@ impl MpsItemOpFactory<FieldAssignItemOp> for FieldAssignItemOpFactory {
if tokens[0].is_dot() {
var_name = "item".to_string();
} else {
var_name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("variable_name".into()), tokens)?
var_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?
}
assert_token_raw(MpsToken::Dot, tokens)?;
let f_name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("field_name".into()), tokens)?;
let f_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("field_name".into()),
tokens,
)?;
assert_token_raw(MpsToken::Equals, tokens)?;
let inner_op = factory.try_build_item_statement(tokens, dict)?;
Ok(FieldAssignItemOp {

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{check_name, assert_name};
use crate::lang::utility::{assert_name, check_name};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsOp};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsOp};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -38,8 +38,7 @@ pub struct IterItemOpFactory;
impl MpsItemOpFactory<IterItemOp> for IterItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
!tokens.is_empty()
&& check_name("iter", &tokens[0])
!tokens.is_empty() && check_name("iter", &tokens[0])
}
fn build_item_op(
@ -50,8 +49,6 @@ impl MpsItemOpFactory<IterItemOp> for IterItemOpFactory {
) -> Result<IterItemOp, SyntaxError> {
assert_name("iter", tokens)?;
let inner_op = dict.try_build_statement(tokens)?;
Ok(IterItemOp {
inner: inner_op,
})
Ok(IterItemOp { inner: inner_op })
}
}

View file

@ -4,8 +4,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -41,10 +41,16 @@ impl MpsItemOp for AndItemOp {
if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs {
Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs)))
} else {
Err(RuntimeMsg(format!("Cannot apply logical AND to right-hand side of `{}` ({}): not Bool type", self.rhs, rhs)))
Err(RuntimeMsg(format!(
"Cannot apply logical AND to right-hand side of `{}` ({}): not Bool type",
self.rhs, rhs
)))
}
} else {
Err(RuntimeMsg(format!("Cannot apply logical AND to left-hand side of `{}` ({}): not Bool type", self.lhs, lhs)))
Err(RuntimeMsg(format!(
"Cannot apply logical AND to left-hand side of `{}` ({}): not Bool type",
self.lhs, lhs
)))
}
}
}
@ -81,9 +87,9 @@ impl MpsItemOpFactory<AndItemOp> for AndItemOpFactory {
fn first_and(tokens: &VecDeque<MpsToken>) -> Option<usize> {
let mut bracket_depth = 0;
for i in 0..tokens.len()-1 {
for i in 0..tokens.len() - 1 {
let token = &tokens[i];
if token.is_ampersand() && bracket_depth == 0 && tokens[i+1].is_ampersand() {
if token.is_ampersand() && bracket_depth == 0 && tokens[i + 1].is_ampersand() {
return Some(i);
} else if token.is_open_bracket() {
bracket_depth += 1;

View file

@ -4,8 +4,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -41,10 +41,16 @@ impl MpsItemOp for OrItemOp {
if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs {
Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs)))
} else {
Err(RuntimeMsg(format!("Cannot apply logical OR to right-hand side of `{}` ({}): not Bool type", self.rhs, rhs)))
Err(RuntimeMsg(format!(
"Cannot apply logical OR to right-hand side of `{}` ({}): not Bool type",
self.rhs, rhs
)))
}
} else {
Err(RuntimeMsg(format!("Cannot apply logical OR to left-hand side of `{}` ({}): not Bool type", self.lhs, lhs)))
Err(RuntimeMsg(format!(
"Cannot apply logical OR to left-hand side of `{}` ({}): not Bool type",
self.lhs, lhs
)))
}
}
}
@ -81,9 +87,9 @@ impl MpsItemOpFactory<OrItemOp> for OrItemOpFactory {
fn first_or(tokens: &VecDeque<MpsToken>) -> Option<usize> {
let mut bracket_depth = 0;
for i in 0..tokens.len()-1 {
for i in 0..tokens.len() - 1 {
let token = &tokens[i];
if token.is_pipe() && bracket_depth == 0 && tokens[i+1].is_pipe() {
if token.is_pipe() && bracket_depth == 0 && tokens[i + 1].is_pipe() {
return Some(i);
} else if token.is_open_bracket() {
bracket_depth += 1;

View file

@ -18,22 +18,22 @@ mod subtract;
mod variable_assign;
mod variable_declare;
pub use add::{AddItemOpFactory, AddItemOp};
pub use add::{AddItemOp, AddItemOpFactory};
pub use brackets::BracketsItemOpFactory;
pub use branch::{BranchItemOpFactory, BranchItemOp};
pub use constant::{ConstantItemOpFactory, ConstantItemOp};
pub use compare::{CompareItemOpFactory, CompareItemOp};
pub use constructor::{ConstructorItemOpFactory, ConstructorItemOp};
pub use empty::{EmptyItemOpFactory, EmptyItemOp};
pub use field_assign::{FieldAssignItemOpFactory, FieldAssignItemOp};
pub use iter_op::{IterItemOpFactory, IterItemOp};
pub use logical_and::{AndItemOpFactory, AndItemOp};
pub use logical_or::{OrItemOpFactory, OrItemOp};
pub use negate::{NegateItemOpFactory, NegateItemOp};
pub use not::{NotItemOpFactory, NotItemOp};
pub use remove_variable::{RemoveItemOpFactory, RemoveItemOp};
pub use retrieve_variable::{VariableRetrieveItemOpFactory, VariableRetrieveItemOp};
pub use string_interpolate::{InterpolateStringItemOpFactory, InterpolateStringItemOp};
pub use subtract::{SubtractItemOpFactory, SubtractItemOp};
pub use variable_assign::{VariableAssignItemOpFactory, VariableAssignItemOp};
pub use variable_declare::{VariableDeclareItemOpFactory, VariableDeclareItemOp};
pub use branch::{BranchItemOp, BranchItemOpFactory};
pub use compare::{CompareItemOp, CompareItemOpFactory};
pub use constant::{ConstantItemOp, ConstantItemOpFactory};
pub use constructor::{ConstructorItemOp, ConstructorItemOpFactory};
pub use empty::{EmptyItemOp, EmptyItemOpFactory};
pub use field_assign::{FieldAssignItemOp, FieldAssignItemOpFactory};
pub use iter_op::{IterItemOp, IterItemOpFactory};
pub use logical_and::{AndItemOp, AndItemOpFactory};
pub use logical_or::{OrItemOp, OrItemOpFactory};
pub use negate::{NegateItemOp, NegateItemOpFactory};
pub use not::{NotItemOp, NotItemOpFactory};
pub use remove_variable::{RemoveItemOp, RemoveItemOpFactory};
pub use retrieve_variable::{VariableRetrieveItemOp, VariableRetrieveItemOpFactory};
pub use string_interpolate::{InterpolateStringItemOp, InterpolateStringItemOpFactory};
pub use subtract::{SubtractItemOp, SubtractItemOpFactory};
pub use variable_assign::{VariableAssignItemOp, VariableAssignItemOpFactory};
pub use variable_declare::{VariableDeclareItemOp, VariableDeclareItemOpFactory};

View file

@ -4,8 +4,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -32,9 +32,14 @@ impl MpsItemOp for NegateItemOp {
fn execute(&self, context: &mut MpsContext) -> Result<MpsType, RuntimeMsg> {
let rhs = self.rhs.execute(context)?;
if let MpsType::Primitive(rhs) = &rhs {
Ok(MpsType::Primitive(rhs.try_negate().map_err(|e| RuntimeMsg(e))?))
Ok(MpsType::Primitive(
rhs.try_negate().map_err(|e| RuntimeMsg(e))?,
))
} else {
Err(RuntimeMsg(format!("Cannot negate `{}` ({}): not primitive type", self.rhs, rhs)))
Err(RuntimeMsg(format!(
"Cannot negate `{}` ({}): not primitive type",
self.rhs, rhs
)))
}
}
}
@ -54,8 +59,6 @@ impl MpsItemOpFactory<NegateItemOp> for NegateItemOpFactory {
) -> Result<NegateItemOp, SyntaxError> {
assert_token_raw(MpsToken::Minus, tokens)?;
let rhs_op = factory.try_build_item_statement(tokens, dict)?;
Ok(NegateItemOp {
rhs: rhs_op,
})
Ok(NegateItemOp { rhs: rhs_op })
}
}

View file

@ -4,8 +4,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -32,9 +32,14 @@ impl MpsItemOp for NotItemOp {
fn execute(&self, context: &mut MpsContext) -> Result<MpsType, RuntimeMsg> {
let rhs = self.rhs.execute(context)?;
if let MpsType::Primitive(rhs) = &rhs {
Ok(MpsType::Primitive(rhs.try_not().map_err(|e| RuntimeMsg(e))?))
Ok(MpsType::Primitive(
rhs.try_not().map_err(|e| RuntimeMsg(e))?,
))
} else {
Err(RuntimeMsg(format!("Cannot apply logical NOT to `{}` ({}): not primitive type", self.rhs, rhs)))
Err(RuntimeMsg(format!(
"Cannot apply logical NOT to `{}` ({}): not primitive type",
self.rhs, rhs
)))
}
}
}
@ -54,8 +59,6 @@ impl MpsItemOpFactory<NotItemOp> for NotItemOpFactory {
) -> Result<NotItemOp, SyntaxError> {
assert_token_raw(MpsToken::Exclamation, tokens)?;
let rhs_op = factory.try_build_item_statement(tokens, dict)?;
Ok(NotItemOp {
rhs: rhs_op,
})
Ok(NotItemOp { rhs: rhs_op })
}
}

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token, check_name, assert_name, assert_token_raw};
use crate::lang::utility::{assert_name, assert_token, assert_token_raw, check_name};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -13,7 +13,7 @@ use crate::MpsContext;
#[derive(Debug)]
pub struct RemoveItemOp {
variable_name: String,
field_name: Option<String>
field_name: Option<String>,
}
impl Deref for RemoveItemOp {
@ -41,7 +41,10 @@ impl MpsItemOp for RemoveItemOp {
item.remove_field(field_name);
Ok(MpsType::empty())
} else {
Err(RuntimeMsg(format!("Cannot access field `{}` on variable `{}` ({} is not Item)", field_name, &self.variable_name, var)))
Err(RuntimeMsg(format!(
"Cannot access field `{}` on variable `{}` ({} is not Item)",
field_name, &self.variable_name, var
)))
}
} else {
context.variables.remove(&self.variable_name)?;
@ -54,7 +57,7 @@ pub struct RemoveItemOpFactory;
impl MpsItemOpFactory<RemoveItemOp> for RemoveItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
(tokens.len() == 2 || tokens.len() == 4)&& check_name("remove", &tokens[0])
(tokens.len() == 2 || tokens.len() == 4) && check_name("remove", &tokens[0])
}
fn build_item_op(
@ -64,19 +67,27 @@ impl MpsItemOpFactory<RemoveItemOp> for RemoveItemOpFactory {
_dict: &MpsLanguageDictionary,
) -> Result<RemoveItemOp, SyntaxError> {
assert_name("remove", tokens)?;
let name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("variable_name".into()), tokens)?;
let name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?;
let field_opt;
if tokens.is_empty() {
field_opt = None;
} else {
assert_token_raw(MpsToken::Dot, tokens)?;
field_opt = Some(assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("field_name".into()), tokens)?);
field_opt = Some(assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("field_name".into()),
tokens,
)?);
}
Ok(RemoveItemOp {
variable_name: name,

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -30,7 +30,6 @@ impl Display for VariableRetrieveItemOp {
} else {
write!(f, "{}", &self.variable_name)
}
}
}
@ -44,13 +43,16 @@ impl MpsItemOp for VariableRetrieveItemOp {
None => MpsType::empty(),
})
} else {
Err(RuntimeMsg(format!("Cannot access field `{}` on variable `{}` ({} is not Item)", field_name, self.variable_name, var)))
Err(RuntimeMsg(format!(
"Cannot access field `{}` on variable `{}` ({} is not Item)",
field_name, self.variable_name, var
)))
}
} else {
match var {
MpsType::Op(op) => Ok(MpsType::Op(op.dup())),
MpsType::Primitive(x) => Ok(MpsType::Primitive(x.clone())),
MpsType::Item(item) => Ok(MpsType::Item(item.clone()))
MpsType::Item(item) => Ok(MpsType::Item(item.clone())),
}
}
}
@ -61,8 +63,10 @@ pub struct VariableRetrieveItemOpFactory;
impl MpsItemOpFactory<VariableRetrieveItemOp> for VariableRetrieveItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
(tokens.len() == 1 && tokens[0].is_name())
||
(tokens.len() == 3 && tokens[0].is_name() && tokens[1].is_dot() && tokens[2].is_name())
|| (tokens.len() == 3
&& tokens[0].is_name()
&& tokens[1].is_dot()
&& tokens[2].is_name())
}
fn build_item_op(
@ -71,19 +75,27 @@ impl MpsItemOpFactory<VariableRetrieveItemOp> for VariableRetrieveItemOpFactory
_factory: &MpsItemBlockFactory,
_dict: &MpsLanguageDictionary,
) -> Result<VariableRetrieveItemOp, SyntaxError> {
let var_name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("variable_name".into()), tokens)?;
let var_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?;
let field_opt;
if tokens.is_empty() {
field_opt = None;
} else {
assert_token_raw(MpsToken::Dot, tokens)?;
field_opt = Some(assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("field_name".into()), tokens)?);
field_opt = Some(assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("field_name".into()),
tokens,
)?);
}
Ok(VariableRetrieveItemOp {
variable_name: var_name,

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -36,7 +36,7 @@ impl MpsItemOp for InterpolateStringItemOp {
MpsType::Primitive(val) => {
let result = self.format.replace("{}", &val.as_str());
Ok(MpsType::Primitive(MpsTypePrimitive::String(result)))
},
}
MpsType::Item(item) => {
let mut result;
if item.len() == 0 {
@ -44,17 +44,23 @@ impl MpsItemOp for InterpolateStringItemOp {
} else {
let mut iter = item.iter();
let field1 = iter.next().unwrap();
result = self.format.replace(&format!("{{{}}}", field1), &item.field(field1).unwrap().as_str());
result = self.format.replace(
&format!("{{{}}}", field1),
&item.field(field1).unwrap().as_str(),
);
for field in iter {
result = result.replace(&format!("{{{}}}", field), &item.field(field).unwrap().as_str());
result = result.replace(
&format!("{{{}}}", field),
&item.field(field).unwrap().as_str(),
);
}
}
Ok(MpsType::Primitive(MpsTypePrimitive::String(result)))
},
}
MpsType::Op(op) => {
let result = self.format.replace("{}", &format!("{}", op));
Ok(MpsType::Primitive(MpsTypePrimitive::String(result)))
},
}
//val => Err(RuntimeMsg(format!("Cannot insert {} ({}) into format string", self.inner_op, val)))
}
//Ok(MpsType::empty())
@ -65,8 +71,7 @@ pub struct InterpolateStringItemOpFactory;
impl MpsItemOpFactory<InterpolateStringItemOp> for InterpolateStringItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
!tokens.is_empty()
&& tokens[0].is_tilde()
!tokens.is_empty() && tokens[0].is_tilde()
}
fn build_item_op(
@ -76,14 +81,18 @@ impl MpsItemOpFactory<InterpolateStringItemOp> for InterpolateStringItemOpFactor
dict: &MpsLanguageDictionary,
) -> Result<InterpolateStringItemOp, SyntaxError> {
assert_token_raw(MpsToken::Tilde, tokens)?;
let format_str = assert_token(|t| match t {
MpsToken::Literal(s) => Some(s),
_ => None,
}, MpsToken::Literal("format_string".into()), tokens)?;
let format_str = assert_token(
|t| match t {
MpsToken::Literal(s) => Some(s),
_ => None,
},
MpsToken::Literal("format_string".into()),
tokens,
)?;
let inner = factory.try_build_item_statement(tokens, dict)?;
Ok(InterpolateStringItemOp {
format: format_str,
inner_op: inner
inner_op: inner,
})
}
}

View file

@ -4,8 +4,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -35,12 +35,20 @@ impl MpsItemOp for SubtractItemOp {
if let MpsType::Primitive(lhs) = &lhs {
let rhs = self.rhs.execute(context)?;
if let MpsType::Primitive(rhs) = &rhs {
Ok(MpsType::Primitive(lhs.try_subtract(rhs).map_err(|e| RuntimeMsg(e))?))
Ok(MpsType::Primitive(
lhs.try_subtract(rhs).map_err(|e| RuntimeMsg(e))?,
))
} else {
Err(RuntimeMsg(format!("Cannot subtract right-hand side `{}` ({}): not primitive type", self.rhs, rhs)))
Err(RuntimeMsg(format!(
"Cannot subtract right-hand side `{}` ({}): not primitive type",
self.rhs, rhs
)))
}
} else {
Err(RuntimeMsg(format!("Cannot subtract left-hand side `{}` ({}): not primitive type", self.lhs, lhs)))
Err(RuntimeMsg(format!(
"Cannot subtract left-hand side `{}` ({}): not primitive type",
self.lhs, lhs
)))
}
}
}

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -50,10 +50,14 @@ impl MpsItemOpFactory<VariableAssignItemOp> for VariableAssignItemOpFactory {
factory: &MpsItemBlockFactory,
dict: &MpsLanguageDictionary,
) -> Result<VariableAssignItemOp, SyntaxError> {
let var_name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("variable_name".into()), tokens)?;
let var_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?;
assert_token_raw(MpsToken::Equals, tokens)?;
let inner_op = factory.try_build_item_statement(tokens, dict)?;
Ok(VariableAssignItemOp {

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType;
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -42,7 +42,9 @@ impl MpsItemOp for VariableDeclareItemOp {
}
} else {
if !context.variables.exists(&self.variable_name) {
context.variables.declare(&self.variable_name, MpsType::empty())?;
context
.variables
.declare(&self.variable_name, MpsType::empty())?;
}
}
Ok(MpsType::empty())
@ -64,10 +66,14 @@ impl MpsItemOpFactory<VariableDeclareItemOp> for VariableDeclareItemOpFactory {
) -> Result<VariableDeclareItemOp, SyntaxError> {
assert_token_raw(MpsToken::Let, tokens)?;
//assert_name("let", tokens)?;
let var_name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("variable_name".into()), tokens)?;
let var_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?;
let inner_op: Option<Box<dyn MpsItemOp>>;
if !tokens.is_empty() {
assert_token_raw(MpsToken::Equals, tokens)?;

View file

@ -211,7 +211,11 @@ impl MpsOp for RepeatStatement {
context: None,
cache: Vec::new(),
cache_position: 0,
repetitions: if self.original_repetitions != 0 {self.original_repetitions-1} else {0},
repetitions: if self.original_repetitions != 0 {
self.original_repetitions - 1
} else {
0
},
loop_forever: self.loop_forever,
original_repetitions: self.original_repetitions,
};

View file

@ -2,7 +2,6 @@ use std::collections::VecDeque;
#[cfg(feature = "bliss-audio")]
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_name, check_name};
use crate::lang::SyntaxError;
#[cfg(feature = "bliss-audio")]
@ -40,7 +39,7 @@ impl Default for BlissNextSorter {
up_to: usize::MAX,
algorithm_done: false,
init_done: false,
item_buf: VecDeque::new()
item_buf: VecDeque::new(),
}
}
}
@ -73,8 +72,8 @@ impl MpsSorter for BlissNextSorter {
Err(e) => {
iterator.enter(ctx);
return Err(e);
},
Ok(_) => {},
}
Ok(_) => {}
}
}
iterator.enter(ctx);
@ -92,13 +91,13 @@ impl MpsSorter for BlissNextSorter {
Err(e) => {
iterator.enter(ctx);
return Err(e);
},
}
Ok(distance) => {
if distance < best_distance {
best_index = i;
best_distance = distance;
}
},
}
}
}
if best_index != 0 {
@ -112,8 +111,8 @@ impl MpsSorter for BlissNextSorter {
Err(e) => {
iterator.enter(ctx);
return Err(e);
},
Ok(_) => {},
}
Ok(_) => {}
}
}
iterator.enter(ctx);

View file

@ -75,19 +75,20 @@ impl MpsSorter for BlissSorter {
let mut ctx = iterator.escape();
for i in 0..item_buf.len() {
if let Ok(item) = &item_buf[i] {
if item == first {continue;}
if item == first {
continue;
}
match ctx.analysis.prepare_distance(first, item) {
Err(e) => {
iterator.enter(ctx);
return Err(e);
},
Ok(_) => {},
}
Ok(_) => {}
}
}
}
iterator.enter(ctx);
}
} else if self.first_song.is_some() {
// Sort songs on second call to this function
let first = self.first_song.take().unwrap();
@ -96,15 +97,17 @@ impl MpsSorter for BlissSorter {
let mut ctx = iterator.escape();
for i in 0..item_buf.len() {
if let Ok(item) = &item_buf[i] {
if item == &first {continue;}
if item == &first {
continue;
}
match ctx.analysis.get_distance(&first, item) {
Err(e) => {
iterator.enter(ctx);
return Err(e);
},
}
Ok(distance) => {
cache.insert(item.clone(), distance);
},
}
}
}
}

View file

@ -10,7 +10,7 @@ use crate::lang::repeated_tokens;
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsOp};
use crate::lang::{PseudoOp, RuntimeOp, SyntaxError, RuntimeError};
use crate::lang::{PseudoOp, RuntimeError, RuntimeOp, SyntaxError};
#[derive(Debug)]
pub struct SqlInitStatement {

View file

@ -140,7 +140,10 @@ impl MpsOp for AssignStatement {
fn dup(&self) -> Box<dyn MpsOp> {
Box::new(Self {
variable_name: self.variable_name.clone(),
inner_statement: self.inner_statement.as_ref().map(|x| PseudoOp::from(x.try_real_ref().unwrap().dup())),
inner_statement: self
.inner_statement
.as_ref()
.map(|x| PseudoOp::from(x.try_real_ref().unwrap().dup())),
assign_type: self.assign_type.clone(),
context: None,
is_declaration: self.is_declaration,

View file

@ -167,6 +167,64 @@
//!
//! Sort by the distance (similarity) between the last played song in the iterator. Similar to bliss_first. The song which is the most similar (lower distance) to the previous song in the iterator will be placed next to it, then the process is repeated. This uses the [bliss music analyser](https://github.com/polochon-street/bliss-rs), which is a very slow operation and can cause music playback interruptions for large iterators. This requires the `advanced` feature to be enabled (without the feature enabled this is still valid syntax but doesn't change the order).
//!
//! ## Procedures
//! Operations to apply to each item in an iterable: iterable.{step1, step2, ...}
//!
//! Comma-separated procedure steps will be executed sequentially (like a for loop in regular programming languages). The variable item contains the current item of the iterable.
//!
//! ### let variable = something -- e.g. let my_var = 42
//!
//! Declare the variable and (optionally) set the initial value to something. The assignment will only be performed when the variable has not yet been declared. When the initial value (and equals sign) is omitted, the variable is initialized as empty().
//!
//! ### variable = something -- e.g. my_var = 42
//!
//! Assign something to the variable. The variable must have already been declared.
//!
//! ### empty() -- e.g. empty()
//!
//! The empty or null constant.
//!
//! ### if condition { something } else { something_else } -- e.g.
//! ```mps
//! if item.title == `Romantic Traffic` {
//!
//! } else {
//! remove item
//! }
//! ```
//!
//! Branch based on a boolean condition. Multiple comma-separated procedure steps may be supplied in the if and else branches. This does not currently support if else chains, but they can be nested to accomplish similar behaviour.
//!
//! ### something1 == something2
//! ### something1 != something2
//! ### something1 >= something2
//! ### something1 > something2
//! ### something1 <= something2
//! ### something1 < something2 -- e.g. item.filename != item.title
//!
//! Compare something1 to something2. The result is a boolean which is useful for branch conditions.
//!
//! ### op iterable_operation -- e.g. op files().(0..=42)~(shuffle)
//!
//! An iterable operation inside of the procedure. When assigned to item, this can be used to replace item with multiple others. Note that iterable operations are never executed inside the procedure; when item is iterable, it will be executed immediately after the end of the procedure for the current item.
//!
//! ### (something1)
//! ### -something1
//! ### something1 - something2
//! ### something1 + something2
//! ### something1 || something2
//! ### something1 && something2 -- e.g. 42 + (128 - 64)
//!
//! Various algebraic operations: brackets (order of operations), negation, subtraction, addition, logical OR, logical AND; respectively.
//!
//! ### Item(field1 = something1, field2 = something2, ...) - e.g. item = Item(title = item.title, filename = `/dev/null`)
//!
//! Constructor for a new item. Each function parameter defines a new field and it's value.
//! ### ~`string_format` something -- e.g. ~`{filename}` item
//!
//! 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.
//!
mod context;
mod interpretor;

View file

@ -17,5 +17,5 @@ pub mod general {
#[cfg(feature = "advanced")]
pub mod advanced {
pub use super::music_analysis::{MpsMusicAnalyzer, MpsDefaultAnalyzer};
pub use super::music_analysis::{MpsDefaultAnalyzer, MpsMusicAnalyzer};
}

View file

@ -2,12 +2,12 @@ use core::fmt::Debug;
#[cfg(feature = "bliss-audio")]
use std::collections::{HashMap, HashSet};
#[cfg(feature = "bliss-audio")]
use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::mpsc::{channel, Receiver, Sender};
#[cfg(feature = "bliss-audio")]
use bliss_audio::{Song, BlissError};
#[cfg(feature = "bliss-audio")]
use crate::lang::MpsTypePrimitive;
#[cfg(feature = "bliss-audio")]
use bliss_audio::{BlissError, Song};
use crate::lang::RuntimeMsg;
use crate::MpsItem;
@ -49,16 +49,21 @@ impl std::default::Default for MpsDefaultAnalyzer {
#[cfg(feature = "bliss-audio")]
impl MpsDefaultAnalyzer {
fn request_distance(&mut self, from: &MpsItem, to: &MpsItem, ack: bool) -> Result<(), RuntimeMsg> {
fn request_distance(
&mut self,
from: &MpsItem,
to: &MpsItem,
ack: bool,
) -> Result<(), RuntimeMsg> {
let path_from = Self::get_path(from)?;
let path_to = Self::get_path(to)?;
self.requests.send(
RequestType::Distance {
self.requests
.send(RequestType::Distance {
path1: path_from.to_owned(),
path2: path_to.to_owned(),
ack: ack,
}
).map_err(|e| RuntimeMsg(format!("Channel send err {}", e)))
})
.map_err(|e| RuntimeMsg(format!("Channel send err {}", e)))
}
fn get_path(item: &MpsItem) -> Result<&str, RuntimeMsg> {
@ -66,7 +71,10 @@ impl MpsDefaultAnalyzer {
if let MpsTypePrimitive::String(path) = path {
Ok(path)
} else {
Err(RuntimeMsg(format!("Field {} on item is not String, it's {}", PATH_FIELD, path)))
Err(RuntimeMsg(format!(
"Field {} on item is not String, it's {}",
PATH_FIELD, path
)))
}
} else {
Err(RuntimeMsg(format!("Missing field {} on item", PATH_FIELD)))
@ -75,12 +83,12 @@ impl MpsDefaultAnalyzer {
fn request_song(&mut self, item: &MpsItem, ack: bool) -> Result<(), RuntimeMsg> {
let path = Self::get_path(item)?;
self.requests.send(
RequestType::Song {
self.requests
.send(RequestType::Song {
path: path.to_owned(),
ack: ack,
}
).map_err(|e| RuntimeMsg(format!("Channel send error: {}", e)))
})
.map_err(|e| RuntimeMsg(format!("Channel send error: {}", e)))
}
}
@ -99,22 +107,29 @@ impl MpsMusicAnalyzer for MpsDefaultAnalyzer {
let path_from = Self::get_path(from)?;
let path_to = Self::get_path(to)?;
for response in self.responses.iter() {
if let ResponseType::Distance{path1, path2, distance} = response {
if let ResponseType::Distance {
path1,
path2,
distance,
} = response
{
if path1 == path_from && path2 == path_to {
return match distance {
Ok(d) => Ok(d as f64),
Err(e) => Err(RuntimeMsg(format!("Bliss error: {}", e)))
Err(e) => Err(RuntimeMsg(format!("Bliss error: {}", e))),
};
}
}
}
Err(RuntimeMsg("Channel closed without response: internal error".to_owned()))
Err(RuntimeMsg(
"Channel closed without response: internal error".to_owned(),
))
}
fn clear_cache(&mut self) -> Result<(), RuntimeMsg> {
self.requests.send(
RequestType::Clear {}
).map_err(|e| RuntimeMsg(format!("Channel send error: {}", e)))
self.requests
.send(RequestType::Clear {})
.map_err(|e| RuntimeMsg(format!("Channel send error: {}", e)))
}
}
@ -191,10 +206,14 @@ impl CacheThread {
fn non_blocking_read_some(&mut self, results: &Receiver<ResponseType>) {
for result in results.try_iter() {
match result {
ResponseType::Distance {path1, path2, distance} => {
ResponseType::Distance {
path1,
path2,
distance,
} => {
self.insert_distance(path1, path2, distance);
},
ResponseType::Song {path, song} => {
}
ResponseType::Song { path, song } => {
self.insert_song(path, song);
}
}
@ -206,21 +225,35 @@ impl CacheThread {
self.song_cache.insert(path, song_result);
}
fn insert_distance(&mut self, path1: String, path2: String, distance_result: Result<f32, BlissError>) {
fn insert_distance(
&mut self,
path1: String,
path2: String,
distance_result: Result<f32, BlissError>,
) {
let key = (path1, path2);
self.distance_in_progress.remove(&key);
self.distance_cache.insert(key, distance_result);
}
fn get_song_option(&mut self, path: &str, auto_add: bool, results: &Receiver<ResponseType>) -> Option<Song> {
fn get_song_option(
&mut self,
path: &str,
auto_add: bool,
results: &Receiver<ResponseType>,
) -> Option<Song> {
// wait for song if already in progress
if self.song_in_progress.contains(path) {
for result in results.iter() {
match result {
ResponseType::Distance {path1, path2, distance} => {
ResponseType::Distance {
path1,
path2,
distance,
} => {
self.insert_distance(path1, path2, distance);
},
ResponseType::Song {path: path2, song} => {
}
ResponseType::Song { path: path2, song } => {
if path2 == path {
self.insert_song(path2, song.clone());
let result = song.ok();
@ -235,7 +268,10 @@ impl CacheThread {
}
}
} else if self.song_cache.contains_key(path) {
let result = self.song_cache.get(path).and_then(|r| r.clone().ok().to_owned());
let result = self
.song_cache
.get(path)
.and_then(|r| r.clone().ok().to_owned());
if result.is_none() && auto_add {
self.song_in_progress.insert(path.to_owned());
}
@ -247,7 +283,8 @@ impl CacheThread {
return None;
}
fn handle_distance_req(&mut self,
fn handle_distance_req(
&mut self,
path1: String,
path2: String,
ack: bool,
@ -258,13 +295,13 @@ impl CacheThread {
if let Some(result) = self.distance_cache.get(&key) {
if ack {
let result = result.to_owned();
if let Err(_) = self.responses.send(
ResponseType::Distance {
path1: path1,
path2: path2,
distance: result,
}
) { return true; }
if let Err(_) = self.responses.send(ResponseType::Distance {
path1: path1,
path2: path2,
distance: result,
}) {
return true;
}
}
} else {
if path1 == path2 {
@ -277,43 +314,51 @@ impl CacheThread {
path1: path1,
path2: path2,
distance: Ok(0.0),
}) { return true; }
}) {
return true;
}
}
} else if !self.distance_in_progress.contains(&key) {
let results = worker_tx.clone();
let song1_clone = self.get_song_option(&path1, true, worker_results);
let song2_clone = self.get_song_option(&path2, true, worker_results);
std::thread::spawn(move || {
let distance_result = worker_distance(
&results,
(&path1, song1_clone),
(&path2, song2_clone),
);
results.send(
ResponseType::Distance {
let distance_result =
worker_distance(&results, (&path1, song1_clone), (&path2, song2_clone));
results
.send(ResponseType::Distance {
path1: path1,
path2: path2,
distance: distance_result,
}
).unwrap_or(());
})
.unwrap_or(());
});
}
if ack {
'inner1: for result in worker_results.iter() {
match result {
ResponseType::Distance {path1: path1_2, path2: path2_2, distance} => {
self.insert_distance(path1_2.clone(), path2_2.clone(), distance.clone());
ResponseType::Distance {
path1: path1_2,
path2: path2_2,
distance,
} => {
self.insert_distance(
path1_2.clone(),
path2_2.clone(),
distance.clone(),
);
if path1_2 == key.0 && path2_2 == key.1 {
if let Err(_) = self.responses.send(ResponseType::Distance {
path1: path1_2,
path2: path2_2,
distance: distance,
}) { return true; }
}) {
return true;
}
break 'inner1;
}
},
ResponseType::Song {path, song} => {
}
ResponseType::Song { path, song } => {
self.insert_song(path, song);
}
}
@ -323,7 +368,8 @@ impl CacheThread {
false
}
fn handle_song_req(&mut self,
fn handle_song_req(
&mut self,
path: String,
ack: bool,
worker_tx: &Sender<ResponseType>,
@ -332,12 +378,12 @@ impl CacheThread {
if let Some(song) = self.song_cache.get(&path) {
if ack {
let song = song.to_owned();
if let Err(_) = self.responses.send(
ResponseType::Song {
path: path,
song: song,
}
) { return true; }
if let Err(_) = self.responses.send(ResponseType::Song {
path: path,
song: song,
}) {
return true;
}
}
} else {
if !self.song_in_progress.contains(&path) {
@ -345,27 +391,33 @@ impl CacheThread {
let results = worker_tx.clone();
std::thread::spawn(move || {
let song_result = Song::new(&path_clone);
results.send(
ResponseType::Song {
results
.send(ResponseType::Song {
path: path_clone,
song: song_result,
}
).unwrap_or(());
})
.unwrap_or(());
});
}
if ack {
'inner2: for result in worker_results.iter() {
match result {
ResponseType::Distance {path1, path2, distance} => {
ResponseType::Distance {
path1,
path2,
distance,
} => {
self.insert_distance(path1, path2, distance);
},
ResponseType::Song {path: path2, song} => {
}
ResponseType::Song { path: path2, song } => {
self.insert_song(path2.clone(), song.clone());
if path2 == path {
if let Err(_) = self.responses.send(ResponseType::Song {
path: path,
song: song,
}) { return false; }
}) {
return false;
}
break 'inner2;
}
}
@ -376,23 +428,23 @@ impl CacheThread {
false
}
fn run_loop(&mut self, requests: Receiver<RequestType>,) {
fn run_loop(&mut self, requests: Receiver<RequestType>) {
let (worker_tx, worker_results): (Sender<ResponseType>, Receiver<ResponseType>) = channel();
'outer: for request in requests.iter() {
self.non_blocking_read_some(&worker_results);
match request {
//RequestType::End{} => break,
RequestType::Distance{path1, path2, ack} => {
RequestType::Distance { path1, path2, ack } => {
if self.handle_distance_req(path1, path2, ack, &worker_tx, &worker_results) {
break 'outer;
}
},
RequestType::Song{path, ack} => {
}
RequestType::Song { path, ack } => {
if self.handle_song_req(path, ack, &worker_tx, &worker_results) {
break 'outer;
}
},
RequestType::Clear{} => {
}
RequestType::Clear {} => {
self.distance_cache.clear();
self.song_cache.clear();
}
@ -402,16 +454,22 @@ impl CacheThread {
}
#[cfg(feature = "bliss-audio")]
fn worker_distance(results: &Sender<ResponseType>, song1: (&str, Option<Song>), song2: (&str, Option<Song>)) -> Result<f32, BlissError> {
fn worker_distance(
results: &Sender<ResponseType>,
song1: (&str, Option<Song>),
song2: (&str, Option<Song>),
) -> Result<f32, BlissError> {
let path1 = song1.0;
let song1 = if let Some(song) = song1.1 {
song
} else {
let new_song1 = Song::new(path1);
results.send(ResponseType::Song {
path: path1.to_string(),
song: new_song1.clone(),
}).unwrap_or(());
results
.send(ResponseType::Song {
path: path1.to_string(),
song: new_song1.clone(),
})
.unwrap_or(());
new_song1?
};
let path2 = song2.0;
@ -419,10 +477,12 @@ fn worker_distance(results: &Sender<ResponseType>, song1: (&str, Option<Song>),
song
} else {
let new_song2 = Song::new(path2);
results.send(ResponseType::Song {
path: path2.to_string(),
song: new_song2.clone(),
}).unwrap_or(());
results
.send(ResponseType::Song {
path: path2.to_string(),
song: new_song2.clone(),
})
.unwrap_or(());
new_song2?
};
Ok(song1.distance(&song2))

View file

@ -261,9 +261,8 @@ impl ReaderStateMachine {
'\n' | '\r' | '\t' | ' ' => Self::EndToken {},
';' => Self::EndStatement {},
'\0' => Self::EndOfFile {},
'(' | ')' | ',' | '=' | '<' | '>' | '.' | '!' | '?' | '|' | '&' | ':' | '{' | '}' | '+' | '-' | '~' => {
Self::SingleCharToken { out: input }
}
'(' | ')' | ',' | '=' | '<' | '>' | '.' | '!' | '?' | '|' | '&' | ':' | '{'
| '}' | '+' | '-' | '~' => Self::SingleCharToken { out: input },
_ => Self::Regular { out: input },
},
Self::Escaped { inside } => match inside {

View file

@ -45,7 +45,10 @@ fn execute_single_line(
should_complete: bool,
) -> Result<(), Box<dyn MpsLanguageError>> {
if line.contains('\n') {
println!("--- Executing MPS code ---\n{}\n--- Executing MPS code ---", line);
println!(
"--- Executing MPS code ---\n{}\n--- Executing MPS code ---",
line
);
} else {
println!("--- Executing MPS code: '{}' ---", line);
}

View file

@ -2,7 +2,7 @@
pub const HELP_STRING: &str =
"This language is all about iteration. Almost everything is an iterator or operates on iterators. By default, any operation which is not an assignment will cause the script runner to handle (play/save) the items which that statement contains.
To view the currently-supported operations, try ?functions, ?filters, or ?sorters";
To view the currently-supported operations, try ?functions, ?filters, ?procedures, or ?sorters";
pub const FUNCTIONS: &str =
"FUNCTIONS (?functions)
@ -96,3 +96,50 @@ Operations to sort the items in an iterable: iterable~(sorter) OR iterable.sort(
advanced bliss_next -- e.g. iterable~(advanced bliss_next)
Sort by the distance (similarity) between the last played song in the iterator. Similar to bliss_first. Songs which are more similar (lower distance) to the first song in the iterator will be placed closer to the first song, while less similar songs will be sorted to the end. This uses the bliss music analyser, which is a very slow operation and can cause music playback interruptions for large iterators. Requires `advanced` mps-interpreter feature.";
pub const PROCEDURES: &str =
"PROCEDURES (?procedures)
Operations to apply to each item in an iterable: iterable.{step1, step2, ...}
Comma-separated procedure steps will be executed sequentially (like a for loop in regular programming languages). The variable item contains the current item of the iterable.
let variable = something -- e.g. let my_var = 42
Declare the variable and (optionally) set the initial value to something. The assignment will only be performed when the variable has not yet been declared. When the initial value (and equals sign) is omitted, the variable is initialized as empty().
variable = something -- e.g. my_var = 42
Assign something to the variable. The variable must have already been declared.
empty() -- e.g. empty()
The empty or null constant.
if condition { something } else { something_else } -- e.g.
if item.title == `Romantic Traffic` {
} else {
remove item
}
Branch based on a boolean condition. Multiple comma-separated procedure steps may be supplied in the if and else branches. This does not currently support if else chains, but they can be nested to accomplish similar behaviour.
something1 == something2
something1 != something2
something1 >= something2
something1 > something2
something1 <= something2
something1 < something2 -- e.g. item.filename != item.title
Compare something1 to something2. The result is a boolean which is useful for branch conditions.
op iterable_operation -- e.g. op files().(0..=42)~(shuffle)
An iterable operation inside of the procedure. When assigned to item, this can be used to replace item with multiple others. Note that iterable operations are never executed inside the procedure; when item is iterable, it will be executed immediately after the end of the procedure for the current item.
(something1)
-something1
something1 - something2
something1 + something2
something1 || something2
something1 && something2 -- e.g. 42 + (128 - 64)
Various algebraic operations: brackets (order of operations), negation, subtraction, addition, logical OR, logical AND; respectively.
Item(field1 = something1, field2 = something2, ...) - e.g. item = Item(title = item.title, filename = `/dev/null`)
Constructor for a new item. Each function parameter defines a new field and it's value.
~`string_format` something -- e.g. ~`{filename}` item
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.";

View file

@ -171,6 +171,7 @@ fn repl_commands(command_str: &str) {
"?function" | "?functions" => println!("{}", super::help::FUNCTIONS),
"?filter" | "?filters" => println!("{}", super::help::FILTERS),
"?sort" | "?sorter" | "?sorters" => println!("{}", super::help::SORTERS),
"?proc" | "?procedure" | "?procedures" => println!("{}", super::help::PROCEDURES),
_ => println!("Unknown command, try ?help"),
}
}