Initial API work for reset() support on statements

This commit is contained in:
NGnius (Graham) 2022-01-08 20:51:31 -05:00
parent e93d79f28d
commit 90a310bbbe
6 changed files with 191 additions and 58 deletions

View file

@ -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 CLI interface includes a REPL for running scripts.
The REPL interactive mode also provides more details about using MPS through the `?help` command. 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 License: LGPL-2.1-only OR GPL-2.0-or-later

View file

@ -78,6 +78,45 @@ impl<P: MpsFilterPredicate + 'static> MpsOp for MpsFilterStatement<P> {
fn escape(&mut self) -> MpsContext { fn escape(&mut self) -> MpsContext {
self.context.take().unwrap() 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<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> { impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {

View file

@ -4,6 +4,7 @@ use std::iter::Iterator;
use super::MpsLanguageDictionary; use super::MpsLanguageDictionary;
use super::{RuntimeError, SyntaxError}; use super::{RuntimeError, SyntaxError};
use super::PseudoOp;
use crate::tokens::MpsToken; use crate::tokens::MpsToken;
use crate::MpsContext; use crate::MpsContext;
use crate::MpsMusicItem; use crate::MpsMusicItem;
@ -61,4 +62,18 @@ pub trait MpsOp: Iterator<Item = Result<MpsMusicItem, RuntimeError>> + Debug + D
fn enter(&mut self, ctx: MpsContext); fn enter(&mut self, ctx: MpsContext);
fn escape(&mut self) -> 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(),
})
}
} }

View file

@ -46,11 +46,47 @@ impl Iterator for RepeatStatement {
type Item = Result<MpsMusicItem, RuntimeError>; type Item = Result<MpsMusicItem, RuntimeError>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if !self.inner_done {
let real_op = match self.inner_statement.try_real() { let real_op = match self.inner_statement.try_real() {
Err(e) => return Some(Err(e)), Err(e) => return Some(Err(e)),
Ok(real) => real, 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);
}
while self.loop_forever || !self.inner_done {
while let Some(item) = real_op.next() {
return Some(item);
}
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(_) => {},
}
}
}
if self.context.is_none() {
self.context = Some(real_op.escape());
}
None
} else {
// cache items in RepeatStatement since inner_statement cannot be reset
if !self.inner_done {
if self.context.is_some() { if self.context.is_some() {
let ctx = self.context.take().unwrap(); let ctx = self.context.take().unwrap();
real_op.enter(ctx); real_op.enter(ctx);
@ -67,6 +103,7 @@ impl Iterator for RepeatStatement {
} }
} }
None => { None => {
// inner has completed it's only run
self.inner_done = true; self.inner_done = true;
self.context = Some(real_op.escape()); self.context = Some(real_op.escape());
} }
@ -99,6 +136,7 @@ impl Iterator for RepeatStatement {
} }
} }
} }
}
} }
impl MpsOp for RepeatStatement { impl MpsOp for RepeatStatement {

View file

@ -24,9 +24,31 @@ impl Display for MpsType {
} }
pub trait MpsVariableStorer: Debug { 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) fn assign(&mut self, name: &str, value: MpsType, op: &mut OpGetter)
-> Result<(), RuntimeError>; -> Result<(), RuntimeError>;
@ -47,26 +69,13 @@ pub struct MpsOpStorage {
} }
impl MpsVariableStorer for MpsOpStorage { impl MpsVariableStorer for MpsOpStorage {
fn get(&self, key: &str, op: &mut OpGetter) -> Result<&MpsType, RuntimeError> {
match self.storage.get(key) { fn get_opt(&self, key: &str) -> Option<&MpsType> {
Some(item) => Ok(item), self.storage.get(key)
None => Err(RuntimeError {
line: 0,
op: op(),
msg: format!("Variable {} not found", key),
}),
}
} }
fn get_mut(&mut self, key: &str, op: &mut OpGetter) -> Result<&mut MpsType, RuntimeError> { fn get_mut_opt(&mut self, key: &str) -> Option<&mut MpsType> {
match self.storage.get_mut(key) { self.storage.get_mut(key)
Some(item) => Ok(item),
None => Err(RuntimeError {
line: 0,
op: op(),
msg: format!("Variable {} not found", key),
}),
}
} }
fn assign(&mut self, key: &str, item: MpsType, op: &mut OpGetter) -> Result<(), RuntimeError> { fn assign(&mut self, key: &str, item: MpsType, op: &mut OpGetter) -> Result<(), RuntimeError> {

View file

@ -3,6 +3,22 @@
//! The CLI interface includes a REPL for running scripts. //! The CLI interface includes a REPL for running scripts.
//! The REPL interactive mode also provides more details about using MPS through the `?help` command. //! 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 channel_io;
mod cli; mod cli;