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). 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::database::{MpsDatabaseQuerier, MpsSQLiteExecutor};
use super::processing::general::{ use super::processing::general::{
MpsFilesystemExecutor, MpsFilesystemQuerier, MpsOpStorage, MpsVariableStorer, MpsFilesystemExecutor, MpsFilesystemQuerier, MpsOpStorage, MpsVariableStorer,
}; };
#[cfg(feature = "advanced")]
use super::processing::advanced::{
MpsMusicAnalyzer, MpsDefaultAnalyzer
};
use std::fmt::{Debug, Display, Error, Formatter}; use std::fmt::{Debug, Display, Error, Formatter};
#[derive(Debug)] #[derive(Debug)]

View file

@ -184,7 +184,7 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
.add(crate::lang::vocabulary::item_ops::SubtractItemOpFactory) .add(crate::lang::vocabulary::item_ops::SubtractItemOpFactory)
.add(crate::lang::vocabulary::item_ops::OrItemOpFactory) .add(crate::lang::vocabulary::item_ops::OrItemOpFactory)
.add(crate::lang::vocabulary::item_ops::AndItemOpFactory) .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 and misc
// functions don't enforce bracket coherence // functions don't enforce bracket coherence

View file

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

View file

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

View file

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

View file

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

View file

@ -85,28 +85,46 @@ impl MpsTypePrimitive {
match self { match self {
Self::String(s) => match other { Self::String(s) => match other {
Self::String(other_s) => Ok(Self::String(s.to_owned() + other_s)), 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(i) => match other {
Self::Int(other_i) => Ok(Self::Int(i + other_i)), Self::Int(other_i) => Ok(Self::Int(i + other_i)),
Self::UInt(u) => Ok(Self::Int(i + *u as i64)), Self::UInt(u) => Ok(Self::Int(i + *u as i64)),
Self::Float(f) => Ok(Self::Float(*i as f64 + f)), 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(u) => match other {
Self::UInt(other_u) => Ok(Self::UInt(u + other_u)), Self::UInt(other_u) => Ok(Self::UInt(u + other_u)),
Self::Int(i) => Ok(Self::UInt(u + *i as u64)), Self::Int(i) => Ok(Self::UInt(u + *i as u64)),
Self::Float(f) => Ok(Self::Float(*u as f64 + f)), 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(f) => match other {
Self::Float(other_f) => Ok(Self::Float(f + other_f)), Self::Float(other_f) => Ok(Self::Float(f + other_f)),
Self::Int(i) => Ok(Self::Float(f + *i as f64)), Self::Int(i) => Ok(Self::Float(f + *i as f64)),
Self::UInt(u) => Ok(Self::Float(f + *u 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::Bool(_) => Err(format!(
Self::Empty => Err(format!("Cannot add {} and {}: incompatible types", self, other)), "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> { pub fn try_not(&self) -> Result<Self, String> {
match self { match self {
Self::Bool(b) => Ok(Self::Bool(!*b)), 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 => match other {
Self::Empty => Some(std::cmp::Ordering::Equal), Self::Empty => Some(std::cmp::Ordering::Equal),
_ => None, _ => None,
} },
} }
} }
} }
@ -222,7 +243,7 @@ impl std::hash::Hash for MpsTypePrimitive {
Self::UInt(u) => u.hash(state), Self::UInt(u) => u.hash(state),
Self::Float(f_) => (*f_ as u64).hash(state), Self::Float(f_) => (*f_ as u64).hash(state),
Self::Bool(b) => b.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, init_needed: true,
}; };
for op in self.ops.iter() { 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) 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::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -35,12 +35,20 @@ impl MpsItemOp for AddItemOp {
if let MpsType::Primitive(lhs) = &lhs { if let MpsType::Primitive(lhs) = &lhs {
let rhs = self.rhs.execute(context)?; let rhs = self.rhs.execute(context)?;
if let MpsType::Primitive(rhs) = &rhs { 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 { } 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 { } 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::collections::VecDeque;
use std::convert::AsRef;
use crate::lang::utility::{assert_token_raw, assert_token_raw_back}; use crate::lang::utility::{assert_token_raw, assert_token_raw_back};
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -13,7 +13,9 @@ pub struct BracketsItemOpFactory;
impl MpsItemOpFactory<Box<dyn MpsItemOp>> for BracketsItemOpFactory { impl MpsItemOpFactory<Box<dyn MpsItemOp>> for BracketsItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool { 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( fn build_item_op(

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter}; 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::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -76,7 +76,10 @@ impl MpsItemOp for BranchItemOp {
Ok(MpsType::empty()) Ok(MpsType::empty())
} }
} else { } 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() { for i in 0..tokens.len() {
match &tokens[i] { match &tokens[i] {
MpsToken::OpenBracket => bracket_depth += 1, MpsToken::OpenBracket => bracket_depth += 1,
MpsToken::CloseBracket => if bracket_depth != 0 { MpsToken::CloseBracket => {
if bracket_depth != 0 {
bracket_depth -= 1; bracket_depth -= 1;
}, }
MpsToken::OpenCurly => if bracket_depth == 0 { }
MpsToken::OpenCurly => {
if bracket_depth == 0 {
return Some(i); return Some(i);
}, }
_ => {}, }
_ => {}
} }
} }
None None
@ -201,16 +208,20 @@ fn next_curly_close_bracket(tokens: &VecDeque<MpsToken>) -> Option<usize> {
for i in 0..tokens.len() { for i in 0..tokens.len() {
match &tokens[i] { match &tokens[i] {
MpsToken::OpenBracket => bracket_depth += 1, MpsToken::OpenBracket => bracket_depth += 1,
MpsToken::CloseBracket => if bracket_depth != 0 { MpsToken::CloseBracket => {
if bracket_depth != 0 {
bracket_depth -= 1; bracket_depth -= 1;
}, }
}
MpsToken::OpenCurly => curly_depth += 1, MpsToken::OpenCurly => curly_depth += 1,
MpsToken::CloseCurly => if bracket_depth == 0 && curly_depth == 0 { MpsToken::CloseCurly => {
if bracket_depth == 0 && curly_depth == 0 {
return Some(i); return Some(i);
} else if curly_depth != 0 { } else if curly_depth != 0 {
curly_depth -= 1; curly_depth -= 1;
}, }
_ => {}, }
_ => {}
} }
} }
None None

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter}; 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::{MpsLanguageDictionary, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError}; 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::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -26,7 +26,13 @@ impl Deref for CompareItemOp {
impl Display for CompareItemOp { impl Display for CompareItemOp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 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))) Ok(MpsType::Primitive(MpsTypePrimitive::Bool(is_match)))
} else { } 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 { } 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,19 +99,31 @@ fn find_first_comparison(tokens: &VecDeque<MpsToken>) -> Option<usize> {
for i in 0..tokens.len() { for i in 0..tokens.len() {
match &tokens[i] { match &tokens[i] {
MpsToken::OpenCurly => curly_depth += 1, MpsToken::OpenCurly => curly_depth += 1,
MpsToken::CloseCurly => if curly_depth != 0 { MpsToken::CloseCurly => {
if curly_depth != 0 {
curly_depth -= 1; curly_depth -= 1;
}, }
}
MpsToken::OpenBracket => bracket_depth += 1, MpsToken::OpenBracket => bracket_depth += 1,
MpsToken::CloseBracket => if bracket_depth != 0 { MpsToken::CloseBracket => {
if bracket_depth != 0 {
curly_depth -= 1; curly_depth -= 1;
}, }
MpsToken::OpenAngleBracket | MpsToken::CloseAngleBracket => if curly_depth == 0 && bracket_depth == 0 { }
MpsToken::OpenAngleBracket | MpsToken::CloseAngleBracket => {
if curly_depth == 0 && bracket_depth == 0 {
return Some(i); return Some(i);
}, }
MpsToken::Equals | MpsToken::Exclamation => if curly_depth == 0 && bracket_depth == 0 && i+1 != tokens.len() && tokens[i+1].is_equals() { }
MpsToken::Equals | MpsToken::Exclamation => {
if curly_depth == 0
&& bracket_depth == 0
&& i + 1 != tokens.len()
&& tokens[i + 1].is_equals()
{
return Some(i); return Some(i);
} }
}
_ => {} _ => {}
} }
} }

View file

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

View file

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

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter}; 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::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter}; 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::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -26,7 +26,11 @@ impl Deref for FieldAssignItemOp {
impl Display for FieldAssignItemOp { impl Display for FieldAssignItemOp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 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); var.set_field(&self.field_name, val);
Ok(MpsType::empty()) Ok(MpsType::empty())
} else { } 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 { } 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 { impl MpsItemOpFactory<FieldAssignItemOp> for FieldAssignItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool { 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() > 4
|| && tokens[0].is_name()
(tokens.len() > 3 && tokens[0].is_dot() && tokens[1].is_name() && tokens[2].is_equals()) && 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( fn build_item_op(
@ -66,16 +82,24 @@ impl MpsItemOpFactory<FieldAssignItemOp> for FieldAssignItemOpFactory {
if tokens[0].is_dot() { if tokens[0].is_dot() {
var_name = "item".to_string(); var_name = "item".to_string();
} else { } else {
var_name = assert_token(|t| match t { var_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s), MpsToken::Name(s) => Some(s),
_ => None, _ => None,
}, MpsToken::Name("variable_name".into()), tokens)? },
MpsToken::Name("variable_name".into()),
tokens,
)?
} }
assert_token_raw(MpsToken::Dot, tokens)?; assert_token_raw(MpsToken::Dot, tokens)?;
let f_name = assert_token(|t| match t { let f_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s), MpsToken::Name(s) => Some(s),
_ => None, _ => None,
}, MpsToken::Name("field_name".into()), tokens)?; },
MpsToken::Name("field_name".into()),
tokens,
)?;
assert_token_raw(MpsToken::Equals, tokens)?; assert_token_raw(MpsToken::Equals, tokens)?;
let inner_op = factory.try_build_item_statement(tokens, dict)?; let inner_op = factory.try_build_item_statement(tokens, dict)?;
Ok(FieldAssignItemOp { Ok(FieldAssignItemOp {

View file

@ -2,10 +2,10 @@ use core::ops::Deref;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter}; 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::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsOp};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsOp};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -38,8 +38,7 @@ pub struct IterItemOpFactory;
impl MpsItemOpFactory<IterItemOp> for IterItemOpFactory { impl MpsItemOpFactory<IterItemOp> for IterItemOpFactory {
fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool { fn is_item_op(&self, tokens: &VecDeque<MpsToken>) -> bool {
!tokens.is_empty() !tokens.is_empty() && check_name("iter", &tokens[0])
&& check_name("iter", &tokens[0])
} }
fn build_item_op( fn build_item_op(
@ -50,8 +49,6 @@ impl MpsItemOpFactory<IterItemOp> for IterItemOpFactory {
) -> Result<IterItemOp, SyntaxError> { ) -> Result<IterItemOp, SyntaxError> {
assert_name("iter", tokens)?; assert_name("iter", tokens)?;
let inner_op = dict.try_build_statement(tokens)?; let inner_op = dict.try_build_statement(tokens)?;
Ok(IterItemOp { Ok(IterItemOp { inner: inner_op })
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::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -41,10 +41,16 @@ impl MpsItemOp for AndItemOp {
if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs { if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs {
Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs))) Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs)))
} else { } 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 { } 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> { fn first_and(tokens: &VecDeque<MpsToken>) -> Option<usize> {
let mut bracket_depth = 0; let mut bracket_depth = 0;
for i in 0..tokens.len()-1 { for i in 0..tokens.len() - 1 {
let token = &tokens[i]; 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); return Some(i);
} else if token.is_open_bracket() { } else if token.is_open_bracket() {
bracket_depth += 1; 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::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory, MpsTypePrimitive};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -41,10 +41,16 @@ impl MpsItemOp for OrItemOp {
if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs { if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs {
Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs))) Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs)))
} else { } 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 { } 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> { fn first_or(tokens: &VecDeque<MpsToken>) -> Option<usize> {
let mut bracket_depth = 0; let mut bracket_depth = 0;
for i in 0..tokens.len()-1 { for i in 0..tokens.len() - 1 {
let token = &tokens[i]; 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); return Some(i);
} else if token.is_open_bracket() { } else if token.is_open_bracket() {
bracket_depth += 1; bracket_depth += 1;

View file

@ -18,22 +18,22 @@ mod subtract;
mod variable_assign; mod variable_assign;
mod variable_declare; mod variable_declare;
pub use add::{AddItemOpFactory, AddItemOp}; pub use add::{AddItemOp, AddItemOpFactory};
pub use brackets::BracketsItemOpFactory; pub use brackets::BracketsItemOpFactory;
pub use branch::{BranchItemOpFactory, BranchItemOp}; pub use branch::{BranchItemOp, BranchItemOpFactory};
pub use constant::{ConstantItemOpFactory, ConstantItemOp}; pub use compare::{CompareItemOp, CompareItemOpFactory};
pub use compare::{CompareItemOpFactory, CompareItemOp}; pub use constant::{ConstantItemOp, ConstantItemOpFactory};
pub use constructor::{ConstructorItemOpFactory, ConstructorItemOp}; pub use constructor::{ConstructorItemOp, ConstructorItemOpFactory};
pub use empty::{EmptyItemOpFactory, EmptyItemOp}; pub use empty::{EmptyItemOp, EmptyItemOpFactory};
pub use field_assign::{FieldAssignItemOpFactory, FieldAssignItemOp}; pub use field_assign::{FieldAssignItemOp, FieldAssignItemOpFactory};
pub use iter_op::{IterItemOpFactory, IterItemOp}; pub use iter_op::{IterItemOp, IterItemOpFactory};
pub use logical_and::{AndItemOpFactory, AndItemOp}; pub use logical_and::{AndItemOp, AndItemOpFactory};
pub use logical_or::{OrItemOpFactory, OrItemOp}; pub use logical_or::{OrItemOp, OrItemOpFactory};
pub use negate::{NegateItemOpFactory, NegateItemOp}; pub use negate::{NegateItemOp, NegateItemOpFactory};
pub use not::{NotItemOpFactory, NotItemOp}; pub use not::{NotItemOp, NotItemOpFactory};
pub use remove_variable::{RemoveItemOpFactory, RemoveItemOp}; pub use remove_variable::{RemoveItemOp, RemoveItemOpFactory};
pub use retrieve_variable::{VariableRetrieveItemOpFactory, VariableRetrieveItemOp}; pub use retrieve_variable::{VariableRetrieveItemOp, VariableRetrieveItemOpFactory};
pub use string_interpolate::{InterpolateStringItemOpFactory, InterpolateStringItemOp}; pub use string_interpolate::{InterpolateStringItemOp, InterpolateStringItemOpFactory};
pub use subtract::{SubtractItemOpFactory, SubtractItemOp}; pub use subtract::{SubtractItemOp, SubtractItemOpFactory};
pub use variable_assign::{VariableAssignItemOpFactory, VariableAssignItemOp}; pub use variable_assign::{VariableAssignItemOp, VariableAssignItemOpFactory};
pub use variable_declare::{VariableDeclareItemOpFactory, VariableDeclareItemOp}; 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::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -32,9 +32,14 @@ impl MpsItemOp for NegateItemOp {
fn execute(&self, context: &mut MpsContext) -> Result<MpsType, RuntimeMsg> { fn execute(&self, context: &mut MpsContext) -> Result<MpsType, RuntimeMsg> {
let rhs = self.rhs.execute(context)?; let rhs = self.rhs.execute(context)?;
if let MpsType::Primitive(rhs) = &rhs { 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 { } 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> { ) -> Result<NegateItemOp, SyntaxError> {
assert_token_raw(MpsToken::Minus, tokens)?; assert_token_raw(MpsToken::Minus, tokens)?;
let rhs_op = factory.try_build_item_statement(tokens, dict)?; let rhs_op = factory.try_build_item_statement(tokens, dict)?;
Ok(NegateItemOp { Ok(NegateItemOp { rhs: rhs_op })
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::utility::assert_token_raw;
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory};
use crate::processing::general::MpsType; use crate::processing::general::MpsType;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
@ -32,9 +32,14 @@ impl MpsItemOp for NotItemOp {
fn execute(&self, context: &mut MpsContext) -> Result<MpsType, RuntimeMsg> { fn execute(&self, context: &mut MpsContext) -> Result<MpsType, RuntimeMsg> {
let rhs = self.rhs.execute(context)?; let rhs = self.rhs.execute(context)?;
if let MpsType::Primitive(rhs) = &rhs { 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 { } 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> { ) -> Result<NotItemOp, SyntaxError> {
assert_token_raw(MpsToken::Exclamation, tokens)?; assert_token_raw(MpsToken::Exclamation, tokens)?;
let rhs_op = factory.try_build_item_statement(tokens, dict)?; let rhs_op = factory.try_build_item_statement(tokens, dict)?;
Ok(NotItemOp { Ok(NotItemOp { rhs: rhs_op })
rhs: rhs_op,
})
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -211,7 +211,11 @@ impl MpsOp for RepeatStatement {
context: None, context: None,
cache: Vec::new(), cache: Vec::new(),
cache_position: 0, 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, loop_forever: self.loop_forever,
original_repetitions: self.original_repetitions, original_repetitions: self.original_repetitions,
}; };

View file

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

View file

@ -75,19 +75,20 @@ impl MpsSorter for BlissSorter {
let mut ctx = iterator.escape(); let mut ctx = iterator.escape();
for i in 0..item_buf.len() { for i in 0..item_buf.len() {
if let Ok(item) = &item_buf[i] { if let Ok(item) = &item_buf[i] {
if item == first {continue;} if item == first {
continue;
}
match ctx.analysis.prepare_distance(first, item) { match ctx.analysis.prepare_distance(first, item) {
Err(e) => { Err(e) => {
iterator.enter(ctx); iterator.enter(ctx);
return Err(e); return Err(e);
}, }
Ok(_) => {}, Ok(_) => {}
} }
} }
} }
iterator.enter(ctx); iterator.enter(ctx);
} }
} else if self.first_song.is_some() { } else if self.first_song.is_some() {
// Sort songs on second call to this function // Sort songs on second call to this function
let first = self.first_song.take().unwrap(); let first = self.first_song.take().unwrap();
@ -96,15 +97,17 @@ impl MpsSorter for BlissSorter {
let mut ctx = iterator.escape(); let mut ctx = iterator.escape();
for i in 0..item_buf.len() { for i in 0..item_buf.len() {
if let Ok(item) = &item_buf[i] { if let Ok(item) = &item_buf[i] {
if item == &first {continue;} if item == &first {
continue;
}
match ctx.analysis.get_distance(&first, item) { match ctx.analysis.get_distance(&first, item) {
Err(e) => { Err(e) => {
iterator.enter(ctx); iterator.enter(ctx);
return Err(e); return Err(e);
}, }
Ok(distance) => { Ok(distance) => {
cache.insert(item.clone(), 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::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsOp}; use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsIteratorItem, MpsOp};
use crate::lang::{PseudoOp, RuntimeOp, SyntaxError, RuntimeError}; use crate::lang::{PseudoOp, RuntimeError, RuntimeOp, SyntaxError};
#[derive(Debug)] #[derive(Debug)]
pub struct SqlInitStatement { pub struct SqlInitStatement {

View file

@ -140,7 +140,10 @@ impl MpsOp for AssignStatement {
fn dup(&self) -> Box<dyn MpsOp> { fn dup(&self) -> Box<dyn MpsOp> {
Box::new(Self { Box::new(Self {
variable_name: self.variable_name.clone(), 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(), assign_type: self.assign_type.clone(),
context: None, context: None,
is_declaration: self.is_declaration, 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). //! 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 context;
mod interpretor; mod interpretor;

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
pub const HELP_STRING: &str = 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. "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 = pub const FUNCTIONS: &str =
"FUNCTIONS (?functions) "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) 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."; 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), "?function" | "?functions" => println!("{}", super::help::FUNCTIONS),
"?filter" | "?filters" => println!("{}", super::help::FILTERS), "?filter" | "?filters" => println!("{}", super::help::FILTERS),
"?sort" | "?sorter" | "?sorters" => println!("{}", super::help::SORTERS), "?sort" | "?sorter" | "?sorters" => println!("{}", super::help::SORTERS),
"?proc" | "?procedure" | "?procedures" => println!("{}", super::help::PROCEDURES),
_ => println!("Unknown command, try ?help"), _ => println!("Unknown command, try ?help"),
} }
} }