add basic non-sql lookup files() & cargo fmt
This commit is contained in:
parent
e937a7221f
commit
ae0872e417
37 changed files with 1400 additions and 586 deletions
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
use super::processing::database::{MpsDatabaseQuerier, MpsSQLiteExecutor};
|
||||
use super::processing::general::{MpsVariableStorer, MpsOpStorage};
|
||||
use super::processing::general::{
|
||||
MpsFilesystemExecutor, MpsFilesystemQuerier, MpsOpStorage, MpsVariableStorer,
|
||||
};
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MpsContext {
|
||||
pub database: Box<dyn MpsDatabaseQuerier>,
|
||||
pub variables: Box<dyn MpsVariableStorer>,
|
||||
pub filesystem: Box<dyn MpsFilesystemQuerier>,
|
||||
}
|
||||
|
||||
impl Default for MpsContext {
|
||||
|
@ -13,6 +16,7 @@ impl Default for MpsContext {
|
|||
Self {
|
||||
database: Box::new(MpsSQLiteExecutor::default()),
|
||||
variables: Box::new(MpsOpStorage::default()),
|
||||
filesystem: Box::new(MpsFilesystemExecutor::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -11,14 +11,19 @@ pub trait DatabaseObj: Sized {
|
|||
}
|
||||
|
||||
pub fn generate_default_db() -> rusqlite::Result<rusqlite::Connection> {
|
||||
generate_db(super::utility::music_folder(), DEFAULT_SQLITE_FILEPATH, true)
|
||||
generate_db(
|
||||
super::utility::music_folder(),
|
||||
DEFAULT_SQLITE_FILEPATH,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
|
||||
music_path: P1,
|
||||
sqlite_path: P2,
|
||||
generate: bool
|
||||
generate: bool,
|
||||
) -> rusqlite::Result<rusqlite::Connection> {
|
||||
#[allow(unused_variables)]
|
||||
let music_path = music_path.as_ref();
|
||||
let sqlite_path = sqlite_path.as_ref();
|
||||
let db_exists = std::path::Path::new(sqlite_path).exists();
|
||||
|
@ -73,18 +78,18 @@ pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
|
|||
// generate data and store in db
|
||||
#[cfg(feature = "music_library")]
|
||||
if generate {
|
||||
|
||||
let mut lib = crate::music::MpsLibrary::new();
|
||||
if db_exists {
|
||||
crate::music::build_library_from_sqlite(&conn, &mut lib)?;
|
||||
}
|
||||
lib.clear_modified();
|
||||
match crate::music::build_library_from_files(&music_path, &mut lib) {
|
||||
Ok(_) => if lib.is_modified() {
|
||||
let transaction = conn.transaction()?;
|
||||
{
|
||||
let mut song_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO songs (
|
||||
Ok(_) => {
|
||||
if lib.is_modified() {
|
||||
let transaction = conn.transaction()?;
|
||||
{
|
||||
let mut song_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO songs (
|
||||
song_id,
|
||||
title,
|
||||
artist,
|
||||
|
@ -93,13 +98,13 @@ pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
|
|||
metadata,
|
||||
genre
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
)?;
|
||||
for song in lib.all_songs() {
|
||||
song_insert.execute(song.to_params().as_slice())?;
|
||||
}
|
||||
)?;
|
||||
for song in lib.all_songs() {
|
||||
song_insert.execute(song.to_params().as_slice())?;
|
||||
}
|
||||
|
||||
let mut metadata_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO metadata (
|
||||
let mut metadata_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO metadata (
|
||||
meta_id,
|
||||
plays,
|
||||
track,
|
||||
|
@ -107,47 +112,48 @@ pub fn generate_db<P1: AsRef<Path>, P2: AsRef<Path>>(
|
|||
duration,
|
||||
date
|
||||
) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
)?;
|
||||
for meta in lib.all_metadata() {
|
||||
metadata_insert.execute(meta.to_params().as_slice())?;
|
||||
}
|
||||
)?;
|
||||
for meta in lib.all_metadata() {
|
||||
metadata_insert.execute(meta.to_params().as_slice())?;
|
||||
}
|
||||
|
||||
let mut artist_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO artists (
|
||||
let mut artist_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO artists (
|
||||
artist_id,
|
||||
name,
|
||||
genre
|
||||
) VALUES (?, ?, ?)",
|
||||
)?;
|
||||
for artist in lib.all_artists() {
|
||||
artist_insert.execute(artist.to_params().as_slice())?;
|
||||
}
|
||||
)?;
|
||||
for artist in lib.all_artists() {
|
||||
artist_insert.execute(artist.to_params().as_slice())?;
|
||||
}
|
||||
|
||||
let mut album_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO albums (
|
||||
let mut album_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO albums (
|
||||
album_id,
|
||||
title,
|
||||
metadata,
|
||||
artist,
|
||||
genre
|
||||
) VALUES (?, ?, ?, ?, ?)",
|
||||
)?;
|
||||
for album in lib.all_albums() {
|
||||
album_insert.execute(album.to_params().as_slice())?;
|
||||
}
|
||||
)?;
|
||||
for album in lib.all_albums() {
|
||||
album_insert.execute(album.to_params().as_slice())?;
|
||||
}
|
||||
|
||||
let mut genre_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO genres (
|
||||
let mut genre_insert = transaction.prepare(
|
||||
"INSERT OR REPLACE INTO genres (
|
||||
genre_id,
|
||||
title
|
||||
) VALUES (?, ?)",
|
||||
)?;
|
||||
for genre in lib.all_genres() {
|
||||
genre_insert.execute(genre.to_params().as_slice())?;
|
||||
)?;
|
||||
for genre in lib.all_genres() {
|
||||
genre_insert.execute(genre.to_params().as_slice())?;
|
||||
}
|
||||
}
|
||||
transaction.commit()?;
|
||||
}
|
||||
}
|
||||
transaction.commit()?;
|
||||
},
|
||||
}
|
||||
Err(e) => println!("Unable to load music from {}: {}", music_path.display(), e),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ impl MpsLanguageDictionary {
|
|||
line: 0,
|
||||
token: MpsToken::Name("???".into()),
|
||||
got: None,
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
Err(SyntaxError {
|
||||
line: 0,
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::tokens::MpsToken;
|
|||
pub struct SyntaxError {
|
||||
pub line: usize,
|
||||
pub token: MpsToken,
|
||||
pub got: Option<MpsToken>
|
||||
pub got: Option<MpsToken>,
|
||||
}
|
||||
|
||||
impl Display for SyntaxError {
|
||||
|
|
|
@ -3,18 +3,23 @@ use std::fmt::{Debug, Display, Error, Formatter};
|
|||
use std::iter::Iterator;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::lang::utility::{assert_token, assert_token_raw};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::{BoxedMpsOpFactory, MpsOp, PseudoOp};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::processing::general::MpsType;
|
||||
use crate::processing::OpGetter;
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::lang::{MpsOp, PseudoOp, BoxedMpsOpFactory};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::processing::general::MpsType;
|
||||
use crate::lang::utility::{assert_token_raw, assert_token};
|
||||
use crate::processing::OpGetter;
|
||||
|
||||
pub trait MpsFilterPredicate: Clone + Debug + Display {
|
||||
fn matches(&mut self, item: &MpsMusicItem, ctx: &mut MpsContext, op: &mut OpGetter) -> Result<bool, RuntimeError>;
|
||||
fn matches(
|
||||
&mut self,
|
||||
item: &MpsMusicItem,
|
||||
ctx: &mut MpsContext,
|
||||
op: &mut OpGetter,
|
||||
) -> Result<bool, RuntimeError>;
|
||||
}
|
||||
|
||||
pub trait MpsFilterFactory<P: MpsFilterPredicate + 'static> {
|
||||
|
@ -37,7 +42,7 @@ impl Display for VariableOrOp {
|
|||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
match self {
|
||||
Self::Variable(s) => write!(f, "{}", s),
|
||||
Self::Op(op) => write!(f, "{}", op)
|
||||
Self::Op(op) => write!(f, "{}", op),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,60 +89,68 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
|
|||
let mut op_getter = move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into();
|
||||
//let ctx = self.context.as_mut().unwrap();
|
||||
match &mut self.iterable {
|
||||
VariableOrOp::Op(op) =>
|
||||
match op.try_real() {
|
||||
Ok(real_op) => {
|
||||
let ctx = self.context.take().unwrap();
|
||||
real_op.enter(ctx);
|
||||
let mut maybe_result = None;
|
||||
while let Some(item) = real_op.next() {
|
||||
let mut ctx = real_op.escape();
|
||||
match item {
|
||||
Err(e) => {
|
||||
//self.context = Some(op.escape());
|
||||
maybe_result = Some(Err(e));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
},
|
||||
Ok(item) => {
|
||||
let matches_result = self.predicate.matches(&item, &mut ctx, &mut op_getter);
|
||||
let matches = match matches_result {
|
||||
Err(e) => {
|
||||
maybe_result = Some(Err(e));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
},
|
||||
Ok(b) => b,
|
||||
};
|
||||
if matches {
|
||||
//self.context = Some(op.escape());
|
||||
maybe_result = Some(Ok(item));
|
||||
VariableOrOp::Op(op) => match op.try_real() {
|
||||
Ok(real_op) => {
|
||||
let ctx = self.context.take().unwrap();
|
||||
real_op.enter(ctx);
|
||||
let mut maybe_result = None;
|
||||
while let Some(item) = real_op.next() {
|
||||
let mut ctx = real_op.escape();
|
||||
match item {
|
||||
Err(e) => {
|
||||
//self.context = Some(op.escape());
|
||||
maybe_result = Some(Err(e));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
}
|
||||
Ok(item) => {
|
||||
let matches_result =
|
||||
self.predicate.matches(&item, &mut ctx, &mut op_getter);
|
||||
let matches = match matches_result {
|
||||
Err(e) => {
|
||||
maybe_result = Some(Err(e));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
}
|
||||
Ok(b) => b,
|
||||
};
|
||||
if matches {
|
||||
//self.context = Some(op.escape());
|
||||
maybe_result = Some(Ok(item));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
real_op.enter(ctx);
|
||||
}
|
||||
if self.context.is_none() {
|
||||
self.context = Some(real_op.escape());
|
||||
}
|
||||
maybe_result
|
||||
},
|
||||
Err(e) => return Some(Err(e)),
|
||||
},
|
||||
real_op.enter(ctx);
|
||||
}
|
||||
if self.context.is_none() {
|
||||
self.context = Some(real_op.escape());
|
||||
}
|
||||
maybe_result
|
||||
}
|
||||
Err(e) => return Some(Err(e)),
|
||||
},
|
||||
VariableOrOp::Variable(variable_name) => {
|
||||
let mut variable = match self.context.as_mut().unwrap().variables.remove(
|
||||
&variable_name,
|
||||
&mut op_getter
|
||||
) {
|
||||
let mut variable = match self
|
||||
.context
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.variables
|
||||
.remove(&variable_name, &mut op_getter)
|
||||
{
|
||||
Ok(MpsType::Op(op)) => op,
|
||||
Ok(x) => return Some(Err(RuntimeError {
|
||||
line: 0,
|
||||
op: (Box::new(self_clone2.clone()) as Box<dyn MpsOp>).into(),
|
||||
msg: format!("Expected operation/iterable type in variable {}, got {}", &variable_name, x)
|
||||
})),
|
||||
Err(e) => return Some(Err(e))
|
||||
Ok(x) => {
|
||||
return Some(Err(RuntimeError {
|
||||
line: 0,
|
||||
op: (Box::new(self_clone2.clone()) as Box<dyn MpsOp>).into(),
|
||||
msg: format!(
|
||||
"Expected operation/iterable type in variable {}, got {}",
|
||||
&variable_name, x
|
||||
),
|
||||
}))
|
||||
}
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
let mut maybe_result = None;
|
||||
let ctx = self.context.take().unwrap();
|
||||
|
@ -146,18 +159,19 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
|
|||
let mut ctx = variable.escape();
|
||||
match item {
|
||||
Err(e) => {
|
||||
maybe_result = Some(Err(e));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
},
|
||||
maybe_result = Some(Err(e));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
}
|
||||
Ok(item) => {
|
||||
let matches_result = self.predicate.matches(&item, &mut ctx, &mut op_getter);
|
||||
let matches_result =
|
||||
self.predicate.matches(&item, &mut ctx, &mut op_getter);
|
||||
let matches = match matches_result {
|
||||
Err(e) => {
|
||||
maybe_result = Some(Err(e));
|
||||
self.context = Some(ctx);
|
||||
break;
|
||||
},
|
||||
}
|
||||
Ok(b) => b,
|
||||
};
|
||||
if matches {
|
||||
|
@ -175,21 +189,27 @@ impl<P: MpsFilterPredicate + 'static> Iterator for MpsFilterStatement<P> {
|
|||
match self.context.as_mut().unwrap().variables.declare(
|
||||
&variable_name,
|
||||
MpsType::Op(variable),
|
||||
&mut op_getter) {
|
||||
&mut op_getter,
|
||||
) {
|
||||
Err(e) => return Some(Err(e)),
|
||||
Ok(_) => maybe_result
|
||||
Ok(_) => maybe_result,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MpsFilterStatementFactory<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> {
|
||||
pub struct MpsFilterStatementFactory<
|
||||
P: MpsFilterPredicate + 'static,
|
||||
F: MpsFilterFactory<P> + 'static,
|
||||
> {
|
||||
filter_factory: F,
|
||||
idc: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> MpsFilterStatementFactory<P, F> {
|
||||
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static>
|
||||
MpsFilterStatementFactory<P, F>
|
||||
{
|
||||
pub fn new(factory: F) -> Self {
|
||||
Self {
|
||||
filter_factory: factory,
|
||||
|
@ -198,7 +218,9 @@ impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> MpsFilte
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMpsOpFactory for MpsFilterStatementFactory<P, F> {
|
||||
impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMpsOpFactory
|
||||
for MpsFilterStatementFactory<P, F>
|
||||
{
|
||||
fn is_op_boxed(&self, tokens: &VecDeque<MpsToken>) -> bool {
|
||||
let tokens_len = tokens.len();
|
||||
if last_open_bracket_is_after_dot(tokens) {
|
||||
|
@ -206,7 +228,8 @@ impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMps
|
|||
if start_of_predicate > tokens_len - 1 {
|
||||
false
|
||||
} else {
|
||||
let tokens2: VecDeque<&MpsToken> = VecDeque::from_iter(tokens.range(start_of_predicate..tokens_len-1));
|
||||
let tokens2: VecDeque<&MpsToken> =
|
||||
VecDeque::from_iter(tokens.range(start_of_predicate..tokens_len - 1));
|
||||
self.filter_factory.is_filter(&tokens2)
|
||||
}
|
||||
} else {
|
||||
|
@ -223,10 +246,14 @@ impl<P: MpsFilterPredicate + 'static, F: MpsFilterFactory<P> + 'static> BoxedMps
|
|||
let op;
|
||||
if start_of_op == 1 && tokens[0].is_name() {
|
||||
// variable_name.(predicate)
|
||||
let variable_name = assert_token(|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
_ => None
|
||||
}, MpsToken::Name("variable_name".into()), tokens)?;
|
||||
let variable_name = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("variable_name".into()),
|
||||
tokens,
|
||||
)?;
|
||||
op = VariableOrOp::Variable(variable_name);
|
||||
} else {
|
||||
// <some other op>.(predicate)
|
||||
|
@ -255,9 +282,9 @@ fn last_open_bracket_is_after_dot(tokens: &VecDeque<MpsToken>) -> bool {
|
|||
open_bracket_found = true;
|
||||
} else if open_bracket_found {
|
||||
if tokens[i].is_dot() {
|
||||
return true
|
||||
return true;
|
||||
} else {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -271,9 +298,9 @@ fn last_dot_before_open_bracket(tokens: &VecDeque<MpsToken>) -> usize {
|
|||
open_bracket_found = true;
|
||||
} else if open_bracket_found {
|
||||
if tokens[i].is_dot() {
|
||||
return i
|
||||
return i;
|
||||
} else {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ use std::collections::VecDeque;
|
|||
//use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::lang::{MpsOp, BoxedMpsOpFactory};
|
||||
use crate::lang::SyntaxError;
|
||||
use crate::lang::utility::{assert_token, assert_token_raw, assert_token_raw_back};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::utility::{assert_token_raw, assert_token_raw_back, assert_token};
|
||||
use crate::lang::SyntaxError;
|
||||
use crate::lang::{BoxedMpsOpFactory, MpsOp};
|
||||
use crate::tokens::MpsToken;
|
||||
|
||||
pub trait MpsFunctionFactory<Op: MpsOp + 'static> {
|
||||
fn is_function(&self, name: &str) -> bool;
|
||||
|
@ -33,10 +33,8 @@ impl<Op: MpsOp + 'static, F: MpsFunctionFactory<Op> + 'static> MpsFunctionStatem
|
|||
}
|
||||
}
|
||||
|
||||
impl<Op: MpsOp + 'static, F: MpsFunctionFactory<Op> + 'static>
|
||||
BoxedMpsOpFactory
|
||||
for
|
||||
MpsFunctionStatementFactory<Op, F>
|
||||
impl<Op: MpsOp + 'static, F: MpsFunctionFactory<Op> + 'static> BoxedMpsOpFactory
|
||||
for MpsFunctionStatementFactory<Op, F>
|
||||
{
|
||||
fn is_op_boxed(&self, tokens: &VecDeque<MpsToken>) -> bool {
|
||||
let tokens_len = tokens.len();
|
||||
|
@ -44,10 +42,12 @@ MpsFunctionStatementFactory<Op, F>
|
|||
false
|
||||
} else {
|
||||
match &tokens[0] {
|
||||
MpsToken::Name(n) => self.op_factory.is_function(n)
|
||||
&& tokens[1].is_open_bracket()
|
||||
&& tokens[tokens_len - 1].is_close_bracket(),
|
||||
_ => false
|
||||
MpsToken::Name(n) => {
|
||||
self.op_factory.is_function(n)
|
||||
&& tokens[1].is_open_bracket()
|
||||
&& tokens[tokens_len - 1].is_close_bracket()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,12 +57,18 @@ MpsFunctionStatementFactory<Op, F>
|
|||
tokens: &mut VecDeque<MpsToken>,
|
||||
dict: &MpsLanguageDictionary,
|
||||
) -> Result<Box<dyn MpsOp>, SyntaxError> {
|
||||
let name = assert_token(|t| match t {
|
||||
MpsToken::Name(n) => Some(n),
|
||||
_ => None
|
||||
}, MpsToken::Name("function_name".into()), tokens)?;
|
||||
let name = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(n) => Some(n),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("function_name".into()),
|
||||
tokens,
|
||||
)?;
|
||||
assert_token_raw(MpsToken::OpenBracket, tokens)?;
|
||||
assert_token_raw_back(MpsToken::CloseBracket, tokens)?;
|
||||
Ok(Box::new(self.op_factory.build_function_params(name, tokens, dict)?))
|
||||
Ok(Box::new(
|
||||
self.op_factory.build_function_params(name, tokens, dict)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,7 @@ use crate::MpsMusicItem;
|
|||
pub trait SimpleMpsOpFactory<T: MpsOp + 'static> {
|
||||
fn is_op_simple(&self, tokens: &VecDeque<MpsToken>) -> bool;
|
||||
|
||||
fn build_op_simple(
|
||||
&self,
|
||||
tokens: &mut VecDeque<MpsToken>,
|
||||
) -> Result<T, SyntaxError>;
|
||||
fn build_op_simple(&self, tokens: &mut VecDeque<MpsToken>) -> Result<T, SyntaxError>;
|
||||
}
|
||||
|
||||
impl<T: MpsOp + 'static, X: SimpleMpsOpFactory<T> + 'static> MpsOpFactory<T> for X {
|
||||
|
|
|
@ -7,7 +7,7 @@ use super::RuntimeError;
|
|||
#[derive(Debug)]
|
||||
pub enum PseudoOp {
|
||||
Real(Box<dyn MpsOp>),
|
||||
Fake(String)
|
||||
Fake(String),
|
||||
}
|
||||
|
||||
impl PseudoOp {
|
||||
|
@ -18,7 +18,7 @@ impl PseudoOp {
|
|||
line: 0,
|
||||
op: self.clone(),
|
||||
msg: "PseudoOp::Fake is not a real MpsOp".into(),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,12 @@ impl PseudoOp {
|
|||
Self::Real(op) => {
|
||||
let result = Ok(op);
|
||||
result
|
||||
},
|
||||
}
|
||||
Self::Fake(_) => Err(RuntimeError {
|
||||
line: 0,
|
||||
op: self.clone(),
|
||||
msg: "PseudoOp::Fake is not a real MpsOp".into(),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ impl Clone for PseudoOp {
|
|||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Real(op) => Self::Fake(format!("{}", op)),
|
||||
Self::Fake(s) => Self::Fake(s.clone())
|
||||
Self::Fake(s) => Self::Fake(s.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ impl Display for PseudoOp {
|
|||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
match self {
|
||||
Self::Real(op) => write!(f, "{}", op),
|
||||
Self::Fake(s) => write!(f, "{}", s)
|
||||
Self::Fake(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use crate::lang::utility::{assert_token_raw, check_token_raw};
|
||||
use crate::lang::SyntaxError;
|
||||
use crate::lang::utility::{check_token_raw, assert_token_raw};
|
||||
use crate::tokens::MpsToken;
|
||||
|
||||
/// Convenient parser for repeated patterns of tokens
|
||||
pub struct RepeatedTokens<
|
||||
X: 'static,
|
||||
F1: FnMut(&mut VecDeque<MpsToken>) -> Result<Option<X>, SyntaxError>,
|
||||
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError> >
|
||||
{
|
||||
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError>,
|
||||
> {
|
||||
pattern_ingest: F1,
|
||||
separator_ingest: F2,
|
||||
}
|
||||
|
||||
impl<
|
||||
X: 'static,
|
||||
F1: FnMut(&mut VecDeque<MpsToken>) -> Result<Option<X>, SyntaxError>,
|
||||
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError> >
|
||||
RepeatedTokens<X, F1, F2>
|
||||
X: 'static,
|
||||
F1: FnMut(&mut VecDeque<MpsToken>) -> Result<Option<X>, SyntaxError>,
|
||||
F2: FnMut(&mut VecDeque<MpsToken>) -> Result<bool, SyntaxError>,
|
||||
> RepeatedTokens<X, F1, F2>
|
||||
{
|
||||
pub fn ingest_all(&mut self, tokens: &mut VecDeque<MpsToken>) -> Result<Vec<X>, SyntaxError> {
|
||||
let mut result = Vec::<X>::new();
|
||||
|
@ -29,7 +29,7 @@ impl<
|
|||
while (self.separator_ingest)(tokens)? {
|
||||
match (self.pattern_ingest)(tokens)? {
|
||||
Some(x) => result.push(x),
|
||||
None => break
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
|
|
|
@ -15,62 +15,63 @@ impl MpsTypePrimitive {
|
|||
#[inline]
|
||||
pub fn compare(&self, other: &Self) -> Result<i8, String> {
|
||||
let result = match self {
|
||||
Self::String(s1) => {
|
||||
match other {
|
||||
Self::String(s2) => Some(map_ordering(s1.cmp(s2))),
|
||||
_ => None,
|
||||
}
|
||||
Self::String(s1) => match other {
|
||||
Self::String(s2) => Some(map_ordering(s1.cmp(s2))),
|
||||
_ => None,
|
||||
},
|
||||
Self::Int(i1) => {
|
||||
match other {
|
||||
Self::Int(i2) => Some(map_ordering(i1.cmp(i2))),
|
||||
Self::UInt(i2) => Some(map_ordering((*i1 as i128).cmp(&(*i2 as i128)))),
|
||||
Self::Float(i2) => Some(
|
||||
map_ordering((*i1 as f64).partial_cmp(&(*i2 as f64)).unwrap_or(std::cmp::Ordering::Less))
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
Self::Int(i1) => match other {
|
||||
Self::Int(i2) => Some(map_ordering(i1.cmp(i2))),
|
||||
Self::UInt(i2) => Some(map_ordering((*i1 as i128).cmp(&(*i2 as i128)))),
|
||||
Self::Float(i2) => Some(map_ordering(
|
||||
(*i1 as f64)
|
||||
.partial_cmp(&(*i2 as f64))
|
||||
.unwrap_or(std::cmp::Ordering::Less),
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
Self::UInt(u1) => {
|
||||
match other {
|
||||
Self::UInt(u2) => Some(map_ordering(u1.cmp(u2))),
|
||||
Self::Int(u2) => Some(map_ordering((*u1 as i128).cmp(&(*u2 as i128)))),
|
||||
Self::Float(u2) => Some(
|
||||
map_ordering((*u1 as f64).partial_cmp(&(*u2 as f64)).unwrap_or(std::cmp::Ordering::Less))
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
Self::UInt(u1) => match other {
|
||||
Self::UInt(u2) => Some(map_ordering(u1.cmp(u2))),
|
||||
Self::Int(u2) => Some(map_ordering((*u1 as i128).cmp(&(*u2 as i128)))),
|
||||
Self::Float(u2) => Some(map_ordering(
|
||||
(*u1 as f64)
|
||||
.partial_cmp(&(*u2 as f64))
|
||||
.unwrap_or(std::cmp::Ordering::Less),
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
Self::Float(f1) => {
|
||||
match other {
|
||||
Self::Float(f2) => Some(map_ordering(f1.partial_cmp(f2).unwrap_or(std::cmp::Ordering::Less))),
|
||||
Self::Int(f2) => Some(
|
||||
map_ordering(f1.partial_cmp(&(*f2 as f64)).unwrap_or(std::cmp::Ordering::Less))
|
||||
),
|
||||
Self::UInt(f2) => Some(
|
||||
map_ordering(f1.partial_cmp(&(*f2 as f64)).unwrap_or(std::cmp::Ordering::Less))
|
||||
),
|
||||
_ => None
|
||||
}
|
||||
Self::Float(f1) => match other {
|
||||
Self::Float(f2) => Some(map_ordering(
|
||||
f1.partial_cmp(f2).unwrap_or(std::cmp::Ordering::Less),
|
||||
)),
|
||||
Self::Int(f2) => Some(map_ordering(
|
||||
f1.partial_cmp(&(*f2 as f64))
|
||||
.unwrap_or(std::cmp::Ordering::Less),
|
||||
)),
|
||||
Self::UInt(f2) => Some(map_ordering(
|
||||
f1.partial_cmp(&(*f2 as f64))
|
||||
.unwrap_or(std::cmp::Ordering::Less),
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
Self::Bool(b1) => {
|
||||
match other {
|
||||
Self::Bool(b2) => {
|
||||
if *b2 == *b1 {
|
||||
Some(0)
|
||||
} else if *b1{
|
||||
Some(1)
|
||||
} else {
|
||||
Some(-1)
|
||||
}
|
||||
},
|
||||
_ => None
|
||||
Self::Bool(b1) => match other {
|
||||
Self::Bool(b2) => {
|
||||
if *b2 == *b1 {
|
||||
Some(0)
|
||||
} else if *b1 {
|
||||
Some(1)
|
||||
} else {
|
||||
Some(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
match result {
|
||||
Some(x) => Ok(x),
|
||||
None => Err(format!("Cannot compare {} to {}: incompatible types", self, other))
|
||||
None => Err(format!(
|
||||
"Cannot compare {} to {}: incompatible types",
|
||||
self, other
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,4 +96,3 @@ fn map_ordering(ordering: std::cmp::Ordering) -> i8 {
|
|||
std::cmp::Ordering::Greater => 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use std::collections::VecDeque;
|
||||
#[cfg(feature = "music_library")]
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::MpsTypePrimitive;
|
||||
use super::SyntaxError;
|
||||
use crate::tokens::MpsToken;
|
||||
use super::MpsTypePrimitive;
|
||||
|
||||
pub fn assert_token<T, F: FnOnce(MpsToken) -> Option<T>>(
|
||||
caster: F,
|
||||
|
@ -17,7 +16,7 @@ pub fn assert_token<T, F: FnOnce(MpsToken) -> Option<T>>(
|
|||
line: 0,
|
||||
token: token.clone(),
|
||||
got: None,
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
if let Some(out) = caster(result.clone()) {
|
||||
Ok(out)
|
||||
|
@ -40,7 +39,7 @@ pub fn assert_token_raw(
|
|||
line: 0,
|
||||
token: token.clone(),
|
||||
got: None,
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
if std::mem::discriminant(&token) == std::mem::discriminant(&result) {
|
||||
Ok(result)
|
||||
|
@ -63,7 +62,7 @@ pub fn assert_token_raw_back(
|
|||
line: 0,
|
||||
token: token.clone(),
|
||||
got: None,
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
if std::mem::discriminant(&token) == std::mem::discriminant(&result) {
|
||||
Ok(result)
|
||||
|
@ -76,10 +75,7 @@ pub fn assert_token_raw_back(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check_token_raw(
|
||||
token: MpsToken,
|
||||
token_target: &MpsToken,
|
||||
) -> bool {
|
||||
pub fn check_token_raw(token: MpsToken, token_target: &MpsToken) -> bool {
|
||||
std::mem::discriminant(&token) == std::mem::discriminant(token_target)
|
||||
}
|
||||
|
||||
|
@ -91,27 +87,25 @@ pub fn assert_name(name: &str, tokens: &mut VecDeque<MpsToken>) -> Result<String
|
|||
line: 0,
|
||||
token: MpsToken::Name(name.to_owned()),
|
||||
got: None,
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
match result {
|
||||
MpsToken::Name(n) => {
|
||||
if n == name {
|
||||
Ok(n)
|
||||
} else {
|
||||
Err(
|
||||
SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Name(name.to_owned()),
|
||||
got: Some(MpsToken::Name(n)),
|
||||
}
|
||||
)
|
||||
Err(SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Name(name.to_owned()),
|
||||
got: Some(MpsToken::Name(n)),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
token => Err(SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Name(name.to_owned()),
|
||||
got: Some(token),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,20 +113,21 @@ pub fn assert_name(name: &str, tokens: &mut VecDeque<MpsToken>) -> Result<String
|
|||
pub fn check_name(name: &str, token: &MpsToken) -> bool {
|
||||
match token {
|
||||
MpsToken::Name(n) => n == name,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_is_type(token: &MpsToken) -> bool {
|
||||
match token {
|
||||
MpsToken::Literal(_) => true,
|
||||
MpsToken::Name(s) =>
|
||||
MpsToken::Name(s) => {
|
||||
s.parse::<f64>().is_ok()
|
||||
|| s.parse::<i64>().is_ok()
|
||||
|| s.parse::<u64>().is_ok()
|
||||
|| s == "false"
|
||||
|| s == "true",
|
||||
_ => false
|
||||
|| s.parse::<i64>().is_ok()
|
||||
|| s.parse::<u64>().is_ok()
|
||||
|| s == "false"
|
||||
|| s == "true"
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +138,7 @@ pub fn assert_type(tokens: &mut VecDeque<MpsToken>) -> Result<MpsTypePrimitive,
|
|||
line: 0,
|
||||
token: MpsToken::Name("Float | UInt | Int | Bool".into()),
|
||||
got: None,
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
match token {
|
||||
MpsToken::Literal(s) => Ok(MpsTypePrimitive::String(s)),
|
||||
|
@ -162,15 +157,15 @@ pub fn assert_type(tokens: &mut VecDeque<MpsToken>) -> Result<MpsTypePrimitive,
|
|||
Err(SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Name("Float | UInt | Int | Bool".into()),
|
||||
got: Some(MpsToken::Name(s))
|
||||
got: Some(MpsToken::Name(s)),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
token => Err(SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Name("Float | UInt | Int | Bool | \"String\"".into()),
|
||||
got: Some(token)
|
||||
})
|
||||
got: Some(token),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,4 +174,3 @@ pub fn music_folder() -> PathBuf {
|
|||
.unwrap_or_else(|| PathBuf::from("./"))
|
||||
.join("Music")
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@ use std::collections::VecDeque;
|
|||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::iter::Iterator;
|
||||
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
use crate::tokens::MpsToken;
|
||||
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::lang::{MpsOp, SimpleMpsOpFactory, MpsOpFactory, BoxedMpsOpFactory};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::utility::assert_token;
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::{BoxedMpsOpFactory, MpsOp, MpsOpFactory, SimpleMpsOpFactory};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommentStatement {
|
||||
|
@ -66,21 +66,24 @@ pub struct CommentStatementFactory;
|
|||
|
||||
impl SimpleMpsOpFactory<CommentStatement> for CommentStatementFactory {
|
||||
fn is_op_simple(&self, tokens: &VecDeque<MpsToken>) -> bool {
|
||||
tokens.len() == 1
|
||||
&& tokens[0].is_comment()
|
||||
tokens.len() == 1 && tokens[0].is_comment()
|
||||
}
|
||||
|
||||
fn build_op_simple(
|
||||
&self,
|
||||
tokens: &mut VecDeque<MpsToken>,
|
||||
) -> Result<CommentStatement, SyntaxError> {
|
||||
let comment = assert_token(|t| match t {
|
||||
MpsToken::Comment(c) => Some(c),
|
||||
_ => None
|
||||
}, MpsToken::Comment("comment".into()), tokens)?;
|
||||
let comment = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Comment(c) => Some(c),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Comment("comment".into()),
|
||||
tokens,
|
||||
)?;
|
||||
Ok(CommentStatement {
|
||||
comment: comment,
|
||||
context: None
|
||||
context: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
230
mps-interpreter/src/lang/vocabulary/files.rs
Normal file
230
mps-interpreter/src/lang/vocabulary/files.rs
Normal file
|
@ -0,0 +1,230 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::iter::Iterator;
|
||||
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
|
||||
use crate::lang::repeated_tokens;
|
||||
use crate::lang::utility::{assert_token, assert_token_raw};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsOp};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::processing::general::FileIter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FilesStatement {
|
||||
context: Option<MpsContext>,
|
||||
// function params
|
||||
folder: Option<String>,
|
||||
regex: Option<String>,
|
||||
recursive: Option<bool>,
|
||||
// state
|
||||
file_iter: Option<FileIter>,
|
||||
}
|
||||
|
||||
impl Display for FilesStatement {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
write!(f, "files(")?;
|
||||
let mut preceding = false;
|
||||
if let Some(folder) = &self.folder {
|
||||
write!(f, "folder={}", folder)?;
|
||||
preceding = true;
|
||||
}
|
||||
if let Some(regex) = &self.regex {
|
||||
if preceding {
|
||||
write!(f, ", ")?;
|
||||
} else {
|
||||
preceding = true;
|
||||
}
|
||||
write!(f, "regex={}", regex)?;
|
||||
}
|
||||
if let Some(recursive) = self.recursive {
|
||||
if preceding {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "recursive={}", recursive)?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::clone::Clone for FilesStatement {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
context: None,
|
||||
folder: self.folder.clone(),
|
||||
regex: self.regex.clone(),
|
||||
recursive: self.recursive.clone(),
|
||||
file_iter: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for FilesStatement {
|
||||
type Item = Result<MpsMusicItem, RuntimeError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.file_iter.is_none() {
|
||||
let self_clone = self.clone();
|
||||
let iter = self.context.as_mut().unwrap().filesystem.raw(
|
||||
self.folder.as_deref(),
|
||||
self.regex.as_deref(),
|
||||
self.recursive.unwrap_or(true),
|
||||
&mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into(),
|
||||
);
|
||||
self.file_iter = Some(match iter {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Some(Err(e)),
|
||||
});
|
||||
}
|
||||
match self.file_iter.as_mut().unwrap().next() {
|
||||
Some(Ok(item)) => Some(Ok(item)),
|
||||
Some(Err(e)) => Some(Err(RuntimeError {
|
||||
line: 0,
|
||||
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
|
||||
msg: e,
|
||||
})),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MpsOp for FilesStatement {
|
||||
fn enter(&mut self, ctx: MpsContext) {
|
||||
self.context = Some(ctx)
|
||||
}
|
||||
|
||||
fn escape(&mut self) -> MpsContext {
|
||||
self.context.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FilesFunctionFactory;
|
||||
|
||||
impl MpsFunctionFactory<FilesStatement> for FilesFunctionFactory {
|
||||
fn is_function(&self, name: &str) -> bool {
|
||||
name == "files"
|
||||
}
|
||||
|
||||
fn build_function_params(
|
||||
&self,
|
||||
_name: String,
|
||||
tokens: &mut VecDeque<MpsToken>,
|
||||
_dict: &MpsLanguageDictionary,
|
||||
) -> Result<FilesStatement, SyntaxError> {
|
||||
// files([folder|dir=]"path", [regex|re = "pattern"], [recursive = true|false])
|
||||
let mut root_path = None;
|
||||
let mut pattern = None;
|
||||
let mut recursive = None;
|
||||
if tokens[0].is_literal() {
|
||||
// folder is specified without keyword
|
||||
root_path = Some(assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Literal(s) => Some(s),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Literal("/path/to/music/folder".into()),
|
||||
tokens,
|
||||
)?);
|
||||
if tokens.len() > 1 && tokens[0].is_comma() {
|
||||
assert_token_raw(MpsToken::Comma, tokens)?;
|
||||
}
|
||||
}
|
||||
if tokens.len() != 0 {
|
||||
// parse keyword function parameters
|
||||
let ingest = |tokens2: &mut VecDeque<MpsToken>| {
|
||||
if tokens2.len() < 3 {
|
||||
return Ok(None);
|
||||
} // nothing wrong, nothing left to ingest
|
||||
let param_name = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("param".into()),
|
||||
tokens2,
|
||||
)?;
|
||||
assert_token_raw(MpsToken::Equals, tokens2)?;
|
||||
let param_val = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(s) => Some(MpsToken::Name(s)),
|
||||
MpsToken::Literal(s) => Some(MpsToken::Literal(s)),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("value".into()),
|
||||
tokens2,
|
||||
)?;
|
||||
Ok(Some((param_name, param_val))) // successfully ingested one phrase
|
||||
};
|
||||
let params = repeated_tokens(ingest, MpsToken::Comma).ingest_all(tokens)?;
|
||||
// assign parameters to variables
|
||||
for (param, val) in params {
|
||||
match ¶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<FilesStatement, FilesFunctionFactory>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn files_function_factory() -> FilesStatementFactory {
|
||||
FilesStatementFactory::new(FilesFunctionFactory)
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::processing::OpGetter;
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::lang::{MpsFilterPredicate, MpsFilterFactory, MpsFilterStatementFactory};
|
||||
use crate::lang::{SyntaxError, RuntimeError};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::processing::OpGetter;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EmptyFilter;
|
||||
|
@ -19,7 +19,12 @@ impl Display for EmptyFilter {
|
|||
}
|
||||
|
||||
impl MpsFilterPredicate for EmptyFilter {
|
||||
fn matches(&mut self, _item: &MpsMusicItem, _ctx: &mut MpsContext, _op: &mut OpGetter) -> Result<bool, RuntimeError> {
|
||||
fn matches(
|
||||
&mut self,
|
||||
_item: &MpsMusicItem,
|
||||
_ctx: &mut MpsContext,
|
||||
_op: &mut OpGetter,
|
||||
) -> Result<bool, RuntimeError> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::lang::{MpsFilterPredicate, MpsFilterFactory, MpsFilterStatementFactory};
|
||||
use crate::lang::{SyntaxError, RuntimeError};
|
||||
use super::utility::{assert_comparison_operator, item_to_primitive_lut};
|
||||
use crate::lang::utility::{assert_token, assert_type, check_is_type};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::MpsTypePrimitive;
|
||||
use crate::lang::utility::{assert_token, assert_type, check_is_type};
|
||||
use super::utility::{item_to_primitive_lut, assert_comparison_operator};
|
||||
use crate::processing::OpGetter;
|
||||
use crate::lang::{MpsFilterFactory, MpsFilterPredicate, MpsFilterStatementFactory};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::processing::general::MpsType;
|
||||
use crate::processing::OpGetter;
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum VariableOrValue {
|
||||
|
@ -23,7 +23,7 @@ enum VariableOrValue {
|
|||
pub struct FieldFilter {
|
||||
field_name: String,
|
||||
val: VariableOrValue,
|
||||
comparison: [i8; 2]
|
||||
comparison: [i8; 2],
|
||||
}
|
||||
|
||||
impl Display for FieldFilter {
|
||||
|
@ -33,12 +33,16 @@ impl Display for FieldFilter {
|
|||
VariableOrValue::Variable(name) => write!(f, "{} == {}", self.field_name, name),
|
||||
VariableOrValue::Value(t) => write!(f, "{} == {}", self.field_name, t),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl MpsFilterPredicate for FieldFilter {
|
||||
fn matches(&mut self, item: &MpsMusicItem, ctx: &mut MpsContext, op: &mut OpGetter) -> Result<bool, RuntimeError> {
|
||||
fn matches(
|
||||
&mut self,
|
||||
item: &MpsMusicItem,
|
||||
ctx: &mut MpsContext,
|
||||
op: &mut OpGetter,
|
||||
) -> Result<bool, RuntimeError> {
|
||||
let music_item_lut = item_to_primitive_lut(item.to_owned());
|
||||
let variable = match &self.val {
|
||||
VariableOrValue::Variable(name) => match ctx.variables.get(&name, op)? {
|
||||
|
@ -47,17 +51,16 @@ impl MpsFilterPredicate for FieldFilter {
|
|||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Variable {} is not comparable", name),
|
||||
})
|
||||
}),
|
||||
},
|
||||
VariableOrValue::Value(val) => Ok(val)
|
||||
VariableOrValue::Value(val) => Ok(val),
|
||||
}?;
|
||||
if let Some(field) = music_item_lut.get(&self.field_name) {
|
||||
let compare = field.compare(variable)
|
||||
.map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: e,
|
||||
})?;
|
||||
let compare = field.compare(variable).map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: e,
|
||||
})?;
|
||||
let mut is_match = false;
|
||||
for comparator in self.comparison {
|
||||
if comparator == compare {
|
||||
|
@ -73,7 +76,6 @@ impl MpsFilterPredicate for FieldFilter {
|
|||
msg: format!("Field {} does not exist", &self.field_name),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,14 +87,12 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
|
|||
(tokens_len == 3 // field > variable OR field < variable
|
||||
&& tokens[0].is_name()
|
||||
&& (tokens[1].is_open_angle_bracket() || tokens[1].is_close_angle_bracket())
|
||||
&& (tokens[2].is_name() || check_is_type(&tokens[2]))
|
||||
)
|
||||
|| (tokens_len == 4 // field >= variable OR field <= variable
|
||||
&& (tokens[2].is_name() || check_is_type(&tokens[2])))
|
||||
|| (tokens_len == 4 // field >= variable OR field <= variable
|
||||
&& tokens[0].is_name()
|
||||
&& (tokens[1].is_open_angle_bracket() || tokens[1].is_close_angle_bracket() || tokens[1].is_equals())
|
||||
&& tokens[2].is_equals()
|
||||
&& (tokens[3].is_name() || check_is_type(&tokens[3]))
|
||||
)
|
||||
&& (tokens[3].is_name() || check_is_type(&tokens[3])))
|
||||
}
|
||||
|
||||
fn build_filter(
|
||||
|
@ -100,26 +100,32 @@ impl MpsFilterFactory<FieldFilter> for FieldFilterFactory {
|
|||
tokens: &mut VecDeque<MpsToken>,
|
||||
_dict: &MpsLanguageDictionary,
|
||||
) -> Result<FieldFilter, SyntaxError> {
|
||||
let field = assert_token(|t| match t {
|
||||
MpsToken::Name(n) => Some(n),
|
||||
_ => None
|
||||
}, MpsToken::Name("field_name".into()), tokens)?;
|
||||
let field = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(n) => Some(n),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("field_name".into()),
|
||||
tokens,
|
||||
)?;
|
||||
let compare_operator = assert_comparison_operator(tokens)?;
|
||||
if check_is_type(&tokens[0]) {
|
||||
let value = VariableOrValue::Value(assert_type(tokens)?);
|
||||
Ok(FieldFilter{
|
||||
Ok(FieldFilter {
|
||||
field_name: field,
|
||||
val: value,
|
||||
comparison: compare_operator,
|
||||
})
|
||||
} else {
|
||||
let variable = VariableOrValue::Variable(
|
||||
assert_token(|t| match t {
|
||||
let variable = VariableOrValue::Variable(assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(n) => Some(n),
|
||||
_ => None
|
||||
}, MpsToken::Name("variable_name".into()), tokens)?
|
||||
);
|
||||
Ok(FieldFilter{
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("variable_name".into()),
|
||||
tokens,
|
||||
)?);
|
||||
Ok(FieldFilter {
|
||||
field_name: field,
|
||||
val: variable,
|
||||
comparison: compare_operator,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -1,21 +1,36 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::MpsMusicItem;
|
||||
use crate::lang::SyntaxError;
|
||||
use crate::lang::MpsTypePrimitive;
|
||||
use crate::lang::utility::assert_token_raw;
|
||||
use crate::lang::MpsTypePrimitive;
|
||||
use crate::lang::SyntaxError;
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsMusicItem;
|
||||
|
||||
pub fn item_to_primitive_lut(item: MpsMusicItem) -> HashMap<String, MpsTypePrimitive> {
|
||||
let mut result = HashMap::new();
|
||||
result.insert("title".into(), MpsTypePrimitive::String(item.title));
|
||||
result.insert("artist".into(), MpsTypePrimitive::String(item.artist.unwrap_or("".to_owned())));
|
||||
result.insert("album".into(), MpsTypePrimitive::String(item.album.unwrap_or("".to_owned())));
|
||||
result.insert(
|
||||
"artist".into(),
|
||||
MpsTypePrimitive::String(item.artist.unwrap_or("".to_owned())),
|
||||
);
|
||||
result.insert(
|
||||
"album".into(),
|
||||
MpsTypePrimitive::String(item.album.unwrap_or("".to_owned())),
|
||||
);
|
||||
result.insert("filename".into(), MpsTypePrimitive::String(item.filename));
|
||||
result.insert("genre".into(), MpsTypePrimitive::String(item.genre.unwrap_or("".to_owned())));
|
||||
result.insert("track".into(), MpsTypePrimitive::UInt(item.track.unwrap_or(0)));
|
||||
result.insert("year".into(), MpsTypePrimitive::UInt(item.year.unwrap_or(0)));
|
||||
result.insert(
|
||||
"genre".into(),
|
||||
MpsTypePrimitive::String(item.genre.unwrap_or("".to_owned())),
|
||||
);
|
||||
result.insert(
|
||||
"track".into(),
|
||||
MpsTypePrimitive::UInt(item.track.unwrap_or(0)),
|
||||
);
|
||||
result.insert(
|
||||
"year".into(),
|
||||
MpsTypePrimitive::UInt(item.year.unwrap_or(0)),
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
|
@ -23,37 +38,46 @@ pub fn assert_comparison_operator(tokens: &mut VecDeque<MpsToken>) -> Result<[i8
|
|||
let token1 = tokens.pop_front().unwrap();
|
||||
match token1 {
|
||||
MpsToken::Equals => {
|
||||
if tokens.len() != 0 && tokens[0].is_equals() { // tokens: ==
|
||||
if tokens.len() != 0 && tokens[0].is_equals() {
|
||||
// tokens: ==
|
||||
assert_token_raw(MpsToken::Equals, tokens)?;
|
||||
Ok([0, 0])
|
||||
} else {
|
||||
Err(SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Equals,
|
||||
got: if tokens.len() != 0 {Some(tokens[0].clone())} else {None},
|
||||
got: if tokens.len() != 0 {
|
||||
Some(tokens[0].clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
MpsToken::OpenAngleBracket => {
|
||||
if tokens.len() != 0 && tokens[0].is_equals() { // tokens: <=
|
||||
if tokens.len() != 0 && tokens[0].is_equals() {
|
||||
// tokens: <=
|
||||
assert_token_raw(MpsToken::Equals, tokens)?;
|
||||
Ok([0, -1])
|
||||
} else { // token: <
|
||||
} else {
|
||||
// token: <
|
||||
Ok([-1, -1])
|
||||
}
|
||||
},
|
||||
}
|
||||
MpsToken::CloseAngleBracket => {
|
||||
if tokens.len() != 0 && tokens[0].is_equals() { // tokens: >=
|
||||
if tokens.len() != 0 && tokens[0].is_equals() {
|
||||
// tokens: >=
|
||||
assert_token_raw(MpsToken::Equals, tokens)?;
|
||||
Ok([0, 1])
|
||||
} else { // token: >
|
||||
} else {
|
||||
// token: >
|
||||
Ok([1, 1])
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => Err(SyntaxError {
|
||||
line: 0,
|
||||
token: MpsToken::Equals, // TODO this can be < > or =
|
||||
got: Some(token1),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -2,14 +2,14 @@ use std::collections::VecDeque;
|
|||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::iter::Iterator;
|
||||
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
use crate::tokens::MpsToken;
|
||||
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::lang::{MpsOp, PseudoOp, MpsFunctionFactory, MpsFunctionStatementFactory};
|
||||
use crate::lang::utility::{assert_token, assert_token_raw};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::utility::{assert_token_raw, assert_token};
|
||||
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsOp, PseudoOp};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RepeatStatement {
|
||||
|
@ -49,7 +49,7 @@ impl Iterator for RepeatStatement {
|
|||
if !self.inner_done {
|
||||
let real_op = match self.inner_statement.try_real() {
|
||||
Err(e) => return Some(Err(e)),
|
||||
Ok(real) => real
|
||||
Ok(real) => real,
|
||||
};
|
||||
if self.context.is_some() {
|
||||
let ctx = self.context.take().unwrap();
|
||||
|
@ -62,14 +62,14 @@ impl Iterator for RepeatStatement {
|
|||
Ok(music) => {
|
||||
self.cache.push(music.clone());
|
||||
Some(Ok(music))
|
||||
},
|
||||
Err(e) => Some(Err(e))
|
||||
}
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
self.inner_done = true;
|
||||
self.context = Some(real_op.escape());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// inner is done
|
||||
|
@ -78,10 +78,10 @@ impl Iterator for RepeatStatement {
|
|||
} else {
|
||||
if self.cache.len() == 0 {
|
||||
if self.loop_forever {
|
||||
Some(Err(RuntimeError {
|
||||
Some(Err(RuntimeError {
|
||||
line: 0,
|
||||
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
|
||||
msg: "Cannot repeat nothing".into()
|
||||
msg: "Cannot repeat nothing".into(),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
|
@ -90,7 +90,9 @@ impl Iterator for RepeatStatement {
|
|||
let music_item = self.cache[self.cache_position].clone();
|
||||
self.cache_position += 1;
|
||||
if self.cache_position == self.cache.len() {
|
||||
if self.repetitions != 0 { self.repetitions -= 1; }
|
||||
if self.repetitions != 0 {
|
||||
self.repetitions -= 1;
|
||||
}
|
||||
self.cache_position = 0;
|
||||
}
|
||||
Some(Ok(music_item))
|
||||
|
@ -131,12 +133,17 @@ impl MpsFunctionFactory<RepeatStatement> for RepeatFunctionFactory {
|
|||
let inner_statement = dict.try_build_statement(tokens)?;
|
||||
tokens.extend(end_tokens);
|
||||
let mut count: Option<usize> = None;
|
||||
if tokens.len() != 0 { // repititions specified
|
||||
if tokens.len() != 0 {
|
||||
// repititions specified
|
||||
assert_token_raw(MpsToken::Comma, tokens)?;
|
||||
count = Some(assert_token(|t| match t {
|
||||
MpsToken::Name(n) => n.parse::<usize>().map(|d| d - 1).ok(),
|
||||
_ => None
|
||||
}, MpsToken::Name("usize".into()), tokens)?);
|
||||
count = Some(assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(n) => n.parse::<usize>().map(|d| d - 1).ok(),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("usize".into()),
|
||||
tokens,
|
||||
)?);
|
||||
}
|
||||
Ok(RepeatStatement {
|
||||
inner_statement: inner_statement.into(),
|
||||
|
@ -145,7 +152,7 @@ impl MpsFunctionFactory<RepeatStatement> for RepeatFunctionFactory {
|
|||
cache: Vec::new(),
|
||||
cache_position: 0,
|
||||
repetitions: count.unwrap_or(0),
|
||||
loop_forever: count.is_none()
|
||||
loop_forever: count.is_none(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +166,8 @@ fn next_comma(tokens: &VecDeque<MpsToken>) -> usize {
|
|||
tokens.len()
|
||||
}
|
||||
|
||||
pub type RepeatStatementFactory = MpsFunctionStatementFactory<RepeatStatement, RepeatFunctionFactory>;
|
||||
pub type RepeatStatementFactory =
|
||||
MpsFunctionStatementFactory<RepeatStatement, RepeatFunctionFactory>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn repeat_function_factory() -> RepeatStatementFactory {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::iter::Iterator;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
use crate::tokens::MpsToken;
|
||||
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::lang::{MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::utility::{assert_token_raw, assert_token};
|
||||
use crate::lang::repeated_tokens;
|
||||
use crate::lang::utility::{assert_token, assert_token_raw};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsOp};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SqlInitStatement {
|
||||
|
@ -44,10 +44,16 @@ impl Iterator for SqlInitStatement {
|
|||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let pseudo_clone = self.clone();
|
||||
// execute
|
||||
match self.context.as_mut().unwrap().database.init_with_params(&self.params,
|
||||
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()) {
|
||||
match self
|
||||
.context
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.database
|
||||
.init_with_params(&self.params, &mut move || {
|
||||
(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
}) {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(Err(e))
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,17 +82,27 @@ impl MpsFunctionFactory<SqlInitStatement> for SqlInitFunctionFactory {
|
|||
_dict: &MpsLanguageDictionary,
|
||||
) -> Result<SqlInitStatement, SyntaxError> {
|
||||
let ingest = |tokens2: &mut VecDeque<MpsToken>| {
|
||||
if tokens2.len() < 3 {return Ok(None);} // nothing wrong, nothing left to ingest
|
||||
let param_name = assert_token(|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
_ => None,
|
||||
}, MpsToken::Name("param".into()), tokens2)?;
|
||||
if tokens2.len() < 3 {
|
||||
return Ok(None);
|
||||
} // nothing wrong, nothing left to ingest
|
||||
let param_name = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("param".into()),
|
||||
tokens2,
|
||||
)?;
|
||||
assert_token_raw(MpsToken::Equals, tokens2)?;
|
||||
let param_val = assert_token(|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
MpsToken::Literal(s) => Some(s),
|
||||
_ => None,
|
||||
}, MpsToken::Name("value".into()), tokens2)?;
|
||||
let param_val = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
MpsToken::Literal(s) => Some(s),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("value".into()),
|
||||
tokens2,
|
||||
)?;
|
||||
Ok(Some((param_name, param_val))) // successfully ingested one phrase
|
||||
};
|
||||
let params = repeated_tokens(ingest, MpsToken::Comma).ingest_all(tokens)?;
|
||||
|
@ -97,7 +113,8 @@ impl MpsFunctionFactory<SqlInitStatement> for SqlInitFunctionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
pub type SqlInitStatementFactory = MpsFunctionStatementFactory<SqlInitStatement, SqlInitFunctionFactory>;
|
||||
pub type SqlInitStatementFactory =
|
||||
MpsFunctionStatementFactory<SqlInitStatement, SqlInitFunctionFactory>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn sql_init_function_factory() -> SqlInitStatementFactory {
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::{Debug, Display, Error, Formatter};
|
|||
use std::iter::Iterator;
|
||||
|
||||
use crate::lang::utility::assert_token;
|
||||
use crate::lang::{MpsLanguageDictionary, MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory};
|
||||
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsLanguageDictionary, MpsOp};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
|
@ -82,10 +82,9 @@ impl Iterator for SqlStatement {
|
|||
let self_clone = self.clone();
|
||||
let ctx = self.context.as_mut().unwrap();
|
||||
// query has not been executed yet
|
||||
match ctx
|
||||
.database
|
||||
.raw(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into())
|
||||
{
|
||||
match ctx.database.raw(&self.query, &mut move || {
|
||||
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
}) {
|
||||
Err(e) => return Some(Err(e)),
|
||||
Ok(rows) => {
|
||||
self.rows = Some(rows);
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::{Debug, Display, Error, Formatter};
|
|||
use std::iter::Iterator;
|
||||
|
||||
use crate::lang::utility::assert_token;
|
||||
use crate::lang::{MpsLanguageDictionary, MpsOp, MpsFunctionFactory, MpsFunctionStatementFactory};
|
||||
use crate::lang::{MpsFunctionFactory, MpsFunctionStatementFactory, MpsLanguageDictionary, MpsOp};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
|
@ -133,18 +133,18 @@ impl Iterator for SimpleSqlStatement {
|
|||
let ctx = self.context.as_mut().unwrap();
|
||||
// query has not been executed yet
|
||||
let query_result = match self.mode {
|
||||
QueryMode::Artist => ctx
|
||||
.database
|
||||
.artist_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
|
||||
QueryMode::Album => ctx
|
||||
.database
|
||||
.album_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
|
||||
QueryMode::Song => ctx
|
||||
.database
|
||||
.song_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
|
||||
QueryMode::Genre => ctx
|
||||
.database
|
||||
.genre_like(&self.query, &mut move || (Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()),
|
||||
QueryMode::Artist => ctx.database.artist_like(&self.query, &mut move || {
|
||||
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
}),
|
||||
QueryMode::Album => ctx.database.album_like(&self.query, &mut move || {
|
||||
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
}),
|
||||
QueryMode::Song => ctx.database.song_like(&self.query, &mut move || {
|
||||
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
}),
|
||||
QueryMode::Genre => ctx.database.genre_like(&self.query, &mut move || {
|
||||
(Box::new(self_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
}),
|
||||
};
|
||||
match query_result {
|
||||
Err(e) => return Some(Err(e)),
|
||||
|
@ -195,7 +195,8 @@ impl MpsFunctionFactory<SimpleSqlStatement> for SimpleSqlFunctionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
pub type SimpleSqlStatementFactory = MpsFunctionStatementFactory<SimpleSqlStatement, SimpleSqlFunctionFactory>;
|
||||
pub type SimpleSqlStatementFactory =
|
||||
MpsFunctionStatementFactory<SimpleSqlStatement, SimpleSqlFunctionFactory>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn simple_sql_function_factory() -> SimpleSqlStatementFactory {
|
||||
|
|
|
@ -2,14 +2,14 @@ use std::collections::VecDeque;
|
|||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::iter::Iterator;
|
||||
|
||||
use crate::tokens::MpsToken;
|
||||
use crate::MpsContext;
|
||||
use crate::MpsMusicItem;
|
||||
use crate::tokens::MpsToken;
|
||||
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::lang::{MpsOp, PseudoOp, MpsOpFactory, BoxedMpsOpFactory, MpsTypePrimitive};
|
||||
use crate::lang::utility::{assert_token, assert_token_raw, assert_type, check_is_type};
|
||||
use crate::lang::MpsLanguageDictionary;
|
||||
use crate::lang::utility::{assert_token_raw, assert_token, check_is_type, assert_type};
|
||||
use crate::lang::{BoxedMpsOpFactory, MpsOp, MpsOpFactory, MpsTypePrimitive, PseudoOp};
|
||||
use crate::lang::{RuntimeError, SyntaxError};
|
||||
use crate::processing::general::MpsType;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -54,7 +54,10 @@ impl Iterator for AssignStatement {
|
|||
Some(Err(RuntimeError {
|
||||
line: 0,
|
||||
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
|
||||
msg: format!("Variable {} already assigned, cannot redo assignment", self.variable_name),
|
||||
msg: format!(
|
||||
"Variable {} already assigned, cannot redo assignment",
|
||||
self.variable_name
|
||||
),
|
||||
}))
|
||||
} else {
|
||||
let mut inner = inner_statement.clone();
|
||||
|
@ -66,27 +69,28 @@ impl Iterator for AssignStatement {
|
|||
let pseudo_clone = self.clone();
|
||||
let result;
|
||||
if self.is_declaration {
|
||||
result = self.context.as_mut().unwrap()
|
||||
.variables.declare(
|
||||
&self.variable_name,
|
||||
MpsType::Op(real),
|
||||
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
result = self.context.as_mut().unwrap().variables.declare(
|
||||
&self.variable_name,
|
||||
MpsType::Op(real),
|
||||
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
|
||||
);
|
||||
} else {
|
||||
result = self.context.as_mut().unwrap()
|
||||
.variables.assign(
|
||||
&self.variable_name,
|
||||
MpsType::Op(real),
|
||||
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
result = self.context.as_mut().unwrap().variables.assign(
|
||||
&self.variable_name,
|
||||
MpsType::Op(real),
|
||||
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
|
||||
);
|
||||
}
|
||||
match result {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(Err(e))
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
} else if !self.is_simple {
|
||||
panic!("Assignee statement for {} is None but assignment is not simple type", self.variable_name)
|
||||
panic!(
|
||||
"Assignee statement for {} is None but assignment is not simple type",
|
||||
self.variable_name
|
||||
)
|
||||
/*Some(Err(RuntimeError {
|
||||
line: 0,
|
||||
op: (Box::new(self.clone()) as Box<dyn MpsOp>).into(),
|
||||
|
@ -97,29 +101,26 @@ impl Iterator for AssignStatement {
|
|||
let pseudo_clone = self.clone();
|
||||
let result;
|
||||
if self.is_declaration {
|
||||
result = self.context.as_mut().unwrap()
|
||||
.variables.declare(
|
||||
&self.variable_name,
|
||||
MpsType::Primitive(assign_type),
|
||||
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
result = self.context.as_mut().unwrap().variables.declare(
|
||||
&self.variable_name,
|
||||
MpsType::Primitive(assign_type),
|
||||
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
|
||||
);
|
||||
} else {
|
||||
result = self.context.as_mut().unwrap()
|
||||
.variables.assign(
|
||||
&self.variable_name,
|
||||
MpsType::Primitive(assign_type),
|
||||
&mut move ||(Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into()
|
||||
result = self.context.as_mut().unwrap().variables.assign(
|
||||
&self.variable_name,
|
||||
MpsType::Primitive(assign_type),
|
||||
&mut move || (Box::new(pseudo_clone.clone()) as Box<dyn MpsOp>).into(),
|
||||
);
|
||||
}
|
||||
match result {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(Err(e))
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl MpsOp for AssignStatement {
|
||||
fn enter(&mut self, ctx: MpsContext) {
|
||||
self.context = Some(ctx)
|
||||
|
@ -137,7 +138,7 @@ impl MpsOpFactory<AssignStatement> for AssignStatementFactory {
|
|||
(tokens.len() >= 3
|
||||
&&tokens[0].is_name() // can be any (valid) variable name
|
||||
&& tokens[1].is_equals())
|
||||
|| (tokens.len() >= 4
|
||||
|| (tokens.len() >= 4
|
||||
&& tokens[0].is_let()
|
||||
&& tokens[1].is_name() // any name
|
||||
&& tokens[2].is_equals())
|
||||
|
@ -150,13 +151,18 @@ impl MpsOpFactory<AssignStatement> for AssignStatementFactory {
|
|||
) -> Result<AssignStatement, SyntaxError> {
|
||||
// [let] variable_name = inner_statement
|
||||
let is_decl = tokens[0].is_let();
|
||||
if is_decl { // variable declarations start with let, assignments do not
|
||||
if is_decl {
|
||||
// variable declarations start with let, assignments do not
|
||||
assert_token_raw(MpsToken::Let, tokens)?;
|
||||
}
|
||||
let name = assert_token(|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
_ => None
|
||||
}, MpsToken::Name("variable_name".into()), tokens)?;
|
||||
let name = assert_token(
|
||||
|t| match t {
|
||||
MpsToken::Name(s) => Some(s),
|
||||
_ => None,
|
||||
},
|
||||
MpsToken::Name("variable_name".into()),
|
||||
tokens,
|
||||
)?;
|
||||
assert_token_raw(MpsToken::Equals, tokens)?;
|
||||
let is_simple_assign = check_is_type(&tokens[0]);
|
||||
if is_simple_assign {
|
||||
|
|
|
@ -1,38 +1,54 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::lang::db::*;
|
||||
use super::MpsLibrary;
|
||||
use crate::lang::db::*;
|
||||
|
||||
pub fn build_library_from_files<P: AsRef<Path>>(path: P, lib: &mut MpsLibrary) -> std::io::Result<()> {
|
||||
pub fn build_library_from_files<P: AsRef<Path>>(
|
||||
path: P,
|
||||
lib: &mut MpsLibrary,
|
||||
) -> std::io::Result<()> {
|
||||
//let mut result = MpsLibrary::new();
|
||||
lib.read_path(path, 10)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build_library_from_sqlite(conn: &rusqlite::Connection, lib: &mut MpsLibrary) -> rusqlite::Result<()> {
|
||||
pub fn build_library_from_sqlite(
|
||||
conn: &rusqlite::Connection,
|
||||
lib: &mut MpsLibrary,
|
||||
) -> rusqlite::Result<()> {
|
||||
// build songs
|
||||
for song in conn.prepare("SELECT * from songs")?
|
||||
.query_map([], DbMusicItem::map_row)? {
|
||||
for song in conn
|
||||
.prepare("SELECT * from songs")?
|
||||
.query_map([], DbMusicItem::map_row)?
|
||||
{
|
||||
lib.add_song(song?);
|
||||
}
|
||||
// build metadata
|
||||
for meta in conn.prepare("SELECT * from metadata")?
|
||||
.query_map([], DbMetaItem::map_row)? {
|
||||
for meta in conn
|
||||
.prepare("SELECT * from metadata")?
|
||||
.query_map([], DbMetaItem::map_row)?
|
||||
{
|
||||
lib.add_metadata(meta?);
|
||||
}
|
||||
// build artists
|
||||
for artist in conn.prepare("SELECT * from artists")?
|
||||
.query_map([], DbArtistItem::map_row)? {
|
||||
for artist in conn
|
||||
.prepare("SELECT * from artists")?
|
||||
.query_map([], DbArtistItem::map_row)?
|
||||
{
|
||||
lib.add_artist(artist?);
|
||||
}
|
||||
// build albums
|
||||
for album in conn.prepare("SELECT * from albums")?
|
||||
.query_map([], DbAlbumItem::map_row)? {
|
||||
for album in conn
|
||||
.prepare("SELECT * from albums")?
|
||||
.query_map([], DbAlbumItem::map_row)?
|
||||
{
|
||||
lib.add_album(album?);
|
||||
}
|
||||
// build genres
|
||||
for genre in conn.prepare("SELECT * from genres")?
|
||||
.query_map([], DbGenreItem::map_row)? {
|
||||
for genre in conn
|
||||
.prepare("SELECT * from genres")?
|
||||
.query_map([], DbGenreItem::map_row)?
|
||||
{
|
||||
lib.add_genre(genre?);
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -84,7 +84,8 @@ impl MpsLibrary {
|
|||
#[inline]
|
||||
pub fn add_artist(&mut self, artist: DbArtistItem) {
|
||||
self.modify();
|
||||
self.artists.insert(Self::sanitise_key(&artist.name), artist);
|
||||
self.artists
|
||||
.insert(Self::sanitise_key(&artist.name), artist);
|
||||
}
|
||||
|
||||
pub fn all_albums<'a>(&'a self) -> Vec<&'a DbAlbumItem> {
|
||||
|
@ -109,7 +110,9 @@ impl MpsLibrary {
|
|||
|
||||
pub fn read_path<P: AsRef<Path>>(&mut self, path: P, depth: usize) -> std::io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
if self.contains_path(path) { return Ok(()); } // skip existing entries
|
||||
if self.contains_path(path) {
|
||||
return Ok(());
|
||||
} // skip existing entries
|
||||
if path.is_dir() && depth != 0 {
|
||||
for entry in path.read_dir()? {
|
||||
self.read_path(entry?.path(), depth - 1)?;
|
||||
|
@ -120,6 +123,38 @@ impl MpsLibrary {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_media_tags<P: AsRef<Path>>(path: P) -> std::io::Result<Tags> {
|
||||
let path = path.as_ref();
|
||||
let file = Box::new(std::fs::File::open(path)?);
|
||||
// use symphonia to get metadata
|
||||
let mss = MediaSourceStream::new(file, Default::default() /* options */);
|
||||
let probed = symphonia::default::get_probe().format(
|
||||
&Hint::new(),
|
||||
mss,
|
||||
&Default::default(),
|
||||
&Default::default(),
|
||||
);
|
||||
let mut tags = Tags::new(path);
|
||||
if let Ok(mut probed) = probed {
|
||||
// collect metadata
|
||||
if let Some(metadata) = probed.metadata.get() {
|
||||
if let Some(rev) = metadata.current() {
|
||||
for tag in rev.tags() {
|
||||
//println!("(pre) metadata tag ({},{})", tag.key, tag.value);
|
||||
tags.add(tag.key.clone(), &tag.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(rev) = probed.format.metadata().current() {
|
||||
for tag in rev.tags() {
|
||||
//println!("(post) metadata tag ({},{})", tag.key, tag.value);
|
||||
tags.add(tag.key.clone(), &tag.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(tags)
|
||||
}
|
||||
|
||||
fn read_file<P: AsRef<Path>>(&mut self, path: P) -> std::io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let file = Box::new(std::fs::File::open(path)?);
|
||||
|
@ -162,7 +197,7 @@ impl MpsLibrary {
|
|||
let song_id = self.songs.len() as u64; // guaranteed to be created
|
||||
let meta_id = self.metadata.len() as u64; // guaranteed to be created
|
||||
self.add_metadata(tags.meta(meta_id)); // definitely necessary
|
||||
// genre has no links to others, so find that first
|
||||
// genre has no links to others, so find that first
|
||||
let mut genre = tags.genre(0);
|
||||
genre.genre_id = Self::find_or_gen_id(&self.genres, &genre.title);
|
||||
if genre.genre_id == self.genres.len() as u64 {
|
||||
|
@ -191,15 +226,13 @@ impl MpsLibrary {
|
|||
}
|
||||
//let meta_album_id = self.metadata.len() as u64;
|
||||
//let album = tags.album(album_id, meta_album_id);
|
||||
self.add_song(
|
||||
tags.song(
|
||||
song_id,
|
||||
artist.artist_id,
|
||||
Some(album.album_id),
|
||||
meta_id,
|
||||
genre.genre_id,
|
||||
),
|
||||
);
|
||||
self.add_song(tags.song(
|
||||
song_id,
|
||||
artist.artist_id,
|
||||
Some(album.album_id),
|
||||
meta_id,
|
||||
genre.genre_id,
|
||||
));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -28,6 +28,70 @@ impl Tags {
|
|||
self.data.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track_title(&self) -> String {
|
||||
self.data
|
||||
.get("TITLE")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.and_then(|s| Some(s.to_string()))
|
||||
.unwrap_or_else(|| self.default_title())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn default_title(&self) -> String {
|
||||
let extension = self
|
||||
.filename
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("");
|
||||
self.filename
|
||||
.file_name()
|
||||
.and_then(|file| file.to_str())
|
||||
.and_then(|file| Some(file.replacen(&format!(".{}", extension), "", 1)))
|
||||
.unwrap_or("Unknown Title".into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn artist_name(&self) -> Option<String> {
|
||||
self.data
|
||||
.get("ARTIST")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.and_then(|s| Some(s.to_string()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn album_title(&self) -> Option<String> {
|
||||
self.data
|
||||
.get("ALBUM")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.and_then(|s| Some(s.to_string()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn genre_title(&self) -> Option<String> {
|
||||
self.data
|
||||
.get("GENRE")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.and_then(|s| Some(s.to_string()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track_number(&self) -> Option<u64> {
|
||||
self.data
|
||||
.get("TRACKNUMBER")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.uint()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn track_date(&self) -> Option<u64> {
|
||||
self.data.get("DATE").unwrap_or(&TagType::Unknown).uint()
|
||||
}
|
||||
|
||||
pub fn song(
|
||||
&self,
|
||||
id: u64,
|
||||
|
@ -36,27 +100,9 @@ impl Tags {
|
|||
meta_id: u64,
|
||||
genre_id: u64,
|
||||
) -> DbMusicItem {
|
||||
let default_title = || {
|
||||
let extension = self
|
||||
.filename
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("");
|
||||
self.filename
|
||||
.file_name()
|
||||
.and_then(|file| file.to_str())
|
||||
.and_then(|file| Some(file.replacen(&format!(".{}", extension), "", 1)))
|
||||
.unwrap_or("Unknown Title".into())
|
||||
};
|
||||
DbMusicItem {
|
||||
song_id: id,
|
||||
title: self
|
||||
.data
|
||||
.get("TITLE")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.and_then(|s| Some(s.to_string()))
|
||||
.unwrap_or_else(default_title),
|
||||
title: self.track_title(),
|
||||
artist: artist_id,
|
||||
album: album_id,
|
||||
filename: self.filename.to_str().unwrap_or("").into(),
|
||||
|
@ -74,12 +120,7 @@ impl Tags {
|
|||
.unwrap_or(&TagType::Unknown)
|
||||
.uint()
|
||||
.unwrap_or(0),
|
||||
track: self
|
||||
.data
|
||||
.get("TRACKNUMBER")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.uint()
|
||||
.unwrap_or(id),
|
||||
track: self.track_number().unwrap_or(id),
|
||||
disc: self
|
||||
.data
|
||||
.get("DISCNUMBER")
|
||||
|
@ -92,25 +133,14 @@ impl Tags {
|
|||
.unwrap_or(&TagType::Unknown)
|
||||
.uint()
|
||||
.unwrap_or(0),
|
||||
date: self
|
||||
.data
|
||||
.get("DATE")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.uint()
|
||||
.unwrap_or(0),
|
||||
date: self.track_date().unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn artist(&self, id: u64, genre_id: u64) -> DbArtistItem {
|
||||
DbArtistItem {
|
||||
artist_id: id,
|
||||
name: self
|
||||
.data
|
||||
.get("ARTIST")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.unwrap_or("Unknown Artist")
|
||||
.into(),
|
||||
name: self.artist_name().unwrap_or("Unknown Artist".into()),
|
||||
genre: genre_id,
|
||||
}
|
||||
}
|
||||
|
@ -132,13 +162,7 @@ impl Tags {
|
|||
pub fn album(&self, id: u64, meta_id: u64, artist_id: u64, genre_id: u64) -> DbAlbumItem {
|
||||
DbAlbumItem {
|
||||
album_id: id,
|
||||
title: self
|
||||
.data
|
||||
.get("ALBUM")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.unwrap_or("Unknown Album")
|
||||
.into(),
|
||||
title: self.album_title().unwrap_or("Unknown Album".into()),
|
||||
metadata: meta_id,
|
||||
artist: artist_id,
|
||||
genre: genre_id,
|
||||
|
@ -179,13 +203,7 @@ impl Tags {
|
|||
pub fn genre(&self, id: u64) -> DbGenreItem {
|
||||
DbGenreItem {
|
||||
genre_id: id,
|
||||
title: self
|
||||
.data
|
||||
.get("GENRE")
|
||||
.unwrap_or(&TagType::Unknown)
|
||||
.str()
|
||||
.unwrap_or("Unknown Genre")
|
||||
.into(),
|
||||
title: self.genre_title().unwrap_or("Unknown Genre".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
309
mps-interpreter/src/processing/filesystem.rs
Normal file
309
mps-interpreter/src/processing/filesystem.rs
Normal file
|
@ -0,0 +1,309 @@
|
|||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::fs::ReadDir;
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use super::OpGetter;
|
||||
use crate::lang::RuntimeError;
|
||||
use crate::MpsMusicItem;
|
||||
|
||||
const DEFAULT_REGEX: &str = r"/(?P<artist>[^/]+)/(?P<album>[^/]+)/(?:(?:(?P<disc>\d+)\s+)?(?P<track>\d+)\.?\s+)?(?P<title>[^/]+)\.[a-zA-Z0-9]+$";
|
||||
|
||||
const DEFAULT_VEC_CACHE_SIZE: usize = 4;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileIter {
|
||||
root: PathBuf,
|
||||
pattern: Regex,
|
||||
recursive: bool,
|
||||
dir_iters: Vec<ReadDir>,
|
||||
is_complete: bool,
|
||||
}
|
||||
|
||||
impl Display for FileIter {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
write!(
|
||||
f,
|
||||
"root=`{}`, pattern={}, recursive={}",
|
||||
self.root.to_str().unwrap_or(""),
|
||||
self.pattern,
|
||||
self.recursive
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FileIter {
|
||||
pub fn new<P: AsRef<Path>>(
|
||||
root: Option<P>,
|
||||
pattern: Option<&str>,
|
||||
recurse: bool,
|
||||
op: &mut OpGetter,
|
||||
) -> Result<Self, RuntimeError> {
|
||||
let root_path = match root {
|
||||
None => crate::lang::utility::music_folder(),
|
||||
Some(p) => p.as_ref().to_path_buf(),
|
||||
};
|
||||
let dir_vec = if root_path.is_dir() {
|
||||
let mut vec = Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE);
|
||||
vec.push(root_path.read_dir().map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Directory read error: {}", e),
|
||||
})?);
|
||||
vec
|
||||
} else {
|
||||
Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE)
|
||||
};
|
||||
Ok(Self {
|
||||
root: root_path,
|
||||
pattern: Regex::new(pattern.unwrap_or(DEFAULT_REGEX)).map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Regex compile error: {}", e),
|
||||
})?,
|
||||
recursive: recurse,
|
||||
dir_iters: dir_vec,
|
||||
is_complete: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn common_defaults(recurse: bool) -> Self {
|
||||
let root_path = crate::lang::utility::music_folder();
|
||||
let read_dir = root_path.read_dir().unwrap();
|
||||
let mut dir_vec = Vec::with_capacity(DEFAULT_VEC_CACHE_SIZE);
|
||||
dir_vec.push(read_dir);
|
||||
Self {
|
||||
root: root_path,
|
||||
pattern: Regex::new(DEFAULT_REGEX).unwrap(),
|
||||
recursive: recurse,
|
||||
dir_iters: dir_vec,
|
||||
is_complete: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_item<P: AsRef<Path>>(&self, filepath: P) -> Option<MpsMusicItem> {
|
||||
let path = filepath.as_ref();
|
||||
let path_str = path.to_str()?;
|
||||
#[cfg(debug_assertions)]
|
||||
if !path.is_file() {
|
||||
panic!("Got non-file path `{}` when building music item", path_str)
|
||||
}
|
||||
let captures = self.pattern.captures(path_str)?;
|
||||
// populate fields
|
||||
self.populate_item_impl(path, path_str, captures)
|
||||
}
|
||||
|
||||
#[cfg(feature = "music_library")]
|
||||
fn populate_item_impl(
|
||||
&self,
|
||||
path: &Path,
|
||||
path_str: &str,
|
||||
captures: regex::Captures,
|
||||
) -> Option<MpsMusicItem> {
|
||||
match crate::music::MpsLibrary::read_media_tags(path) {
|
||||
Ok(tags) => Some(MpsMusicItem {
|
||||
title: captures
|
||||
.name("title")
|
||||
.and_then(|m| Some(m.as_str().to_string()))
|
||||
.unwrap_or_else(|| tags.track_title()),
|
||||
artist: captures
|
||||
.name("artist")
|
||||
.and_then(|m| Some(m.as_str().to_string()))
|
||||
.or_else(|| tags.artist_name()),
|
||||
album: captures
|
||||
.name("album")
|
||||
.and_then(|m| Some(m.as_str().to_string()))
|
||||
.or_else(|| tags.album_title()),
|
||||
filename: path_str.to_string(),
|
||||
genre: captures
|
||||
.name("genre")
|
||||
.and_then(|m| Some(m.as_str().to_string()))
|
||||
.or_else(|| tags.genre_title()),
|
||||
track: match captures.name("track") {
|
||||
None => tags.track_number(),
|
||||
Some(m) => match m.as_str().parse::<u64>() {
|
||||
Ok(u) => Some(u),
|
||||
Err(_) => tags.track_number(),
|
||||
},
|
||||
},
|
||||
year: match captures.name("year") {
|
||||
None => tags.track_date(),
|
||||
Some(m) => match m.as_str().parse::<u64>() {
|
||||
Ok(u) => Some(u),
|
||||
Err(_) => tags.track_date(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
Err(_) => self.populate_item_impl_simple(path, path_str, captures),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "music_library"))]
|
||||
fn populate_item_impl(
|
||||
&self,
|
||||
path: &Path,
|
||||
path_str: &str,
|
||||
captures: regex::Captures,
|
||||
) -> Option<MpsMusicItem> {
|
||||
self.populate_item_impl_simple(path, path_str, captures)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn populate_item_impl_simple(
|
||||
&self,
|
||||
path: &Path,
|
||||
path_str: &str,
|
||||
captures: regex::Captures,
|
||||
) -> Option<MpsMusicItem> {
|
||||
Some(MpsMusicItem {
|
||||
title: captures
|
||||
.name("title")
|
||||
.and_then(|m| Some(m.as_str().to_string()))
|
||||
.unwrap_or_else(|| Self::default_title(path)),
|
||||
artist: captures
|
||||
.name("artist")
|
||||
.and_then(|m| Some(m.as_str().to_string())),
|
||||
album: captures
|
||||
.name("album")
|
||||
.and_then(|m| Some(m.as_str().to_string())),
|
||||
filename: path_str.to_string(),
|
||||
genre: captures
|
||||
.name("genre")
|
||||
.and_then(|m| Some(m.as_str().to_string())),
|
||||
track: match captures.name("track") {
|
||||
None => None,
|
||||
Some(m) => match m.as_str().parse::<u64>() {
|
||||
Ok(u) => Some(u),
|
||||
Err(_) => None,
|
||||
},
|
||||
},
|
||||
year: match captures.name("year") {
|
||||
None => None,
|
||||
Some(m) => match m.as_str().parse::<u64>() {
|
||||
Ok(u) => Some(u),
|
||||
Err(_) => None,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn default_title(path: &Path) -> String {
|
||||
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
|
||||
path.file_name()
|
||||
.and_then(|file| file.to_str())
|
||||
.and_then(|file| Some(file.replacen(&format!(".{}", extension), "", 1)))
|
||||
.unwrap_or("Unknown Title".into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for FileIter {
|
||||
type Item = Result<MpsMusicItem, String>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.is_complete {
|
||||
None
|
||||
} else {
|
||||
if self.dir_iters.is_empty() {
|
||||
if self.root.is_file() {
|
||||
self.is_complete = true;
|
||||
match self.build_item(&self.root) {
|
||||
None => None,
|
||||
Some(item) => Some(Ok(item)),
|
||||
}
|
||||
} else {
|
||||
// should be impossible to get here
|
||||
self.dir_iters.push(match self.root.read_dir() {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Some(Err(format!("Directory read error: {}", e))),
|
||||
});
|
||||
return self.next();
|
||||
}
|
||||
} else {
|
||||
while !self.dir_iters.is_empty() {
|
||||
let mut dir_iter = self.dir_iters.pop().unwrap();
|
||||
while let Some(path_result) = dir_iter.next() {
|
||||
match path_result {
|
||||
Ok(dir_entry) => {
|
||||
if dir_entry.path().is_dir() {
|
||||
if self.recursive {
|
||||
self.dir_iters.push(dir_iter);
|
||||
self.dir_iters.push(match dir_entry.path().read_dir() {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Some(Err(format!(
|
||||
"Directory read error: {}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
});
|
||||
return self.next();
|
||||
}
|
||||
} else {
|
||||
if let Some(item) = self.build_item(dir_entry.path()) {
|
||||
self.dir_iters.push(dir_iter);
|
||||
return Some(Ok(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.dir_iters.push(dir_iter);
|
||||
return Some(Err(format!("Path read error: {}", e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MpsFilesystemQuerier: Debug {
|
||||
fn raw(
|
||||
&mut self,
|
||||
folder: Option<&str>,
|
||||
pattern: Option<&str>,
|
||||
recursive: bool,
|
||||
op: &mut OpGetter,
|
||||
) -> Result<FileIter, RuntimeError>;
|
||||
|
||||
fn expand(
|
||||
&self,
|
||||
folder: Option<&str>,
|
||||
#[allow(unused_variables)] op: &mut OpGetter,
|
||||
) -> Result<Option<String>, RuntimeError> {
|
||||
#[cfg(feature = "shellexpand")]
|
||||
match folder {
|
||||
Some(path) => Ok(Some(
|
||||
shellexpand::full(path)
|
||||
.map_err(|e| RuntimeError {
|
||||
line: 0,
|
||||
op: op(),
|
||||
msg: format!("Path expansion error: {}", e),
|
||||
})?
|
||||
.into_owned(),
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
#[cfg(not(feature = "shellexpand"))]
|
||||
Ok(folder.and_then(|s| Some(s.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct MpsFilesystemExecutor {}
|
||||
|
||||
impl MpsFilesystemQuerier for MpsFilesystemExecutor {
|
||||
fn raw(
|
||||
&mut self,
|
||||
folder: Option<&str>,
|
||||
pattern: Option<&str>,
|
||||
recursive: bool,
|
||||
op: &mut OpGetter,
|
||||
) -> Result<FileIter, RuntimeError> {
|
||||
let folder = self.expand(folder, op)?;
|
||||
FileIter::new(folder, pattern, recursive, op)
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue