From 90a310bbbe756e3226f0bf62abc884103a19ac35 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sat, 8 Jan 2022 20:51:31 -0500 Subject: [PATCH] Initial API work for reset() support on statements --- README.md | 16 +++ mps-interpreter/src/lang/filter.rs | 39 ++++++ mps-interpreter/src/lang/operation.rs | 15 +++ mps-interpreter/src/lang/vocabulary/repeat.rs | 114 ++++++++++++------ mps-interpreter/src/processing/variables.rs | 49 +++++--- src/main.rs | 16 +++ 6 files changed, 191 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 8c7cbd1..86b13dd 100644 --- a/README.md +++ b/README.md @@ -5,5 +5,21 @@ This project implements the interpreter (mps-interpreter), music player (mps-pla The CLI interface includes a REPL for running scripts. The REPL interactive mode also provides more details about using MPS through the `?help` command. +### FAQ +#### Is MPS Turing-Complete? +**No**. It can't perform arbitrary calculations (yet), which easily disqualifies MPS from being Turing-complete. + +#### Can I use MPS right now? +**Sure!** It's not complete, but MPS is completely useable for basic music queries right now. Hopefully most of the bugs have been ironed out as well... + +#### Why write a new language? +**I thought it would be fun**. I also wanted to be able to play my music without having to be at the whim of someone else's algorithm (and music), and playing just by album or artist was getting boring. I also thought designing a language specifically for iteration would be a novel approach to a language (though every approach is a novel approach for me). + +#### What is MPS? +**Music Playlist Script (MPS) is technically a query language for music files.** It uses an (auto-generated) SQLite3 database for SQL queries and can also directly query the filesystem. Queries can be filtered and extended by using filters and functions built-in to MPS (see mps-interpreter's README.md). + +#### Is MPS a scripting language? +**No**. Technically, it was designed to be one, but it doesn't meet the requirements of a scripting language (yet). One day, I would like it be Turing-complete and then it could be considered a scripting language. At the moment it is barely a query language. + License: LGPL-2.1-only OR GPL-2.0-or-later diff --git a/mps-interpreter/src/lang/filter.rs b/mps-interpreter/src/lang/filter.rs index 3245beb..86ee13c 100644 --- a/mps-interpreter/src/lang/filter.rs +++ b/mps-interpreter/src/lang/filter.rs @@ -78,6 +78,45 @@ impl MpsOp for MpsFilterStatement

{ fn escape(&mut self) -> MpsContext { self.context.take().unwrap() } + + fn is_resetable(&self) -> bool { + match &self.iterable { + VariableOrOp::Variable(s) => { + let var = self.context.as_ref().unwrap().variables.get_opt(s); + if let Some(MpsType::Op(var)) = var { + var.is_resetable() + } else { + false + } + }, + VariableOrOp::Op(PseudoOp::Real(op)) => op.is_resetable(), + VariableOrOp::Op(PseudoOp::Fake(_)) => false, + } + } + + fn reset(&mut self) -> Result<(), RuntimeError> { + let fake = PseudoOp::Fake(format!("{}", self)); + match &mut self.iterable { + VariableOrOp::Variable(s) => { + let fake_getter = &mut move || fake.clone(); + if let MpsType::Op(var) = self.context.as_mut().unwrap().variables.get_mut(s, fake_getter)? { + var.reset() + } else { + Err(RuntimeError { + line: 0, + op: PseudoOp::Fake(format!("{}", self)), + msg: "Cannot reset non-iterable filter variable".to_string(), + }) + } + }, + VariableOrOp::Op(PseudoOp::Real(op)) => op.reset(), + VariableOrOp::Op(PseudoOp::Fake(_)) => Err(RuntimeError { + line: 0, + op: fake, + msg: "Cannot reset fake filter".to_string(), + }), + } + } } impl Iterator for MpsFilterStatement

{ diff --git a/mps-interpreter/src/lang/operation.rs b/mps-interpreter/src/lang/operation.rs index a9579a2..cf02517 100644 --- a/mps-interpreter/src/lang/operation.rs +++ b/mps-interpreter/src/lang/operation.rs @@ -4,6 +4,7 @@ use std::iter::Iterator; use super::MpsLanguageDictionary; use super::{RuntimeError, SyntaxError}; +use super::PseudoOp; use crate::tokens::MpsToken; use crate::MpsContext; use crate::MpsMusicItem; @@ -61,4 +62,18 @@ pub trait MpsOp: Iterator> + Debug + D fn enter(&mut self, ctx: MpsContext); fn escape(&mut self) -> MpsContext; + + fn is_resetable(&self) -> bool {false} + + fn reset(&mut self) -> Result<(), RuntimeError> { + #[cfg(debug_assertions)] + if self.is_resetable() { + panic!("MpsOp reported that it can be reset but did not implement reset (op: {})", self) + } + Err(RuntimeError { + line: 0, + op: PseudoOp::Fake(format!("{}", self)), + msg: "Op does not support reset()".to_string(), + }) + } } diff --git a/mps-interpreter/src/lang/vocabulary/repeat.rs b/mps-interpreter/src/lang/vocabulary/repeat.rs index 6a88e34..ec4ec52 100644 --- a/mps-interpreter/src/lang/vocabulary/repeat.rs +++ b/mps-interpreter/src/lang/vocabulary/repeat.rs @@ -46,56 +46,94 @@ impl Iterator for RepeatStatement { type Item = Result; fn next(&mut self) -> Option { - if !self.inner_done { - let real_op = match self.inner_statement.try_real() { - Err(e) => return Some(Err(e)), - Ok(real) => real, - }; + let real_op = match self.inner_statement.try_real() { + Err(e) => return Some(Err(e)), + Ok(real) => real, + }; + if real_op.is_resetable() { + // give context to inner (should only occur on first run) if self.context.is_some() { let ctx = self.context.take().unwrap(); real_op.enter(ctx); } - let inner_item = real_op.next(); - match inner_item { - Some(x) => { - return match x { - Ok(music) => { - self.cache.push(music.clone()); - Some(Ok(music)) - } - Err(e) => Some(Err(e)), - } + while self.loop_forever || !self.inner_done { + while let Some(item) = real_op.next() { + return Some(item); } - None => { - self.inner_done = true; - self.context = Some(real_op.escape()); + if !self.loop_forever { + if self.repetitions == 0 { + self.inner_done = true; + // take context from inner (should only occur when inner is no longer needed) + self.context = Some(real_op.escape()); + } else { + self.repetitions -= 1; + match real_op.reset() { + Err(e) => return Some(Err(e)), + Ok(_) => {}, + } + } + } else { + // always reset in infinite loop mode + match real_op.reset() { + Err(e) => return Some(Err(e)), + Ok(_) => {}, + } } } - } - // inner is done - if self.repetitions == 0 && !self.loop_forever { + if self.context.is_none() { + self.context = Some(real_op.escape()); + } None } else { - if self.cache.len() == 0 { - if self.loop_forever { - Some(Err(RuntimeError { - line: 0, - op: (Box::new(self.clone()) as Box).into(), - msg: "Cannot repeat nothing".into(), - })) - } else { - None + // cache items in RepeatStatement since inner_statement cannot be reset + if !self.inner_done { + if self.context.is_some() { + let ctx = self.context.take().unwrap(); + real_op.enter(ctx); } - } else { - let music_item = self.cache[self.cache_position].clone(); - self.cache_position += 1; - if self.cache_position == self.cache.len() { - if self.repetitions != 0 { - self.repetitions -= 1; + let inner_item = real_op.next(); + match inner_item { + Some(x) => { + return match x { + Ok(music) => { + self.cache.push(music.clone()); + Some(Ok(music)) + } + Err(e) => Some(Err(e)), + } + } + None => { + // inner has completed it's only run + self.inner_done = true; + self.context = Some(real_op.escape()); } - self.cache_position = 0; } - Some(Ok(music_item)) + } + // inner is done + if self.repetitions == 0 && !self.loop_forever { + None + } else { + if self.cache.len() == 0 { + if self.loop_forever { + Some(Err(RuntimeError { + line: 0, + op: (Box::new(self.clone()) as Box).into(), + msg: "Cannot repeat nothing".into(), + })) + } else { + None + } + } else { + let music_item = self.cache[self.cache_position].clone(); + self.cache_position += 1; + if self.cache_position == self.cache.len() { + if self.repetitions != 0 { + self.repetitions -= 1; + } + self.cache_position = 0; + } + Some(Ok(music_item)) + } } } } diff --git a/mps-interpreter/src/processing/variables.rs b/mps-interpreter/src/processing/variables.rs index f8f6fd7..c4856b9 100644 --- a/mps-interpreter/src/processing/variables.rs +++ b/mps-interpreter/src/processing/variables.rs @@ -24,9 +24,31 @@ impl Display for MpsType { } pub trait MpsVariableStorer: Debug { - fn get(&self, name: &str, op: &mut OpGetter) -> Result<&MpsType, RuntimeError>; + fn get(&self, name: &str, op: &mut OpGetter) -> Result<&MpsType, RuntimeError> { + match self.get_opt(name) { + Some(item) => Ok(item), + None => Err(RuntimeError { + line: 0, + op: op(), + msg: format!("Variable {} not found", name), + }), + } + } - fn get_mut(&mut self, name: &str, op: &mut OpGetter) -> Result<&mut MpsType, RuntimeError>; + fn get_opt(&self, name: &str) -> Option<&MpsType>; + + fn get_mut(&mut self, name: &str, op: &mut OpGetter) -> Result<&mut MpsType, RuntimeError> { + match self.get_mut_opt(name) { + Some(item) => Ok(item), + None => Err(RuntimeError { + line: 0, + op: op(), + msg: format!("Variable {} not found", name), + }), + } + } + + fn get_mut_opt(&mut self, name: &str) -> Option<&mut MpsType>; fn assign(&mut self, name: &str, value: MpsType, op: &mut OpGetter) -> Result<(), RuntimeError>; @@ -47,26 +69,13 @@ pub struct MpsOpStorage { } impl MpsVariableStorer for MpsOpStorage { - fn get(&self, key: &str, op: &mut OpGetter) -> Result<&MpsType, RuntimeError> { - match self.storage.get(key) { - Some(item) => Ok(item), - None => Err(RuntimeError { - line: 0, - op: op(), - msg: format!("Variable {} not found", key), - }), - } + + fn get_opt(&self, key: &str) -> Option<&MpsType> { + self.storage.get(key) } - fn get_mut(&mut self, key: &str, op: &mut OpGetter) -> Result<&mut MpsType, RuntimeError> { - match self.storage.get_mut(key) { - Some(item) => Ok(item), - None => Err(RuntimeError { - line: 0, - op: op(), - msg: format!("Variable {} not found", key), - }), - } + fn get_mut_opt(&mut self, key: &str) -> Option<&mut MpsType> { + self.storage.get_mut(key) } fn assign(&mut self, key: &str, item: MpsType, op: &mut OpGetter) -> Result<(), RuntimeError> { diff --git a/src/main.rs b/src/main.rs index 94321f9..f4584fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,22 @@ //! The CLI interface includes a REPL for running scripts. //! The REPL interactive mode also provides more details about using MPS through the `?help` command. //! +//! ## FAQ +//! ### Is MPS Turing-Complete? +//! **No**. It can't perform arbitrary calculations (yet), which easily disqualifies MPS from being Turing-complete. +//! +//! ### Can I use MPS right now? +//! **Sure!** It's not complete, but MPS is completely useable for basic music queries right now. Hopefully most of the bugs have been ironed out as well... +//! +//! ### Why write a new language? +//! **I thought it would be fun**. I also wanted to be able to play my music without having to be at the whim of someone else's algorithm (and music), and playing just by album or artist was getting boring. I also thought designing a language specifically for iteration would be a novel approach to a language (though every approach is a novel approach for me). +//! +//! ### What is MPS? +//! **Music Playlist Script (MPS) is technically a query language for music files.** It uses an (auto-generated) SQLite3 database for SQL queries and can also directly query the filesystem. Queries can be filtered and extended by using filters and functions built-in to MPS (see mps-interpreter's README.md). +//! +//! ### Is MPS a scripting language? +//! **No**. Technically, it was designed to be one, but it doesn't meet the requirements of a scripting language (yet). One day, I would like it be Turing-complete and then it could be considered a scripting language. At the moment it is barely a query language. +//! mod channel_io; mod cli;