diff --git a/Cargo.lock b/Cargo.lock index f85b46e..f60c404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/mps-interpreter/Cargo.toml b/mps-interpreter/Cargo.toml index dcaaf0d..28f8126 100644 --- a/mps-interpreter/Cargo.toml +++ b/mps-interpreter/Cargo.toml @@ -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"] diff --git a/mps-interpreter/src/context.rs b/mps-interpreter/src/context.rs index b186273..1a5d340 100644 --- a/mps-interpreter/src/context.rs +++ b/mps-interpreter/src/context.rs @@ -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, pub variables: Box, + pub filesystem: Box, } 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()), } } } diff --git a/mps-interpreter/src/interpretor.rs b/mps-interpreter/src/interpretor.rs index ca47b60..21e9028 100644 --- a/mps-interpreter/src/interpretor.rs +++ b/mps-interpreter/src/interpretor.rs @@ -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()); } diff --git a/mps-interpreter/src/lang/db_items.rs b/mps-interpreter/src/lang/db_items.rs index 4e55869..56f6d6b 100644 --- a/mps-interpreter/src/lang/db_items.rs +++ b/mps-interpreter/src/lang/db_items.rs @@ -11,14 +11,19 @@ pub trait DatabaseObj: Sized { } pub fn generate_default_db() -> rusqlite::Result { - generate_db(super::utility::music_folder(), DEFAULT_SQLITE_FILEPATH, true) + generate_db( + super::utility::music_folder(), + DEFAULT_SQLITE_FILEPATH, + true, + ) } pub fn generate_db, P2: AsRef>( music_path: P1, sqlite_path: P2, - generate: bool + generate: bool, ) -> rusqlite::Result { + #[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, P2: AsRef>( // 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, P2: AsRef>( 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, P2: AsRef>( 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), } } diff --git a/mps-interpreter/src/lang/dictionary.rs b/mps-interpreter/src/lang/dictionary.rs index 2cda023..fc4c4a1 100644 --- a/mps-interpreter/src/lang/dictionary.rs +++ b/mps-interpreter/src/lang/dictionary.rs @@ -31,7 +31,7 @@ impl MpsLanguageDictionary { line: 0, token: MpsToken::Name("???".into()), got: None, - }) + }), }?; Err(SyntaxError { line: 0, diff --git a/mps-interpreter/src/lang/error.rs b/mps-interpreter/src/lang/error.rs index e744bc9..40a24c7 100644 --- a/mps-interpreter/src/lang/error.rs +++ b/mps-interpreter/src/lang/error.rs @@ -7,7 +7,7 @@ use crate::tokens::MpsToken; pub struct SyntaxError { pub line: usize, pub token: MpsToken, - pub got: Option + pub got: Option, } impl Display for SyntaxError { diff --git a/mps-interpreter/src/lang/filter.rs b/mps-interpreter/src/lang/filter.rs index 3dcf7ec..3245beb 100644 --- a/mps-interpreter/src/lang/filter.rs +++ b/mps-interpreter/src/lang/filter.rs @@ -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; + fn matches( + &mut self, + item: &MpsMusicItem, + ctx: &mut MpsContext, + op: &mut OpGetter, + ) -> Result; } pub trait MpsFilterFactory { @@ -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 Iterator for MpsFilterStatement

{ let mut op_getter = move || (Box::new(self_clone.clone()) as Box).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).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).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 Iterator for MpsFilterStatement

{ 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 Iterator for MpsFilterStatement

{ 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 + 'static> { +pub struct MpsFilterStatementFactory< + P: MpsFilterPredicate + 'static, + F: MpsFilterFactory

+ 'static, +> { filter_factory: F, idc: PhantomData

, } -impl + 'static> MpsFilterStatementFactory { +impl + 'static> + MpsFilterStatementFactory +{ pub fn new(factory: F) -> Self { Self { filter_factory: factory, @@ -198,7 +218,9 @@ impl + 'static> MpsFilte } } -impl + 'static> BoxedMpsOpFactory for MpsFilterStatementFactory { +impl + 'static> BoxedMpsOpFactory + for MpsFilterStatementFactory +{ fn is_op_boxed(&self, tokens: &VecDeque) -> bool { let tokens_len = tokens.len(); if last_open_bracket_is_after_dot(tokens) { @@ -206,7 +228,8 @@ impl + '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 + '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 { // .(predicate) @@ -255,9 +282,9 @@ fn last_open_bracket_is_after_dot(tokens: &VecDeque) -> 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) -> usize { open_bracket_found = true; } else if open_bracket_found { if tokens[i].is_dot() { - return i + return i; } else { - return 0 + return 0; } } } diff --git a/mps-interpreter/src/lang/function.rs b/mps-interpreter/src/lang/function.rs index 12021a0..00d95dc 100644 --- a/mps-interpreter/src/lang/function.rs +++ b/mps-interpreter/src/lang/function.rs @@ -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 { fn is_function(&self, name: &str) -> bool; @@ -33,10 +33,8 @@ impl + 'static> MpsFunctionStatem } } -impl + 'static> -BoxedMpsOpFactory -for -MpsFunctionStatementFactory +impl + 'static> BoxedMpsOpFactory + for MpsFunctionStatementFactory { fn is_op_boxed(&self, tokens: &VecDeque) -> bool { let tokens_len = tokens.len(); @@ -44,10 +42,12 @@ MpsFunctionStatementFactory 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 tokens: &mut VecDeque, dict: &MpsLanguageDictionary, ) -> Result, 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)?, + )) } } diff --git a/mps-interpreter/src/lang/mod.rs b/mps-interpreter/src/lang/mod.rs index e9b68c8..57cf818 100644 --- a/mps-interpreter/src/lang/mod.rs +++ b/mps-interpreter/src/lang/mod.rs @@ -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, }; } diff --git a/mps-interpreter/src/lang/operation.rs b/mps-interpreter/src/lang/operation.rs index 8e9a459..a9579a2 100644 --- a/mps-interpreter/src/lang/operation.rs +++ b/mps-interpreter/src/lang/operation.rs @@ -11,10 +11,7 @@ use crate::MpsMusicItem; pub trait SimpleMpsOpFactory { fn is_op_simple(&self, tokens: &VecDeque) -> bool; - fn build_op_simple( - &self, - tokens: &mut VecDeque, - ) -> Result; + fn build_op_simple(&self, tokens: &mut VecDeque) -> Result; } impl + 'static> MpsOpFactory for X { diff --git a/mps-interpreter/src/lang/pseudo_op.rs b/mps-interpreter/src/lang/pseudo_op.rs index 9599691..e17e191 100644 --- a/mps-interpreter/src/lang/pseudo_op.rs +++ b/mps-interpreter/src/lang/pseudo_op.rs @@ -7,7 +7,7 @@ use super::RuntimeError; #[derive(Debug)] pub enum PseudoOp { Real(Box), - 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), } } } diff --git a/mps-interpreter/src/lang/repeated_meme.rs b/mps-interpreter/src/lang/repeated_meme.rs index 0dc1455..b0becde 100644 --- a/mps-interpreter/src/lang/repeated_meme.rs +++ b/mps-interpreter/src/lang/repeated_meme.rs @@ -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) -> Result, SyntaxError>, - F2: FnMut(&mut VecDeque) -> Result > -{ + F2: FnMut(&mut VecDeque) -> Result, +> { pattern_ingest: F1, separator_ingest: F2, } impl< - X: 'static, - F1: FnMut(&mut VecDeque) -> Result, SyntaxError>, - F2: FnMut(&mut VecDeque) -> Result > - RepeatedTokens + X: 'static, + F1: FnMut(&mut VecDeque) -> Result, SyntaxError>, + F2: FnMut(&mut VecDeque) -> Result, + > RepeatedTokens { pub fn ingest_all(&mut self, tokens: &mut VecDeque) -> Result, SyntaxError> { let mut result = Vec::::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) diff --git a/mps-interpreter/src/lang/type_primitives.rs b/mps-interpreter/src/lang/type_primitives.rs index 4c77979..bcd7e55 100644 --- a/mps-interpreter/src/lang/type_primitives.rs +++ b/mps-interpreter/src/lang/type_primitives.rs @@ -15,62 +15,63 @@ impl MpsTypePrimitive { #[inline] pub fn compare(&self, other: &Self) -> Result { 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, } } - diff --git a/mps-interpreter/src/lang/utility.rs b/mps-interpreter/src/lang/utility.rs index 183ff6e..ba3078d 100644 --- a/mps-interpreter/src/lang/utility.rs +++ b/mps-interpreter/src/lang/utility.rs @@ -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 Option>( caster: F, @@ -17,7 +16,7 @@ pub fn assert_token Option>( 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) -> Result { 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) -> Result 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::().is_ok() - || s.parse::().is_ok() - || s.parse::().is_ok() - || s == "false" - || s == "true", - _ => false + || s.parse::().is_ok() + || s.parse::().is_ok() + || s == "false" + || s == "true" + } + _ => false, } } @@ -143,7 +138,7 @@ pub fn assert_type(tokens: &mut VecDeque) -> Result Ok(MpsTypePrimitive::String(s)), @@ -162,15 +157,15 @@ pub fn assert_type(tokens: &mut VecDeque) -> Result 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") } - diff --git a/mps-interpreter/src/lang/vocabulary/comment.rs b/mps-interpreter/src/lang/vocabulary/comment.rs index 03e4b03..7d0afbf 100644 --- a/mps-interpreter/src/lang/vocabulary/comment.rs +++ b/mps-interpreter/src/lang/vocabulary/comment.rs @@ -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 for CommentStatementFactory { fn is_op_simple(&self, tokens: &VecDeque) -> bool { - tokens.len() == 1 - && tokens[0].is_comment() + tokens.len() == 1 && tokens[0].is_comment() } fn build_op_simple( &self, tokens: &mut VecDeque, ) -> Result { - 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, }) } } diff --git a/mps-interpreter/src/lang/vocabulary/files.rs b/mps-interpreter/src/lang/vocabulary/files.rs new file mode 100644 index 0000000..35be613 --- /dev/null +++ b/mps-interpreter/src/lang/vocabulary/files.rs @@ -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, + // function params + folder: Option, + regex: Option, + recursive: Option, + // state + file_iter: Option, +} + +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; + + fn next(&mut self) -> Option { + 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).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).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 for FilesFunctionFactory { + fn is_function(&self, name: &str) -> bool { + name == "files" + } + + fn build_function_params( + &self, + _name: String, + tokens: &mut VecDeque, + _dict: &MpsLanguageDictionary, + ) -> Result { + // 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| { + 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 ¶m 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; + +#[inline(always)] +pub fn files_function_factory() -> FilesStatementFactory { + FilesStatementFactory::new(FilesFunctionFactory) +} diff --git a/mps-interpreter/src/lang/vocabulary/filters/empty_filter.rs b/mps-interpreter/src/lang/vocabulary/filters/empty_filter.rs index bc2ab6d..5d22904 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/empty_filter.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/empty_filter.rs @@ -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 { + fn matches( + &mut self, + _item: &MpsMusicItem, + _ctx: &mut MpsContext, + _op: &mut OpGetter, + ) -> Result { Ok(true) } } diff --git a/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs b/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs index 28ccf33..c5a19c6 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/field_filter.rs @@ -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 { + fn matches( + &mut self, + item: &MpsMusicItem, + ctx: &mut MpsContext, + op: &mut OpGetter, + ) -> Result { 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 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 for FieldFilterFactory { tokens: &mut VecDeque, _dict: &MpsLanguageDictionary, ) -> Result { - 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, diff --git a/mps-interpreter/src/lang/vocabulary/filters/mod.rs b/mps-interpreter/src/lang/vocabulary/filters/mod.rs index e77c88c..5cc6410 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/mod.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/mod.rs @@ -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, +}; diff --git a/mps-interpreter/src/lang/vocabulary/filters/utility.rs b/mps-interpreter/src/lang/vocabulary/filters/utility.rs index 9b2eada..9a158b2 100644 --- a/mps-interpreter/src/lang/vocabulary/filters/utility.rs +++ b/mps-interpreter/src/lang/vocabulary/filters/utility.rs @@ -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 { 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) -> 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), - }) + }), } } diff --git a/mps-interpreter/src/lang/vocabulary/mod.rs b/mps-interpreter/src/lang/vocabulary/mod.rs index 6b91041..a0fdc23 100644 --- a/mps-interpreter/src/lang/vocabulary/mod.rs +++ b/mps-interpreter/src/lang/vocabulary/mod.rs @@ -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; diff --git a/mps-interpreter/src/lang/vocabulary/repeat.rs b/mps-interpreter/src/lang/vocabulary/repeat.rs index 4e643e9..c9cb4a4 100644 --- a/mps-interpreter/src/lang/vocabulary/repeat.rs +++ b/mps-interpreter/src/lang/vocabulary/repeat.rs @@ -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).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 for RepeatFunctionFactory { let inner_statement = dict.try_build_statement(tokens)?; tokens.extend(end_tokens); let mut count: Option = 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::().map(|d| d - 1).ok(), - _ => None - }, MpsToken::Name("usize".into()), tokens)?); + count = Some(assert_token( + |t| match t { + MpsToken::Name(n) => n.parse::().map(|d| d - 1).ok(), + _ => None, + }, + MpsToken::Name("usize".into()), + tokens, + )?); } Ok(RepeatStatement { inner_statement: inner_statement.into(), @@ -145,7 +152,7 @@ impl MpsFunctionFactory 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) -> usize { tokens.len() } -pub type RepeatStatementFactory = MpsFunctionStatementFactory; +pub type RepeatStatementFactory = + MpsFunctionStatementFactory; #[inline(always)] pub fn repeat_function_factory() -> RepeatStatementFactory { diff --git a/mps-interpreter/src/lang/vocabulary/sql_init.rs b/mps-interpreter/src/lang/vocabulary/sql_init.rs index 85e5632..e5430ea 100644 --- a/mps-interpreter/src/lang/vocabulary/sql_init.rs +++ b/mps-interpreter/src/lang/vocabulary/sql_init.rs @@ -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 { 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).into()) { + match self + .context + .as_mut() + .unwrap() + .database + .init_with_params(&self.params, &mut move || { + (Box::new(pseudo_clone.clone()) as Box).into() + }) { Ok(_) => None, - Err(e) => Some(Err(e)) + Err(e) => Some(Err(e)), } } } @@ -76,17 +82,27 @@ impl MpsFunctionFactory for SqlInitFunctionFactory { _dict: &MpsLanguageDictionary, ) -> Result { let ingest = |tokens2: &mut VecDeque| { - 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 for SqlInitFunctionFactory { } } -pub type SqlInitStatementFactory = MpsFunctionStatementFactory; +pub type SqlInitStatementFactory = + MpsFunctionStatementFactory; #[inline(always)] pub fn sql_init_function_factory() -> SqlInitStatementFactory { diff --git a/mps-interpreter/src/lang/vocabulary/sql_query.rs b/mps-interpreter/src/lang/vocabulary/sql_query.rs index f0fb590..41a58db 100644 --- a/mps-interpreter/src/lang/vocabulary/sql_query.rs +++ b/mps-interpreter/src/lang/vocabulary/sql_query.rs @@ -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).into()) - { + match ctx.database.raw(&self.query, &mut move || { + (Box::new(self_clone.clone()) as Box).into() + }) { Err(e) => return Some(Err(e)), Ok(rows) => { self.rows = Some(rows); diff --git a/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs b/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs index 1e08ab6..8fb9a08 100644 --- a/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs +++ b/mps-interpreter/src/lang/vocabulary/sql_simple_query.rs @@ -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).into()), - QueryMode::Album => ctx - .database - .album_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box).into()), - QueryMode::Song => ctx - .database - .song_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box).into()), - QueryMode::Genre => ctx - .database - .genre_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box).into()), + QueryMode::Artist => ctx.database.artist_like(&self.query, &mut move || { + (Box::new(self_clone.clone()) as Box).into() + }), + QueryMode::Album => ctx.database.album_like(&self.query, &mut move || { + (Box::new(self_clone.clone()) as Box).into() + }), + QueryMode::Song => ctx.database.song_like(&self.query, &mut move || { + (Box::new(self_clone.clone()) as Box).into() + }), + QueryMode::Genre => ctx.database.genre_like(&self.query, &mut move || { + (Box::new(self_clone.clone()) as Box).into() + }), }; match query_result { Err(e) => return Some(Err(e)), @@ -195,7 +195,8 @@ impl MpsFunctionFactory for SimpleSqlFunctionFactory { } } -pub type SimpleSqlStatementFactory = MpsFunctionStatementFactory; +pub type SimpleSqlStatementFactory = + MpsFunctionStatementFactory; #[inline(always)] pub fn simple_sql_function_factory() -> SimpleSqlStatementFactory { diff --git a/mps-interpreter/src/lang/vocabulary/variable_assign.rs b/mps-interpreter/src/lang/vocabulary/variable_assign.rs index 9e8be16..4f8369c 100644 --- a/mps-interpreter/src/lang/vocabulary/variable_assign.rs +++ b/mps-interpreter/src/lang/vocabulary/variable_assign.rs @@ -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).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).into() + result = self.context.as_mut().unwrap().variables.declare( + &self.variable_name, + MpsType::Op(real), + &mut move || (Box::new(pseudo_clone.clone()) as Box).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).into() + result = self.context.as_mut().unwrap().variables.assign( + &self.variable_name, + MpsType::Op(real), + &mut move || (Box::new(pseudo_clone.clone()) as Box).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).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).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).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).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).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 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 for AssignStatementFactory { ) -> Result { // [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 { diff --git a/mps-interpreter/src/music/build_library.rs b/mps-interpreter/src/music/build_library.rs index 4dd397c..b26b7cd 100644 --- a/mps-interpreter/src/music/build_library.rs +++ b/mps-interpreter/src/music/build_library.rs @@ -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>(path: P, lib: &mut MpsLibrary) -> std::io::Result<()> { +pub fn build_library_from_files>( + 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(()) diff --git a/mps-interpreter/src/music/library.rs b/mps-interpreter/src/music/library.rs index a196121..1ab6597 100644 --- a/mps-interpreter/src/music/library.rs +++ b/mps-interpreter/src/music/library.rs @@ -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>(&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>(path: P) -> std::io::Result { + 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>(&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] diff --git a/mps-interpreter/src/music/tag.rs b/mps-interpreter/src/music/tag.rs index 525a5c6..4669c0b 100644 --- a/mps-interpreter/src/music/tag.rs +++ b/mps-interpreter/src/music/tag.rs @@ -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 { + self.data + .get("ARTIST") + .unwrap_or(&TagType::Unknown) + .str() + .and_then(|s| Some(s.to_string())) + } + + #[inline] + pub fn album_title(&self) -> Option { + self.data + .get("ALBUM") + .unwrap_or(&TagType::Unknown) + .str() + .and_then(|s| Some(s.to_string())) + } + + #[inline] + pub fn genre_title(&self) -> Option { + self.data + .get("GENRE") + .unwrap_or(&TagType::Unknown) + .str() + .and_then(|s| Some(s.to_string())) + } + + #[inline] + pub fn track_number(&self) -> Option { + self.data + .get("TRACKNUMBER") + .unwrap_or(&TagType::Unknown) + .uint() + } + + #[inline] + pub fn track_date(&self) -> Option { + 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()), } } } diff --git a/mps-interpreter/src/music_item.rs b/mps-interpreter/src/music_item.rs index 4bee873..8d5de4d 100644 --- a/mps-interpreter/src/music_item.rs +++ b/mps-interpreter/src/music_item.rs @@ -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, diff --git a/mps-interpreter/src/processing/filesystem.rs b/mps-interpreter/src/processing/filesystem.rs new file mode 100644 index 0000000..e14108f --- /dev/null +++ b/mps-interpreter/src/processing/filesystem.rs @@ -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[^/]+)/(?P[^/]+)/(?:(?:(?P\d+)\s+)?(?P\d+)\.?\s+)?(?P[^/]+)\.[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) + } +} diff --git a/mps-interpreter/src/processing/mod.rs b/mps-interpreter/src/processing/mod.rs index 213a145..c32d9e2 100644 --- a/mps-interpreter/src/processing/mod.rs +++ b/mps-interpreter/src/processing/mod.rs @@ -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}; } diff --git a/mps-interpreter/src/processing/sql.rs b/mps-interpreter/src/processing/sql.rs index 8c3501b..aa4b382 100644 --- a/mps-interpreter/src/processing/sql.rs +++ b/mps-interpreter/src/processing/sql.rs @@ -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()) } diff --git a/mps-interpreter/src/processing/variables.rs b/mps-interpreter/src/processing/variables.rs index afb37a9..f8f6fd7 100644 --- a/mps-interpreter/src/processing/variables.rs +++ b/mps-interpreter/src/processing/variables.rs @@ -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), - }) + }), } } diff --git a/mps-interpreter/src/tokens/tokenizer.rs b/mps-interpreter/src/tokens/tokenizer.rs index 6e801e7..b0a8bce 100644 --- a/mps-interpreter/src/tokens/tokenizer.rs +++ b/mps-interpreter/src/tokens/tokenizer.rs @@ -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, diff --git a/mps-interpreter/tests/single_line.rs b/mps-interpreter/tests/single_line.rs index 85f6a87..a71be0c 100644 --- a/mps-interpreter/tests/single_line.rs +++ b/mps-interpreter/tests/single_line.rs @@ -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, + ) +}