add basic non-sql lookup files() & cargo fmt

This commit is contained in:
NGnius (Graham) 2021-12-31 19:56:46 -05:00
parent e937a7221f
commit ae0872e417
37 changed files with 1400 additions and 586 deletions

43
Cargo.lock generated
View file

@ -13,6 +13,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "alsa"
version = "0.5.0"
@ -268,6 +277,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
@ -279,6 +298,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "encoding_rs"
version = "0.8.29"
@ -546,7 +576,9 @@ name = "mps-interpreter"
version = "0.1.0"
dependencies = [
"dirs",
"regex",
"rusqlite",
"shellexpand",
"symphonia",
]
@ -844,6 +876,8 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
@ -908,6 +942,15 @@ version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
[[package]]
name = "shellexpand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829"
dependencies = [
"dirs-next",
]
[[package]]
name = "shlex"
version = "0.1.1"

View file

@ -9,7 +9,10 @@ symphonia = { version = "0.4.0", optional = true, features = [
"aac", "flac", "mp3", "pcm", "vorbis", "isomp4", "ogg", "wav"
] }
dirs = { version = "4.0.0" }
regex = { version = "1" }
shellexpand = { version = "2.1", optional = true }
[features]
default = [ "music_library" ]
default = [ "music_library", "ergonomics" ]
music_library = [ "symphonia" ] # song metadata parsing and database auto-population
ergonomics = ["shellexpand"]

View file

@ -1,11 +1,14 @@
use super::processing::database::{MpsDatabaseQuerier, MpsSQLiteExecutor};
use super::processing::general::{MpsVariableStorer, MpsOpStorage};
use super::processing::general::{
MpsFilesystemExecutor, MpsFilesystemQuerier, MpsOpStorage, MpsVariableStorer,
};
use std::fmt::{Debug, Display, Error, Formatter};
#[derive(Debug)]
pub struct MpsContext {
pub database: Box<dyn MpsDatabaseQuerier>,
pub variables: Box<dyn MpsVariableStorer>,
pub filesystem: Box<dyn MpsFilesystemQuerier>,
}
impl Default for MpsContext {
@ -13,6 +16,7 @@ impl Default for MpsContext {
Self {
database: Box::new(MpsSQLiteExecutor::default()),
variables: Box::new(MpsOpStorage::default()),
filesystem: Box::new(MpsFilesystemExecutor::default()),
}
}
}

View file

@ -114,7 +114,9 @@ where
match stmt {
Ok(mut stmt) => {
#[cfg(debug_assertions)]
if self.buffer.len() != 0 {panic!("Token buffer was not emptied! (rem: {:?})", self.buffer)}
if self.buffer.len() != 0 {
panic!("Token buffer was not emptied! (rem: {:?})", self.buffer)
}
stmt.enter(self.context.take().unwrap_or_else(|| MpsContext::default()));
self.current_stmt = Some(stmt);
let next_item = self.current_stmt.as_mut().unwrap().next();
@ -157,5 +159,6 @@ pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
.add(crate::lang::vocabulary::CommentStatementFactory)
.add(crate::lang::vocabulary::repeat_function_factory())
.add(crate::lang::vocabulary::AssignStatementFactory)
.add(crate::lang::vocabulary::sql_init_function_factory());
.add(crate::lang::vocabulary::sql_init_function_factory())
.add(crate::lang::vocabulary::files_function_factory());
}

View file

@ -11,14 +11,19 @@ pub trait DatabaseObj: Sized {
}
pub fn generate_default_db() -> rusqlite::Result<rusqlite::Connection> {
generate_db(super::utility::music_folder(), DEFAULT_SQLITE_FILEPATH, true)
generate_db(
super::utility::music_folder(),
DEFAULT_SQLITE_FILEPATH,
true,
)
}
pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
music_path: P1,
sqlite_path: P2,
generate: bool
generate: bool,
) -> rusqlite::Result<rusqlite::Connection> {
#[allow(unused_variables)]
let music_path = music_path.as_ref();
let sqlite_path = sqlite_path.as_ref();
let db_exists = std::path::Path::new(sqlite_path).exists();
@ -73,18 +78,18 @@ pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
// generate data and store in db
#[cfg(feature = "music_library")]
if generate {
let mut lib = crate::music::MpsLibrary::new();
if db_exists {
crate::music::build_library_from_sqlite(&conn, &mut lib)?;
}
lib.clear_modified();
match crate::music::build_library_from_files(&music_path, &mut lib) {
Ok(_) => if lib.is_modified() {
let transaction = conn.transaction()?;
{
let mut song_insert = transaction.prepare(
"INSERT OR REPLACE INTO songs (
Ok(_) => {
if lib.is_modified() {
let transaction = conn.transaction()?;
{
let mut song_insert = transaction.prepare(
"INSERT OR REPLACE INTO songs (
song_id,
title,
artist,
@ -93,13 +98,13 @@ pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
metadata,
genre
) VALUES (?, ?, ?, ?, ?, ?, ?)",
)?;
for song in lib.all_songs() {
song_insert.execute(song.to_params().as_slice())?;
}
)?;
for song in lib.all_songs() {
song_insert.execute(song.to_params().as_slice())?;
}
let mut metadata_insert = transaction.prepare(
"INSERT OR REPLACE INTO metadata (
let mut metadata_insert = transaction.prepare(
"INSERT OR REPLACE INTO metadata (
meta_id,
plays,
track,
@ -107,47 +112,48 @@ pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
duration,
date
) VALUES (?, ?, ?, ?, ?, ?)",
)?;
for meta in lib.all_metadata() {
metadata_insert.execute(meta.to_params().as_slice())?;
}
)?;
for meta in lib.all_metadata() {
metadata_insert.execute(meta.to_params().as_slice())?;
}
let mut artist_insert = transaction.prepare(
"INSERT OR REPLACE INTO artists (
let mut artist_insert = transaction.prepare(
"INSERT OR REPLACE INTO artists (
artist_id,
name,
genre
) VALUES (?, ?, ?)",
)?;
for artist in lib.all_artists() {
artist_insert.execute(artist.to_params().as_slice())?;
}
)?;
for artist in lib.all_artists() {
artist_insert.execute(artist.to_params().as_slice())?;
}
let mut album_insert = transaction.prepare(
"INSERT OR REPLACE INTO albums (
let mut album_insert = transaction.prepare(
"INSERT OR REPLACE INTO albums (
album_id,
title,
metadata,
artist,
genre
) VALUES (?, ?, ?, ?, ?)",
)?;
for album in lib.all_albums() {
album_insert.execute(album.to_params().as_slice())?;
}
)?;
for album in lib.all_albums() {
album_insert.execute(album.to_params().as_slice())?;
}
let mut genre_insert = transaction.prepare(
"INSERT OR REPLACE INTO genres (
let mut genre_insert = transaction.prepare(
"INSERT OR REPLACE INTO genres (
genre_id,
title
) VALUES (?, ?)",
)?;
for genre in lib.all_genres() {
genre_insert.execute(genre.to_params().as_slice())?;
)?;
for genre in lib.all_genres() {
genre_insert.execute(genre.to_params().as_slice())?;
}
}
transaction.commit()?;
}
}
transaction.commit()?;
},
}
Err(e) => println!("Unable to load music from {}: {}", music_path.display(), e),
}
}

View file

