From 3a2c2629caa280821cce352d05aabaa55c98f1a7 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sat, 19 Feb 2022 20:17:31 -0500 Subject: [PATCH] Create non-iter operations and implement basics for Turing completeness --- mps-interpreter/src/interpretor.rs | 27 +- mps-interpreter/src/item.rs | 8 + mps-interpreter/src/lang/iter_block.rs | 353 ++++++++++++++++++ mps-interpreter/src/lang/mod.rs | 2 + mps-interpreter/src/lang/type_primitives.rs | 68 ++++ .../lang/vocabulary/filters/field_filter.rs | 15 +- .../src/lang/vocabulary/filters/utility.rs | 13 + .../src/lang/vocabulary/item_ops/add.rs | 90 +++++ .../src/lang/vocabulary/item_ops/brackets.rs | 36 ++ .../src/lang/vocabulary/item_ops/branch.rs | 237 ++++++++++++ .../src/lang/vocabulary/item_ops/compare.rs | 107 ++++++ .../src/lang/vocabulary/item_ops/constant.rs | 56 +++ .../lang/vocabulary/item_ops/constructor.rs | 131 +++++++ .../src/lang/vocabulary/item_ops/empty.rs | 56 +++ .../lang/vocabulary/item_ops/field_assign.rs | 87 +++++ .../src/lang/vocabulary/item_ops/iter_op.rs | 57 +++ .../lang/vocabulary/item_ops/logical_and.rs | 95 +++++ .../lang/vocabulary/item_ops/logical_or.rs | 95 +++++ .../src/lang/vocabulary/item_ops/mod.rs | 39 ++ .../src/lang/vocabulary/item_ops/negate.rs | 61 +++ .../src/lang/vocabulary/item_ops/not.rs | 61 +++ .../vocabulary/item_ops/remove_variable.rs | 86 +++++ .../vocabulary/item_ops/retrieve_variable.rs | 93 +++++ .../vocabulary/item_ops/string_interpolate.rs | 89 +++++ .../src/lang/vocabulary/item_ops/subtract.rs | 90 +++++ .../vocabulary/item_ops/variable_assign.rs | 64 ++++ .../vocabulary/item_ops/variable_declare.rs | 83 ++++ mps-interpreter/src/lang/vocabulary/mod.rs | 1 + .../src/processing/music_analysis.rs | 13 + mps-interpreter/src/processing/variables.rs | 10 + mps-interpreter/src/tokens/token_enum.rs | 40 ++ mps-interpreter/src/tokens/tokenizer.rs | 2 +- mps-interpreter/tests/single_line.rs | 260 ++++++++++++- mps-player/src/os_controls.rs | 7 +- 34 files changed, 2510 insertions(+), 22 deletions(-) create mode 100644 mps-interpreter/src/lang/iter_block.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/add.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/brackets.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/branch.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/compare.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/constant.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/constructor.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/empty.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/field_assign.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/iter_op.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/logical_and.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/logical_or.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/mod.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/negate.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/not.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/remove_variable.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/retrieve_variable.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/subtract.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/variable_assign.rs create mode 100644 mps-interpreter/src/lang/vocabulary/item_ops/variable_declare.rs diff --git a/mps-interpreter/src/interpretor.rs b/mps-interpreter/src/interpretor.rs index e63c157..ab9815d 100644 --- a/mps-interpreter/src/interpretor.rs +++ b/mps-interpreter/src/interpretor.rs @@ -159,10 +159,33 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) { .add(crate::lang::vocabulary::filters::field_re_filter()) // sorters .add(crate::lang::vocabulary::sorters::empty_sort()) - .add(crate::lang::vocabulary::sorters::shuffle_sort()) // accepts valid field ~(shuffle) - .add(crate::lang::vocabulary::sorters::field_sort()) + .add(crate::lang::vocabulary::sorters::shuffle_sort()) // accepts ~(shuffle) + .add(crate::lang::vocabulary::sorters::field_sort()) // accepts any ~(something) .add(crate::lang::vocabulary::sorters::bliss_sort()) .add(crate::lang::vocabulary::sorters::bliss_next_sort()) + // iter blocks + .add( + crate::lang::MpsItemBlockFactory::new() + .add(crate::lang::vocabulary::item_ops::ConstantItemOpFactory) + .add(crate::lang::vocabulary::item_ops::VariableAssignItemOpFactory) + .add(crate::lang::vocabulary::item_ops::FieldAssignItemOpFactory) + .add(crate::lang::vocabulary::item_ops::VariableDeclareItemOpFactory) + .add(crate::lang::vocabulary::item_ops::InterpolateStringItemOpFactory) + .add(crate::lang::vocabulary::item_ops::BranchItemOpFactory) + //.add(crate::lang::vocabulary::item_ops::IterItemOpFactory) + .add(crate::lang::vocabulary::item_ops::ConstructorItemOpFactory) + .add(crate::lang::vocabulary::item_ops::EmptyItemOpFactory) + .add(crate::lang::vocabulary::item_ops::RemoveItemOpFactory) + .add(crate::lang::vocabulary::item_ops::VariableRetrieveItemOpFactory) + .add(crate::lang::vocabulary::item_ops::NegateItemOpFactory) + .add(crate::lang::vocabulary::item_ops::NotItemOpFactory) + .add(crate::lang::vocabulary::item_ops::CompareItemOpFactory) + .add(crate::lang::vocabulary::item_ops::AddItemOpFactory) + .add(crate::lang::vocabulary::item_ops::SubtractItemOpFactory) + .add(crate::lang::vocabulary::item_ops::OrItemOpFactory) + .add(crate::lang::vocabulary::item_ops::AndItemOpFactory) + .add(crate::lang::vocabulary::item_ops::BracketsItemOpFactory) + ) // functions and misc // functions don't enforce bracket coherence // -- function().() is valid despite the ).( in between brackets diff --git a/mps-interpreter/src/item.rs b/mps-interpreter/src/item.rs index 9629294..d5a1ff2 100644 --- a/mps-interpreter/src/item.rs +++ b/mps-interpreter/src/item.rs @@ -32,6 +32,14 @@ impl MpsItem { pub fn remove_field(&mut self, name: &str) -> Option { self.fields.remove(name) } + + pub fn iter(&self) -> impl Iterator { + self.fields.keys() + } + + pub fn len(&self) -> usize { + self.fields.len() + } } impl Display for MpsItem { diff --git a/mps-interpreter/src/lang/iter_block.rs b/mps-interpreter/src/lang/iter_block.rs new file mode 100644 index 0000000..3f8c2e4 --- /dev/null +++ b/mps-interpreter/src/lang/iter_block.rs @@ -0,0 +1,353 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::Iterator; +use std::marker::PhantomData; + +use crate::lang::utility::{assert_token_raw, assert_token_raw_back}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{BoxedMpsOpFactory, MpsIteratorItem, MpsOp, PseudoOp}; +use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; +//use crate::MpsItem; + +const ITEM_VARIABLE_NAME: &str = "item"; + +pub trait MpsItemOp: Debug + Display { + fn execute(&self, context: &mut MpsContext) -> Result; +} + +pub trait MpsItemOpFactory + 'static> { + fn is_item_op(&self, tokens: &VecDeque) -> bool; + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result; +} + +pub struct MpsItemOpBoxer + 'static, Y: Deref + MpsItemOp + 'static> { + idc: PhantomData, + factory: X, +} + +impl + 'static, Y: Deref + MpsItemOp + 'static> MpsItemOpFactory> + for MpsItemOpBoxer +{ + fn is_item_op(&self, tokens: &VecDeque) -> bool { + self.factory.is_item_op(tokens) + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result, SyntaxError> { + Ok(Box::new(self.factory.build_item_op(tokens, factory, dict)?)) + } +} + +#[derive(Debug)] +pub struct MpsItemBlockStatement { + statements: Vec>, + iterable: PseudoOp, + // state + last_item: Option, +} + +impl Display for MpsItemBlockStatement { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{}.{{", self.iterable)?; + if self.statements.len() > 0 { + write!(f, "\n")?; + } + for statement in self.statements.iter() { + write!(f, "{}\n", statement)?; + } + write!(f, "}}") + } +} + +impl std::clone::Clone for MpsItemBlockStatement { + fn clone(&self) -> Self { + Self { + statements: Vec::with_capacity(0), + iterable: self.iterable.clone(), + last_item: None, + } + } +} + + +impl MpsOp for MpsItemBlockStatement { + fn enter(&mut self, ctx: MpsContext) { + self.iterable.try_real().unwrap().enter(ctx) + } + + fn escape(&mut self) -> MpsContext { + self.iterable.try_real().unwrap().escape() + } + + fn is_resetable(&self) -> bool { + if let Ok(iter) = self.iterable.try_real_ref() { + iter.is_resetable() + } else { + false + } + } + + fn reset(&mut self) -> Result<(), RuntimeError> { + self.iterable.try_real()?.reset() + } +} + +impl Iterator for MpsItemBlockStatement { + type Item = MpsIteratorItem; + + fn next(&mut self) -> Option { + let real_op = match self.iterable.try_real() { + Ok(op) => op, + Err(e) => return Some(Err(e)), + }; + if let Some(last_item) = self.last_item.as_mut() { + let real_last = match last_item.try_real() { + Ok(op) => op, + Err(e) => return Some(Err(e)), + }; + real_last.enter(real_op.escape()); + let next_item = real_last.next(); + real_op.enter(real_last.escape()); + if let Some(item) = next_item { + return Some(item); + } else { + self.last_item = None; + } + } + while let Some(item) = real_op.next() { + if let Err(e) = item { + return Some(Err(e)); + } + let item = item.unwrap(); + let mut ctx = real_op.escape(); + let old_var = replace_item_var(&mut ctx, MpsType::Item(item)); + for op in self.statements.iter_mut() { + match op.execute(&mut ctx) { + Ok(_) => {}, + Err(e) => { + #[allow(unused_must_use)] + {restore_item_var(&mut ctx, old_var);} + real_op.enter(ctx); + return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))); + } + } + } + let item = match restore_item_var(&mut ctx, old_var) { + Ok(item) => item, + Err(e) => { + real_op.enter(ctx); + return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))); + } + }; + real_op.enter(ctx); + match item { + Some(MpsType::Item(item)) => { + return Some(Ok(item)) + }, + Some(MpsType::Op(mut op)) => { + op.enter(real_op.escape()); + if let Some(item) = op.next() { + real_op.enter(op.escape()); + self.last_item = Some(op.into()); + return Some(item); + } else { + real_op.enter(op.escape()); + } + }, + Some(x) => return Some(Err(RuntimeError { + line: 0, + op: PseudoOp::from_printable(self), + msg: format!("Expected `item` like MpsType::Item(MpsItem[...]), got {}", x), + })), + None => {} + } + } + None + } + + fn size_hint(&self) -> (usize, Option) { + self.iterable + .try_real_ref() + .map(|x| x.size_hint()) + .unwrap_or((0, None)) + } +} + +pub struct MpsItemBlockFactory { + vocabulary: Vec>>>, +} + +impl MpsItemBlockFactory { + pub fn add + 'static, Y: Deref + MpsItemOp + 'static>( + mut self, + factory: T + ) -> Self { + self.vocabulary.push(Box::new(MpsItemOpBoxer { + factory: factory, + idc: PhantomData, + })); + self + } + + pub fn try_build_item_statement( + &self, + tokens: &mut VecDeque, + dict: &MpsLanguageDictionary, + ) -> Result, SyntaxError> { + for factory in &self.vocabulary { + if factory.is_item_op(tokens) { + return factory.build_item_op(tokens, self, dict); + } + } + Err(match tokens.pop_front() { + Some(x) => SyntaxError { + line: 0, + token: MpsToken::Name("{any item op}".into()), + got: Some(x), + }, + None => SyntaxError { + line: 0, + token: MpsToken::Name("{item op}".into()), + got: None, + }, + }) + } + + pub fn new() -> Self { + Self { + vocabulary: Vec::new(), + } + } +} + +impl BoxedMpsOpFactory for MpsItemBlockFactory { + fn is_op_boxed(&self, tokens: &VecDeque) -> bool { + tokens[tokens.len()-1].is_close_curly() + } + + fn build_op_boxed( + &self, + tokens: &mut VecDeque, + dict: &MpsLanguageDictionary, + ) -> Result, SyntaxError> { + let open_curly_pos = if let Some(pos) = find_last_open_curly(tokens) { + Ok(pos) + } else { + Err(SyntaxError { + line: 0, + token: MpsToken::OpenCurly, + got: None, + }) + }?; + let block_tokens = tokens.split_off(open_curly_pos - 1); // . always before { + let inner_op = dict.try_build_statement(tokens)?; + tokens.extend(block_tokens); + assert_token_raw(MpsToken::Dot, tokens)?; + assert_token_raw(MpsToken::OpenCurly, tokens)?; + assert_token_raw_back(MpsToken::CloseCurly, tokens)?; + let mut item_ops = Vec::with_capacity(tokens.len() / 8); + while !tokens.is_empty() { + if let Some(next_comma) = find_next_comma(tokens) { + let end_tokens = tokens.split_off(next_comma); + item_ops.push(self.try_build_item_statement(tokens, dict)?); + tokens.extend(end_tokens); + assert_token_raw(MpsToken::Comma, tokens)?; + } else { + item_ops.push(self.try_build_item_statement(tokens, dict)?); + } + } + Ok(Box::new(MpsItemBlockStatement { + statements: item_ops, + iterable: inner_op.into(), + last_item: None, + })) + } +} + +fn replace_item_var(ctx: &mut MpsContext, item: MpsType) -> Option { + // remove existing item variable if exists + let old_var = if ctx.variables.exists(ITEM_VARIABLE_NAME) { + ctx.variables.remove(ITEM_VARIABLE_NAME).ok() + } else { + None + }; + ctx.variables.declare(ITEM_VARIABLE_NAME, item).unwrap(); + old_var +} + +fn restore_item_var(ctx: &mut MpsContext, old_var: Option) -> Result, RuntimeMsg> { + let new_var; + if ctx.variables.exists(ITEM_VARIABLE_NAME) { + new_var = Some(ctx.variables.remove(ITEM_VARIABLE_NAME)?); + } else { + new_var = None; + } + if let Some(old_var) = old_var { + ctx.variables.declare(ITEM_VARIABLE_NAME, old_var)?; + } + Ok(new_var) +} + +fn find_last_open_curly(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + let mut curly_found = false; + for i in (0..tokens.len()).rev() { + let token = &tokens[i]; + match token { + MpsToken::OpenCurly => { + if bracket_depth != 0 { + bracket_depth -= 1; + } + }, + MpsToken::CloseCurly => { + bracket_depth += 1; + }, + MpsToken::Dot => { + if bracket_depth == 0 && curly_found { + return Some(i+1); + } + } + _ => {}, + } + if token.is_open_curly() { + curly_found = true; + } else { + curly_found = false; + } + } + None +} + +fn find_next_comma(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + let mut curly_depth = 0; + for i in 0..tokens.len() { + let token = &tokens[i]; + if token.is_comma() && bracket_depth == 0 && curly_depth == 0 { + return Some(i); + } else if token.is_open_bracket() { + bracket_depth += 1; + } else if token.is_close_bracket() && bracket_depth != 0 { + bracket_depth -= 1; + } else if token.is_open_curly() { + curly_depth += 1; + } else if token.is_close_curly() && curly_depth != 0 { + curly_depth -= 1; + } + } + None +} diff --git a/mps-interpreter/src/lang/mod.rs b/mps-interpreter/src/lang/mod.rs index f3e30e0..b71f1ad 100644 --- a/mps-interpreter/src/lang/mod.rs +++ b/mps-interpreter/src/lang/mod.rs @@ -4,6 +4,7 @@ mod error; mod filter; mod filter_replace; mod function; +mod iter_block; mod lookup; mod operation; mod pseudo_op; @@ -21,6 +22,7 @@ pub use filter::{ }; pub use filter_replace::MpsFilterReplaceStatement; pub use function::{MpsFunctionFactory, MpsFunctionStatementFactory}; +pub use iter_block::{MpsItemBlockFactory, MpsItemOp, MpsItemOpFactory}; pub use lookup::Lookup; pub use operation::{BoxedMpsOpFactory, MpsIteratorItem, MpsOp, MpsOpFactory, SimpleMpsOpFactory}; pub use pseudo_op::PseudoOp; diff --git a/mps-interpreter/src/lang/type_primitives.rs b/mps-interpreter/src/lang/type_primitives.rs index c90bd67..7c9f98e 100644 --- a/mps-interpreter/src/lang/type_primitives.rs +++ b/mps-interpreter/src/lang/type_primitives.rs @@ -10,6 +10,7 @@ pub enum MpsTypePrimitive { UInt(u64), Float(f64), Bool(bool), + Empty, } impl MpsTypePrimitive { @@ -39,6 +40,7 @@ impl MpsTypePrimitive { Self::Int(x) => format!("{}", x), Self::Float(x) => format!("{}", x), Self::Bool(x) => format!("{}", x), + Self::Empty => "".to_owned(), } } @@ -75,6 +77,66 @@ impl MpsTypePrimitive { Self::String(s) } } + + // math operations + + #[inline] + pub fn try_add(&self, other: &Self) -> Result { + match self { + Self::String(s) => match other { + Self::String(other_s) => Ok(Self::String(s.to_owned() + other_s)), + other => Err(format!("Cannot add {} and {}: incompatible types", self, other)), + }, + Self::Int(i) => match other { + Self::Int(other_i) => Ok(Self::Int(i + other_i)), + Self::UInt(u) => Ok(Self::Int(i + *u as i64)), + Self::Float(f) => Ok(Self::Float(*i as f64 + f)), + other => Err(format!("Cannot add {} and {}: incompatible types", self, other)), + }, + Self::UInt(u) => match other { + Self::UInt(other_u) => Ok(Self::UInt(u + other_u)), + Self::Int(i) => Ok(Self::UInt(u + *i as u64)), + Self::Float(f) => Ok(Self::Float(*u as f64 + f)), + other => Err(format!("Cannot add {} and {}: incompatible types", self, other)), + }, + Self::Float(f) => match other { + Self::Float(other_f) => Ok(Self::Float(f + other_f)), + Self::Int(i) => Ok(Self::Float(f + *i as f64)), + Self::UInt(u) => Ok(Self::Float(f + *u as f64)), + other => Err(format!("Cannot add {} and {}: incompatible types", self, other)), + }, + Self::Bool(_) => Err(format!("Cannot add {} and {}: incompatible types", self, other)), + Self::Empty => Err(format!("Cannot add {} and {}: incompatible types", self, other)), + } + } + + #[inline] + pub fn try_subtract(&self, other: &Self) -> Result { + match other { + Self::UInt(other_u) => match self { + Self::UInt(u) => Ok(Self::UInt(u - other_u)), + _ => self.try_add(&Self::Int(-(*other_u as i64))), + }, + other => self.try_add(&other.try_negate()?), + } + } + + #[inline] + pub fn try_negate(&self) -> Result { + match self { + Self::Int(i) => Ok(Self::Int(-*i)), + Self::Float(f) => Ok(Self::Float(-*f)), + _ => Err(format!("Cannot negate {}: incompatible type", self)), + } + } + + #[inline] + pub fn try_not(&self) -> Result { + match self { + Self::Bool(b) => Ok(Self::Bool(!*b)), + _ => Err(format!("Cannot apply logical NOT to {}: incompatible type", self)), + } + } } impl Display for MpsTypePrimitive { @@ -85,6 +147,7 @@ impl Display for MpsTypePrimitive { Self::UInt(u) => write!(f, "UInt[{}]", *u), Self::Float(f_) => write!(f, "Float[{}]", *f_), Self::Bool(b) => write!(f, "Bool[{}]", *b), + Self::Empty => write!(f, "Empty[]"), } } } @@ -140,6 +203,10 @@ impl PartialOrd for MpsTypePrimitive { } _ => None, }, + Self::Empty => match other { + Self::Empty => Some(std::cmp::Ordering::Equal), + _ => None, + } } } } @@ -155,6 +222,7 @@ impl std::hash::Hash for MpsTypePrimitive { Self::UInt(u) => u.hash(state), Self::Float(f_) => (*f_ as u64).hash(state), Self::Bool(b) => b.hash(state), + Self::Empty => {}, } } } diff --git a/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs b/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs index aea737c..9a01680 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; use std::fmt::{Debug, Display, Error, Formatter}; -use super::utility::assert_comparison_operator; +use super::utility::{assert_comparison_operator, comparison_op}; use crate::lang::utility::{assert_token, assert_type, check_is_type}; use crate::lang::MpsLanguageDictionary; use crate::lang::MpsTypePrimitive; @@ -34,19 +34,6 @@ pub enum FieldFilterErrorHandling { Include, // return Ok(true) when error encountered } -#[inline(always)] -fn comparison_op(c: &[i8; 2]) -> &str { - match c { - [-1, -1] => "<", - [0, 0] => "==", - [1, 1] => ">", - [0, -1] => "<=", - [0, 1] => ">=", - [-1, 1] => "!=", - _ => "??", - } -} - impl Display for FieldFilter { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { let comp_op = comparison_op(&self.comparison); diff --git a/mps-interpreter/src/lang/vocabulary/filters/utility.rs b/mps-interpreter/src/lang/vocabulary/filters/utility.rs index 7e8f179..548beeb 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/utility.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/utility.rs @@ -55,3 +55,16 @@ pub fn assert_comparison_operator(tokens: &mut VecDeque) -> Result<[i8 }), } } + +#[inline(always)] +pub fn comparison_op(c: &[i8; 2]) -> &str { + match c { + [-1, -1] => "<", + [0, 0] => "==", + [1, 1] => ">", + [0, -1] => "<=", + [0, 1] => ">=", + [-1, 1] => "!=", + _ => "??", + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/add.rs b/mps-interpreter/src/lang/vocabulary/item_ops/add.rs new file mode 100644 index 0000000..540441a --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/add.rs @@ -0,0 +1,90 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::assert_token_raw; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct AddItemOp { + lhs: Box, + rhs: Box, +} + +impl Deref for AddItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for AddItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{} + {}", self.lhs, self.rhs) + } +} + +impl MpsItemOp for AddItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let lhs = self.lhs.execute(context)?; + if let MpsType::Primitive(lhs) = &lhs { + let rhs = self.rhs.execute(context)?; + if let MpsType::Primitive(rhs) = &rhs { + Ok(MpsType::Primitive(lhs.try_add(rhs).map_err(|e| RuntimeMsg(e))?)) + } else { + Err(RuntimeMsg(format!("Cannot add right-hand side `{}` ({}): not primitive type", self.rhs, rhs))) + } + } else { + Err(RuntimeMsg(format!("Cannot add left-hand side `{}` ({}): not primitive type", self.lhs, lhs))) + } + } +} + +pub struct AddItemOpFactory; + +impl MpsItemOpFactory for AddItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + if let Some(plus_location) = first_plus(tokens) { + plus_location != 0 + } else { + false + } + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + let plus_location = first_plus(tokens).unwrap(); + let mut end_tokens = tokens.split_off(plus_location); + let lhs_op = factory.try_build_item_statement(tokens, dict)?; + assert_token_raw(MpsToken::Plus, &mut end_tokens)?; + let rhs_op = factory.try_build_item_statement(&mut end_tokens, dict)?; + Ok(AddItemOp { + lhs: lhs_op, + rhs: rhs_op, + }) + } +} + +fn first_plus(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + for i in 0..tokens.len() { + let token = &tokens[i]; + if token.is_plus() && bracket_depth == 0 { + return Some(i); + } else if token.is_open_bracket() { + bracket_depth += 1; + } else if token.is_close_bracket() && bracket_depth != 0 { + bracket_depth -= 1; + } + } + None +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/brackets.rs b/mps-interpreter/src/lang/vocabulary/item_ops/brackets.rs new file mode 100644 index 0000000..7c2e584 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/brackets.rs @@ -0,0 +1,36 @@ +use std::convert::AsRef; +use std::collections::VecDeque; + +use crate::lang::utility::{assert_token_raw, assert_token_raw_back}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +pub struct BracketsItemOpFactory; + +impl MpsItemOpFactory> for BracketsItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() >= 2 && tokens[0].is_open_bracket() && tokens[tokens.len()-1].is_close_bracket() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result, SyntaxError> { + assert_token_raw(MpsToken::OpenBracket, tokens)?; + assert_token_raw_back(MpsToken::CloseBracket, tokens)?; + factory.try_build_item_statement(tokens, dict) + } +} + +impl MpsItemOp for Box { + fn execute(&self, context: &mut MpsContext) -> Result { + // while this sort of looks like it's (infinitely) recursive, it's actually not + self.as_ref().execute(context) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/branch.rs b/mps-interpreter/src/lang/vocabulary/item_ops/branch.rs new file mode 100644 index 0000000..79c7fd5 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/branch.rs @@ -0,0 +1,237 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, check_name, assert_name}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct BranchItemOp { + condition: Box, + inner_ifs: Vec>, + inner_elses: Vec>, +} + +impl Deref for BranchItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for BranchItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "if {} {{", self.condition)?; + if self.inner_ifs.len() > 1 { + write!(f, "\n")?; + } + for i in 0..self.inner_ifs.len() { + write!(f, "{}", self.inner_ifs[i])?; + if i != self.inner_ifs.len() - 1 { + write!(f, ",\n")?; + } + } + if self.inner_ifs.len() > 1 { + write!(f, "\n")?; + } + write!(f, "}} else {{")?; + if self.inner_elses.len() > 1 { + write!(f, "\n")?; + } + for i in 0..self.inner_elses.len() { + write!(f, "{}", self.inner_elses[i])?; + if i != self.inner_elses.len() - 1 { + write!(f, ",\n")?; + } + } + if self.inner_elses.len() > 1 { + write!(f, "\n")?; + } + write!(f, "}}") + } +} + +impl MpsItemOp for BranchItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let condition_val = self.condition.execute(context)?; + if let MpsType::Primitive(MpsTypePrimitive::Bool(condition)) = condition_val { + let mut last_result = None; + if condition { + for op in self.inner_ifs.iter() { + last_result = Some(op.execute(context)?); + } + } else { + for op in self.inner_elses.iter() { + last_result = Some(op.execute(context)?); + } + } + if let Some(result) = last_result { + Ok(result) + } else { + Ok(MpsType::empty()) + } + } else { + Err(RuntimeMsg(format!("Cannot use {} ({}) as if branch condition (should be Bool)", self.condition, condition_val))) + } + } +} + +pub struct BranchItemOpFactory; + +impl MpsItemOpFactory for BranchItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + !tokens.is_empty() && check_name("if", &tokens[0]) + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + assert_name("if", tokens)?; + // if condition + let condition_op; + if let Some(curly_pos) = next_curly_open_bracket(tokens) { + let end_tokens = tokens.split_off(curly_pos); + condition_op = factory.try_build_item_statement(tokens, dict)?; + tokens.extend(end_tokens); + } else { + return Err(SyntaxError { + line: 0, + token: MpsToken::OpenCurly, + got: tokens.pop_front(), + }); + } + // if block + assert_token_raw(MpsToken::OpenCurly, tokens)?; + let next_close_curly; + if let Some(curly_pos) = next_curly_close_bracket(tokens) { + next_close_curly = curly_pos; + } else { + return Err(SyntaxError { + line: 0, + token: MpsToken::CloseCurly, + got: tokens.pop_back(), + }); + } + let end_tokens = tokens.split_off(next_close_curly); + let mut inner_if_ops = Vec::new(); + while !tokens.is_empty() { + if let Some(next_comma) = find_next_comma(tokens) { + let end_tokens = tokens.split_off(next_comma); + inner_if_ops.push(factory.try_build_item_statement(tokens, dict)?); + tokens.extend(end_tokens); + assert_token_raw(MpsToken::Comma, tokens)?; + } else { + inner_if_ops.push(factory.try_build_item_statement(tokens, dict)?); + } + } + tokens.extend(end_tokens); + assert_token_raw(MpsToken::CloseCurly, tokens)?; + if tokens.is_empty() { + // else block is omitted + Ok(BranchItemOp { + condition: condition_op, + inner_ifs: inner_if_ops, + inner_elses: Vec::with_capacity(0), + }) + } else { + // else block + assert_name("else", tokens)?; + assert_token_raw(MpsToken::OpenCurly, tokens)?; + let next_close_curly; + if let Some(curly_pos) = next_curly_close_bracket(tokens) { + next_close_curly = curly_pos; + } else { + return Err(SyntaxError { + line: 0, + token: MpsToken::CloseCurly, + got: tokens.pop_back(), + }); + } + let end_tokens = tokens.split_off(next_close_curly); + let mut inner_else_ops = Vec::new(); + while !tokens.is_empty() { + if let Some(next_comma) = find_next_comma(tokens) { + let end_tokens = tokens.split_off(next_comma); + inner_else_ops.push(factory.try_build_item_statement(tokens, dict)?); + tokens.extend(end_tokens); + assert_token_raw(MpsToken::Comma, tokens)?; + } else { + inner_else_ops.push(factory.try_build_item_statement(tokens, dict)?); + } + } + tokens.extend(end_tokens); + assert_token_raw(MpsToken::CloseCurly, tokens)?; + Ok(BranchItemOp { + condition: condition_op, + inner_ifs: inner_if_ops, + inner_elses: inner_else_ops, + }) + } + } +} + +fn next_curly_open_bracket(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + for i in 0..tokens.len() { + match &tokens[i] { + MpsToken::OpenBracket => bracket_depth += 1, + MpsToken::CloseBracket => if bracket_depth != 0 { + bracket_depth -= 1; + }, + MpsToken::OpenCurly => if bracket_depth == 0 { + return Some(i); + }, + _ => {}, + } + } + None +} + +fn next_curly_close_bracket(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + let mut curly_depth = 0; + for i in 0..tokens.len() { + match &tokens[i] { + MpsToken::OpenBracket => bracket_depth += 1, + MpsToken::CloseBracket => if bracket_depth != 0 { + bracket_depth -= 1; + }, + MpsToken::OpenCurly => curly_depth += 1, + MpsToken::CloseCurly => if bracket_depth == 0 && curly_depth == 0 { + return Some(i); + } else if curly_depth != 0 { + curly_depth -= 1; + }, + _ => {}, + } + } + None +} + +fn find_next_comma(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + let mut curly_depth = 0; + for i in 0..tokens.len() { + let token = &tokens[i]; + if token.is_comma() && bracket_depth == 0 && curly_depth == 0 { + return Some(i); + } else if token.is_open_bracket() { + bracket_depth += 1; + } else if token.is_close_bracket() && bracket_depth != 0 { + bracket_depth -= 1; + } else if token.is_open_curly() { + curly_depth += 1; + } else if token.is_close_curly() && curly_depth != 0 { + curly_depth -= 1; + } + } + None +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/compare.rs b/mps-interpreter/src/lang/vocabulary/item_ops/compare.rs new file mode 100644 index 0000000..cf7228a --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/compare.rs @@ -0,0 +1,107 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::{MpsLanguageDictionary, MpsTypePrimitive}; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::lang::vocabulary::filters::utility::{assert_comparison_operator, comparison_op}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct CompareItemOp { + comparison: [i8; 2], + lhs: Box, + rhs: Box, +} + +impl Deref for CompareItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for CompareItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{} {} {}", self.lhs, comparison_op(&self.comparison), self.rhs) + } +} + +impl MpsItemOp for CompareItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let lhs_val = self.lhs.execute(context)?; + let rhs_val = self.rhs.execute(context)?; + if let MpsType::Primitive(lhs) = lhs_val { + if let MpsType::Primitive(rhs) = rhs_val { + let compare = lhs.compare(&rhs).map_err(|e| RuntimeMsg(e))?; + let mut is_match = false; + for comparator in self.comparison { + if comparator == compare { + is_match = true; + break; + } + } + Ok(MpsType::Primitive(MpsTypePrimitive::Bool(is_match))) + } else { + Err(RuntimeMsg(format!("Cannot compare non-primitive right-hand side {} ({})", self.rhs, rhs_val))) + } + } else { + Err(RuntimeMsg(format!("Cannot compare non-primitive left-hand side {} ({})", self.lhs, lhs_val))) + } + } +} + +pub struct CompareItemOpFactory; + +impl MpsItemOpFactory for CompareItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + find_first_comparison(tokens).is_some() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + let comparison_loc = find_first_comparison(tokens).unwrap(); + let end_tokens = tokens.split_off(comparison_loc); + let lhs_op = factory.try_build_item_statement(tokens, dict)?; + tokens.extend(end_tokens); + let comparison_arr = assert_comparison_operator(tokens)?; + let rhs_op = factory.try_build_item_statement(tokens, dict)?; + Ok(CompareItemOp { + comparison: comparison_arr, + lhs: lhs_op, + rhs: rhs_op, + }) + } +} + +fn find_first_comparison(tokens: &VecDeque) -> Option { + let mut curly_depth = 0; + let mut bracket_depth = 0; + for i in 0..tokens.len() { + match &tokens[i] { + MpsToken::OpenCurly => curly_depth += 1, + MpsToken::CloseCurly => if curly_depth != 0 { + curly_depth -= 1; + }, + MpsToken::OpenBracket => bracket_depth += 1, + MpsToken::CloseBracket => if bracket_depth != 0 { + curly_depth -= 1; + }, + MpsToken::OpenAngleBracket | MpsToken::CloseAngleBracket => if curly_depth == 0 && bracket_depth == 0 { + return Some(i); + }, + MpsToken::Equals | MpsToken::Exclamation => if curly_depth == 0 && bracket_depth == 0 && i+1 != tokens.len() && tokens[i+1].is_equals() { + return Some(i); + } + _ => {} + } + } + None +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/constant.rs b/mps-interpreter/src/lang/vocabulary/item_ops/constant.rs new file mode 100644 index 0000000..4f586f9 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/constant.rs @@ -0,0 +1,56 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{check_is_type, assert_type}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct ConstantItemOp { + value: MpsTypePrimitive, +} + +impl Deref for ConstantItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for ConstantItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{}", self.value) + } +} + +impl MpsItemOp for ConstantItemOp { + fn execute(&self, _context: &mut MpsContext) -> Result { + Ok(MpsType::Primitive(self.value.clone())) + } +} + +pub struct ConstantItemOpFactory; + +impl MpsItemOpFactory for ConstantItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() == 1 + && check_is_type(&tokens[0]) + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + _factory: &MpsItemBlockFactory, + _dict: &MpsLanguageDictionary, + ) -> Result { + let const_value = assert_type(tokens)?; + Ok(ConstantItemOp { + value: const_value, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/constructor.rs b/mps-interpreter/src/lang/vocabulary/item_ops/constructor.rs new file mode 100644 index 0000000..fed727a --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/constructor.rs @@ -0,0 +1,131 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, assert_token_raw_back, check_name, assert_name, assert_token}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; +use crate::MpsItem; + +#[derive(Debug)] +struct FieldAssignment { + name: String, + value: Box, +} + +#[derive(Debug)] +pub struct ConstructorItemOp { + fields: Vec, +} + +impl Deref for ConstructorItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for ConstructorItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "Item(")?; + if self.fields.len() > 1 { + write!(f, "\n")?; + for i in 0..self.fields.len()-1 { + let field = &self.fields[i]; + write!(f, "{}: {}, ", field.name, field.value)?; + } + let field = &self.fields[self.fields.len()-1]; + write!(f, "{}: {}", field.name, field.value)?; + } else if !self.fields.is_empty() { + let field = &self.fields[0]; + write!(f, "{}: {}", field.name, field.value)?; + } + write!(f, ")") + } +} + +impl MpsItemOp for ConstructorItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let mut result = MpsItem::new(); + for field in &self.fields { + let value = field.value.execute(context)?; + if let MpsType::Primitive(value) = value { + result.set_field(&field.name, value); + } else { + return Err(RuntimeMsg(format!("Cannot assign non-primitive {} to Item field `{}`", value, &field.name))); + } + } + Ok(MpsType::Item(result)) + } +} + +pub struct ConstructorItemOpFactory; + +impl MpsItemOpFactory for ConstructorItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() > 2 + && check_name("Item", &tokens[0]) + && tokens[1].is_open_bracket() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + assert_name("Item", tokens)?; + assert_token_raw(MpsToken::OpenBracket, tokens)?; + assert_token_raw_back(MpsToken::CloseBracket, tokens)?; + let mut field_descriptors = Vec::new(); + while !tokens.is_empty() { + let field_name = assert_token(|t| match t { + MpsToken::Name(n) => Some(n), + _ => None, + }, MpsToken::Name("field_name".into()), tokens)?; + assert_token_raw(MpsToken::Equals, tokens)?; + let field_val; + if let Some(comma_pos) = find_next_comma(tokens) { + let end_tokens = tokens.split_off(comma_pos); + field_val = factory.try_build_item_statement(tokens, dict)?; + tokens.extend(end_tokens); + assert_token_raw(MpsToken::Comma, tokens)?; + } else { + field_val = factory.try_build_item_statement(tokens, dict)?; + } + field_descriptors.push( + FieldAssignment { + name: field_name, + value: field_val, + } + ); + } + Ok(ConstructorItemOp { + fields: field_descriptors, + }) + } +} + +fn find_next_comma(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + let mut curly_depth = 0; + for i in 0..tokens.len() { + let token = &tokens[i]; + if token.is_comma() && bracket_depth == 0 && curly_depth == 0 { + return Some(i); + } else if token.is_open_bracket() { + bracket_depth += 1; + } else if token.is_close_bracket() && bracket_depth != 0 { + bracket_depth -= 1; + } else if token.is_open_curly() { + curly_depth += 1; + } else if token.is_close_curly() && curly_depth != 0 { + curly_depth -= 1; + } + } + None +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/empty.rs b/mps-interpreter/src/lang/vocabulary/item_ops/empty.rs new file mode 100644 index 0000000..b349ed2 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/empty.rs @@ -0,0 +1,56 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, check_name, assert_name}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct EmptyItemOp; + +impl Deref for EmptyItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for EmptyItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "empty()") + } +} + +impl MpsItemOp for EmptyItemOp { + fn execute(&self, _context: &mut MpsContext) -> Result { + Ok(MpsType::empty()) + } +} + +pub struct EmptyItemOpFactory; + +impl MpsItemOpFactory for EmptyItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() == 3 + && check_name("empty", &tokens[0]) + && tokens[1].is_open_bracket() + && tokens[2].is_close_bracket() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + _factory: &MpsItemBlockFactory, + _dict: &MpsLanguageDictionary, + ) -> Result { + assert_name("empty", tokens)?; + assert_token_raw(MpsToken::OpenBracket, tokens)?; + assert_token_raw(MpsToken::CloseBracket, tokens)?; + Ok(EmptyItemOp) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/field_assign.rs b/mps-interpreter/src/lang/vocabulary/item_ops/field_assign.rs new file mode 100644 index 0000000..6ca8d3a --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/field_assign.rs @@ -0,0 +1,87 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, assert_token}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct FieldAssignItemOp { + variable_name: String, + field_name: String, + inner: Box, +} + +impl Deref for FieldAssignItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for FieldAssignItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{}.{} = {}", &self.variable_name, &self.field_name, &self.inner) + } +} + +impl MpsItemOp for FieldAssignItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let mps_type = self.inner.execute(context)?; + let var = context.variables.get_mut(&self.variable_name)?; + if let MpsType::Item(var) = var { + if let MpsType::Primitive(val) = mps_type { + var.set_field(&self.field_name, val); + Ok(MpsType::empty()) + } else { + Err(RuntimeMsg(format!("Cannot assign non-primitive {} to variable field `{}.{}`", mps_type, &self.variable_name, &self.field_name))) + } + } else { + Err(RuntimeMsg(format!("Cannot access field `{}` on variable `{}` ({} is not Item)", &self.field_name, &self.variable_name, var))) + } + } +} + +pub struct FieldAssignItemOpFactory; + +impl MpsItemOpFactory for FieldAssignItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + (tokens.len() > 4 && tokens[0].is_name() && tokens[1].is_dot() && tokens[2].is_name() && tokens[3].is_equals()) + || + (tokens.len() > 3 && tokens[0].is_dot() && tokens[1].is_name() && tokens[2].is_equals()) + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + let var_name; + if tokens[0].is_dot() { + var_name = "item".to_string(); + } else { + var_name = assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("variable_name".into()), tokens)? + } + assert_token_raw(MpsToken::Dot, tokens)?; + let f_name = assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("field_name".into()), tokens)?; + assert_token_raw(MpsToken::Equals, tokens)?; + let inner_op = factory.try_build_item_statement(tokens, dict)?; + Ok(FieldAssignItemOp { + variable_name: var_name, + field_name: f_name, + inner: inner_op, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/iter_op.rs b/mps-interpreter/src/lang/vocabulary/item_ops/iter_op.rs new file mode 100644 index 0000000..56c44d3 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/iter_op.rs @@ -0,0 +1,57 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{check_name, assert_name}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsOp}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct IterItemOp { + inner: Box, +} + +impl Deref for IterItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for IterItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "iter {}", self.inner) + } +} + +impl MpsItemOp for IterItemOp { + fn execute(&self, _context: &mut MpsContext) -> Result { + Ok(MpsType::Op(self.inner.duplicate().into())) + } +} + +pub struct IterItemOpFactory; + +impl MpsItemOpFactory for IterItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + !tokens.is_empty() + && check_name("iter", &tokens[0]) + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + _factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + assert_name("iter", tokens)?; + let inner_op = dict.try_build_statement(tokens)?; + Ok(IterItemOp { + inner: inner_op, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/logical_and.rs b/mps-interpreter/src/lang/vocabulary/item_ops/logical_and.rs new file mode 100644 index 0000000..2ffd94e --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/logical_and.rs @@ -0,0 +1,95 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::assert_token_raw; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct AndItemOp { + lhs: Box, + rhs: Box, +} + +impl Deref for AndItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for AndItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{} && {}", self.lhs, self.rhs) + } +} + +impl MpsItemOp for AndItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let lhs = self.lhs.execute(context)?; + if let MpsType::Primitive(MpsTypePrimitive::Bool(lhs)) = lhs { + if !lhs { + // short-circuit + return Ok(MpsType::Primitive(MpsTypePrimitive::Bool(false))); + } + let rhs = self.rhs.execute(context)?; + if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs { + Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs))) + } else { + Err(RuntimeMsg(format!("Cannot apply logical AND to right-hand side of `{}` ({}): not Bool type", self.rhs, rhs))) + } + } else { + Err(RuntimeMsg(format!("Cannot apply logical AND to left-hand side of `{}` ({}): not Bool type", self.lhs, lhs))) + } + } +} + +pub struct AndItemOpFactory; + +impl MpsItemOpFactory for AndItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + if let Some(and_location) = first_and(tokens) { + and_location != 0 + } else { + false + } + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + let and_location = first_and(tokens).unwrap(); + let mut end_tokens = tokens.split_off(and_location); + let lhs_op = factory.try_build_item_statement(tokens, dict)?; + assert_token_raw(MpsToken::Ampersand, &mut end_tokens)?; + assert_token_raw(MpsToken::Ampersand, &mut end_tokens)?; + let rhs_op = factory.try_build_item_statement(&mut end_tokens, dict)?; + Ok(AndItemOp { + lhs: lhs_op, + rhs: rhs_op, + }) + } +} + +fn first_and(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + for i in 0..tokens.len()-1 { + let token = &tokens[i]; + if token.is_ampersand() && bracket_depth == 0 && tokens[i+1].is_ampersand() { + return Some(i); + } else if token.is_open_bracket() { + bracket_depth += 1; + } else if token.is_close_bracket() && bracket_depth != 0 { + bracket_depth -= 1; + } + } + None +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/logical_or.rs b/mps-interpreter/src/lang/vocabulary/item_ops/logical_or.rs new file mode 100644 index 0000000..f429937 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/logical_or.rs @@ -0,0 +1,95 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::assert_token_raw; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct OrItemOp { + lhs: Box, + rhs: Box, +} + +impl Deref for OrItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for OrItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{} || {}", self.lhs, self.rhs) + } +} + +impl MpsItemOp for OrItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let lhs = self.lhs.execute(context)?; + if let MpsType::Primitive(MpsTypePrimitive::Bool(lhs)) = lhs { + if lhs { + // short-circuit + return Ok(MpsType::Primitive(MpsTypePrimitive::Bool(true))); + } + let rhs = self.rhs.execute(context)?; + if let MpsType::Primitive(MpsTypePrimitive::Bool(rhs)) = rhs { + Ok(MpsType::Primitive(MpsTypePrimitive::Bool(rhs))) + } else { + Err(RuntimeMsg(format!("Cannot apply logical OR to right-hand side of `{}` ({}): not Bool type", self.rhs, rhs))) + } + } else { + Err(RuntimeMsg(format!("Cannot apply logical OR to left-hand side of `{}` ({}): not Bool type", self.lhs, lhs))) + } + } +} + +pub struct OrItemOpFactory; + +impl MpsItemOpFactory for OrItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + if let Some(or_location) = first_or(tokens) { + or_location != 0 + } else { + false + } + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + let or_location = first_or(tokens).unwrap(); + let mut end_tokens = tokens.split_off(or_location); + let lhs_op = factory.try_build_item_statement(tokens, dict)?; + assert_token_raw(MpsToken::Pipe, &mut end_tokens)?; + assert_token_raw(MpsToken::Pipe, &mut end_tokens)?; + let rhs_op = factory.try_build_item_statement(&mut end_tokens, dict)?; + Ok(OrItemOp { + lhs: lhs_op, + rhs: rhs_op, + }) + } +} + +fn first_or(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + for i in 0..tokens.len()-1 { + let token = &tokens[i]; + if token.is_pipe() && bracket_depth == 0 && tokens[i+1].is_pipe() { + return Some(i); + } else if token.is_open_bracket() { + bracket_depth += 1; + } else if token.is_close_bracket() && bracket_depth != 0 { + bracket_depth -= 1; + } + } + None +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/mod.rs b/mps-interpreter/src/lang/vocabulary/item_ops/mod.rs new file mode 100644 index 0000000..b9ba94f --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/mod.rs @@ -0,0 +1,39 @@ +mod add; +mod brackets; +mod branch; +mod compare; +mod constant; +mod constructor; +mod empty; +mod field_assign; +//mod iter_op; +mod logical_and; +mod logical_or; +mod negate; +mod not; +mod remove_variable; +mod retrieve_variable; +mod string_interpolate; +mod subtract; +mod variable_assign; +mod variable_declare; + +pub use add::{AddItemOpFactory, AddItemOp}; +pub use brackets::BracketsItemOpFactory; +pub use branch::{BranchItemOpFactory, BranchItemOp}; +pub use constant::{ConstantItemOpFactory, ConstantItemOp}; +pub use compare::{CompareItemOpFactory, CompareItemOp}; +pub use constructor::{ConstructorItemOpFactory, ConstructorItemOp}; +pub use empty::{EmptyItemOpFactory, EmptyItemOp}; +pub use field_assign::{FieldAssignItemOpFactory, FieldAssignItemOp}; +//pub use iter_op::{IterItemOpFactory, IterItemOp}; +pub use logical_and::{AndItemOpFactory, AndItemOp}; +pub use logical_or::{OrItemOpFactory, OrItemOp}; +pub use negate::{NegateItemOpFactory, NegateItemOp}; +pub use not::{NotItemOpFactory, NotItemOp}; +pub use remove_variable::{RemoveItemOpFactory, RemoveItemOp}; +pub use retrieve_variable::{VariableRetrieveItemOpFactory, VariableRetrieveItemOp}; +pub use string_interpolate::{InterpolateStringItemOpFactory, InterpolateStringItemOp}; +pub use subtract::{SubtractItemOpFactory, SubtractItemOp}; +pub use variable_assign::{VariableAssignItemOpFactory, VariableAssignItemOp}; +pub use variable_declare::{VariableDeclareItemOpFactory, VariableDeclareItemOp}; diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/negate.rs b/mps-interpreter/src/lang/vocabulary/item_ops/negate.rs new file mode 100644 index 0000000..628e28f --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/negate.rs @@ -0,0 +1,61 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::assert_token_raw; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct NegateItemOp { + rhs: Box, +} + +impl Deref for NegateItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for NegateItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "- {}", self.rhs) + } +} + +impl MpsItemOp for NegateItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let rhs = self.rhs.execute(context)?; + if let MpsType::Primitive(rhs) = &rhs { + Ok(MpsType::Primitive(rhs.try_negate().map_err(|e| RuntimeMsg(e))?)) + } else { + Err(RuntimeMsg(format!("Cannot negate `{}` ({}): not primitive type", self.rhs, rhs))) + } + } +} + +pub struct NegateItemOpFactory; + +impl MpsItemOpFactory for NegateItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() >= 2 && tokens[0].is_minus() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + assert_token_raw(MpsToken::Minus, tokens)?; + let rhs_op = factory.try_build_item_statement(tokens, dict)?; + Ok(NegateItemOp { + rhs: rhs_op, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/not.rs b/mps-interpreter/src/lang/vocabulary/item_ops/not.rs new file mode 100644 index 0000000..ce3f6df --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/not.rs @@ -0,0 +1,61 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::assert_token_raw; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct NotItemOp { + rhs: Box, +} + +impl Deref for NotItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for NotItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "! {}", self.rhs) + } +} + +impl MpsItemOp for NotItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let rhs = self.rhs.execute(context)?; + if let MpsType::Primitive(rhs) = &rhs { + Ok(MpsType::Primitive(rhs.try_not().map_err(|e| RuntimeMsg(e))?)) + } else { + Err(RuntimeMsg(format!("Cannot apply logical NOT to `{}` ({}): not primitive type", self.rhs, rhs))) + } + } +} + +pub struct NotItemOpFactory; + +impl MpsItemOpFactory for NotItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() >= 2 && tokens[0].is_exclamation() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + assert_token_raw(MpsToken::Exclamation, tokens)?; + let rhs_op = factory.try_build_item_statement(tokens, dict)?; + Ok(NotItemOp { + rhs: rhs_op, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/remove_variable.rs b/mps-interpreter/src/lang/vocabulary/item_ops/remove_variable.rs new file mode 100644 index 0000000..f62acb9 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/remove_variable.rs @@ -0,0 +1,86 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token, check_name, assert_name, assert_token_raw}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct RemoveItemOp { + variable_name: String, + field_name: Option +} + +impl Deref for RemoveItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for RemoveItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + if let Some(field_name) = &self.field_name { + write!(f, "remove {}.{}", self.variable_name, field_name) + } else { + write!(f, "remove {}", self.variable_name) + } + } +} + +impl MpsItemOp for RemoveItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + if let Some(field_name) = &self.field_name { + let var = context.variables.get_mut(&self.variable_name)?; + if let MpsType::Item(item) = var { + item.remove_field(field_name); + Ok(MpsType::empty()) + } else { + Err(RuntimeMsg(format!("Cannot access field `{}` on variable `{}` ({} is not Item)", field_name, &self.variable_name, var))) + } + } else { + context.variables.remove(&self.variable_name)?; + Ok(MpsType::empty()) + } + } +} + +pub struct RemoveItemOpFactory; + +impl MpsItemOpFactory for RemoveItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + (tokens.len() == 2 || tokens.len() == 4)&& check_name("remove", &tokens[0]) + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + _factory: &MpsItemBlockFactory, + _dict: &MpsLanguageDictionary, + ) -> Result { + assert_name("remove", tokens)?; + let name = assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("variable_name".into()), tokens)?; + let field_opt; + if tokens.is_empty() { + field_opt = None; + } else { + assert_token_raw(MpsToken::Dot, tokens)?; + field_opt = Some(assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("field_name".into()), tokens)?); + } + Ok(RemoveItemOp { + variable_name: name, + field_name: field_opt, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/retrieve_variable.rs b/mps-interpreter/src/lang/vocabulary/item_ops/retrieve_variable.rs new file mode 100644 index 0000000..878e0da --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/retrieve_variable.rs @@ -0,0 +1,93 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, assert_token}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct VariableRetrieveItemOp { + variable_name: String, + field_name: Option, +} + +impl Deref for VariableRetrieveItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for VariableRetrieveItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + if let Some(field) = &self.field_name { + write!(f, "{}.{}", &self.variable_name, field) + } else { + write!(f, "{}", &self.variable_name) + } + + } +} + +impl MpsItemOp for VariableRetrieveItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let var = context.variables.get(&self.variable_name)?; + if let Some(field_name) = &self.field_name { + if let MpsType::Item(item) = var { + Ok(match item.field(field_name) { + Some(val) => MpsType::Primitive(val.clone()), + None => MpsType::empty(), + }) + } else { + Err(RuntimeMsg(format!("Cannot access field `{}` on variable `{}` ({} is not Item)", field_name, self.variable_name, var))) + } + } else { + match var { + MpsType::Op(op) => Err(RuntimeMsg(format!("Cannot clone op-type `{}` variable `{}`", op, self.variable_name))), + MpsType::Primitive(x) => Ok(MpsType::Primitive(x.clone())), + MpsType::Item(item) => Ok(MpsType::Item(item.clone())) + } + } + } +} + +pub struct VariableRetrieveItemOpFactory; + +impl MpsItemOpFactory for VariableRetrieveItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + (tokens.len() == 1 && tokens[0].is_name()) + || + (tokens.len() == 3 && tokens[0].is_name() && tokens[1].is_dot() && tokens[2].is_name()) + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + _factory: &MpsItemBlockFactory, + _dict: &MpsLanguageDictionary, + ) -> Result { + let var_name = assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("variable_name".into()), tokens)?; + let field_opt; + if tokens.is_empty() { + field_opt = None; + } else { + assert_token_raw(MpsToken::Dot, tokens)?; + field_opt = Some(assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("field_name".into()), tokens)?); + } + Ok(VariableRetrieveItemOp { + variable_name: var_name, + field_name: field_opt, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs b/mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs new file mode 100644 index 0000000..19d8de5 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/string_interpolate.rs @@ -0,0 +1,89 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, assert_token}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory, MpsTypePrimitive}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct InterpolateStringItemOp { + format: String, + inner_op: Box, +} + +impl Deref for InterpolateStringItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for InterpolateStringItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "~ `{}` {}", &self.format, self.inner_op) + } +} + +impl MpsItemOp for InterpolateStringItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let inner_val = self.inner_op.execute(context)?; + match inner_val { + MpsType::Primitive(val) => { + let result = self.format.replace("{}", &val.as_str()); + Ok(MpsType::Primitive(MpsTypePrimitive::String(result))) + }, + MpsType::Item(item) => { + let mut result; + if item.len() == 0 { + result = self.format.clone(); + } else { + let mut iter = item.iter(); + let field1 = iter.next().unwrap(); + result = self.format.replace(&format!("{{{}}}", field1), &item.field(field1).unwrap().as_str()); + for field in iter { + result = result.replace(&format!("{{{}}}", field), &item.field(field).unwrap().as_str()); + } + } + Ok(MpsType::Primitive(MpsTypePrimitive::String(result))) + }, + MpsType::Op(op) => { + let result = self.format.replace("{}", &format!("{}", op)); + Ok(MpsType::Primitive(MpsTypePrimitive::String(result))) + }, + //val => Err(RuntimeMsg(format!("Cannot insert {} ({}) into format string", self.inner_op, val))) + } + //Ok(MpsType::empty()) + } +} + +pub struct InterpolateStringItemOpFactory; + +impl MpsItemOpFactory for InterpolateStringItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + !tokens.is_empty() + && tokens[0].is_tilde() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + assert_token_raw(MpsToken::Tilde, tokens)?; + let format_str = assert_token(|t| match t { + MpsToken::Literal(s) => Some(s), + _ => None, + }, MpsToken::Literal("format_string".into()), tokens)?; + let inner = factory.try_build_item_statement(tokens, dict)?; + Ok(InterpolateStringItemOp { + format: format_str, + inner_op: inner + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/subtract.rs b/mps-interpreter/src/lang/vocabulary/item_ops/subtract.rs new file mode 100644 index 0000000..28b7e39 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/subtract.rs @@ -0,0 +1,90 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::assert_token_raw; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct SubtractItemOp { + lhs: Box, + rhs: Box, +} + +impl Deref for SubtractItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for SubtractItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{} - {}", self.lhs, self.rhs) + } +} + +impl MpsItemOp for SubtractItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let lhs = self.lhs.execute(context)?; + if let MpsType::Primitive(lhs) = &lhs { + let rhs = self.rhs.execute(context)?; + if let MpsType::Primitive(rhs) = &rhs { + Ok(MpsType::Primitive(lhs.try_subtract(rhs).map_err(|e| RuntimeMsg(e))?)) + } else { + Err(RuntimeMsg(format!("Cannot subtract right-hand side `{}` ({}): not primitive type", self.rhs, rhs))) + } + } else { + Err(RuntimeMsg(format!("Cannot subtract left-hand side `{}` ({}): not primitive type", self.lhs, lhs))) + } + } +} + +pub struct SubtractItemOpFactory; + +impl MpsItemOpFactory for SubtractItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + if let Some(minus_location) = first_minus(tokens) { + minus_location != 0 + } else { + false + } + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + let minus_location = first_minus(tokens).unwrap(); + let mut end_tokens = tokens.split_off(minus_location); + let lhs_op = factory.try_build_item_statement(tokens, dict)?; + assert_token_raw(MpsToken::Minus, &mut end_tokens)?; + let rhs_op = factory.try_build_item_statement(&mut end_tokens, dict)?; + Ok(SubtractItemOp { + lhs: lhs_op, + rhs: rhs_op, + }) + } +} + +fn first_minus(tokens: &VecDeque) -> Option { + let mut bracket_depth = 0; + for i in 0..tokens.len() { + let token = &tokens[i]; + if token.is_minus() && bracket_depth == 0 { + return Some(i); + } else if token.is_open_bracket() { + bracket_depth += 1; + } else if token.is_close_bracket() && bracket_depth != 0 { + bracket_depth -= 1; + } + } + None +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/variable_assign.rs b/mps-interpreter/src/lang/vocabulary/item_ops/variable_assign.rs new file mode 100644 index 0000000..113419b --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/variable_assign.rs @@ -0,0 +1,64 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, assert_token}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct VariableAssignItemOp { + variable_name: String, + inner: Box, +} + +impl Deref for VariableAssignItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for VariableAssignItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{} = {}", &self.variable_name, &self.inner) + } +} + +impl MpsItemOp for VariableAssignItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + let mps_type = self.inner.execute(context)?; + context.variables.assign(&self.variable_name, mps_type)?; + Ok(MpsType::empty()) + } +} + +pub struct VariableAssignItemOpFactory; + +impl MpsItemOpFactory for VariableAssignItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() > 2 && tokens[0].is_name() && tokens[1].is_equals() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + let var_name = assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("variable_name".into()), tokens)?; + assert_token_raw(MpsToken::Equals, tokens)?; + let inner_op = factory.try_build_item_statement(tokens, dict)?; + Ok(VariableAssignItemOp { + variable_name: var_name, + inner: inner_op, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/item_ops/variable_declare.rs b/mps-interpreter/src/lang/vocabulary/item_ops/variable_declare.rs new file mode 100644 index 0000000..04f258c --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/item_ops/variable_declare.rs @@ -0,0 +1,83 @@ +use core::ops::Deref; +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; + +use crate::lang::utility::{assert_token_raw, assert_token}; +use crate::lang::MpsLanguageDictionary; +use crate::lang::{RuntimeMsg, SyntaxError}; +use crate::lang::{MpsItemOp, MpsItemOpFactory, MpsItemBlockFactory}; +use crate::processing::general::MpsType; +use crate::tokens::MpsToken; +use crate::MpsContext; + +#[derive(Debug)] +pub struct VariableDeclareItemOp { + variable_name: String, + inner: Option>, +} + +impl Deref for VariableDeclareItemOp { + type Target = dyn MpsItemOp; + fn deref(&self) -> &Self::Target { + self + } +} + +impl Display for VariableDeclareItemOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + if let Some(inner) = &self.inner { + write!(f, "let {} = {}", &self.variable_name, inner) + } else { + write!(f, "let {}", &self.variable_name) + } + } +} + +impl MpsItemOp for VariableDeclareItemOp { + fn execute(&self, context: &mut MpsContext) -> Result { + if let Some(inner) = &self.inner { + let mps_type = inner.execute(context)?; + if !context.variables.exists(&self.variable_name) { + context.variables.declare(&self.variable_name, mps_type)?; + } + } else { + if !context.variables.exists(&self.variable_name) { + context.variables.declare(&self.variable_name, MpsType::empty())?; + } + } + Ok(MpsType::empty()) + } +} + +pub struct VariableDeclareItemOpFactory; + +impl MpsItemOpFactory for VariableDeclareItemOpFactory { + fn is_item_op(&self, tokens: &VecDeque) -> bool { + tokens.len() > 2 && tokens[0].is_let() && tokens[1].is_name() + } + + fn build_item_op( + &self, + tokens: &mut VecDeque, + factory: &MpsItemBlockFactory, + dict: &MpsLanguageDictionary, + ) -> Result { + assert_token_raw(MpsToken::Let, tokens)?; + //assert_name("let", tokens)?; + let var_name = assert_token(|t| match t { + MpsToken::Name(s) => Some(s), + _ => None, + }, MpsToken::Name("variable_name".into()), tokens)?; + let inner_op: Option>; + if !tokens.is_empty() { + assert_token_raw(MpsToken::Equals, tokens)?; + inner_op = Some(factory.try_build_item_statement(tokens, dict)?); + } else { + inner_op = None; + } + Ok(VariableDeclareItemOp { + variable_name: var_name, + inner: inner_op, + }) + } +} diff --git a/mps-interpreter/src/lang/vocabulary/mod.rs b/mps-interpreter/src/lang/vocabulary/mod.rs index 94338ee..70d9ffe 100644 --- a/mps-interpreter/src/lang/vocabulary/mod.rs +++ b/mps-interpreter/src/lang/vocabulary/mod.rs @@ -23,4 +23,5 @@ pub use union::{union_function_factory, UnionStatementFactory}; pub use variable_assign::{AssignStatement, AssignStatementFactory}; pub mod filters; +pub mod item_ops; pub mod sorters; diff --git a/mps-interpreter/src/processing/music_analysis.rs b/mps-interpreter/src/processing/music_analysis.rs index f4263fd..4e202b0 100644 --- a/mps-interpreter/src/processing/music_analysis.rs +++ b/mps-interpreter/src/processing/music_analysis.rs @@ -20,6 +20,8 @@ pub trait MpsMusicAnalyzer: Debug { fn prepare_item(&mut self, item: &MpsItem) -> Result<(), RuntimeMsg>; fn get_distance(&mut self, from: &MpsItem, to: &MpsItem) -> Result; + + fn clear_cache(&mut self) -> Result<(), RuntimeMsg>; } #[cfg(feature = "bliss-audio")] @@ -108,6 +110,12 @@ impl MpsMusicAnalyzer for MpsDefaultAnalyzer { } Err(RuntimeMsg("Channel closed without response: internal error".to_owned())) } + + fn clear_cache(&mut self) -> Result<(), RuntimeMsg> { + self.requests.send( + RequestType::Clear {} + ).map_err(|e| RuntimeMsg(format!("Channel send error: {}", e))) + } } #[cfg(not(feature = "bliss-audio"))] @@ -140,6 +148,7 @@ enum RequestType { path: String, ack: bool, }, + Clear {}, //End {} } @@ -383,6 +392,10 @@ impl CacheThread { break 'outer; } }, + RequestType::Clear{} => { + self.distance_cache.clear(); + self.song_cache.clear(); + } } } } diff --git a/mps-interpreter/src/processing/variables.rs b/mps-interpreter/src/processing/variables.rs index 28c4b7c..1c52130 100644 --- a/mps-interpreter/src/processing/variables.rs +++ b/mps-interpreter/src/processing/variables.rs @@ -5,11 +5,13 @@ use std::collections::HashMap; use crate::lang::MpsOp; use crate::lang::MpsTypePrimitive; use crate::lang::RuntimeMsg; +use crate::MpsItem; #[derive(Debug)] pub enum MpsType { Op(Box), Primitive(MpsTypePrimitive), + Item(MpsItem), } impl Display for MpsType { @@ -17,10 +19,18 @@ impl Display for MpsType { match self { Self::Op(op) => write!(f, "Op({})", op), Self::Primitive(p) => write!(f, "{}", p), + Self::Item(item) => write!(f, "{}", item), } } } +impl MpsType { + #[inline(always)] + pub const fn empty() -> Self { + MpsType::Primitive(MpsTypePrimitive::Empty) + } +} + pub trait MpsVariableStorer: Debug { fn get(&self, name: &str) -> Result<&'_ MpsType, RuntimeMsg> { match self.get_opt(name) { diff --git a/mps-interpreter/src/tokens/token_enum.rs b/mps-interpreter/src/tokens/token_enum.rs index 83e70df..19a0df6 100644 --- a/mps-interpreter/src/tokens/token_enum.rs +++ b/mps-interpreter/src/tokens/token_enum.rs @@ -21,6 +21,10 @@ pub enum MpsToken { Ampersand, Colon, Tilde, + OpenCurly, + CloseCurly, + Plus, + Minus, } impl MpsToken { @@ -42,6 +46,10 @@ impl MpsToken { "&" => Ok(Self::Ampersand), ":" => Ok(Self::Colon), "~" => Ok(Self::Tilde), + "{" => Ok(Self::OpenCurly), + "}" => Ok(Self::CloseCurly), + "+" => Ok(Self::Plus), + "-" => Ok(Self::Minus), _ => { // name validation let mut ok = true; @@ -194,6 +202,34 @@ impl MpsToken { _ => false, } } + + pub fn is_open_curly(&self) -> bool { + match self { + Self::OpenCurly => true, + _ => false, + } + } + + pub fn is_close_curly(&self) -> bool { + match self { + Self::CloseCurly => true, + _ => false, + } + } + + pub fn is_plus(&self) -> bool { + match self { + Self::Plus => true, + _ => false, + } + } + + pub fn is_minus(&self) -> bool { + match self { + Self::Minus => true, + _ => false, + } + } } impl Display for MpsToken { @@ -218,6 +254,10 @@ impl Display for MpsToken { Self::Ampersand => write!(f, "&"), Self::Colon => write!(f, ":"), Self::Tilde => write!(f, "~"), + Self::OpenCurly => write!(f, "{{"), + Self::CloseCurly => write!(f, "}}"), + Self::Plus => write!(f, "+"), + Self::Minus => write!(f, "-"), } } } diff --git a/mps-interpreter/src/tokens/tokenizer.rs b/mps-interpreter/src/tokens/tokenizer.rs index 5512d5d..98b709c 100644 --- a/mps-interpreter/src/tokens/tokenizer.rs +++ b/mps-interpreter/src/tokens/tokenizer.rs @@ -261,7 +261,7 @@ impl ReaderStateMachine { '\n' | '\r' | '\t' | ' ' => Self::EndToken {}, ';' => Self::EndStatement {}, '\0' => Self::EndOfFile {}, - '(' | ')' | ',' | '=' | '<' | '>' | '.' | '!' | '?' | '|' | ':' => { + '(' | ')' | ',' | '=' | '<' | '>' | '.' | '!' | '?' | '|' | '&' | ':' | '{' | '}' | '+' | '-' | '~' => { Self::SingleCharToken { out: input } } _ => Self::Regular { out: input }, diff --git a/mps-interpreter/tests/single_line.rs b/mps-interpreter/tests/single_line.rs index 91401d8..6a64cf9 100644 --- a/mps-interpreter/tests/single_line.rs +++ b/mps-interpreter/tests/single_line.rs @@ -44,7 +44,11 @@ fn execute_single_line( should_be_emtpy: bool, should_complete: bool, ) -> Result<(), Box> { - println!("--- Executing MPS code: '{}' ---", line); + if line.contains('\n') { + println!("--- Executing MPS code ---\n{}\n--- Executing MPS code ---", line); + } else { + println!("--- Executing MPS code: '{}' ---", line); + } let cursor = Cursor::new(line); let tokenizer = MpsTokenizer::new(cursor); @@ -63,7 +67,7 @@ fn execute_single_line( } } // no need to spam the rest of the songs println!( - "Got song `{}` (file: `{}`)", + "Got song `{}` (filename: `{}`)", item.field("title") .expect("Expected field `title` to exist") .clone() @@ -463,3 +467,255 @@ fn execute_intersectionfn_line() -> Result<(), Box> { true, ) } + +#[test] +fn execute_declareitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{let x = empty()}", + false, + true, + ) +} + +#[test] +fn execute_removeitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{remove item.title, remove item}", + true, + true, + ) +} + +#[test] +fn execute_multiitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + let x = empty(), + remove item, + remove x +}", + true, + true, + ) +} + +#[test] +fn execute_fieldassignitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.potato = empty(), + .test = empty() +}", + false, + true, + ) +} + +#[test] +fn execute_constitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + `str const`, + 1234, + false, + item.test_field = 1234, + let foo = false +}", + false, + true, + ) +} + +#[test] +fn execute_retrieveitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.path = item.filename, + item.not_a_field, + item.new_field = 42, + item.title = item.path, +}", + false, + true, + ) +} + +#[test] +fn execute_additemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.title = `TEST` + item.title, + item.test = 1234 + 94, +}", + false, + true, + ) +} + +#[test] +fn execute_subtractitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.test = 1234 - 94, +}", + false, + true, + ) +} + +#[test] +fn execute_negateitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.test = 1234, + item.test = -item.test, + item.test = -42, +}", + false, + true, + ) +} + +#[test] +fn execute_notitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.test = false, + item.test = !item.test, + item.test = !true, +}", + false, + true, + ) +} + +#[test] +fn execute_orlogicalitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.test = true || true, + item.test = !true || false, +}", + false, + true, + ) +} + +#[test] +fn execute_andlogicalitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.test = true && true, + item.test = !true && false, +}", + false, + true, + ) +} + +#[test] +fn execute_bracketsitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.test = true && true && (false || false), + item.test = (!true && false || (false || !false)), +}", + false, + true, + ) +} + +#[test] +fn execute_stringifyitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item.filepath = ~`test out: {test}` item, + item.test = true && true && (false || false), + item.test = item.test || ((!true && false) || (false || !false)), + item.title = ~`test out: {test}` item +}", + false, + true, + ) +} + +#[test] +fn execute_branchitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + if false { + item.title = 42, + item.title = `THIS IS WRONG ` + item.title + } else { + item.title = `OK `+ item.title, + if true {item.filename = `RIGHT`}, + if true {} else {item.filename = `WRONG`}, + } +}", + false, + true, + ) +} + +#[test] +fn execute_compareitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + if 42 != 42 { + item.title = `THIS IS WRONG ` + item.title + } else { + item.title = `OK `+ item.title + } +}", + false, + true, + ) +} + +#[test] +fn execute_computeitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + let count = 1, + item.track = count, + item.title = ~`Song #{track}` item, + if count > 5 { + item.filename = `¯\\\\_(ツ)_/¯` + } else { + item.filename = `/shrug`, + }, + count = count + 1, +}", + false, + true, + ) +} + +#[test] +fn execute_constructitemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + let other_item = Item (), + let temp_item = Item ( + filename= `???`, + title= `???`, + ), + other_item = temp_item, + temp_item = item, + item = other_item, +}", + false, + true, + ) +} + +/*#[test] +fn execute_iteritemop_line() -> Result<(), Box> { + execute_single_line( + "files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`).{ + item = iter empty() +}", + false, + true, + ) +}*/ diff --git a/mps-player/src/os_controls.rs b/mps-player/src/os_controls.rs index 654b019..b05ef0d 100644 --- a/mps-player/src/os_controls.rs +++ b/mps-player/src/os_controls.rs @@ -179,16 +179,17 @@ impl SystemControlWrapper { fn enqueued(item: MpsItem, dbus_ctrl: &Sender) { //println!("Got enqueued item {}", &item.title); + let file_uri = item.field("filename").and_then(|x| x.to_owned().to_str()).map(|x| format!("file://{}", x)); dbus_ctrl .send(DbusControl::SetMetadata(Metadata { length: None, - art_url: None, + art_url: None, //file_uri.clone() TODO do this without having to rip the art image from the file like Elisa album: item.field("album").and_then(|x| x.to_owned().to_str()), album_artist: None, // TODO maybe? artist: item .field("artist") .and_then(|x| x.to_owned().to_str()) - .map(|x| vec![x]), + .map(|x| x.split(",").map(|s| s.trim().to_owned()).collect()), composer: None, disc_number: None, genre: item @@ -200,7 +201,7 @@ impl SystemControlWrapper { .field("track") .and_then(|x| x.to_owned().to_i64()) .map(|track| track as i32), - url: item.field("filename").and_then(|x| x.to_owned().to_str()), + url: file_uri, })) .unwrap_or(()); }