@ -31,7 +31,7 @@ impl MpsLanguageDictionary {
line: 0,
token: MpsToken::Name("???".into()),
got: None,
})
}),
}?;
Err(SyntaxError {
line: 0,

View file

@ -7,7 +7,7 @@ use crate::tokens::MpsToken;
pub struct SyntaxError {
pub line: usize,
pub token: MpsToken,
pub got: Option<MpsToken>
pub got: Option<MpsToken>,
}
impl Display for SyntaxError {

View file

@ -3,18 +3,23 @@ use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use std::marker::PhantomData;
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{BoxedMpsOpFactory, MpsOp, PseudoOp};
use crate::lang::{RuntimeError, SyntaxError};
use crate::processing::general::MpsType;
use crate::processing::OpGetter;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::tokens::MpsToken;
use crate::lang::{MpsOp, PseudoOp, BoxedMpsOpFactory};
use crate::lang::{RuntimeError, SyntaxError};
use crate::lang::MpsLanguageDictionary;
use crate::processing::general::MpsType;
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::processing::OpGetter;
pub trait MpsFilterPredicate: Clone + Debug + Display {
fn matches(&mut self, item: &MpsMusicItem, ctx: &mut MpsContext, op: &mut OpGetter) -> Result<bool, RuntimeError>;
fn matches(
&mut self,
item: &MpsMusicItem,
ctx: &mut MpsContext,
op: &mut OpGetter,
) -> Result<bool, RuntimeError>;
}
pub trait MpsFilterFactory<P: MpsFilterPredicate + 'static> {
@ -37,7 +42,7 @@ impl Display for VariableOrOp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
Self::Variable(s) => write!(f, "{}", s),
Self::Op(op) => write!(f, "{}", op)
Self::Op(op) => write!(f, "{}", op),
}
}
}
@ -84,60 +89,68 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
let mut op_getter = move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into();
//let ctx = self.context.as_mut().unwrap();
match &mut self.iterable {
VariableOrOp::Op(op) =>
match op.try_real() {
Ok(real_op) => {
let ctx = self.context.take().unwrap();
real_op.enter(ctx);
let mut maybe_result = None;
while let Some(item) = real_op.next() {
let mut ctx = real_op.escape();
match item {
Err(e) => {
//self.context = Some(op.escape());
maybe_result = Some(Err(e));
self.context = Some(ctx);
break;
},
Ok(item) => {
let matches_result = self.predicate.matches(&item, &mut ctx, &mut op_getter);
let matches = match matches_result {
Err(e) => {
maybe_result = Some(Err(e));
self.context = Some(ctx);
break;
},
Ok(b) => b,
};
if matches {
//self.context = Some(op.escape());
maybe_result = Some(Ok(item));
VariableOrOp::Op(op) => match op.try_real() {
Ok(real_op) => {
let ctx = self.context.take().unwrap();
real_op.enter(ctx);
let mut maybe_result = None;
while let Some(item) = real_op.next() {
let mut ctx = real_op.escape();
match item {
Err(e) => {
//self.context = Some(op.escape());
maybe_result = Some(Err(e));
self.context = Some(ctx);
break;
}
Ok(item) => {
let matches_result =
self.predicate.matches(&item, &mut ctx, &mut op_getter);
let matches = match matches_result {
Err(e) => {
maybe_result = Some(Err(e));
self.context = Some(ctx);
break;
}
Ok(b) => b,
};
if matches {
//self.context = Some(op.escape());
maybe_result = Some(Ok(item));
self.context = Some(ctx);
break;
}
}
real_op.enter(ctx);
}
if self.context.is_none() {
self.context = Some(real_op.escape());
}
maybe_result
},
Err(e) => return Some(Err(e)),
},
real_op.enter(ctx);
}
if self.context.is_none() {
self.context = Some(real_op.escape());
}
maybe_result
}
Err(e) => return Some(Err(e)),
},
VariableOrOp::Variable(variable_name) => {
let mut variable = match self.context.as_mut().unwrap().variables.remove(
&variable_name,
&mut op_getter
) {
let mut variable = match self
.context
.as_mut()
.unwrap()
.variables
.remove(&variable_name, &mut op_getter)
{
Ok(MpsType::Op(op)) => op,
Ok(x) => return Some(Err(RuntimeError {
line: 0,
op: (Box::new(self_clone2.clone()) as Box<dyn MpsOp>).into(),
msg: format!("Expected operation/iterable type in variable {}, got {}", &variable_name, x)
})),
Err(e) => return Some(Err(e))
Ok(x) => {
return Some(Err(RuntimeError {
line: 0,
op: (Box::new(self_clone2.clone()) as Box<dyn MpsOp>).into(),
msg: format!(
"Expected operation/iterable type in variable {}, got {}",
&variable_name, x
),
}))
}
Err(e) => return Some(Err(e)),
};
let mut maybe_result = None;
let ctx = self.context.take().unwrap();
@ -146,18 +159,19 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
let mut ctx = variable.escape();
match item {
Err(e) => {
maybe_result = Some(Err(e));
self.context = Some(ctx);
break;
},
maybe_result = Some(Err(e));
self.context = Some(ctx);
break;
}
Ok(item) => {
let matches_result = self.predicate.matches(&item, &mut ctx, &mut op_getter);
let matches_result =
self.predicate.matches(&item, &mut ctx, &mut op_getter);
let matches = match matches_result {
Err(e) => {
maybe_result = Some(Err(e));
self.context = Some(ctx);
break;
},
}
Ok(b) => b,
};
if matches {
@ -175,21 +189,27 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
match self.context.as_mut().unwrap().variables.declare(
&variable_name,
MpsType::Op(variable),
&mut op_getter) {
&mut op_getter,
) {
Err(e) => return Some(Err(e)),
Ok(_) => maybe_result
Ok(_) => maybe_result,
}
}
}
}
}
pub struct MpsFilterStatementFactory<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> {
pub struct MpsFilterStatementFactory<
P: MpsFilterPredicate + 'static,
F: MpsFilterFactory<P> + 'static,
> {
filter_factory: F,
idc: PhantomData<P>,
}
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> MpsFilterStatementFactory<P, F> {
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static>
MpsFilterStatementFactory<P, F>
{
pub fn new(factory: F) -> Self {
Self {
filter_factory: factory,
@ -198,7 +218,9 @@ impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> MpsFilte
}
}
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMpsOpFactory for MpsFilterStatementFactory<P, F> {
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMpsOpFactory
for MpsFilterStatementFactory<P, F>
{
fn is_op_boxed(&self, tokens: &VecDeque<MpsToken>) -> bool {
let tokens_len = tokens.len();
if last_open_bracket_is_after_dot(tokens) {
@ -206,7 +228,8 @@ impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMps
if start_of_predicate > tokens_len - 1 {
false
} else {
let tokens2: VecDeque<&MpsToken> = VecDeque::from_iter(tokens.range(start_of_predicate..tokens_len-1));
let tokens2: VecDeque<&MpsToken> =
VecDeque::from_iter(tokens.range(start_of_predicate..tokens_len - 1));
self.filter_factory.is_filter(&tokens2)
}
} else {
@ -223,10 +246,14 @@ impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMps
let op;
if start_of_op == 1 && tokens[0].is_name() {
// variable_name.(predicate)
let variable_name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None
}, MpsToken::Name("variable_name".into()), tokens)?;
let variable_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?;
op = VariableOrOp::Variable(variable_name);
} else {
// <some other op>.(predicate)
@ -255,9 +282,9 @@ fn last_open_bracket_is_after_dot(tokens: &VecDeque<MpsToken>) -> bool {
open_bracket_found = true;
} else if open_bracket_found {
if tokens[i].is_dot() {
return true
return true;
} else {
return false
return false;
}
}
}
@ -271,9 +298,9 @@ fn last_dot_before_open_bracket(tokens: &VecDeque<MpsToken>) -> usize {
open_bracket_found = true;
} else if open_bracket_found {
if tokens[i].is_dot() {
return i
return i;
} else {
return 0
return 0;
}
}
}

View file

@ -2,11 +2,11 @@ use std::collections::VecDeque;
//use std::fmt::{Debug, Display, Error, Formatter};
use std::marker::PhantomData;
use crate::tokens::MpsToken;
use crate::lang::{MpsOp, BoxedMpsOpFactory};
use crate::lang::SyntaxError;
use crate::lang::utility::{assert_token, assert_token_raw, assert_token_raw_back};
use crate::lang::MpsLanguageDictionary;
use crate::lang::utility::{assert_token_raw, assert_token_raw_back, assert_token};
use crate::lang::SyntaxError;
use crate::lang::{BoxedMpsOpFactory, MpsOp};
use crate::tokens::MpsToken;
pub trait MpsFunctionFactory<Op: MpsOp + 'static> {
fn is_function(&self, name: &str) -> bool;
@ -33,10 +33,8 @@ impl<Op: MpsOp + 'static, F: MpsFunctionFactory<Op> + 'static> MpsFunctionStatem
}
}
impl<Op: MpsOp + 'static, F: MpsFunctionFactory<Op> + 'static>
BoxedMpsOpFactory
for
MpsFunctionStatementFactory<Op, F>
impl<Op: MpsOp + 'static, F: MpsFunctionFactory<Op> + 'static> BoxedMpsOpFactory
for MpsFunctionStatementFactory<Op, F>
{
fn is_op_boxed(&self, tokens: &VecDeque<MpsToken>) -> bool {
let tokens_len = tokens.len();
@ -44,10 +42,12 @@ MpsFunctionStatementFactory<Op, F>
false
} else {
match &tokens[0] {
MpsToken::Name(n) => self.op_factory.is_function(n)
&& tokens[1].is_open_bracket()
&& tokens[tokens_len - 1].is_close_bracket(),
_ => false
MpsToken::Name(n) => {
self.op_factory.is_function(n)
&& tokens[1].is_open_bracket()
&& tokens[tokens_len - 1].is_close_bracket()
}
_ => false,
}
}
}
@ -57,12 +57,18 @@ MpsFunctionStatementFactory<Op, F>
tokens: &mut VecDeque<MpsToken>,
dict: &MpsLanguageDictionary,
) -> Result<Box<dyn MpsOp>, SyntaxError> {
let name = assert_token(|t| match t {
MpsToken::Name(n) => Some(n),
_ => None
}, MpsToken::Name("function_name".into()), tokens)?;
let name = assert_token(
|t| match t {
MpsToken::Name(n) => Some(n),
_ => None,
},
MpsToken::Name("function_name".into()),
tokens,
)?;
assert_token_raw(MpsToken::OpenBracket, tokens)?;
assert_token_raw_back(MpsToken::CloseBracket, tokens)?;
Ok(Box::new(self.op_factory.build_function_params(name, tokens, dict)?))
Ok(Box::new(
self.op_factory.build_function_params(name, tokens, dict)?,
))
}
}

View file

@ -12,11 +12,13 @@ pub(crate) mod utility;
pub use dictionary::MpsLanguageDictionary;
pub use error::{MpsLanguageError, RuntimeError, SyntaxError};
pub use filter::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatement, MpsFilterStatementFactory};
pub use filter::{
MpsFilterFactory, MpsFilterPredicate, MpsFilterStatement, MpsFilterStatementFactory,
};
pub use function::{MpsFunctionFactory, MpsFunctionStatementFactory};
pub use operation::{BoxedMpsOpFactory, MpsOp, MpsOpFactory, SimpleMpsOpFactory};
pub use pseudo_op::PseudoOp;
pub use repeated_meme::{RepeatedTokens, repeated_tokens};
pub use repeated_meme::{repeated_tokens, RepeatedTokens};
//pub(crate) use statement::MpsStatement;
pub use type_primitives::MpsTypePrimitive;
@ -24,8 +26,8 @@ pub mod vocabulary;
pub mod db {
pub use super::db_items::{
generate_default_db, generate_db, DatabaseObj, DbAlbumItem, DbArtistItem, DbGenreItem, DbMetaItem,
DbMusicItem, DEFAULT_SQLITE_FILEPATH,
generate_db, generate_default_db, DatabaseObj, DbAlbumItem, DbArtistItem, DbGenreItem,
DbMetaItem, DbMusicItem, DEFAULT_SQLITE_FILEPATH,
};
}

View file

@ -11,10 +11,7 @@ use crate::MpsMusicItem;
pub trait SimpleMpsOpFactory<T: MpsOp + 'static> {
fn is_op_simple(&self, tokens: &VecDeque<MpsToken>) -> bool;
fn build_op_simple(
&self,
tokens: &mut VecDeque<MpsToken>,
) -> Result<T, SyntaxError>;
fn build_op_simple(&self, tokens: &mut VecDeque<MpsToken>) -> Result<T, SyntaxError>;
}
impl<T: MpsOp + 'static, X: SimpleMpsOpFactory<T> + 'static> MpsOpFactory<T> for X {

View file

@ -7,7 +7,7 @@ use super::RuntimeError;
#[derive(Debug)]
pub enum PseudoOp {
Real(Box<dyn MpsOp>),
Fake(String)
Fake(String),
}
impl PseudoOp {
@ -18,7 +18,7 @@ impl PseudoOp {
line: 0,
op: self.clone(),
msg: "PseudoOp::Fake is not a real MpsOp".into(),
})
}),
}
}
@ -27,12 +27,12 @@ impl PseudoOp {
Self::Real(op) => {
let result = Ok(op);
result
},
}
Self::Fake(_) => Err(RuntimeError {
line: 0,
op: self.clone(),
msg: "PseudoOp::Fake is not a real MpsOp".into(),
})
}),
}
}
@ -57,7 +57,7 @@ impl Clone for PseudoOp {
fn clone(&self) -> Self {
match self {
Self::Real(op) => Self::Fake(format!("{}", op)),
Self::Fake(s) => Self::Fake(s.clone())
Self::Fake(s) => Self::Fake(s.clone()),
}
}
}
@ -66,7 +66,7 @@ impl Display for PseudoOp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
Self::Real(op) => write!(f, "{}", op),
Self::Fake(s) => write!(f, "{}", s)
Self::Fake(s) => write!(f, "{}", s),
}
}
}

View file

@ -1,24 +1,24 @@
use std::collections::VecDeque;
use crate::lang::utility::{assert_token_raw, check_token_raw};
use crate::lang::SyntaxError;
use crate::lang::utility::{check_token_raw, assert_token_raw};
use crate::tokens::MpsToken;
/// Convenient parser for repeated patterns of tokens
pub struct RepeatedTokens<
X: 'static,
F1: FnMut(&mut VecDeque<MpsToken>) -> Result<Option<X>, SyntaxError>,
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError> >
{
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError>,
> {
pattern_ingest: F1,
separator_ingest: F2,
}
impl<
X: 'static,
F1: FnMut(&mut VecDeque<MpsToken>) -> Result<Option<X>, SyntaxError>,
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError> >
RepeatedTokens<X, F1, F2>
X: 'static,
F1: FnMut(&mut VecDeque<MpsToken>) -> Result<Option<X>, SyntaxError>,
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError>,
> RepeatedTokens<X, F1, F2>
{
pub fn ingest_all(&mut self, tokens: &mut VecDeque<MpsToken>) -> Result<Vec<X>, SyntaxError> {
let mut result = Vec::<X>::new();
@ -29,7 +29,7 @@ impl<
while (self.separator_ingest)(tokens)? {
match (self.pattern_ingest)(tokens)? {
Some(x) => result.push(x),
None => break
None => break,
}
}
Ok(result)

View file

@ -15,62 +15,63 @@ impl MpsTypePrimitive {
#[inline]
pub fn compare(&self, other: &Self) -> Result<i8, String> {
let result = match self {
Self::String(s1) => {
match other {
Self::String(s2) => Some(map_ordering(s1.cmp(s2))),
_ => None,
}
Self::String(s1) => match other {
Self::String(s2) => Some(map_ordering(s1.cmp(s2))),
_ => None,
},
Self::Int(i1) => {
match other {
Self::Int(i2) => Some(map_ordering(i1.cmp(i2))),
Self::UInt(i2) => Some(map_ordering((*i1 as i128).cmp(&(*i2 as i128)))),
Self::Float(i2) => Some(
map_ordering((*i1 as f64).partial_cmp(&(*i2 as f64)).unwrap_or(std::cmp::Ordering::Less))
),
_ => None,
}
Self::Int(i1) => match other {
Self::Int(i2) => Some(map_ordering(i1.cmp(i2))),
Self::UInt(i2) => Some(map_ordering((*i1 as i128).cmp(&(*i2 as i128)))),
Self::Float(i2) => Some(map_ordering(
(*i1 as f64)
.partial_cmp(&(*i2 as f64))
.unwrap_or(std::cmp::Ordering::Less),
)),
_ => None,
},
Self::UInt(u1) => {
match other {
Self::UInt(u2) => Some(map_ordering(u1.cmp(u2))),
Self::Int(u2) => Some(map_ordering((*u1 as i128).cmp(&(*u2 as i128)))),
Self::Float(u2) => Some(
map_ordering((*u1 as f64).partial_cmp(&(*u2 as f64)).unwrap_or(std::cmp::Ordering::Less))
),
_ => None,
}
Self::UInt(u1) => match other {
Self::UInt(u2) => Some(map_ordering(u1.cmp(u2))),
Self::Int(u2) => Some(map_ordering((*u1 as i128).cmp(&(*u2 as i128)))),
Self::Float(u2) => Some(map_ordering(
(*u1 as f64)
.partial_cmp(&(*u2 as f64))
.unwrap_or(std::cmp::Ordering::Less),
)),
_ => None,
},
Self::Float(f1) => {
match other {
Self::Float(f2) => Some(map_ordering(f1.partial_cmp(f2).unwrap_or(std::cmp::Ordering::Less))),
Self::Int(f2) => Some(
map_ordering(f1.partial_cmp(&(*f2 as f64)).unwrap_or(std::cmp::Ordering::Less))
),
Self::UInt(f2) => Some(
map_ordering(f1.partial_cmp(&(*f2 as f64)).unwrap_or(std::cmp::Ordering::Less))
),
_ => None
}
Self::Float(f1) => match other {
Self::Float(f2) => Some(map_ordering(
f1.partial_cmp(f2).unwrap_or(std::cmp::Ordering::Less),
)),
Self::Int(f2) => Some(map_ordering(
f1.partial_cmp(&(*f2 as f64))
.unwrap_or(std::cmp::Ordering::Less),
)),
Self::UInt(f2) => Some(map_ordering(
f1.partial_cmp(&(*f2 as f64))
.unwrap_or(std::cmp::Ordering::Less),
)),
_ => None,
},
Self::Bool(b1) => {
match other {
Self::Bool(b2) => {
if *b2 == *b1 {
Some(0)
} else if *b1{
Some(1)
} else {
Some(-1)
}
},
_ => None
Self::Bool(b1) => match other {
Self::Bool(b2) => {
if *b2 == *b1 {
Some(0)
} else if *b1 {
Some(1)
} else {
Some(-1)
}
}
}
_ => None,
},
};
match result {
Some(x) => Ok(x),
None => Err(format!("Cannot compare {} to {}: incompatible types", self, other))
None => Err(format!(
"Cannot compare {} to {}: incompatible types",
self, other
)),
}
}
}
@ -95,4 +96,3 @@ fn map_ordering(ordering: std::cmp::Ordering) -> i8 {
std::cmp::Ordering::Greater => 1,
}
}

View file

@ -1,10 +1,9 @@
use std::collections::VecDeque;
#[cfg(feature = "music_library")]
use std::path::PathBuf;
use super::MpsTypePrimitive;
use super::SyntaxError;
use crate::tokens::MpsToken;
use super::MpsTypePrimitive;
pub fn assert_token<T, F: FnOnce(MpsToken) -> Option<T>>(
caster: F,
@ -17,7 +16,7 @@ pub fn assert_token<T, F: FnOnce(MpsToken) -> Option<T>>(
line: 0,
token: token.clone(),
got: None,
})
}),
}?;
if let Some(out) = caster(result.clone()) {
Ok(out)
@ -40,7 +39,7 @@ pub fn assert_token_raw(
line: 0,
token: token.clone(),
got: None,
})
}),
}?;
if std::mem::discriminant(&token) == std::mem::discriminant(&result) {
Ok(result)
@ -63,7 +62,7 @@ pub fn assert_token_raw_back(
line: 0,
token: token.clone(),
got: None,
})
}),
}?;
if std::mem::discriminant(&token) == std::mem::discriminant(&result) {
Ok(result)
@ -76,10 +75,7 @@ pub fn assert_token_raw_back(
}
}
pub fn check_token_raw(
token: MpsToken,
token_target: &MpsToken,
) -> bool {
pub fn check_token_raw(token: MpsToken, token_target: &MpsToken) -> bool {
std::mem::discriminant(&token) == std::mem::discriminant(token_target)
}
@ -91,27 +87,25 @@ pub fn assert_name(name: &str, tokens: &mut VecDeque<MpsToken>) -> Result<String
line: 0,
token: MpsToken::Name(name.to_owned()),
got: None,
})
}),
}?;
match result {
MpsToken::Name(n) => {
if n == name {
Ok(n)
} else {
Err(
SyntaxError {
line: 0,
token: MpsToken::Name(name.to_owned()),
got: Some(MpsToken::Name(n)),
}
)
Err(SyntaxError {
line: 0,
token: MpsToken::Name(name.to_owned()),
got: Some(MpsToken::Name(n)),
})
}
},
}
token => Err(SyntaxError {
line: 0,
token: MpsToken::Name(name.to_owned()),
got: Some(token),
})
}),
}
}
@ -119,20 +113,21 @@ pub fn assert_name(name: &str, tokens: &mut VecDeque<MpsToken>) -> Result<String
pub fn check_name(name: &str, token: &MpsToken) -> bool {
match token {
MpsToken::Name(n) => n == name,
_ => false
_ => false,
}
}
pub fn check_is_type(token: &MpsToken) -> bool {
match token {
MpsToken::Literal(_) => true,
MpsToken::Name(s) =>
MpsToken::Name(s) => {
s.parse::<f64>().is_ok()
|| s.parse::<i64>().is_ok()
|| s.parse::<u64>().is_ok()
|| s == "false"
|| s == "true",
_ => false
|| s.parse::<i64>().is_ok()
|| s.parse::<u64>().is_ok()
|| s == "false"
|| s == "true"
}
_ => false,
}
}
@ -143,7 +138,7 @@ pub fn assert_type(tokens: &mut VecDeque<MpsToken>) -> Result<MpsTypePrimitive,
line: 0,
token: MpsToken::Name("Float | UInt | Int | Bool".into()),
got: None,
})
}),
}?;
match token {
MpsToken::Literal(s) => Ok(MpsTypePrimitive::String(s)),
@ -162,15 +157,15 @@ pub fn assert_type(tokens: &mut VecDeque<MpsToken>) -> Result<MpsTypePrimitive,
Err(SyntaxError {
line: 0,
token: MpsToken::Name("Float | UInt | Int | Bool".into()),
got: Some(MpsToken::Name(s))
got: Some(MpsToken::Name(s)),
})
}
},
}
token => Err(SyntaxError {
line: 0,
token: MpsToken::Name("Float | UInt | Int | Bool | \"String\"".into()),
got: Some(token)
})
got: Some(token),
}),
}
}
@ -179,4 +174,3 @@ pub fn music_folder() -> PathBuf {
.unwrap_or_else(|| PathBuf::from("./"))
.join("Music")
}

View file

@ -2,14 +2,14 @@ use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::tokens::MpsToken;
use crate::lang::{RuntimeError, SyntaxError};
use crate::lang::{MpsOp, SimpleMpsOpFactory, MpsOpFactory, BoxedMpsOpFactory};
use crate::lang::MpsLanguageDictionary;
use crate::lang::utility::assert_token;
use crate::lang::MpsLanguageDictionary;
use crate::lang::{BoxedMpsOpFactory, MpsOp, MpsOpFactory, SimpleMpsOpFactory};
use crate::lang::{RuntimeError, SyntaxError};
#[derive(Debug)]
pub struct CommentStatement {
@ -66,21 +66,24 @@ pub struct CommentStatementFactory;
impl SimpleMpsOpFactory<CommentStatement> for CommentStatementFactory {
fn is_op_simple(&self, tokens: &VecDeque<MpsToken>) -> bool {
tokens.len() == 1
&& tokens[0].is_comment()
tokens.len() == 1 && tokens[0].is_comment()
}
fn build_op_simple(
&self,
tokens: &mut VecDeque<MpsToken>,
) -> Result<CommentStatement, SyntaxError> {
let comment = assert_token(|t| match t {
MpsToken::Comment(c) => Some(c),
_ => None
}, MpsToken::Comment("comment".into()), tokens)?;
let comment = assert_token(
|t| match t {
MpsToken::Comment(c) => Some(c),
_ => None,
},
MpsToken::Comment("comment".into()),
tokens,
)?;
Ok(CommentStatement {
comment: comment,
context: None
context: None,
})
}
}

View file

@ -0,0 +1,230 @@
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::lang::repeated_tokens;
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsOp};
use crate::lang::{RuntimeError, SyntaxError};
use crate::processing::general::FileIter;
#[derive(Debug)]
pub struct FilesStatement {
context: Option<MpsContext>,
// function params
folder: Option<String>,
regex: Option<String>,
recursive: Option<bool>,
// state
file_iter: Option<FileIter>,
}
impl Display for FilesStatement {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "files(")?;
let mut preceding = false;
if let Some(folder) = &self.folder {
write!(f, "folder={}", folder)?;
preceding = true;
}
if let Some(regex) = &self.regex {
if preceding {
write!(f, ", ")?;
} else {
preceding = true;
}
write!(f, "regex={}", regex)?;
}
if let Some(recursive) = self.recursive {
if preceding {
write!(f, ", ")?;
}
write!(f, "recursive={}", recursive)?;
}
write!(f, ")")
}
}
impl std::clone::Clone for FilesStatement {
fn clone(&self) -> Self {
Self {
context: None,
folder: self.folder.clone(),
regex: self.regex.clone(),
recursive: self.recursive.clone(),
file_iter: None,
}
}
}
impl Iterator for FilesStatement {
type Item = Result<MpsMusicItem, RuntimeError>;
fn next(&mut self) -> Option<Self::Item> {
if self.file_iter.is_none() {
let self_clone = self.clone();
let iter = self.context.as_mut().unwrap().filesystem.raw(
self.folder.as_deref(),
self.regex.as_deref(),
self.recursive.unwrap_or(true),
&mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into(),
);
self.file_iter = Some(match iter {
Ok(x) => x,
Err(e) => return Some(Err(e)),
});
}
match self.file_iter.as_mut().unwrap().next() {
Some(Ok(item)) => Some(Ok(item)),
Some(Err(e)) => Some(Err(RuntimeError {
line: 0,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
msg: e,
})),
None => None,
}
}
}
impl MpsOp for FilesStatement {
fn enter(&mut self, ctx: MpsContext) {
self.context = Some(ctx)
}
fn escape(&mut self) -> MpsContext {
self.context.take().unwrap()
}
}
pub struct FilesFunctionFactory;
impl MpsFunctionFactory<FilesStatement> for FilesFunctionFactory {
fn is_function(&self, name: &str) -> bool {
name == "files"
}
fn build_function_params(
&self,
_name: String,
tokens: &mut VecDeque<MpsToken>,
_dict: &MpsLanguageDictionary,
) -> Result<FilesStatement, SyntaxError> {
// files([folder|dir=]"path", [regex|re = "pattern"], [recursive = true|false])
let mut root_path = None;
let mut pattern = None;
let mut recursive = None;
if tokens[0].is_literal() {
// folder is specified without keyword
root_path = Some(assert_token(
|t| match t {
MpsToken::Literal(s) => Some(s),
_ => None,
},
MpsToken::Literal("/path/to/music/folder".into()),
tokens,
)?);
if tokens.len() > 1 && tokens[0].is_comma() {
assert_token_raw(MpsToken::Comma, tokens)?;
}
}
if tokens.len() != 0 {
// parse keyword function parameters
let ingest = |tokens2: &mut VecDeque<MpsToken>| {
if tokens2.len() < 3 {
return Ok(None);
} // nothing wrong, nothing left to ingest
let param_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("param".into()),
tokens2,
)?;
assert_token_raw(MpsToken::Equals, tokens2)?;
let param_val = assert_token(
|t| match t {
MpsToken::Name(s) => Some(MpsToken::Name(s)),
MpsToken::Literal(s) => Some(MpsToken::Literal(s)),
_ => None,
},
MpsToken::Name("value".into()),
tokens2,
)?;
Ok(Some((param_name, param_val))) // successfully ingested one phrase
};
let params = repeated_tokens(ingest, MpsToken::Comma).ingest_all(tokens)?;
// assign parameters to variables
for (param, val) in params {
match &param as &str {
"folder" | "dir" => match val {
MpsToken::Literal(s) => root_path = Some(s),
token => {
return Err(SyntaxError {
line: 0,
token: MpsToken::Literal("/path/to/music/folder".into()),
got: Some(token),
})
}
},
"regex" | "re" => match val {
MpsToken::Literal(s) => pattern = Some(s),
token => {
return Err(SyntaxError {
line: 0,
token: MpsToken::Literal("regex pattern".into()),
got: Some(token),
})
}
},
"recursive" => match val {
MpsToken::Name(s) => match &s as &str {
"true" => recursive = Some(true),
"false" => recursive = Some(false),
token => {
return Err(SyntaxError {
line: 0,
token: MpsToken::Name("true|false".into()),
got: Some(MpsToken::Name(token.to_owned())),
})
}
},
token => {
return Err(SyntaxError {
line: 0,
token: MpsToken::Name("true|false".into()),
got: Some(token),
})
}
},
s => {
return Err(SyntaxError {
line: 0,
token: MpsToken::Name("folder|regex|recursive".into()),
got: Some(MpsToken::Name(s.to_owned())),
})
}
}
}
}
Ok(FilesStatement {
context: None,
folder: root_path,
regex: pattern,
recursive: recursive,
file_iter: None,
})
}
}
pub type FilesStatementFactory = MpsFunctionStatementFactory<FilesStatement, FilesFunctionFactory>;
#[inline(always)]
pub fn files_function_factory() -> FilesStatementFactory {
FilesStatementFactory::new(FilesFunctionFactory)
}

View file

@ -1,13 +1,13 @@
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory};
use crate::lang::{RuntimeError, SyntaxError};
use crate::processing::OpGetter;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::tokens::MpsToken;
use crate::lang::{MpsFilterPredicate, MpsFilterFactory, MpsFilterStatementFactory};
use crate::lang::{SyntaxError, RuntimeError};
use crate::lang::MpsLanguageDictionary;
use crate::processing::OpGetter;
#[derive(Debug, Clone)]
pub struct EmptyFilter;
@ -19,7 +19,12 @@ impl Display for EmptyFilter {
}
impl MpsFilterPredicate for EmptyFilter {
fn matches(&mut self, _item: &MpsMusicItem, _ctx: &mut MpsContext, _op: &mut OpGetter) -> Result<bool, RuntimeError> {
fn matches(
&mut self,
_item: &MpsMusicItem,
_ctx: &mut MpsContext,
_op: &mut OpGetter,
) -> Result<bool, RuntimeError> {
Ok(true)
}
}

View file

@ -1,17 +1,17 @@
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::tokens::MpsToken;
use crate::lang::{MpsFilterPredicate, MpsFilterFactory, MpsFilterStatementFactory};
use crate::lang::{SyntaxError, RuntimeError};
use super::utility::{assert_comparison_operator, item_to_primitive_lut};
use crate::lang::utility::{assert_token, assert_type, check_is_type};
use crate::lang::MpsLanguageDictionary;
use crate::lang::MpsTypePrimitive;
use crate::lang::utility::{assert_token, assert_type, check_is_type};
use super::utility::{item_to_primitive_lut, assert_comparison_operator};
use crate::processing::OpGetter;
use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory};
use crate::lang::{RuntimeError, SyntaxError};
use crate::processing::general::MpsType;
use crate::processing::OpGetter;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
#[derive(Debug, Clone)]
enum VariableOrValue {
@ -23,7 +23,7 @@ enum VariableOrValue {
pub struct FieldFilter {
field_name: String,
val: VariableOrValue,
comparison: [i8; 2]
comparison: [i8; 2],
}
impl Display for FieldFilter {
@ -33,12 +33,16 @@ impl Display for FieldFilter {
VariableOrValue::Variable(name) => write!(f, "{} == {}", self.field_name, name),
VariableOrValue::Value(t) => write!(f, "{} == {}", self.field_name, t),
}
}
}
impl MpsFilterPredicate for FieldFilter {
fn matches(&mut self, item: &MpsMusicItem, ctx: &mut MpsContext, op: &mut OpGetter) -> Result<bool, RuntimeError> {
fn matches(
&mut self,
item: &MpsMusicItem,
ctx: &mut MpsContext,
op: &mut OpGetter,
) -> Result<bool, RuntimeError> {
let music_item_lut = item_to_primitive_lut(item.to_owned());
let variable = match &self.val {
VariableOrValue::Variable(name) => match ctx.variables.get(&name, op)? {
@ -47,17 +51,16 @@ impl MpsFilterPredicate for FieldFilter {
line: 0,
op: op(),
msg: format!("Variable {} is not comparable", name),
})
}),
},
VariableOrValue::Value(val) => Ok(val)
VariableOrValue::Value(val) => Ok(val),
}?;
if let Some(field) = music_item_lut.get(&self.field_name) {
let compare = field.compare(variable)
.map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: e,
})?;
let compare = field.compare(variable).map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: e,
})?;
let mut is_match = false;
for comparator in self.comparison {
if comparator == compare {
@ -73,7 +76,6 @@ impl MpsFilterPredicate for FieldFilter {
msg: format!("Field {} does not exist", &self.field_name),
})
}
}
}
@ -85,14 +87,12 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
(tokens_len == 3 // field > variable OR field < variable
&& tokens[0].is_name()
&& (tokens[1].is_open_angle_bracket() || tokens[1].is_close_angle_bracket())
&& (tokens[2].is_name() || check_is_type(&tokens[2]))
)
|| (tokens_len == 4 // field >= variable OR field <= variable
&& (tokens[2].is_name() || check_is_type(&tokens[2])))
|| (tokens_len == 4 // field >= variable OR field <= variable
&& tokens[0].is_name()
&& (tokens[1].is_open_angle_bracket() || tokens[1].is_close_angle_bracket() || tokens[1].is_equals())
&& tokens[2].is_equals()
&& (tokens[3].is_name() || check_is_type(&tokens[3]))
)
&& (tokens[3].is_name() || check_is_type(&tokens[3])))
}
fn build_filter(
@ -100,26 +100,32 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
tokens: &mut VecDeque<MpsToken>,
_dict: &MpsLanguageDictionary,
) -> Result<FieldFilter, SyntaxError> {
let field = assert_token(|t| match t {
MpsToken::Name(n) => Some(n),
_ => None
}, MpsToken::Name("field_name".into()), tokens)?;
let field = assert_token(
|t| match t {
MpsToken::Name(n) => Some(n),
_ => None,
},
MpsToken::Name("field_name".into()),
tokens,
)?;
let compare_operator = assert_comparison_operator(tokens)?;
if check_is_type(&tokens[0]) {
let value = VariableOrValue::Value(assert_type(tokens)?);
Ok(FieldFilter{
Ok(FieldFilter {
field_name: field,
val: value,
comparison: compare_operator,
})
} else {
let variable = VariableOrValue::Variable(
assert_token(|t| match t {
let variable = VariableOrValue::Variable(assert_token(
|t| match t {
MpsToken::Name(n) => Some(n),
_ => None
}, MpsToken::Name("variable_name".into()), tokens)?
);
Ok(FieldFilter{
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?);
Ok(FieldFilter {
field_name: field,
val: variable,
comparison: compare_operator,

View file

@ -2,5 +2,9 @@ mod empty_filter;
mod field_filter;
pub(crate) mod utility;
pub use empty_filter::{EmptyFilter, EmptyFilterFactory, EmptyFilterStatementFactory, empty_filter};
pub use field_filter::{FieldFilter, FieldFilterFactory, FieldFilterStatementFactory, field_filter};
pub use empty_filter::{
empty_filter, EmptyFilter, EmptyFilterFactory, EmptyFilterStatementFactory,
};
pub use field_filter::{
field_filter, FieldFilter, FieldFilterFactory, FieldFilterStatementFactory,
};

View file

@ -1,21 +1,36 @@
use std::collections::VecDeque;
use std::collections::HashMap;
use std::collections::VecDeque;
use crate::MpsMusicItem;
use crate::lang::SyntaxError;
use crate::lang::MpsTypePrimitive;
use crate::lang::utility::assert_token_raw;
use crate::lang::MpsTypePrimitive;
use crate::lang::SyntaxError;
use crate::tokens::MpsToken;
use crate::MpsMusicItem;
pub fn item_to_primitive_lut(item: MpsMusicItem) -> HashMap<String, MpsTypePrimitive> {
let mut result = HashMap::new();
result.insert("title".into(), MpsTypePrimitive::String(item.title));
result.insert("artist".into(), MpsTypePrimitive::String(item.artist.unwrap_or("".to_owned())));
result.insert("album".into(), MpsTypePrimitive::String(item.album.unwrap_or("".to_owned())));
result.insert(
"artist".into(),
MpsTypePrimitive::String(item.artist.unwrap_or("".to_owned())),
);
result.insert(
"album".into(),
MpsTypePrimitive::String(item.album.unwrap_or("".to_owned())),
);
result.insert("filename".into(), MpsTypePrimitive::String(item.filename));
result.insert("genre".into(), MpsTypePrimitive::String(item.genre.unwrap_or("".to_owned())));
result.insert("track".into(), MpsTypePrimitive::UInt(item.track.unwrap_or(0)));
result.insert("year".into(), MpsTypePrimitive::UInt(item.year.unwrap_or(0)));
result.insert(
"genre".into(),
MpsTypePrimitive::String(item.genre.unwrap_or("".to_owned())),
);
result.insert(
"track".into(),
MpsTypePrimitive::UInt(item.track.unwrap_or(0)),
);
result.insert(
"year".into(),
MpsTypePrimitive::UInt(item.year.unwrap_or(0)),
);
result
}
@ -23,37 +38,46 @@ pub fn assert_comparison_operator(tokens: &mut VecDeque<MpsToken>) -> Result<[i8
let token1 = tokens.pop_front().unwrap();
match token1 {
MpsToken::Equals => {
if tokens.len() != 0 && tokens[0].is_equals() { // tokens: ==
if tokens.len() != 0 && tokens[0].is_equals() {
// tokens: ==
assert_token_raw(MpsToken::Equals, tokens)?;
Ok([0, 0])
} else {
Err(SyntaxError {
line: 0,
token: MpsToken::Equals,
got: if tokens.len() != 0 {Some(tokens[0].clone())} else {None},
got: if tokens.len() != 0 {
Some(tokens[0].clone())
} else {
None
},
})
}
},
}
MpsToken::OpenAngleBracket => {
if tokens.len() != 0 && tokens[0].is_equals() { // tokens: <=
if tokens.len() != 0 && tokens[0].is_equals() {
// tokens: <=
assert_token_raw(MpsToken::Equals, tokens)?;
Ok([0, -1])
} else { // token: <
} else {
// token: <
Ok([-1, -1])
}
},
}
MpsToken::CloseAngleBracket => {
if tokens.len() != 0 && tokens[0].is_equals() { // tokens: >=
if tokens.len() != 0 && tokens[0].is_equals() {
// tokens: >=
assert_token_raw(MpsToken::Equals, tokens)?;
Ok([0, 1])
} else { // token: >
} else {
// token: >
Ok([1, 1])
}
},
}
_ => Err(SyntaxError {
line: 0,
token: MpsToken::Equals, // TODO this can be < > or =
got: Some(token1),
})
}),
}
}

View file

@ -1,14 +1,16 @@
mod comment;
mod files;
mod repeat;
mod sql_init;
mod sql_query;
mod sql_simple_query;
mod variable_assign;
pub use sql_query::{SqlStatementFactory, sql_function_factory};
pub use sql_simple_query::{SimpleSqlStatementFactory, simple_sql_function_factory};
pub use comment::{CommentStatement, CommentStatementFactory};
pub use repeat::{RepeatStatementFactory, repeat_function_factory};
pub use files::{files_function_factory, FilesStatementFactory};
pub use repeat::{repeat_function_factory, RepeatStatementFactory};
pub use sql_init::{sql_init_function_factory, SqlInitStatementFactory};
pub use sql_query::{sql_function_factory, SqlStatementFactory};
pub use sql_simple_query::{simple_sql_function_factory, SimpleSqlStatementFactory};
pub use variable_assign::{AssignStatement, AssignStatementFactory};
pub use sql_init::{SqlInitStatementFactory, sql_init_function_factory};
pub mod filters;

View file

@ -2,14 +2,14 @@ use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::tokens::MpsToken;
use crate::lang::{RuntimeError, SyntaxError};
use crate::lang::{MpsOp, PseudoOp, MpsFunctionFactory, MpsFunctionStatementFactory};
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsOp, PseudoOp};
use crate::lang::{RuntimeError, SyntaxError};
#[derive(Debug)]
pub struct RepeatStatement {
@ -49,7 +49,7 @@ impl Iterator for RepeatStatement {
if !self.inner_done {
let real_op = match self.inner_statement.try_real() {
Err(e) => return Some(Err(e)),
Ok(real) => real
Ok(real) => real,
};
if self.context.is_some() {
let ctx = self.context.take().unwrap();
@ -62,14 +62,14 @@ impl Iterator for RepeatStatement {
Ok(music) => {
self.cache.push(music.clone());
Some(Ok(music))
},
Err(e) => Some(Err(e))
}
Err(e) => Some(Err(e)),
}
},
}
None => {
self.inner_done = true;
self.context = Some(real_op.escape());
},
}
}
}
// inner is done
@ -78,10 +78,10 @@ impl Iterator for RepeatStatement {
} else {
if self.cache.len() == 0 {
if self.loop_forever {
Some(Err(RuntimeError {
Some(Err(RuntimeError {
line: 0,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
msg: "Cannot repeat nothing".into()
msg: "Cannot repeat nothing".into(),
}))
} else {
None
@ -90,7 +90,9 @@ impl Iterator for RepeatStatement {
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; }
if self.repetitions != 0 {
self.repetitions -= 1;
}
self.cache_position = 0;
}
Some(Ok(music_item))
@ -131,12 +133,17 @@ impl MpsFunctionFactory<RepeatStatement> for RepeatFunctionFactory {
let inner_statement = dict.try_build_statement(tokens)?;
tokens.extend(end_tokens);
let mut count: Option<usize> = None;
if tokens.len() != 0 { // repititions specified
if tokens.len() != 0 {
// repititions specified
assert_token_raw(MpsToken::Comma, tokens)?;
count = Some(assert_token(|t| match t {
MpsToken::Name(n) => n.parse::<usize>().map(|d| d - 1).ok(),
_ => None
}, MpsToken::Name("usize".into()), tokens)?);
count = Some(assert_token(
|t| match t {
MpsToken::Name(n) => n.parse::<usize>().map(|d| d - 1).ok(),
_ => None,
},
MpsToken::Name("usize".into()),
tokens,
)?);
}
Ok(RepeatStatement {
inner_statement: inner_statement.into(),
@ -145,7 +152,7 @@ impl MpsFunctionFactory<RepeatStatement> for RepeatFunctionFactory {
cache: Vec::new(),
cache_position: 0,
repetitions: count.unwrap_or(0),
loop_forever: count.is_none()
loop_forever: count.is_none(),
})
}
}
@ -159,7 +166,8 @@ fn next_comma(tokens: &VecDeque<MpsToken>) -> usize {
tokens.len()
}
pub type RepeatStatementFactory = MpsFunctionStatementFactory<RepeatStatement, RepeatFunctionFactory>;
pub type RepeatStatementFactory =
MpsFunctionStatementFactory<RepeatStatement, RepeatFunctionFactory>;
#[inline(always)]
pub fn repeat_function_factory() -> RepeatStatementFactory {

View file

@ -1,17 +1,17 @@
use std::collections::HashMap;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use std::collections::HashMap;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::tokens::MpsToken;
use crate::lang::{RuntimeError, SyntaxError};
use crate::lang::{MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory};
use crate::lang::MpsLanguageDictionary;
use crate::lang::utility::{assert_token_raw, assert_token};
use crate::lang::repeated_tokens;
use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::MpsLanguageDictionary;
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsOp};
use crate::lang::{RuntimeError, SyntaxError};
#[derive(Debug)]
pub struct SqlInitStatement {
@ -44,10 +44,16 @@ impl Iterator for SqlInitStatement {
fn next(&mut self) -> Option<Self::Item> {
let pseudo_clone = self.clone();
// execute
match self.context.as_mut().unwrap().database.init_with_params(&self.params,
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()) {
match self
.context
.as_mut()
.unwrap()
.database
.init_with_params(&self.params, &mut move || {
(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
}) {
Ok(_) => None,
Err(e) => Some(Err(e))
Err(e) => Some(Err(e)),
}
}
}
@ -76,17 +82,27 @@ impl MpsFunctionFactory<SqlInitStatement> for SqlInitFunctionFactory {
_dict: &MpsLanguageDictionary,
) -> Result<SqlInitStatement, SyntaxError> {
let ingest = |tokens2: &mut VecDeque<MpsToken>| {
if tokens2.len() < 3 {return Ok(None);} // nothing wrong, nothing left to ingest
let param_name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
}, MpsToken::Name("param".into()), tokens2)?;
if tokens2.len() < 3 {
return Ok(None);
} // nothing wrong, nothing left to ingest
let param_name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("param".into()),
tokens2,
)?;
assert_token_raw(MpsToken::Equals, tokens2)?;
let param_val = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
MpsToken::Literal(s) => Some(s),
_ => None,
}, MpsToken::Name("value".into()), tokens2)?;
let param_val = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
MpsToken::Literal(s) => Some(s),
_ => None,
},
MpsToken::Name("value".into()),
tokens2,
)?;
Ok(Some((param_name, param_val))) // successfully ingested one phrase
};
let params = repeated_tokens(ingest, MpsToken::Comma).ingest_all(tokens)?;
@ -97,7 +113,8 @@ impl MpsFunctionFactory<SqlInitStatement> for SqlInitFunctionFactory {
}
}
pub type SqlInitStatementFactory = MpsFunctionStatementFactory<SqlInitStatement, SqlInitFunctionFactory>;
pub type SqlInitStatementFactory =
MpsFunctionStatementFactory<SqlInitStatement, SqlInitFunctionFactory>;
#[inline(always)]
pub fn sql_init_function_factory() -> SqlInitStatementFactory {

View file

@ -3,7 +3,7 @@ use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use crate::lang::utility::assert_token;
use crate::lang::{MpsLanguageDictionary, MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory};
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsLanguageDictionary, MpsOp};
use crate::lang::{RuntimeError, SyntaxError};
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -82,10 +82,9 @@ impl Iterator for SqlStatement {
let self_clone = self.clone();
let ctx = self.context.as_mut().unwrap();
// query has not been executed yet
match ctx
.database
.raw(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into())
{
match ctx.database.raw(&self.query, &mut move || {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}) {
Err(e) => return Some(Err(e)),
Ok(rows) => {
self.rows = Some(rows);

View file

@ -3,7 +3,7 @@ use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use crate::lang::utility::assert_token;
use crate::lang::{MpsLanguageDictionary, MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory};
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsLanguageDictionary, MpsOp};
use crate::lang::{RuntimeError, SyntaxError};
use crate::tokens::MpsToken;
use crate::MpsContext;
@ -133,18 +133,18 @@ impl Iterator for SimpleSqlStatement {
let ctx = self.context.as_mut().unwrap();
// query has not been executed yet
let query_result = match self.mode {
QueryMode::Artist => ctx
.database
.artist_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
QueryMode::Album => ctx
.database
.album_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
QueryMode::Song => ctx
.database
.song_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
QueryMode::Genre => ctx
.database
.genre_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
QueryMode::Artist => ctx.database.artist_like(&self.query, &mut move || {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}),
QueryMode::Album => ctx.database.album_like(&self.query, &mut move || {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}),
QueryMode::Song => ctx.database.song_like(&self.query, &mut move || {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}),
QueryMode::Genre => ctx.database.genre_like(&self.query, &mut move || {
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
}),
};
match query_result {
Err(e) => return Some(Err(e)),
@ -195,7 +195,8 @@ impl MpsFunctionFactory<SimpleSqlStatement> for SimpleSqlFunctionFactory {
}
}
pub type SimpleSqlStatementFactory = MpsFunctionStatementFactory<SimpleSqlStatement, SimpleSqlFunctionFactory>;
pub type SimpleSqlStatementFactory =
MpsFunctionStatementFactory<SimpleSqlStatement, SimpleSqlFunctionFactory>;
#[inline(always)]
pub fn simple_sql_function_factory() -> SimpleSqlStatementFactory {

View file

@ -2,14 +2,14 @@ use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter};
use std::iter::Iterator;
use crate::tokens::MpsToken;
use crate::MpsContext;
use crate::MpsMusicItem;
use crate::tokens::MpsToken;
use crate::lang::{RuntimeError, SyntaxError};
use crate::lang::{MpsOp, PseudoOp, MpsOpFactory, BoxedMpsOpFactory, MpsTypePrimitive};
use crate::lang::utility::{assert_token, assert_token_raw, assert_type, check_is_type};
use crate::lang::MpsLanguageDictionary;
use crate::lang::utility::{assert_token_raw, assert_token, check_is_type, assert_type};
use crate::lang::{BoxedMpsOpFactory, MpsOp, MpsOpFactory, MpsTypePrimitive, PseudoOp};
use crate::lang::{RuntimeError, SyntaxError};
use crate::processing::general::MpsType;
#[derive(Debug)]
@ -54,7 +54,10 @@ impl Iterator for AssignStatement {
Some(Err(RuntimeError {
line: 0,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
msg: format!("Variable {} already assigned, cannot redo assignment", self.variable_name),
msg: format!(
"Variable {} already assigned, cannot redo assignment",
self.variable_name
),
}))
} else {
let mut inner = inner_statement.clone();
@ -66,27 +69,28 @@ impl Iterator for AssignStatement {
let pseudo_clone = self.clone();
let result;
if self.is_declaration {
result = self.context.as_mut().unwrap()
.variables.declare(
&self.variable_name,
MpsType::Op(real),
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
result = self.context.as_mut().unwrap().variables.declare(
&self.variable_name,
MpsType::Op(real),
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
);
} else {
result = self.context.as_mut().unwrap()
.variables.assign(
&self.variable_name,
MpsType::Op(real),
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
result = self.context.as_mut().unwrap().variables.assign(
&self.variable_name,
MpsType::Op(real),
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
);
}
match result {
Ok(_) => None,
Err(e) => Some(Err(e))
Err(e) => Some(Err(e)),
}
}
} else if !self.is_simple {
panic!("Assignee statement for {} is None but assignment is not simple type", self.variable_name)
panic!(
"Assignee statement for {} is None but assignment is not simple type",
self.variable_name
)
/*Some(Err(RuntimeError {
line: 0,
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
@ -97,29 +101,26 @@ impl Iterator for AssignStatement {
let pseudo_clone = self.clone();
let result;
if self.is_declaration {
result = self.context.as_mut().unwrap()
.variables.declare(
&self.variable_name,
MpsType::Primitive(assign_type),
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
result = self.context.as_mut().unwrap().variables.declare(
&self.variable_name,
MpsType::Primitive(assign_type),
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
);
} else {
result = self.context.as_mut().unwrap()
.variables.assign(
&self.variable_name,
MpsType::Primitive(assign_type),
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
result = self.context.as_mut().unwrap().variables.assign(
&self.variable_name,
MpsType::Primitive(assign_type),
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
);
}
match result {
Ok(_) => None,
Err(e) => Some(Err(e))
Err(e) => Some(Err(e)),
}
}
}
}
impl MpsOp for AssignStatement {
fn enter(&mut self, ctx: MpsContext) {
self.context = Some(ctx)
@ -137,7 +138,7 @@ impl MpsOpFactory<AssignStatement> for AssignStatementFactory {
(tokens.len() >= 3
&&tokens[0].is_name() // can be any (valid) variable name
&& tokens[1].is_equals())
|| (tokens.len() >= 4
|| (tokens.len() >= 4
&& tokens[0].is_let()
&& tokens[1].is_name() // any name
&& tokens[2].is_equals())
@ -150,13 +151,18 @@ impl MpsOpFactory<AssignStatement> for AssignStatementFactory {
) -> Result<AssignStatement, SyntaxError> {
// [let] variable_name = inner_statement
let is_decl = tokens[0].is_let();
if is_decl { // variable declarations start with let, assignments do not
if is_decl {
// variable declarations start with let, assignments do not
assert_token_raw(MpsToken::Let, tokens)?;
}
let name = assert_token(|t| match t {
MpsToken::Name(s) => Some(s),
_ => None
}, MpsToken::Name("variable_name".into()), tokens)?;
let name = assert_token(
|t| match t {
MpsToken::Name(s) => Some(s),
_ => None,
},
MpsToken::Name("variable_name".into()),
tokens,
)?;
assert_token_raw(MpsToken::Equals, tokens)?;
let is_simple_assign = check_is_type(&tokens[0]);
if is_simple_assign {

View file

@ -1,38 +1,54 @@
use std::path::Path;
use crate::lang::db::*;
use super::MpsLibrary;
use crate::lang::db::*;
pub fn build_library_from_files<P: AsRef<Path>>(path: P, lib: &mut MpsLibrary) -> std::io::Result<()> {
pub fn build_library_from_files<P: AsRef<Path>>(
path: P,
lib: &mut MpsLibrary,
) -> std::io::Result<()> {
//let mut result = MpsLibrary::new();
lib.read_path(path, 10)?;
Ok(())
}
pub fn build_library_from_sqlite(conn: &rusqlite::Connection, lib: &mut MpsLibrary) -> rusqlite::Result<()> {
pub fn build_library_from_sqlite(
conn: &rusqlite::Connection,
lib: &mut MpsLibrary,
) -> rusqlite::Result<()> {
// build songs
for song in conn.prepare("SELECT * from songs")?
.query_map([], DbMusicItem::map_row)? {
for song in conn
.prepare("SELECT * from songs")?
.query_map([], DbMusicItem::map_row)?
{
lib.add_song(song?);
}
// build metadata
for meta in conn.prepare("SELECT * from metadata")?
.query_map([], DbMetaItem::map_row)? {
for meta in conn
.prepare("SELECT * from metadata")?
.query_map([], DbMetaItem::map_row)?
{
lib.add_metadata(meta?);
}
// build artists
for artist in conn.prepare("SELECT * from artists")?
.query_map([], DbArtistItem::map_row)? {
for artist in conn
.prepare("SELECT * from artists")?
.query_map([], DbArtistItem::map_row)?
{
lib.add_artist(artist?);
}
// build albums
for album in conn.prepare("SELECT * from albums")?
.query_map([], DbAlbumItem::map_row)? {
for album in conn
.prepare("SELECT * from albums")?
.query_map([], DbAlbumItem::map_row)?
{
lib.add_album(album?);
}
// build genres
for genre in conn.prepare("SELECT * from genres")?
.query_map([], DbGenreItem::map_row)? {
for genre in conn
.prepare("SELECT * from genres")?
.query_map([], DbGenreItem::map_row)?
{
lib.add_genre(genre?);
}
Ok(())

View file

@ -84,7 +84,8 @@ impl MpsLibrary {
#[inline]
pub fn add_artist(&mut self, artist: DbArtistItem) {
self.modify();
self.artists.insert(Self::sanitise_key(&artist.name), artist);
self.artists
.insert(Self::sanitise_key(&artist.name), artist);
}
pub fn all_albums<'a>(&'a self) -> Vec<&'a DbAlbumItem> {
@ -109,7 +110,9 @@ impl MpsLibrary {
pub fn read_path<P: AsRef<Path>>(&mut self, path: P, depth: usize) -> std::io::Result<()> {
let path = path.as_ref();
if self.contains_path(path) { return Ok(()); } // skip existing entries
if self.contains_path(path) {
return Ok(());
} // skip existing entries
if path.is_dir() && depth != 0 {
for entry in path.read_dir()? {
self.read_path(entry?.path(), depth - 1)?;
@ -120,6 +123,38 @@ impl MpsLibrary {
Ok(())
}
pub fn read_media_tags<P: AsRef<Path>>(path: P) -> std::io::Result<Tags> {
let path = path.as_ref();
let file = Box::new(std::fs::File::open(path)?);
// use symphonia to get metadata
let mss = MediaSourceStream::new(file, Default::default() /* options */);
let probed = symphonia::default::get_probe().format(
&Hint::new(),
mss,
&Default::default(),
&Default::default(),
);
let mut tags = Tags::new(path);
if let Ok(mut probed) = probed {
// collect metadata
if let Some(metadata) = probed.metadata.get() {
if let Some(rev) = metadata.current() {
for tag in rev.tags() {
//println!("(pre) metadata tag ({},{})", tag.key, tag.value);
tags.add(tag.key.clone(), &tag.value);
}
}
}
if let Some(rev) = probed.format.metadata().current() {
for tag in rev.tags() {
//println!("(post) metadata tag ({},{})", tag.key, tag.value);
tags.add(tag.key.clone(), &tag.value);
}
}
}
Ok(tags)
}
fn read_file<P: AsRef<Path>>(&mut self, path: P) -> std::io::Result<()> {
let path = path.as_ref();
let file = Box::new(std::fs::File::open(path)?);
@ -162,7 +197,7 @@ impl MpsLibrary {
let song_id = self.songs.len() as u64; // guaranteed to be created
let meta_id = self.metadata.len() as u64; // guaranteed to be created
self.add_metadata(tags.meta(meta_id)); // definitely necessary
// genre has no links to others, so find that first
// genre has no links to others, so find that first
let mut genre = tags.genre(0);
genre.genre_id = Self::find_or_gen_id(&self.genres, &genre.title);
if genre.genre_id == self.genres.len() as u64 {
@ -191,15 +226,13 @@ impl MpsLibrary {
}
//let meta_album_id = self.metadata.len() as u64;
//let album = tags.album(album_id, meta_album_id);
self.add_song(
tags.song(
song_id,
artist.artist_id,
Some(album.album_id),
meta_id,
genre.genre_id,
),
);
self.add_song(tags.song(
song_id,
artist.artist_id,
Some(album.album_id),
meta_id,
genre.genre_id,
));
}
#[inline]

View file

@ -28,6 +28,70 @@ impl Tags {
self.data.len()
}
#[inline]
pub fn track_title(&self) -> String {
self.data
.get("TITLE")
.unwrap_or(&TagType::Unknown)
.str()
.and_then(|s| Some(s.to_string()))
.unwrap_or_else(|| self.default_title())
}
#[inline]
fn default_title(&self) -> String {
let extension = self
.filename
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
self.filename
.file_name()
.and_then(|file| file.to_str())
.and_then(|file| Some(file.replacen(&format!(".{}", extension), "", 1)))
.unwrap_or("Unknown Title".into())
}
#[inline]
pub fn artist_name(&self) -> Option<String> {
self.data
.get("ARTIST")
.unwrap_or(&TagType::Unknown)
.str()
.and_then(|s| Some(s.to_string()))
}
#[inline]
pub fn album_title(&self) -> Option<String> {
self.data
.get("ALBUM")
.unwrap_or(&TagType::Unknown)
.str()
.and_then(|s| Some(s.to_string()))
}
#[inline]
pub fn genre_title(&self) -> Option<String> {
self.data
.get("GENRE")
.unwrap_or(&TagType::Unknown)
.str()
.and_then(|s| Some(s.to_string()))
}
#[inline]
pub fn track_number(&self) -> Option<u64> {
self.data
.get("TRACKNUMBER")
.unwrap_or(&TagType::Unknown)
.uint()
}
#[inline]
pub fn track_date(&self) -> Option<u64> {
self.data.get("DATE").unwrap_or(&TagType::Unknown).uint()
}
pub fn song(
&self,
id: u64,
@ -36,27 +100,9 @@ impl Tags {
meta_id: u64,
genre_id: u64,
) -> DbMusicItem {
let default_title = || {
let extension = self
.filename
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("");
self.filename
.file_name()
.and_then(|file| file.to_str())
.and_then(|file| Some(file.replacen(&format!(".{}", extension), "", 1)))
.unwrap_or("Unknown Title".into())
};
DbMusicItem {
song_id: id,
title: self
.data
.get("TITLE")
.unwrap_or(&TagType::Unknown)
.str()
.and_then(|s| Some(s.to_string()))
.unwrap_or_else(default_title),
title: self.track_title(),
artist: artist_id,
album: album_id,
filename: self.filename.to_str().unwrap_or("").into(),
@ -74,12 +120,7 @@ impl Tags {
.unwrap_or(&TagType::Unknown)
.uint()
.unwrap_or(0),
track: self
.data
.get("TRACKNUMBER")
.unwrap_or(&TagType::Unknown)
.uint()
.unwrap_or(id),
track: self.track_number().unwrap_or(id),
disc: self
.data
.get("DISCNUMBER")
@ -92,25 +133,14 @@ impl Tags {
.unwrap_or(&TagType::Unknown)
.uint()
.unwrap_or(0),
date: self
.data
.get("DATE")
.unwrap_or(&TagType::Unknown)
.uint()
.unwrap_or(0),
date: self.track_date().unwrap_or(0),
}
}
pub fn artist(&self, id: u64, genre_id: u64) -> DbArtistItem {
DbArtistItem {
artist_id: id,
name: self
.data
.get("ARTIST")
.unwrap_or(&TagType::Unknown)
.str()
.unwrap_or("Unknown Artist")
.into(),
name: self.artist_name().unwrap_or("Unknown Artist".into()),
genre: genre_id,
}
}
@ -132,13 +162,7 @@ impl Tags {
pub fn album(&self, id: u64, meta_id: u64, artist_id: u64, genre_id: u64) -> DbAlbumItem {
DbAlbumItem {
album_id: id,
title: self
.data
.get("ALBUM")
.unwrap_or(&TagType::Unknown)
.str()
.unwrap_or("Unknown Album")
.into(),
title: self.album_title().unwrap_or("Unknown Album".into()),
metadata: meta_id,
artist: artist_id,
genre: genre_id,
@ -179,13 +203,7 @@ impl Tags {
pub fn genre(&self, id: u64) -> DbGenreItem {
DbGenreItem {
genre_id: id,
title: self
.data
.get("GENRE")
.unwrap_or(&TagType::Unknown)
.str()
.unwrap_or("Unknown Genre")
.into(),
title: self.genre_title().unwrap_or("Unknown Genre".into()),
}
}
}

View file

@ -1,4 +1,6 @@
use super::lang::db::{DatabaseObj, DbMusicItem, DbArtistItem, DbAlbumItem, DbMetaItem, DbGenreItem};
use super::lang::db::{
DatabaseObj, DbAlbumItem, DbArtistItem, DbGenreItem, DbMetaItem, DbMusicItem,
};
#[derive(Clone, Debug)]
pub struct MpsMusicItem {
@ -30,7 +32,7 @@ impl MpsMusicItem {
artist: DbArtistItem,
album: DbAlbumItem,
meta: DbMetaItem,
genre: DbGenreItem
genre: DbGenreItem,
) -> Self {
Self {
title: music.title,

View file

@ -0,0 +1,309 @@
use std::fmt::{Debug, Display, Error, Formatter};
use std::fs::ReadDir;
use std::iter::Iterator;
use std::path::{Path, PathBuf};
use regex::Regex;
use super::OpGetter;
use crate::lang::RuntimeError;
use crate::MpsMusicItem;
const DEFAULT_REGEX: &str = r"/(?P<artist>[^/]+)/(?P<album>[^/]+)/(?:(?:(?P<disc>\d+)\s+)?(?P<track>\d+)\.?\s+)?(?P<title>[^/]+)\.[a-zA-Z0-9]+$";
const DEFAULT_VEC_CACHE_SIZE: usize = 4;
#[derive(Debug)]
pub struct FileIter {
root: PathBuf,
pattern: Regex,
recursive: bool,
dir_iters: Vec<ReadDir>,
is_complete: bool,
}
impl Display for FileIter {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(
f,
"root=`{}`, pattern={}, recursive={}",
self.root.to_str().unwrap_or(""),
self.pattern,
self.recursive
)
}
}
impl FileIter {
pub fn new<P: AsRef<Path>>(
root: Option<P>,
pattern: Option<&str>,
recurse: bool,
op: &mut OpGetter,
) -> Result<Self, RuntimeError> {
let root_path = match root {
None => crate::lang::utility::music_folder(),
Some(p) => p.as_ref().to_path_buf(),
};
let dir_vec = if root_path.is_dir() {
let mut vec = Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE);
vec.push(root_path.read_dir().map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: format!("Directory read error: {}", e),
})?);
vec
} else {
Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE)
};
Ok(Self {
root: root_path,
pattern: Regex::new(pattern.unwrap_or(DEFAULT_REGEX)).map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: format!("Regex compile error: {}", e),
})?,
recursive: recurse,
dir_iters: dir_vec,
is_complete: false,
})
}
pub fn common_defaults(recurse: bool) -> Self {
let root_path = crate::lang::utility::music_folder();
let read_dir = root_path.read_dir().unwrap();
let mut dir_vec = Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE);
dir_vec.push(read_dir);
Self {
root: root_path,
pattern: Regex::new(DEFAULT_REGEX).unwrap(),
recursive: recurse,
dir_iters: dir_vec,
is_complete: false,
}
}
fn build_item<P: AsRef<Path>>(&self, filepath: P) -> Option<MpsMusicItem> {
let path = filepath.as_ref();
let path_str = path.to_str()?;
#[cfg(debug_assertions)]
if !path.is_file() {
panic!("Got non-file path `{}` when building music item", path_str)
}
let captures = self.pattern.captures(path_str)?;
// populate fields
self.populate_item_impl(path, path_str, captures)
}
#[cfg(feature = "music_library")]
fn populate_item_impl(
&self,
path: &Path,
path_str: &str,
captures: regex::Captures,
) -> Option<MpsMusicItem> {
match crate::music::MpsLibrary::read_media_tags(path) {
Ok(tags) => Some(MpsMusicItem {
title: captures
.name("title")
.and_then(|m| Some(m.as_str().to_string()))
.unwrap_or_else(|| tags.track_title()),
artist: captures
.name("artist")
.and_then(|m| Some(m.as_str().to_string()))
.or_else(|| tags.artist_name()),
album: captures
.name("album")
.and_then(|m| Some(m.as_str().to_string()))
.or_else(|| tags.album_title()),
filename: path_str.to_string(),
genre: captures
.name("genre")
.and_then(|m| Some(m.as_str().to_string()))
.or_else(|| tags.genre_title()),
track: match captures.name("track") {
None => tags.track_number(),
Some(m) => match m.as_str().parse::<u64>() {
Ok(u) => Some(u),
Err(_) => tags.track_number(),
},
},
year: match captures.name("year") {
None => tags.track_date(),
Some(m) => match m.as_str().parse::<u64>() {
Ok(u) => Some(u),
Err(_) => tags.track_date(),
},
},
}),
Err(_) => self.populate_item_impl_simple(path, path_str, captures),
}
}
#[cfg(not(feature = "music_library"))]
fn populate_item_impl(
&self,
path: &Path,
path_str: &str,
captures: regex::Captures,
) -> Option<MpsMusicItem> {
self.populate_item_impl_simple(path, path_str, captures)
}
#[inline]
fn populate_item_impl_simple(
&self,
path: &Path,
path_str: &str,
captures: regex::Captures,
) -> Option<MpsMusicItem> {
Some(MpsMusicItem {
title: captures
.name("title")
.and_then(|m| Some(m.as_str().to_string()))
.unwrap_or_else(|| Self::default_title(path)),
artist: captures
.name("artist")
.and_then(|m| Some(m.as_str().to_string())),
album: captures
.name("album")
.and_then(|m| Some(m.as_str().to_string())),
filename: path_str.to_string(),
genre: captures
.name("genre")
.and_then(|m| Some(m.as_str().to_string())),
track: match captures.name("track") {
None => None,
Some(m) => match m.as_str().parse::<u64>() {
Ok(u) => Some(u),
Err(_) => None,
},
},
year: match captures.name("year") {
None => None,
Some(m) => match m.as_str().parse::<u64>() {
Ok(u) => Some(u),
Err(_) => None,
},
},
})
}
fn default_title(path: &Path) -> String {
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
path.file_name()
.and_then(|file| file.to_str())
.and_then(|file| Some(file.replacen(&format!(".{}", extension), "", 1)))
.unwrap_or("Unknown Title".into())
}
}
impl Iterator for FileIter {
type Item = Result<MpsMusicItem, String>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_complete {
None
} else {
if self.dir_iters.is_empty() {
if self.root.is_file() {
self.is_complete = true;
match self.build_item(&self.root) {
None => None,
Some(item) => Some(Ok(item)),
}
} else {
// should be impossible to get here
self.dir_iters.push(match self.root.read_dir() {
Ok(x) => x,
Err(e) => return Some(Err(format!("Directory read error: {}", e))),
});
return self.next();
}
} else {
while !self.dir_iters.is_empty() {
let mut dir_iter = self.dir_iters.pop().unwrap();
while let Some(path_result) = dir_iter.next() {
match path_result {
Ok(dir_entry) => {
if dir_entry.path().is_dir() {
if self.recursive {
self.dir_iters.push(dir_iter);
self.dir_iters.push(match dir_entry.path().read_dir() {
Ok(x) => x,
Err(e) => {
return Some(Err(format!(
"Directory read error: {}",
e
)))
}
});
return self.next();
}
} else {
if let Some(item) = self.build_item(dir_entry.path()) {
self.dir_iters.push(dir_iter);
return Some(Ok(item));
}
}
}
Err(e) => {
self.dir_iters.push(dir_iter);
return Some(Err(format!("Path read error: {}", e)));
}
}
}
}
None
}
}
}
}
pub trait MpsFilesystemQuerier: Debug {
fn raw(
&mut self,
folder: Option<&str>,
pattern: Option<&str>,
recursive: bool,
op: &mut OpGetter,
) -> Result<FileIter, RuntimeError>;
fn expand(
&self,
folder: Option<&str>,
#[allow(unused_variables)] op: &mut OpGetter,
) -> Result<Option<String>, RuntimeError> {
#[cfg(feature = "shellexpand")]
match folder {
Some(path) => Ok(Some(
shellexpand::full(path)
.map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: format!("Path expansion error: {}", e),
})?
.into_owned(),
)),
None => Ok(None),
}
#[cfg(not(feature = "shellexpand"))]
Ok(folder.and_then(|s| Some(s.to_string())))
}
}
#[derive(Default, Debug)]
pub struct MpsFilesystemExecutor {}
impl MpsFilesystemQuerier for MpsFilesystemExecutor {
fn raw(
&mut self,
folder: Option<&str>,
pattern: Option<&str>,
recursive: bool,
op: &mut OpGetter,
) -> Result<FileIter, RuntimeError> {
let folder = self.expand(folder, op)?;
FileIter::new(folder, pattern, recursive, op)
}
}

View file

@ -1,3 +1,4 @@
mod filesystem;
mod sql;
mod variables;
@ -8,5 +9,6 @@ pub mod database {
}
pub mod general {
pub use super::variables::{MpsVariableStorer, MpsOpStorage, MpsType};
pub use super::filesystem::{FileIter, MpsFilesystemExecutor, MpsFilesystemQuerier};
pub use super::variables::{MpsOpStorage, MpsType, MpsVariableStorer};
}

View file

@ -30,7 +30,11 @@ pub trait MpsDatabaseQuerier: Debug {
/// `"folder" = "path"` - path to root music directory
/// `"database" = "uri"` - connection URI for database (for SQLite this is just a filepath)
/// `"generate" = "true"|"yes"|"false"|"no"` - whether to populate the database using the music directory
fn init_with_params(&mut self, params: &HashMap<String, String>, op: &mut QueryOp) -> Result<(), RuntimeError>;
fn init_with_params(
&mut self,
params: &HashMap<String, String>,
op: &mut QueryOp,
) -> Result<(), RuntimeError>;
}
#[derive(Default, Debug)]
@ -113,8 +117,7 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
fn artist_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult {
let param = &format!("%{}%", query);
let query_stmt =
"SELECT songs.* FROM songs
let query_stmt = "SELECT songs.* FROM songs
JOIN artists ON songs.artist = artists.artist_id
JOIN metadata ON songs.metadata = metadata.meta_id
WHERE artists.name like ? ORDER BY songs.album, metadata.track";
@ -123,8 +126,7 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
fn album_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult {
let param = &format!("%{}%", query);
let query_stmt =
"SELECT songs.* FROM songs
let query_stmt = "SELECT songs.* FROM songs
JOIN albums ON songs.album = artists.album_id
JOIN metadata ON songs.metadata = metadata.meta_id
WHERE albums.title like ? ORDER BY songs.album, metadata.track";
@ -133,8 +135,7 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
fn song_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult {
let param = &format!("%{}%", query);
let query_stmt =
"SELECT songs.* FROM songs
let query_stmt = "SELECT songs.* FROM songs
JOIN metadata ON songs.metadata = metadata.meta_id
WHERE songs.title like ? ORDER BY songs.album, metadata.track";
self.music_query_single_param(query_stmt, param, op)
@ -142,15 +143,18 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
fn genre_like(&mut self, query: &str, op: &mut QueryOp) -> QueryResult {
let param = &format!("%{}%", query);
let query_stmt =
"SELECT songs.* FROM songs
let query_stmt = "SELECT songs.* FROM songs
JOIN genres ON songs.genre = genres.genre_id
JOIN metadata ON songs.metadata = metadata.meta_id
WHERE genres.title like ? ORDER BY songs.album, metadata.track";
self.music_query_single_param(query_stmt, param, op)
}
fn init_with_params(&mut self, params: &HashMap<String, String>, op: &mut QueryOp) -> Result<(), RuntimeError> {
fn init_with_params(
&mut self,
params: &HashMap<String, String>,
op: &mut QueryOp,
) -> Result<(), RuntimeError> {
// must be executed before connection is created
if self.sqlite_connection.is_some() {
Err(RuntimeError {
@ -169,21 +173,24 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
"folder" | "dir" => {
match_found = true;
settings.music_path = Some(val.clone());
},
}
"database" | "db" => {
match_found = true;
settings.db_path = Some(val.clone());
},
}
"generate" | "gen" => {
match_found = true;
settings.auto_generate = match val as &str {
"true" | "yes" => Ok(true),
"false" | "no" => Ok(false),
x => Err(RuntimeError{
"true" => Ok(true),
"false" => Ok(false),
x => Err(RuntimeError {
line: 0,
op: op(),
msg: format!("Unrecognised right hand side of param \"{}\" = \"{}\"", key, x),
})
msg: format!(
"Unrecognised right hand side of param \"{}\" = \"{}\"",
key, x
),
}),
}?;
}
_ => {}
@ -207,19 +214,14 @@ impl MpsDatabaseQuerier for MpsSQLiteExecutor {
return Err(RuntimeError {
line: 0,
op: op(),
msg: format!("Unrecognised sql init parameter(s): {}", concat_keys)
})
msg: format!("Unrecognised sql init parameter(s): {}", concat_keys),
});
}
self.sqlite_connection = Some(
settings.try_into()
.map_err(|e|
RuntimeError {
line: 0,
op: op(),
msg: format!("SQL connection error: {}", e)
}
)?
);
self.sqlite_connection = Some(settings.try_into().map_err(|e| RuntimeError {
line: 0,
op: op(),
msg: format!("SQL connection error: {}", e),
})?);
Ok(())
}
}
@ -236,7 +238,7 @@ impl std::default::Default for SqliteSettings {
SqliteSettings {
music_path: None,
db_path: None,
auto_generate: true
auto_generate: true,
}
}
}
@ -245,34 +247,33 @@ impl std::convert::TryInto<rusqlite::Connection> for SqliteSettings {
type Error = rusqlite::Error;
fn try_into(self) -> Result<rusqlite::Connection, Self::Error> {
let music_path = self.music_path
.and_then(
|p| Some(std::path::PathBuf::from(p))
).unwrap_or_else(
|| crate::lang::utility::music_folder()
);
let sqlite_path = self.db_path.unwrap_or_else(|| crate::lang::db::DEFAULT_SQLITE_FILEPATH.to_string());
let music_path = self
.music_path
.and_then(|p| Some(std::path::PathBuf::from(p)))
.unwrap_or_else(|| crate::lang::utility::music_folder());
let sqlite_path = self
.db_path
.unwrap_or_else(|| crate::lang::db::DEFAULT_SQLITE_FILEPATH.to_string());
crate::lang::db::generate_db(music_path, sqlite_path, self.auto_generate)
}
}
#[inline(always)]
fn build_mps_item(conn: &mut rusqlite::Connection, item: DbMusicItem) -> rusqlite::Result<MpsMusicItem> {
fn build_mps_item(
conn: &mut rusqlite::Connection,
item: DbMusicItem,
) -> rusqlite::Result<MpsMusicItem> {
// query artist
let mut stmt = conn
.prepare_cached("SELECT * from artists WHERE artist_id = ?")?;
let mut stmt = conn.prepare_cached("SELECT * from artists WHERE artist_id = ?")?;
let artist = stmt.query_row([item.artist], DbArtistItem::map_row)?;
// query album
let mut stmt = conn
.prepare_cached("SELECT * from albums WHERE album_id = ?")?;
let mut stmt = conn.prepare_cached("SELECT * from albums WHERE album_id = ?")?;
let album = stmt.query_row([item.album], DbAlbumItem::map_row)?;
// query metadata
let mut stmt = conn
.prepare_cached("SELECT * from metadata WHERE meta_id = ?")?;
let mut stmt = conn.prepare_cached("SELECT * from metadata WHERE meta_id = ?")?;
let meta = stmt.query_row([item.metadata], DbMetaItem::map_row)?;
// query genre
let mut stmt = conn
.prepare_cached("SELECT * from genres WHERE genre_id = ?")?;
let mut stmt = conn.prepare_cached("SELECT * from genres WHERE genre_id = ?")?;
let genre = stmt.query_row([item.genre], DbGenreItem::map_row)?;
Ok(MpsMusicItem::merge(item, artist, album, meta, genre))
@ -293,11 +294,10 @@ fn perform_query(
.map_err(|e| format!("SQLite item mapping error: {}", e))?
.collect();
}
let iter2 = collection.into_iter()
.map(|item| match item {
Ok(item) => build_mps_item(conn, item),
Err(e) => Err(e)
});
let iter2 = collection.into_iter().map(|item| match item {
Ok(item) => build_mps_item(conn, item),
Err(e) => Err(e),
});
Ok(iter2.collect())
}
@ -317,10 +317,9 @@ fn perform_single_param_query(
.map_err(|e| format!("SQLite item mapping error: {}", e))?
.collect();
}
let iter2 = collection.into_iter()
.map(|item| match item {
Ok(item) => build_mps_item(conn, item),
Err(e) => Err(e)
});
let iter2 = collection.into_iter().map(|item| match item {
Ok(item) => build_mps_item(conn, item),
Err(e) => Err(e),
});
Ok(iter2.collect())
}

View file

@ -2,23 +2,23 @@ use std::fmt::{Debug, Display, Error, Formatter};
use std::collections::HashMap;
use crate::lang::RuntimeError;
use crate::lang::MpsOp;
use crate::lang::MpsTypePrimitive;
use crate::lang::RuntimeError;
use super::OpGetter;
#[derive(Debug)]
pub enum MpsType {
Op(Box<dyn MpsOp>),
Primitive(MpsTypePrimitive)
Primitive(MpsTypePrimitive),
}
impl Display for MpsType {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
Self::Op(op) => write!(f, "Op({})", op),
Self::Primitive(p) => write!(f, "{}", p)
Self::Primitive(p) => write!(f, "{}", p),
}
}
}
@ -28,9 +28,15 @@ pub trait MpsVariableStorer: Debug {
fn get_mut(&mut self, name: &str, op: &mut OpGetter) -> Result<&mut MpsType, RuntimeError>;
fn assign(&mut self, name: &str, value: MpsType, op: &mut OpGetter) -> Result<(), RuntimeError>;
fn assign(&mut self, name: &str, value: MpsType, op: &mut OpGetter)
-> Result<(), RuntimeError>;
fn declare(&mut self, name: &str, value: MpsType, op: &mut OpGetter) -> Result<(), RuntimeError>;
fn declare(
&mut self,
name: &str,
value: MpsType,
op: &mut OpGetter,
) -> Result<(), RuntimeError>;
fn remove(&mut self, name: &str, op: &mut OpGetter) -> Result<MpsType, RuntimeError>;
}
@ -48,7 +54,7 @@ impl MpsVariableStorer for MpsOpStorage {
line: 0,
op: op(),
msg: format!("Variable {} not found", key),
})
}),
}
}
@ -59,7 +65,7 @@ impl MpsVariableStorer for MpsOpStorage {
line: 0,
op: op(),
msg: format!("Variable {} not found", key),
})
}),
}
}

View file

@ -8,10 +8,7 @@ pub trait MpsTokenReader {
fn current_column(&self) -> usize;
fn next_statement(
&mut self,
token_buffer: &mut VecDeque<MpsToken>,
) -> Result<(), ParseError>;
fn next_statement(&mut self, token_buffer: &mut VecDeque<MpsToken>) -> Result<(), ParseError>;
fn end_of_file(&self) -> bool;
}
@ -67,15 +64,16 @@ where
.map_err(|e| self.error(format!("UTF-8 encoding error: {}", e)))?;
buf.push_back(MpsToken::Literal(literal));
bigger_buf.clear();
},
}
ReaderStateMachine::EndComment {} => {
let comment = String::from_utf8(bigger_buf.clone())
.map_err(|e| self.error(format!("UTF-8 encoding error: {}", e)))?;
buf.push_back(MpsToken::Comment(comment));
bigger_buf.clear();
},
}
ReaderStateMachine::EndToken {} => {
if bigger_buf.len() != 0 { // ignore consecutive end tokens
if bigger_buf.len() != 0 {
// ignore consecutive end tokens
let token = String::from_utf8(bigger_buf.clone())
.map_err(|e| self.error(format!("UTF-8 encoding error: {}", e)))?;
buf.push_back(
@ -84,7 +82,7 @@ where
);
bigger_buf.clear();
}
},
}
ReaderStateMachine::SingleCharToken { .. } => {
let out = bigger_buf.pop().unwrap(); // bracket or comma token
if bigger_buf.len() != 0 {
@ -106,17 +104,17 @@ where
.map_err(|e| self.error(format!("Invalid token {}", e)))?,
);
bigger_buf.clear();
},
}
ReaderStateMachine::EndStatement {} => {
// unnecessary; loop will have already exited
},
}
ReaderStateMachine::EndOfFile {} => {
// unnecessary; loop will have already exited
},
}
ReaderStateMachine::Invalid { .. } => {
let invalid_char = bigger_buf.pop().unwrap(); // invalid single char
Err(self.error(format!("Unexpected character {}", invalid_char)))?;
},
}
_ => {}
}
if self
@ -175,10 +173,7 @@ where
self.column
}
fn next_statement(
&mut self,
buf: &mut VecDeque<MpsToken>,
) -> Result<(), ParseError> {
fn next_statement(&mut self, buf: &mut VecDeque<MpsToken>) -> Result<(), ParseError> {
// read until buffer gets some tokens, in case multiple end of line tokens are at start of stream
let original_size = buf.len();
while original_size == buf.len() && !self.end_of_file() {
@ -212,15 +207,23 @@ enum ReaderStateMachine {
SingleCharToken {
out: u8,
},
Slash {out: u8},
Octothorpe {out: u8},
Comment {out: u8},
Slash {
out: u8,
},
Octothorpe {
out: u8,
},
Comment {
out: u8,
},
EndLiteral {},
EndToken {},
EndComment {},
EndStatement {},
EndOfFile {},
Invalid { out: u8 },
Invalid {
out: u8,
},
}
impl ReaderStateMachine {
@ -235,7 +238,7 @@ impl ReaderStateMachine {
| Self::EndComment {}
| Self::EndStatement {}
| Self::EndOfFile {}
| Self::Invalid {..} => match input_char {
| Self::Invalid { .. } => match input_char {
'\\' => Self::Escaped { inside: '_' },
'/' => Self::Slash { out: input },
'#' => Self::Octothorpe { out: input },
@ -264,18 +267,18 @@ impl ReaderStateMachine {
'\0' => Self::Invalid { out: input },
_ => Self::InsideQuoteLiteral { out: input },
},
Self::Slash {..} => match input_char {
Self::Slash { .. } => match input_char {
'/' => Self::Comment { out: input },
' ' => Self::EndToken {},
'\0' => Self::EndOfFile {},
'\n' | '\r' | ';' => Self::EndStatement {},
_ => Self::Regular { out: input },
},
Self::Octothorpe {..} => match input_char {
Self::Octothorpe { .. } => match input_char {
'\n' | '\r' | '\0' => Self::EndComment {},
_ => Self::Comment { out: input }
_ => Self::Comment { out: input },
},
Self::Comment {..} => match input_char {
Self::Comment { .. } => match input_char {
'\n' | '\r' | '\0' => Self::EndComment {},
_ => Self::Comment { out: input },
},
@ -304,7 +307,7 @@ impl ReaderStateMachine {
| Self::InsideTickLiteral { out, .. }
| Self::InsideQuoteLiteral { out, .. }
| Self::Slash { out, .. }
| Self::Octothorpe { out, ..}
| Self::Octothorpe { out, .. }
| Self::Comment { out, .. }
| Self::Invalid { out, .. } => Some(*out),
_ => None,

View file

@ -27,7 +27,11 @@ fn parse_line() -> Result<(), ParseError> {
// validity tests
assert_eq!(buf.len(), correct_tokens.len());
for i in 0..buf.len() {
assert_eq!(buf[i], correct_tokens[i], "Tokens at position {} do not match ()", i);
assert_eq!(
buf[i], correct_tokens[i],
"Tokens at position {} do not match ()",
i
);
}
tokenizer.read_line(&mut buf)?; // this should immediately return
@ -35,7 +39,11 @@ fn parse_line() -> Result<(), ParseError> {
}
#[inline(always)]
fn execute_single_line(line: &str, should_be_emtpy: bool, should_complete: bool) -> Result<(), Box<dyn MpsLanguageError>> {
fn execute_single_line(
line: &str,
should_be_emtpy: bool,
should_complete: bool,
) -> Result<(), Box<dyn MpsLanguageError>> {
println!("--- Executing MPS code: '{}' ---", line);
let cursor = Cursor::new(line);
@ -57,14 +65,25 @@ fn execute_single_line(line: &str, should_be_emtpy: bool, should_complete: bool)
println!("Got song `{}` (file: `{}`)", item.title, item.filename);
} else {
println!("!!! Got error while iterating (executing) !!!");
eprintln!("{}", result.as_ref().err().unwrap());
result?;
}
}
if should_be_emtpy {
assert_eq!(count, 0, "{} music items found while iterating over line which should be None", count);
assert_eq!(
count, 0,
"{} music items found while iterating over line which should be None",
count
);
} else {
println!("Got {} items, execution complete (no songs were harmed in the making of this test)", count);
assert_ne!(count, 0, "0 music items found while iterating over line which should have Some results"); // assumption: database is populated
println!(
"Got {} items, execution complete (no songs were harmed in the making of this test)",
count
);
assert_ne!(
count, 0,
"0 music items found while iterating over line which should have Some results"
); // assumption: database is populated
}
Ok(())
}
@ -93,12 +112,20 @@ fn execute_repeat_line() -> Result<(), Box<dyn MpsLanguageError>> {
#[test]
fn execute_sql_init_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line("sql_init(generate = no, folder = `/home/ngnius/Music`)", true, true)
execute_single_line(
"sql_init(generate = false, folder = `/home/ngnius/Music`)",
true,
true,
)
}
#[test]
fn execute_assign_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line("let some_var = repeat(song(`Christmas in L.A.`))", true, true)?;
execute_single_line(
"let some_var = repeat(song(`Christmas in L.A.`))",
true,
true,
)?;
execute_single_line("let some_var2 = 1234", true, true)
}
@ -111,3 +138,12 @@ fn execute_emptyfilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
fn execute_fieldfilter_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line("song(`lov`).(year >= 2020)", false, true)
}
#[test]
fn execute_files_line() -> Result<(), Box<dyn MpsLanguageError>> {
execute_single_line(
r"files(`~/Music/MusicFlac/Bruno Mars/24K Magic/`, re=``, recursive=false)",
false,
true,
)
}