cargo fmt

This commit is contained in:
NGnius (Graham) 2022-07-30 00:06:21 -04:00
parent 21f7149660
commit 175d304f1b
52 changed files with 495 additions and 434 deletions

View file

@ -51,5 +51,8 @@ fn faye_benchmark(c: &mut Criterion) {
}); });
} }
criterion_group!(parse_benches, /*interpretor_benchmark,*/ faye_benchmark); criterion_group!(
parse_benches,
/*interpretor_benchmark,*/ faye_benchmark
);
criterion_main!(parse_benches); criterion_main!(parse_benches);

View file

@ -2,7 +2,7 @@
use super::processing::advanced::{DefaultAnalyzer, MusicAnalyzer}; use super::processing::advanced::{DefaultAnalyzer, MusicAnalyzer};
use super::processing::database::{DatabaseQuerier, SQLiteExecutor}; use super::processing::database::{DatabaseQuerier, SQLiteExecutor};
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
use super::processing::database::{MpdQuerier, MpdExecutor}; use super::processing::database::{MpdExecutor, MpdQuerier};
use super::processing::general::{ use super::processing::general::{
FilesystemExecutor, FilesystemQuerier, OpStorage, VariableStorer, FilesystemExecutor, FilesystemQuerier, OpStorage, VariableStorer,
}; };

View file

@ -7,10 +7,7 @@ use super::{Interpreter, InterpreterItem};
pub struct Debugger<'a, T, F> pub struct Debugger<'a, T, F>
where where
T: TokenReader, T: TokenReader,
F: Fn( F: Fn(&mut Interpreter<'a, T>, Option<InterpreterItem>) -> Option<InterpreterItem>,
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{ {
interpreter: Interpreter<'a, T>, interpreter: Interpreter<'a, T>,
transmuter: F, transmuter: F,
@ -19,16 +16,10 @@ where
impl<'a, T, F> Debugger<'a, T, F> impl<'a, T, F> Debugger<'a, T, F>
where where
T: TokenReader, T: TokenReader,
F: Fn( F: Fn(&mut Interpreter<'a, T>, Option<InterpreterItem>) -> Option<InterpreterItem>,
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{ {
/// Create a new instance of Debugger using the provided interpreter and callback. /// Create a new instance of Debugger using the provided interpreter and callback.
pub fn new( pub fn new(faye: Interpreter<'a, T>, item_handler: F) -> Self {
faye: Interpreter<'a, T>,
item_handler: F,
) -> Self {
Self { Self {
interpreter: faye, interpreter: faye,
transmuter: item_handler, transmuter: item_handler,
@ -39,10 +30,7 @@ where
impl<'a, T, F> Iterator for Debugger<'a, T, F> impl<'a, T, F> Iterator for Debugger<'a, T, F>
where where
T: TokenReader, T: TokenReader,
F: Fn( F: Fn(&mut Interpreter<'a, T>, Option<InterpreterItem>) -> Option<InterpreterItem>,
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{ {
type Item = InterpreterItem; type Item = InterpreterItem;

View file

@ -61,7 +61,13 @@ impl<'a, R: Read> Interpreter<'a, Tokenizer<R>> {
Self::with_standard_vocab(tokenizer) Self::with_standard_vocab(tokenizer)
} }
pub fn with_stream_and_callback(stream: R, callback: &'a dyn Fn(&mut Interpreter<'a, Tokenizer<R>>, InterpreterEvent) -> Result<(), InterpreterError>) -> Self { pub fn with_stream_and_callback(
stream: R,
callback: &'a dyn Fn(
&mut Interpreter<'a, Tokenizer<R>>,
InterpreterEvent,
) -> Result<(), InterpreterError>,
) -> Self {
let tokenizer = Tokenizer::new(stream); let tokenizer = Tokenizer::new(stream);
let vocab = LanguageDictionary::standard(); let vocab = LanguageDictionary::standard();
Self::with(vocab, tokenizer, callback) Self::with(vocab, tokenizer, callback)
@ -89,7 +95,10 @@ where
pub fn with( pub fn with(
vocab: LanguageDictionary, vocab: LanguageDictionary,
token_reader: T, token_reader: T,
callback: &'a dyn Fn(&mut Interpreter<'a, T>, InterpreterEvent) -> Result<(), InterpreterError>, callback: &'a dyn Fn(
&mut Interpreter<'a, T>,
InterpreterEvent,
) -> Result<(), InterpreterError>,
) -> Self { ) -> Self {
Self { Self {
tokenizer: token_reader, tokenizer: token_reader,
@ -190,7 +199,10 @@ where
} }
} }
fn error_with_ctx<T: std::convert::Into<InterpreterError>>(error: T, line: usize) -> InterpreterError { fn error_with_ctx<T: std::convert::Into<InterpreterError>>(
error: T,
line: usize,
) -> InterpreterError {
let mut err = error.into(); let mut err = error.into();
err.set_line(line); err.set_line(line);
err err

View file

@ -237,15 +237,14 @@ impl<P: FilterPredicate + 'static> Iterator for FilterStatement<P> {
// handle other filters // handle other filters
// make fake inner item // make fake inner item
let single_op = SingleItem::new_ok(item.clone()); let single_op = SingleItem::new_ok(item.clone());
match ctx.variables.declare( match ctx
INNER_VARIABLE_NAME, .variables
Type::Op(Box::new(single_op)), .declare(INNER_VARIABLE_NAME, Type::Op(Box::new(single_op)))
) { {
Ok(x) => x, Ok(x) => x,
Err(e) => { Err(e) => {
//self.context = Some(op.escape()); //self.context = Some(op.escape());
maybe_result = maybe_result = Some(Err(e.with(RuntimeOp(fake))));
Some(Err(e.with(RuntimeOp(fake))));
self.context = Some(ctx); self.context = Some(ctx);
break; break;
} }
@ -268,9 +267,8 @@ impl<P: FilterPredicate + 'static> Iterator for FilterStatement<P> {
Ok(_) => {} Ok(_) => {}
Err(e) => match maybe_result { Err(e) => match maybe_result {
Some(Ok(_)) => { Some(Ok(_)) => {
maybe_result = Some(Err( maybe_result =
e.with(RuntimeOp(fake)) Some(Err(e.with(RuntimeOp(fake))))
))
} }
Some(Err(e2)) => maybe_result = Some(Err(e2)), // already failing, do not replace error, Some(Err(e2)) => maybe_result = Some(Err(e2)), // already failing, do not replace error,
None => {} // impossible None => {} // impossible
@ -332,8 +330,8 @@ impl<P: FilterPredicate + 'static> Iterator for FilterStatement<P> {
} }
Err(e) => { Err(e) => {
self.is_failing = true; // this is unrecoverable and reproducible, so it shouldn't be tried again (to prevent error spam) self.is_failing = true; // this is unrecoverable and reproducible, so it shouldn't be tried again (to prevent error spam)
return Some(Err(e.with(RuntimeOp(fake)))) return Some(Err(e.with(RuntimeOp(fake))));
}, }
}; };
let mut maybe_result = None; let mut maybe_result = None;
let ctx = self.context.take().unwrap(); let ctx = self.context.take().unwrap();
@ -457,17 +455,12 @@ impl<P: FilterPredicate + 'static> Iterator for FilterStatement<P> {
} }
} }
pub struct FilterStatementFactory< pub struct FilterStatementFactory<P: FilterPredicate + 'static, F: FilterFactory<P> + 'static> {
P: FilterPredicate + 'static,
F: FilterFactory<P> + 'static,
> {
filter_factory: F, filter_factory: F,
idc: PhantomData<P>, idc: PhantomData<P>,
} }
impl<P: FilterPredicate + 'static, F: FilterFactory<P> + 'static> impl<P: FilterPredicate + 'static, F: FilterFactory<P> + 'static> FilterStatementFactory<P, F> {
FilterStatementFactory<P, F>
{
pub fn new(factory: F) -> Self { pub fn new(factory: F) -> Self {
Self { Self {
filter_factory: factory, filter_factory: factory,

View file

@ -354,10 +354,7 @@ fn declare_or_replace_item(
Ok(old_item) Ok(old_item)
} }
fn remove_or_replace_item( fn remove_or_replace_item(old_item: Option<Type>, ctx: &mut Context) -> Result<(), RuntimeMsg> {
old_item: Option<Type>,
ctx: &mut Context,
) -> Result<(), RuntimeMsg> {
ctx.variables.remove(ITEM_VARIABLE_NAME)?; ctx.variables.remove(ITEM_VARIABLE_NAME)?;
if let Some(old_item) = old_item { if let Some(old_item) = old_item {
ctx.variables.declare(ITEM_VARIABLE_NAME, old_item)?; ctx.variables.declare(ITEM_VARIABLE_NAME, old_item)?;

View file

@ -189,10 +189,7 @@ impl Iterator for ItemBlockStatement {
return Some(Err(RuntimeError { return Some(Err(RuntimeError {
line: 0, line: 0,
op: PseudoOp::from_printable(self), op: PseudoOp::from_printable(self),
msg: format!( msg: format!("Expected `item` like Type::Item(Item[...]), got {}", x),
"Expected `item` like Type::Item(Item[...]), got {}",
x
),
})) }))
} }
None => {} None => {}
@ -214,10 +211,7 @@ pub struct ItemBlockFactory {
} }
impl ItemBlockFactory { impl ItemBlockFactory {
pub fn push< pub fn push<T: ItemOpFactory<Y> + 'static, Y: Deref<Target = dyn ItemOp> + ItemOp + 'static>(
T: ItemOpFactory<Y> + 'static,
Y: Deref<Target = dyn ItemOp> + ItemOp + 'static,
>(
mut self, mut self,
factory: T, factory: T,
) -> Self { ) -> Self {
@ -314,10 +308,7 @@ fn replace_item_var(ctx: &mut Context, item: Type) -> Option<Type> {
old_var old_var
} }
fn restore_item_var( fn restore_item_var(ctx: &mut Context, old_var: Option<Type>) -> Result<Option<Type>, RuntimeMsg> {
ctx: &mut Context,
old_var: Option<Type>,
) -> Result<Option<Type>, RuntimeMsg> {
let new_var = if ctx.variables.exists(ITEM_VARIABLE_NAME) { let new_var = if ctx.variables.exists(ITEM_VARIABLE_NAME) {
Some(ctx.variables.remove(ITEM_VARIABLE_NAME)?) Some(ctx.variables.remove(ITEM_VARIABLE_NAME)?)
} else { } else {

View file

@ -20,9 +20,7 @@ pub(crate) mod utility;
pub use dictionary::LanguageDictionary; pub use dictionary::LanguageDictionary;
pub(crate) use error::LanguageError; pub(crate) use error::LanguageError;
pub use error::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError}; pub use error::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError};
pub use filter::{ pub use filter::{FilterFactory, FilterPredicate, FilterStatement, FilterStatementFactory};
FilterFactory, FilterPredicate, FilterStatement, FilterStatementFactory,
};
pub use filter_replace::FilterReplaceStatement; pub use filter_replace::FilterReplaceStatement;
pub use function::{FunctionFactory, FunctionStatementFactory}; pub use function::{FunctionFactory, FunctionStatementFactory};
pub use iter_block::{ItemBlockFactory, ItemOp, ItemOpFactory}; pub use iter_block::{ItemBlockFactory, ItemOp, ItemOpFactory};

View file

@ -1,8 +1,8 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::path::PathBuf; use std::path::PathBuf;
use super::TypePrimitive;
use super::SyntaxError; use super::SyntaxError;
use super::TypePrimitive;
use crate::tokens::Token; use crate::tokens::Token;
pub fn assert_token<T, F: FnOnce(Token) -> Option<T>>( pub fn assert_token<T, F: FnOnce(Token) -> Option<T>>(
@ -29,10 +29,7 @@ pub fn assert_token<T, F: FnOnce(Token) -> Option<T>>(
} }
} }
pub fn assert_token_raw( pub fn assert_token_raw(token: Token, tokens: &mut VecDeque<Token>) -> Result<Token, SyntaxError> {
token: Token,
tokens: &mut VecDeque<Token>,
) -> Result<Token, SyntaxError> {
let result = match tokens.pop_front() { let result = match tokens.pop_front() {
Some(x) => Ok(x), Some(x) => Ok(x),
None => Err(SyntaxError { None => Err(SyntaxError {

View file

@ -5,8 +5,8 @@ use std::iter::Iterator;
use crate::tokens::Token; use crate::tokens::Token;
use crate::Context; use crate::Context;
use crate::lang::{Lookup, LanguageDictionary, PseudoOp};
use crate::lang::{FunctionFactory, FunctionStatementFactory, IteratorItem, Op}; use crate::lang::{FunctionFactory, FunctionStatementFactory, IteratorItem, Op};
use crate::lang::{LanguageDictionary, Lookup, PseudoOp};
use crate::lang::{RuntimeError, RuntimeOp, SyntaxError}; use crate::lang::{RuntimeError, RuntimeOp, SyntaxError};
use crate::processing::general::Type; use crate::processing::general::Type;
use crate::Item; use crate::Item;

View file

@ -47,11 +47,7 @@ impl Display for FieldFilter {
} }
impl FilterPredicate for FieldFilter { impl FilterPredicate for FieldFilter {
fn matches( fn matches(&mut self, music_item_lut: &Item, ctx: &mut Context) -> Result<bool, RuntimeMsg> {
&mut self,
music_item_lut: &Item,
ctx: &mut Context,
) -> Result<bool, RuntimeMsg> {
let variable = match &self.val { let variable = match &self.val {
VariableOrValue::Variable(name) => match ctx.variables.get(name)? { VariableOrValue::Variable(name) => match ctx.variables.get(name)? {
Type::Primitive(t) => Ok(t), Type::Primitive(t) => Ok(t),

View file

@ -40,11 +40,7 @@ impl Display for FieldLikeFilter {
} }
impl FilterPredicate for FieldLikeFilter { impl FilterPredicate for FieldLikeFilter {
fn matches( fn matches(&mut self, music_item_lut: &Item, ctx: &mut Context) -> Result<bool, RuntimeMsg> {
&mut self,
music_item_lut: &Item,
ctx: &mut Context,
) -> Result<bool, RuntimeMsg> {
let variable = match &self.val { let variable = match &self.val {
VariableOrValue::Variable(name) => match ctx.variables.get(name)? { VariableOrValue::Variable(name) => match ctx.variables.get(name)? {
Type::Primitive(TypePrimitive::String(s)) => Ok(s), Type::Primitive(TypePrimitive::String(s)) => Ok(s),
@ -122,13 +118,11 @@ impl FilterFactory<FieldLikeFilter> for FieldLikeFilterFactory {
}; };
let name = assert_token( let name = assert_token(
|t| match t { |t| match t {
Token::Name(s) => { Token::Name(s) => match &s as _ {
match &s as _ {
"unlike" | "like" => Some(s), "unlike" | "like" => Some(s),
_ => None, _ => None,
}
}, },
_ => None _ => None,
}, },
Token::Literal("like|unlike".into()), Token::Literal("like|unlike".into()),
tokens, tokens,

View file

@ -33,11 +33,7 @@ impl Display for FieldRegexFilter {
} }
impl FilterPredicate for FieldRegexFilter { impl FilterPredicate for FieldRegexFilter {
fn matches( fn matches(&mut self, music_item_lut: &Item, ctx: &mut Context) -> Result<bool, RuntimeMsg> {
&mut self,
music_item_lut: &Item,
ctx: &mut Context,
) -> Result<bool, RuntimeMsg> {
let variable = match &self.val { let variable = match &self.val {
VariableOrValue::Variable(name) => match ctx.variables.get(name)? { VariableOrValue::Variable(name) => match ctx.variables.get(name)? {
Type::Primitive(TypePrimitive::String(s)) => Ok(s), Type::Primitive(TypePrimitive::String(s)) => Ok(s),

View file

@ -2,8 +2,8 @@ use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter}; use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::LanguageDictionary; use crate::lang::LanguageDictionary;
use crate::lang::{utility::assert_token_raw, RuntimeMsg, SyntaxError};
use crate::lang::{FilterFactory, FilterPredicate, FilterStatementFactory}; use crate::lang::{FilterFactory, FilterPredicate, FilterStatementFactory};
use crate::lang::{RuntimeMsg, SyntaxError, utility::assert_token_raw};
use crate::tokens::Token; use crate::tokens::Token;
use crate::Context; use crate::Context;
use crate::Item; use crate::Item;
@ -57,7 +57,8 @@ impl FilterFactory<NonEmptyFilter> for NonEmptyFilterFactory {
} }
} }
pub type NonEmptyFilterStatementFactory = FilterStatementFactory<NonEmptyFilter, NonEmptyFilterFactory>; pub type NonEmptyFilterStatementFactory =
FilterStatementFactory<NonEmptyFilter, NonEmptyFilterFactory>;
#[inline(always)] #[inline(always)]
pub fn nonempty_filter() -> NonEmptyFilterStatementFactory { pub fn nonempty_filter() -> NonEmptyFilterStatementFactory {

View file

@ -151,8 +151,7 @@ pub fn unique_field_filter() -> UniqueFieldFilterStatementFactory {
UniqueFieldFilterStatementFactory::new(UniqueFilterFactory) UniqueFieldFilterStatementFactory::new(UniqueFilterFactory)
} }
pub type UniqueFilterStatementFactory = pub type UniqueFilterStatementFactory = FilterStatementFactory<UniqueFilter, UniqueFilterFactory>;
FilterStatementFactory<UniqueFilter, UniqueFilterFactory>;
#[inline(always)] #[inline(always)]
pub fn unique_filter() -> UniqueFilterStatementFactory { pub fn unique_filter() -> UniqueFilterStatementFactory {

View file

@ -35,9 +35,7 @@ impl ItemOp for AddItemOp {
if let Type::Primitive(lhs) = &lhs { if let Type::Primitive(lhs) = &lhs {
let rhs = self.rhs.execute(context)?; let rhs = self.rhs.execute(context)?;
if let Type::Primitive(rhs) = &rhs { if let Type::Primitive(rhs) = &rhs {
Ok(Type::Primitive( Ok(Type::Primitive(lhs.try_add(rhs).map_err(RuntimeMsg)?))
lhs.try_add(rhs).map_err(RuntimeMsg)?,
))
} else { } else {
Err(RuntimeMsg(format!( Err(RuntimeMsg(format!(
"Cannot add right-hand side `{}` ({}): not primitive type", "Cannot add right-hand side `{}` ({}): not primitive type",

View file

@ -78,7 +78,7 @@ impl ItemOpFactory<FieldAssignItemOp> for FieldAssignItemOpFactory {
factory: &ItemBlockFactory, factory: &ItemBlockFactory,
dict: &LanguageDictionary, dict: &LanguageDictionary,
) -> Result<FieldAssignItemOp, SyntaxError> { ) -> Result<FieldAssignItemOp, SyntaxError> {
let var_name =if tokens[0].is_dot() { let var_name = if tokens[0].is_dot() {
"item".to_string() "item".to_string()
} else { } else {
assert_token( assert_token(

View file

@ -32,9 +32,7 @@ impl ItemOp for NegateItemOp {
fn execute(&self, context: &mut Context) -> Result<Type, RuntimeMsg> { fn execute(&self, context: &mut Context) -> Result<Type, RuntimeMsg> {
let rhs = self.rhs.execute(context)?; let rhs = self.rhs.execute(context)?;
if let Type::Primitive(rhs) = &rhs { if let Type::Primitive(rhs) = &rhs {
Ok(Type::Primitive( Ok(Type::Primitive(rhs.try_negate().map_err(RuntimeMsg)?))
rhs.try_negate().map_err(RuntimeMsg)?,
))
} else { } else {
Err(RuntimeMsg(format!( Err(RuntimeMsg(format!(
"Cannot negate `{}` ({}): not primitive type", "Cannot negate `{}` ({}): not primitive type",

View file

@ -32,9 +32,7 @@ impl ItemOp for NotItemOp {
fn execute(&self, context: &mut Context) -> Result<Type, RuntimeMsg> { fn execute(&self, context: &mut Context) -> Result<Type, RuntimeMsg> {
let rhs = self.rhs.execute(context)?; let rhs = self.rhs.execute(context)?;
if let Type::Primitive(rhs) = &rhs { if let Type::Primitive(rhs) = &rhs {
Ok(Type::Primitive( Ok(Type::Primitive(rhs.try_not().map_err(RuntimeMsg)?))
rhs.try_not().map_err(RuntimeMsg)?,
))
} else { } else {
Err(RuntimeMsg(format!( Err(RuntimeMsg(format!(
"Cannot apply logical NOT to `{}` ({}): not primitive type", "Cannot apply logical NOT to `{}` ({}): not primitive type",

View file

@ -35,9 +35,7 @@ impl ItemOp for SubtractItemOp {
if let Type::Primitive(lhs) = &lhs { if let Type::Primitive(lhs) = &lhs {
let rhs = self.rhs.execute(context)?; let rhs = self.rhs.execute(context)?;
if let Type::Primitive(rhs) = &rhs { if let Type::Primitive(rhs) = &rhs {
Ok(Type::Primitive( Ok(Type::Primitive(lhs.try_subtract(rhs).map_err(RuntimeMsg)?))
lhs.try_subtract(rhs).map_err(RuntimeMsg)?,
))
} else { } else {
Err(RuntimeMsg(format!( Err(RuntimeMsg(format!(
"Cannot subtract right-hand side `{}` ({}): not primitive type", "Cannot subtract right-hand side `{}` ({}): not primitive type",

View file

@ -6,11 +6,11 @@ use std::net::SocketAddr;
use crate::tokens::Token; use crate::tokens::Token;
use crate::Context; use crate::Context;
use crate::lang::{LanguageDictionary, repeated_tokens, Lookup};
use crate::lang::{FunctionFactory, FunctionStatementFactory, IteratorItem, Op, PseudoOp};
use crate::lang::{RuntimeError, SyntaxError, RuntimeOp};
use crate::lang::utility::{assert_token, assert_token_raw}; use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::TypePrimitive; use crate::lang::TypePrimitive;
use crate::lang::{repeated_tokens, LanguageDictionary, Lookup};
use crate::lang::{FunctionFactory, FunctionStatementFactory, IteratorItem, Op, PseudoOp};
use crate::lang::{RuntimeError, RuntimeOp, SyntaxError};
use crate::processing::general::Type; use crate::processing::general::Type;
use crate::Item; use crate::Item;
@ -53,68 +53,88 @@ impl Iterator for MpdQueryStatement {
// build address // build address
let addr_str = match self.addr.get(self.context.as_mut().unwrap()) { let addr_str = match self.addr.get(self.context.as_mut().unwrap()) {
Ok(Type::Primitive(a)) => a.as_str(), Ok(Type::Primitive(a)) => a.as_str(),
Ok(x) => return Some(Err( Ok(x) => {
RuntimeError { return Some(Err(RuntimeError {
line: 0, line: 0,
msg: format!("Cannot use non-primitive `{}` as IP address", x), msg: format!("Cannot use non-primitive `{}` as IP address", x),
op: PseudoOp::from_printable(self), op: PseudoOp::from_printable(self),
}))
} }
)),
Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))), Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))),
}; };
#[cfg(not(feature = "ergonomics"))] #[cfg(not(feature = "ergonomics"))]
let addr: SocketAddr = match addr_str.parse() { let addr: SocketAddr = match addr_str.parse() {
Ok(a) => a, Ok(a) => a,
Err(e) => return Some(Err(RuntimeError { Err(e) => {
return Some(Err(RuntimeError {
line: 0, line: 0,
op: PseudoOp::from_printable(self), op: PseudoOp::from_printable(self),
msg: format!("Cannot convert `{}` to IP Address: {}", addr_str, e), msg: format!("Cannot convert `{}` to IP Address: {}", addr_str, e),
})) }))
}
}; };
#[cfg(feature = "ergonomics")] #[cfg(feature = "ergonomics")]
let addr: SocketAddr = if addr_str.starts_with("localhost:") { let addr: SocketAddr = if addr_str.starts_with("localhost:") {
let port_str = addr_str.replace("localhost:", ""); let port_str = addr_str.replace("localhost:", "");
let port = match port_str.parse::<u16>() { let port = match port_str.parse::<u16>() {
Ok(p) => p, Ok(p) => p,
Err(e) => return Some(Err(RuntimeError { Err(e) => {
return Some(Err(RuntimeError {
line: 0, line: 0,
op: PseudoOp::from_printable(self), op: PseudoOp::from_printable(self),
msg: format!("Cannot convert `{}` to IP port: {}", port_str, e), msg: format!("Cannot convert `{}` to IP port: {}", port_str, e),
})) }))
}
}; };
SocketAddr::V4(std::net::SocketAddrV4::new(std::net::Ipv4Addr::LOCALHOST, port)) SocketAddr::V4(std::net::SocketAddrV4::new(
std::net::Ipv4Addr::LOCALHOST,
port,
))
} else if addr_str == "default" { } else if addr_str == "default" {
SocketAddr::V4(std::net::SocketAddrV4::new(std::net::Ipv4Addr::LOCALHOST, 6600)) SocketAddr::V4(std::net::SocketAddrV4::new(
std::net::Ipv4Addr::LOCALHOST,
6600,
))
} else { } else {
match addr_str.parse() { match addr_str.parse() {
Ok(a) => a, Ok(a) => a,
Err(e) => return Some(Err(RuntimeError { Err(e) => {
return Some(Err(RuntimeError {
line: 0, line: 0,
op: PseudoOp::from_printable(self), op: PseudoOp::from_printable(self),
msg: format!("Cannot convert `{}` to IP Address: {}", addr_str, e), msg: format!("Cannot convert `{}` to IP Address: {}", addr_str, e),
})) }))
} }
}
}; };
// build params // build params
let mut new_params = Vec::<(&str, String)>::with_capacity(self.params.len()); let mut new_params = Vec::<(&str, String)>::with_capacity(self.params.len());
for (term, value) in self.params.iter() { for (term, value) in self.params.iter() {
let static_val = match value.get(self.context.as_mut().unwrap()) { let static_val = match value.get(self.context.as_mut().unwrap()) {
Ok(Type::Primitive(a)) => a.as_str(), Ok(Type::Primitive(a)) => a.as_str(),
Ok(x) => return Some(Err( Ok(x) => {
RuntimeError { return Some(Err(RuntimeError {
line: 0, line: 0,
msg: format!("Cannot use non-primitive `{}` MPS query value", x), msg: format!("Cannot use non-primitive `{}` MPS query value", x),
op: PseudoOp::from_printable(self), op: PseudoOp::from_printable(self),
}))
} }
)),
Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))), Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))),
}; };
new_params.push((term, static_val)); new_params.push((term, static_val));
} }
self.results = Some(match self.context.as_mut().unwrap().mpd_database.one_shot_search(addr, new_params) { self.results = Some(
match self
.context
.as_mut()
.unwrap()
.mpd_database
.one_shot_search(addr, new_params)
{
Ok(items) => items, Ok(items) => items,
Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))) Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))),
}); },
);
} }
let results = self.results.as_mut().unwrap(); let results = self.results.as_mut().unwrap();
results.pop_front().map(Ok) results.pop_front().map(Ok)
@ -169,7 +189,10 @@ impl FunctionFactory<MpdQueryStatement> for MpdQueryFunctionFactory {
Ok(MpdQueryStatement { Ok(MpdQueryStatement {
context: None, context: None,
addr: addr_lookup, addr: addr_lookup,
params: vec![("any".to_string(), Lookup::Static(Type::Primitive(TypePrimitive::String("".to_owned()))))], params: vec![(
"any".to_string(),
Lookup::Static(Type::Primitive(TypePrimitive::String("".to_owned()))),
)],
results: None, results: None,
}) })
} else { } else {
@ -182,13 +205,15 @@ impl FunctionFactory<MpdQueryStatement> for MpdQueryFunctionFactory {
_ => None, _ => None,
}, },
Token::Name("term".to_string()), Token::Name("term".to_string()),
tokens)?; tokens,
)?;
assert_token_raw(Token::Equals, tokens)?; assert_token_raw(Token::Equals, tokens)?;
let val = Lookup::parse(tokens)?; let val = Lookup::parse(tokens)?;
Ok(Some((term, val))) Ok(Some((term, val)))
}, },
Token::Comma Token::Comma,
).ingest_all(tokens)?; )
.ingest_all(tokens)?;
Ok(MpdQueryStatement { Ok(MpdQueryStatement {
context: None, context: None,
addr: addr_lookup, addr: addr_lookup,
@ -200,7 +225,8 @@ impl FunctionFactory<MpdQueryStatement> for MpdQueryFunctionFactory {
} }
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
pub type MpdQueryStatementFactory = FunctionStatementFactory<MpdQueryStatement, MpdQueryFunctionFactory>; pub type MpdQueryStatementFactory =
FunctionStatementFactory<MpdQueryStatement, MpdQueryFunctionFactory>;
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
#[inline(always)] #[inline(always)]

View file

@ -8,9 +8,7 @@ use crate::Item;
use crate::lang::utility::{assert_token, assert_token_raw}; use crate::lang::utility::{assert_token, assert_token_raw};
use crate::lang::LanguageDictionary; use crate::lang::LanguageDictionary;
use crate::lang::{ use crate::lang::{FunctionFactory, FunctionStatementFactory, IteratorItem, Op, PseudoOp};
FunctionFactory, FunctionStatementFactory, IteratorItem, Op, PseudoOp,
};
use crate::lang::{RuntimeError, SyntaxError}; use crate::lang::{RuntimeError, SyntaxError};
#[derive(Debug)] #[derive(Debug)]
@ -285,8 +283,7 @@ fn next_comma(tokens: &VecDeque<Token>) -> usize {
tokens.len() tokens.len()
} }
pub type RepeatStatementFactory = pub type RepeatStatementFactory = FunctionStatementFactory<RepeatStatement, RepeatFunctionFactory>;
FunctionStatementFactory<RepeatStatement, RepeatFunctionFactory>;
#[inline(always)] #[inline(always)]
pub fn repeat_function_factory() -> RepeatStatementFactory { pub fn repeat_function_factory() -> RepeatStatementFactory {

View file

@ -5,7 +5,7 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::{assert_name, check_name}; use crate::lang::utility::{assert_name, check_name};
use crate::lang::SyntaxError; use crate::lang::SyntaxError;
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]
use crate::lang::{IteratorItem, Op, Sorter, RuntimeMsg}; use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter};
use crate::lang::{LanguageDictionary, SortStatementFactory, SorterFactory}; use crate::lang::{LanguageDictionary, SortStatementFactory, SorterFactory};
use crate::tokens::Token; use crate::tokens::Token;
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]

View file

@ -8,7 +8,7 @@ use std::collections::HashMap;
use crate::lang::utility::{assert_name, check_name}; use crate::lang::utility::{assert_name, check_name};
use crate::lang::SyntaxError; use crate::lang::SyntaxError;
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]
use crate::lang::{IteratorItem, Op, Sorter, RuntimeMsg}; use crate::lang::{IteratorItem, Op, RuntimeMsg, Sorter};
use crate::lang::{LanguageDictionary, SortStatementFactory, SorterFactory}; use crate::lang::{LanguageDictionary, SortStatementFactory, SorterFactory};
use crate::tokens::Token; use crate::tokens::Token;
#[cfg(feature = "advanced")] #[cfg(feature = "advanced")]

View file

@ -2,8 +2,8 @@ use std::collections::VecDeque;
use std::fmt::{Debug, Display, Error, Formatter}; use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::{IteratorItem, LanguageDictionary, Op}; use crate::lang::{IteratorItem, LanguageDictionary, Op};
use crate::lang::{SortStatementFactory, Sorter, SorterFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{SortStatementFactory, Sorter, SorterFactory};
use crate::tokens::Token; use crate::tokens::Token;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]

View file

@ -4,8 +4,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use crate::lang::utility::assert_token; use crate::lang::utility::assert_token;
use crate::lang::{IteratorItem, LanguageDictionary, Op}; use crate::lang::{IteratorItem, LanguageDictionary, Op};
use crate::lang::{SortStatementFactory, Sorter, SorterFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{SortStatementFactory, Sorter, SorterFactory};
use crate::tokens::Token; use crate::tokens::Token;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -5,8 +5,8 @@ use rand::{thread_rng, Rng};
use crate::lang::utility::{assert_name, check_name}; use crate::lang::utility::{assert_name, check_name};
use crate::lang::{IteratorItem, LanguageDictionary, Op}; use crate::lang::{IteratorItem, LanguageDictionary, Op};
use crate::lang::{SortStatementFactory, Sorter, SorterFactory};
use crate::lang::{RuntimeMsg, SyntaxError}; use crate::lang::{RuntimeMsg, SyntaxError};
use crate::lang::{SortStatementFactory, Sorter, SorterFactory};
use crate::tokens::Token; use crate::tokens::Token;
const RNG_LIMIT_BITMASK: usize = 0xffff; // bits to preserve in RNG const RNG_LIMIT_BITMASK: usize = 0xffff; // bits to preserve in RNG
@ -90,8 +90,7 @@ impl SorterFactory<ShuffleSorter> for ShuffleSorterFactory {
} }
} }
pub type ShuffleSorterStatementFactory = pub type ShuffleSorterStatementFactory = SortStatementFactory<ShuffleSorter, ShuffleSorterFactory>;
SortStatementFactory<ShuffleSorter, ShuffleSorterFactory>;
#[inline(always)] #[inline(always)]
pub fn shuffle_sort() -> ShuffleSorterStatementFactory { pub fn shuffle_sort() -> ShuffleSorterStatementFactory {

View file

@ -4,8 +4,7 @@ use std::iter::Iterator;
use crate::lang::utility::assert_token; use crate::lang::utility::assert_token;
use crate::lang::{ use crate::lang::{
FunctionFactory, FunctionStatementFactory, IteratorItem, LanguageDictionary, Op, FunctionFactory, FunctionStatementFactory, IteratorItem, LanguageDictionary, Op, PseudoOp,
PseudoOp,
}; };
use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError}; use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError};
use crate::tokens::Token; use crate::tokens::Token;

View file

@ -4,8 +4,7 @@ use std::iter::Iterator;
use crate::lang::utility::assert_token; use crate::lang::utility::assert_token;
use crate::lang::{ use crate::lang::{
FunctionFactory, FunctionStatementFactory, IteratorItem, LanguageDictionary, Op, FunctionFactory, FunctionStatementFactory, IteratorItem, LanguageDictionary, Op, PseudoOp,
PseudoOp,
}; };
use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError}; use crate::lang::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError};
use crate::tokens::Token; use crate::tokens::Token;

View file

@ -7,9 +7,7 @@ use crate::Context;
use crate::lang::utility::{assert_token, assert_token_raw, assert_type, check_is_type}; use crate::lang::utility::{assert_token, assert_token_raw, assert_type, check_is_type};
use crate::lang::LanguageDictionary; use crate::lang::LanguageDictionary;
use crate::lang::{ use crate::lang::{BoxedOpFactory, IteratorItem, Op, OpFactory, PseudoOp, TypePrimitive};
BoxedOpFactory, IteratorItem, Op, OpFactory, TypePrimitive, PseudoOp,
};
use crate::lang::{RuntimeError, RuntimeOp, SyntaxError}; use crate::lang::{RuntimeError, RuntimeOp, SyntaxError};
use crate::processing::general::Type; use crate::processing::general::Type;
@ -75,15 +73,13 @@ impl Iterator for AssignStatement {
Err(e) => return Some(Err(e)), Err(e) => return Some(Err(e)),
}; };
let result = if self.is_declaration { let result = if self.is_declaration {
self self.context
.context
.as_mut() .as_mut()
.unwrap() .unwrap()
.variables .variables
.declare(&self.variable_name, Type::Op(real)) .declare(&self.variable_name, Type::Op(real))
} else { } else {
self self.context
.context
.as_mut() .as_mut()
.unwrap() .unwrap()
.variables .variables
@ -107,15 +103,13 @@ impl Iterator for AssignStatement {
} else { } else {
let assign_type = self.assign_type.clone().unwrap(); let assign_type = self.assign_type.clone().unwrap();
let result = if self.is_declaration { let result = if self.is_declaration {
self self.context
.context
.as_mut() .as_mut()
.unwrap() .unwrap()
.variables .variables
.declare(&self.variable_name, Type::Primitive(assign_type)) .declare(&self.variable_name, Type::Primitive(assign_type))
} else { } else {
self self.context
.context
.as_mut() .as_mut()
.unwrap() .unwrap()
.variables .variables

View file

@ -3,10 +3,7 @@ use std::path::Path;
use super::Library; use super::Library;
use crate::lang::db::*; use crate::lang::db::*;
pub fn build_library_from_files<P: AsRef<Path>>( pub fn build_library_from_files<P: AsRef<Path>>(path: P, lib: &mut Library) -> std::io::Result<()> {
path: P,
lib: &mut Library,
) -> std::io::Result<()> {
//let mut result = Library::new(); //let mut result = Library::new();
lib.read_path(path, 10)?; lib.read_path(path, 10)?;
Ok(()) Ok(())

View file

@ -71,7 +71,7 @@ impl Library {
self.songs.insert(song.song_id, song); self.songs.insert(song.song_id, song);
} }
pub fn all_metadata(& self) -> Vec<&'_ DbMetaItem> { pub fn all_metadata(&self) -> Vec<&'_ DbMetaItem> {
self.metadata.values().collect() self.metadata.values().collect()
} }

View file

@ -149,7 +149,9 @@ impl Tags {
pub fn artist(&self, id: u64, genre_id: u64) -> DbArtistItem { pub fn artist(&self, id: u64, genre_id: u64) -> DbArtistItem {
DbArtistItem { DbArtistItem {
artist_id: id, artist_id: id,
name: self.artist_name().unwrap_or_else(|| "Unknown Artist".into()), name: self
.artist_name()
.unwrap_or_else(|| "Unknown Artist".into()),
genre: genre_id, genre: genre_id,
} }
} }

View file

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use regex::Regex; use regex::Regex;
use crate::lang::{TypePrimitive, RuntimeMsg}; use crate::lang::{RuntimeMsg, TypePrimitive};
use crate::Item; use crate::Item;
const DEFAULT_REGEX: &str = r"/(?P<artist>[^/]+)/(?P<album>[^/]+)/(?:(?:(?P<disc>\d+)\s+)?(?P<track>\d+)\.?\s+)?(?P<title>[^/]+)\.(?P<format>(?:mp3)|(?:wav)|(?:ogg)|(?:flac)|(?:mp4)|(?:aac))$"; const DEFAULT_REGEX: &str = r"/(?P<artist>[^/]+)/(?P<album>[^/]+)/(?:(?:(?P<disc>\d+)\s+)?(?P<track>\d+)\.?\s+)?(?P<title>[^/]+)\.(?P<format>(?:mp3)|(?:wav)|(?:ogg)|(?:flac)|(?:mp4)|(?:aac))$";
@ -41,7 +41,8 @@ impl Iterator for SortedReadDir {
} }
} }
self.dir_iter_complete = true; self.dir_iter_complete = true;
self.cache.sort_by_key(|b| std::cmp::Reverse(b.path().to_string_lossy().to_lowercase())); self.cache
.sort_by_key(|b| std::cmp::Reverse(b.path().to_string_lossy().to_lowercase()));
/*self.cache.sort_by( /*self.cache.sort_by(
|a, b| b.path().to_string_lossy().to_lowercase().cmp( |a, b| b.path().to_string_lossy().to_lowercase().cmp(
&a.path().to_string_lossy().to_lowercase()) &a.path().to_string_lossy().to_lowercase())

View file

@ -1,17 +1,17 @@
mod filesystem; mod filesystem;
#[cfg(feature = "advanced")]
mod music_analysis;
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
mod mpd; mod mpd;
#[cfg(feature = "advanced")]
mod music_analysis;
mod sql; mod sql;
mod variables; mod variables;
//pub type OpGetter = dyn FnMut() -> crate::lang::PseudoOp; //pub type OpGetter = dyn FnMut() -> crate::lang::PseudoOp;
pub mod database { pub mod database {
pub use super::sql::{DatabaseQuerier, SQLiteExecutor, QueryResult};
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
pub use super::mpd::{MpdQuerier, MpdExecutor}; pub use super::mpd::{MpdExecutor, MpdQuerier};
pub use super::sql::{DatabaseQuerier, QueryResult, SQLiteExecutor};
} }
pub mod general { pub mod general {

View file

@ -1,14 +1,14 @@
use core::fmt::Debug; use core::fmt::Debug;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::net::{SocketAddr, TcpStream};
use std::iter::Iterator; use std::iter::Iterator;
use std::net::{SocketAddr, TcpStream};
use mpd::Client; use mpd::Client;
use mpd::{Query, Term, Song}; use mpd::{Query, Song, Term};
use crate::lang::RuntimeMsg; use crate::lang::RuntimeMsg;
use crate::Item;
use crate::lang::TypePrimitive; use crate::lang::TypePrimitive;
use crate::Item;
/// Music Player Daemon interface for interacting with it's database /// Music Player Daemon interface for interacting with it's database
pub trait MpdQuerier: Debug { pub trait MpdQuerier: Debug {
@ -16,7 +16,11 @@ pub trait MpdQuerier: Debug {
fn search(&mut self, params: Vec<(&str, String)>) -> Result<VecDeque<Item>, RuntimeMsg>; fn search(&mut self, params: Vec<(&str, String)>) -> Result<VecDeque<Item>, RuntimeMsg>;
fn one_shot_search(&self, addr: SocketAddr, params: Vec<(&str, String)>) -> Result<VecDeque<Item>, RuntimeMsg>; fn one_shot_search(
&self,
addr: SocketAddr,
params: Vec<(&str, String)>,
) -> Result<VecDeque<Item>, RuntimeMsg>;
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -26,7 +30,10 @@ pub struct MpdExecutor {
impl MpdQuerier for MpdExecutor { impl MpdQuerier for MpdExecutor {
fn connect(&mut self, addr: SocketAddr) -> Result<(), RuntimeMsg> { fn connect(&mut self, addr: SocketAddr) -> Result<(), RuntimeMsg> {
self.connection = Some(Client::connect(addr).map_err(|e| RuntimeMsg(format!("MPD connection error: {}", e)))?); self.connection = Some(
Client::connect(addr)
.map_err(|e| RuntimeMsg(format!("MPD connection error: {}", e)))?,
);
Ok(()) Ok(())
} }
@ -40,19 +47,31 @@ impl MpdQuerier for MpdExecutor {
for (term, value) in params { for (term, value) in params {
query_mut = query_mut.and(str_to_term(term), value); query_mut = query_mut.and(str_to_term(term), value);
} }
let songs = self.connection.as_mut().unwrap().search(query_mut, None).map_err(|e| RuntimeMsg(format!("MPD search error: {}", e)))?; let songs = self
.connection
.as_mut()
.unwrap()
.search(query_mut, None)
.map_err(|e| RuntimeMsg(format!("MPD search error: {}", e)))?;
Ok(songs.into_iter().map(song_to_item).collect()) Ok(songs.into_iter().map(song_to_item).collect())
} }
fn one_shot_search(&self, addr: SocketAddr, params: Vec<(&str, String)>) -> Result<VecDeque<Item>, RuntimeMsg> { fn one_shot_search(
let mut connection = Client::connect(addr).map_err(|e| RuntimeMsg(format!("MPD connection error: {}", e)))?; &self,
addr: SocketAddr,
params: Vec<(&str, String)>,
) -> Result<VecDeque<Item>, RuntimeMsg> {
let mut connection = Client::connect(addr)
.map_err(|e| RuntimeMsg(format!("MPD connection error: {}", e)))?;
//let music_dir = connection.music_directory().map_err(|e| RuntimeMsg(format!("MPD command error: {}", e)))?; //let music_dir = connection.music_directory().map_err(|e| RuntimeMsg(format!("MPD command error: {}", e)))?;
let mut query = Query::new(); let mut query = Query::new();
let mut query_mut = &mut query; let mut query_mut = &mut query;
for (term, value) in params { for (term, value) in params {
query_mut = query_mut.and(str_to_term(term), value); query_mut = query_mut.and(str_to_term(term), value);
} }
let songs = connection.search(query_mut, None).map_err(|e| RuntimeMsg(format!("MPD search error: {}", e)))?; let songs = connection
.search(query_mut, None)
.map_err(|e| RuntimeMsg(format!("MPD search error: {}", e)))?;
Ok(songs.into_iter().map(song_to_item).collect()) Ok(songs.into_iter().map(song_to_item).collect())
} }
} }
@ -101,6 +120,6 @@ fn str_to_term(s: &str) -> Term<'_> {
"file" => Term::File, "file" => Term::File,
"base" => Term::Base, "base" => Term::Base,
"lastmod" => Term::LastMod, "lastmod" => Term::LastMod,
x => Term::Tag(x.into()) x => Term::Tag(x.into()),
} }
} }

View file

@ -58,12 +58,7 @@ impl std::default::Default for DefaultAnalyzer {
#[cfg(feature = "bliss-audio-symphonia")] #[cfg(feature = "bliss-audio-symphonia")]
impl DefaultAnalyzer { impl DefaultAnalyzer {
fn request_distance( fn request_distance(&mut self, from: &Item, to: &Item, ack: bool) -> Result<(), RuntimeMsg> {
&mut self,
from: &Item,
to: &Item,
ack: bool,
) -> Result<(), RuntimeMsg> {
let path_from = Self::get_path(from)?; let path_from = Self::get_path(from)?;
let path_to = Self::get_path(to)?; let path_to = Self::get_path(to)?;
self.requests self.requests
@ -89,7 +84,6 @@ impl DefaultAnalyzer {
PATH_FIELD, path PATH_FIELD, path
))) )))
} }
} else { } else {
Err(RuntimeMsg(format!( Err(RuntimeMsg(format!(
"Field {} on item is not String, it's {}", "Field {} on item is not String, it's {}",
@ -141,7 +135,7 @@ impl MusicAnalyzer for DefaultAnalyzer {
}; };
} }
} }
ResponseType::Song { .. } => {}, ResponseType::Song { .. } => {}
ResponseType::UnsupportedSong { path, msg } => { ResponseType::UnsupportedSong { path, msg } => {
if path == path_to || path == path_from { if path == path_to || path == path_from {
return Err(RuntimeMsg(format!("Bliss error: {}", msg))); return Err(RuntimeMsg(format!("Bliss error: {}", msg)));
@ -213,7 +207,7 @@ enum ResponseType {
UnsupportedSong { UnsupportedSong {
path: String, path: String,
msg: String, msg: String,
} },
} }
#[cfg(feature = "bliss-audio-symphonia")] #[cfg(feature = "bliss-audio-symphonia")]
@ -248,11 +242,11 @@ impl CacheThread {
distance, distance,
} => { } => {
self.insert_distance(path1, path2, distance); self.insert_distance(path1, path2, distance);
}, }
ResponseType::Song { path, song } => { ResponseType::Song { path, song } => {
self.insert_song(path, song); self.insert_song(path, song);
}, }
ResponseType::UnsupportedSong { .. } => {}, ResponseType::UnsupportedSong { .. } => {}
} }
} }
} }
@ -263,7 +257,8 @@ impl CacheThread {
// avoid using too much memory -- songs are big memory objects // avoid using too much memory -- songs are big memory objects
self.song_cache.clear(); self.song_cache.clear();
} }
self.song_cache.insert(path, song_result.map(|x| x.as_ref().to_owned())); self.song_cache
.insert(path, song_result.map(|x| x.as_ref().to_owned()));
} }
fn insert_distance( fn insert_distance(
@ -309,8 +304,11 @@ impl CacheThread {
} else { } else {
self.insert_song(path2, song); self.insert_song(path2, song);
} }
}, }
ResponseType::UnsupportedSong {path: unsupported_path, ..} => { ResponseType::UnsupportedSong {
path: unsupported_path,
..
} => {
self.song_in_progress.remove(&unsupported_path); self.song_in_progress.remove(&unsupported_path);
if path == unsupported_path { if path == unsupported_path {
return None; return None;
@ -319,10 +317,7 @@ impl CacheThread {
} }
} }
} else if self.song_cache.contains_key(path) { } else if self.song_cache.contains_key(path) {
let result = self let result = self.song_cache.get(path).and_then(|r| r.clone().ok());
.song_cache
.get(path)
.and_then(|r| r.clone().ok());
if result.is_none() && auto_add { if result.is_none() && auto_add {
self.song_in_progress.insert(path.to_owned()); self.song_in_progress.insert(path.to_owned());
} }
@ -346,11 +341,15 @@ impl CacheThread {
if let Some(result) = self.distance_cache.get(&key) { if let Some(result) = self.distance_cache.get(&key) {
if ack { if ack {
let result = result.to_owned(); let result = result.to_owned();
if self.responses.send(ResponseType::Distance { if self
.responses
.send(ResponseType::Distance {
path1: path1, path1: path1,
path2: path2, path2: path2,
distance: result, distance: result,
}).is_err() { })
.is_err()
{
return true; return true;
} }
} }
@ -360,17 +359,26 @@ impl CacheThread {
// also prevents deadlock in self.get_song_option() // also prevents deadlock in self.get_song_option()
// due to waiting on song that isn't being processed yet // due to waiting on song that isn't being processed yet
// (first call adds it to song_in_progress set, second call just waits) // (first call adds it to song_in_progress set, second call just waits)
if ack && self.responses.send(ResponseType::Distance { if ack
&& self
.responses
.send(ResponseType::Distance {
path1: path1, path1: path1,
path2: path2, path2: path2,
distance: Ok(0.0), distance: Ok(0.0),
}).is_err() { })
.is_err()
{
return true; return true;
} }
} else if !self.distance_in_progress.contains(&key) { } else if !self.distance_in_progress.contains(&key) {
// distance worker uses 3 threads (it's own thread + 1 extra per song) for 2 songs // distance worker uses 3 threads (it's own thread + 1 extra per song) for 2 songs
let available_parallelism = let available_parallelism = (std::thread::available_parallelism()
(std::thread::available_parallelism().ok().map(|x| x.get()).unwrap_or(DEFAULT_PARALLELISM) * 2) / 3; .ok()
.map(|x| x.get())
.unwrap_or(DEFAULT_PARALLELISM)
* 2)
/ 3;
let available_parallelism = if available_parallelism != 0 { let available_parallelism = if available_parallelism != 0 {
available_parallelism - 1 available_parallelism - 1
} else { } else {
@ -386,14 +394,17 @@ impl CacheThread {
distance, distance,
} => { } => {
self.insert_distance(path1, path2, distance); self.insert_distance(path1, path2, distance);
}, }
ResponseType::Song { path: path2, song } => { ResponseType::Song { path: path2, song } => {
self.insert_song(path2.clone(), song.clone()); self.insert_song(path2.clone(), song.clone());
if self.song_in_progress.len() <= available_parallelism { if self.song_in_progress.len() <= available_parallelism {
break 'inner4; break 'inner4;
} }
}, }
ResponseType::UnsupportedSong {path: unsupported_path, ..} => { ResponseType::UnsupportedSong {
path: unsupported_path,
..
} => {
self.song_in_progress.remove(&unsupported_path); self.song_in_progress.remove(&unsupported_path);
if self.song_in_progress.len() <= available_parallelism { if self.song_in_progress.len() <= available_parallelism {
break 'inner4; break 'inner4;
@ -431,11 +442,15 @@ impl CacheThread {
distance.clone(), distance.clone(),
); );
if path1_2 == key.0 && path2_2 == key.1 { if path1_2 == key.0 && path2_2 == key.1 {
if self.responses.send(ResponseType::Distance { if self
.responses
.send(ResponseType::Distance {
path1: path1_2, path1: path1_2,
path2: path2_2, path2: path2_2,
distance: distance, distance: distance,
}).is_err() { })
.is_err()
{
return true; return true;
} }
break 'inner1; break 'inner1;
@ -443,13 +458,20 @@ impl CacheThread {
} }
ResponseType::Song { path, song } => { ResponseType::Song { path, song } => {
self.insert_song(path, song); self.insert_song(path, song);
}, }
ResponseType::UnsupportedSong { path: unsupported_path, msg } => { ResponseType::UnsupportedSong {
path: unsupported_path,
msg,
} => {
self.song_in_progress.remove(&unsupported_path); self.song_in_progress.remove(&unsupported_path);
if self.responses.send(ResponseType::UnsupportedSong { if self
.responses
.send(ResponseType::UnsupportedSong {
path: unsupported_path.clone(), path: unsupported_path.clone(),
msg: msg msg: msg,
}).is_err() { })
.is_err()
{
return true; return true;
} }
if unsupported_path == key.0 || unsupported_path == key.1 { if unsupported_path == key.0 || unsupported_path == key.1 {
@ -476,10 +498,14 @@ impl CacheThread {
} else if !path.contains("://") { } else if !path.contains("://") {
path path
} else { } else {
if self.responses.send(ResponseType::UnsupportedSong { if self
.responses
.send(ResponseType::UnsupportedSong {
msg: format!("Song path is not a supported URI, it's `{}`", path), msg: format!("Song path is not a supported URI, it's `{}`", path),
path: path, path: path,
}).is_err() { })
.is_err()
{
return true; return true;
} }
return false; return false;
@ -487,18 +513,25 @@ impl CacheThread {
if let Some(song) = self.song_cache.get(&path) { if let Some(song) = self.song_cache.get(&path) {
if ack { if ack {
let song = song.to_owned(); let song = song.to_owned();
if self.responses.send(ResponseType::Song { if self
.responses
.send(ResponseType::Song {
path: path, path: path,
song: song.map(Box::new), song: song.map(Box::new),
}).is_err() { })
.is_err()
{
return true; return true;
} }
} }
} else { } else {
if !self.song_in_progress.contains(&path) { if !self.song_in_progress.contains(&path) {
// every song is roughly 2 threads -- Song::from_path(...) spawns a thread // every song is roughly 2 threads -- Song::from_path(...) spawns a thread
let available_parallelism = let available_parallelism = std::thread::available_parallelism()
std::thread::available_parallelism().ok().map(|x| x.get()).unwrap_or(DEFAULT_PARALLELISM) / 2; .ok()
.map(|x| x.get())
.unwrap_or(DEFAULT_PARALLELISM)
/ 2;
let available_parallelism = if available_parallelism != 0 { let available_parallelism = if available_parallelism != 0 {
available_parallelism - 1 available_parallelism - 1
} else { } else {
@ -520,7 +553,7 @@ impl CacheThread {
if self.song_in_progress.len() <= available_parallelism { if self.song_in_progress.len() <= available_parallelism {
break 'inner2; break 'inner2;
} }
}, }
ResponseType::UnsupportedSong { path, .. } => { ResponseType::UnsupportedSong { path, .. } => {
self.song_in_progress.remove(&path); self.song_in_progress.remove(&path);
if self.song_in_progress.len() <= available_parallelism { if self.song_in_progress.len() <= available_parallelism {
@ -555,22 +588,33 @@ impl CacheThread {
ResponseType::Song { path: path2, song } => { ResponseType::Song { path: path2, song } => {
self.insert_song(path2.clone(), song.clone()); self.insert_song(path2.clone(), song.clone());
if path2 == path { if path2 == path {
if self.responses.send(ResponseType::Song { if self
.responses
.send(ResponseType::Song {
path: path, path: path,
song: song, song: song,
}).is_err() { })
.is_err()
{
return true; return true;
} }
break 'inner3; break 'inner3;
} }
} }
ResponseType::UnsupportedSong { path: unsupported_path, msg } => { ResponseType::UnsupportedSong {
path: unsupported_path,
msg,
} => {
self.song_in_progress.remove(&unsupported_path); self.song_in_progress.remove(&unsupported_path);
if unsupported_path == path { if unsupported_path == path {
if self.responses.send(ResponseType::UnsupportedSong { if self
.responses
.send(ResponseType::UnsupportedSong {
path: unsupported_path, path: unsupported_path,
msg: msg msg: msg,
}).is_err() { })
.is_err()
{
return true; return true;
} }
break 'inner3; break 'inner3;
@ -639,7 +683,11 @@ fn worker_distance(
}) })
.unwrap_or(()); .unwrap_or(());
if new_song2.is_err() { if new_song2.is_err() {
eprintln!("Song error on `{}`: {}", path2, new_song2.clone().err().unwrap()); eprintln!(
"Song error on `{}`: {}",
path2,
new_song2.clone().err().unwrap()
);
} }
new_song2? new_song2?
}; };

View file

@ -1,6 +1,6 @@
use core::fmt::Debug; use core::fmt::Debug;
use std::fmt::Write;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use crate::lang::db::*; use crate::lang::db::*;
use crate::lang::RuntimeMsg; use crate::lang::RuntimeMsg;

View file

@ -3,8 +3,8 @@ use std::fmt::{Debug, Display, Error, Formatter};
use std::collections::HashMap; use std::collections::HashMap;
use crate::lang::Op; use crate::lang::Op;
use crate::lang::TypePrimitive;
use crate::lang::RuntimeMsg; use crate::lang::RuntimeMsg;
use crate::lang::TypePrimitive;
use crate::Item; use crate::Item;
#[derive(Debug)] #[derive(Debug)]

View file

@ -1,7 +1,7 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use super::Token;
use super::ParseError; use super::ParseError;
use super::Token;
pub trait TokenReader { pub trait TokenReader {
fn current_line(&self) -> usize; fn current_line(&self) -> usize;

View file

@ -1,6 +1,6 @@
//! Integration tests for every syntax feature //! Integration tests for every syntax feature
use muss_interpreter::tokens::{Token, Tokenizer, ParseError}; use muss_interpreter::tokens::{ParseError, Token, Tokenizer};
use muss_interpreter::*; use muss_interpreter::*;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::io::Cursor; use std::io::Cursor;
@ -823,45 +823,21 @@ fn execute_emptiesop_line() -> Result<(), InterpreterError> {
#[test] #[test]
fn execute_nonemptyfilter_line() -> Result<(), InterpreterError> { fn execute_nonemptyfilter_line() -> Result<(), InterpreterError> {
execute_single_line( execute_single_line("files().(??)", false, true)?;
"files().(??)", execute_single_line("empties(42).(??)", true, true)
false,
true,
)?;
execute_single_line(
"empties(42).(??)",
true,
true,
)
} }
#[test] #[test]
fn execute_mpdfunction_line() -> Result<(), InterpreterError> { fn execute_mpdfunction_line() -> Result<(), InterpreterError> {
execute_single_line( execute_single_line("mpd(`127.0.0.1:6600`, artist=`Bruno Mars`)", false, true)?;
"mpd(`127.0.0.1:6600`, artist=`Bruno Mars`)",
false,
true,
)?;
execute_single_line( execute_single_line(
"mpd(`127.0.0.1:6600`, title=`something very long that should match absolutely nothing, probably, hopefully...`)", "mpd(`127.0.0.1:6600`, title=`something very long that should match absolutely nothing, probably, hopefully...`)",
true, true,
true, true,
)?; )?;
#[cfg(feature = "ergonomics")] #[cfg(feature = "ergonomics")]
execute_single_line( execute_single_line("mpd(`localhost:6600`)", false, true)?;
"mpd(`localhost:6600`)",
false,
true,
)?;
#[cfg(feature = "ergonomics")] #[cfg(feature = "ergonomics")]
execute_single_line( execute_single_line("mpd(`default`)", false, true)?;
"mpd(`default`)", execute_single_line("mpd(`127.0.0.1:6600`)", false, true)
false,
true,
)?;
execute_single_line(
"mpd(`127.0.0.1:6600`)",
false,
true,
)
} }

View file

@ -33,9 +33,7 @@ fn main() {
for item in runner { for item in runner {
match item { match item {
Ok(music) => { Ok(music) => {
if let Some(filename) = if let Some(filename) = music_filename(&music) {
music_filename(&music)
{
playlist.segments.push(MediaSegment { playlist.segments.push(MediaSegment {
uri: filename, uri: filename,
title: music_title(&music), title: music_title(&music),
@ -56,9 +54,7 @@ fn main() {
for item in runner { for item in runner {
match item { match item {
Ok(music) => { Ok(music) => {
if let Some(filename) = if let Some(filename) = music_filename(&music) {
music_filename(&music)
{
playlist.segments.push(MediaSegment { playlist.segments.push(MediaSegment {
uri: filename, uri: filename,
title: music_title(&music), title: music_title(&music),
@ -99,5 +95,3 @@ fn music_filename(item: &Item) -> Option<String> {
None None
} }
} }

View file

@ -1,12 +1,12 @@
use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::JoinHandle; use std::thread::JoinHandle;
use muss_interpreter::{Item, InterpreterError}; use muss_interpreter::{InterpreterError, Item};
use super::os_controls::SystemControlWrapper; use super::os_controls::SystemControlWrapper;
use super::player_wrapper::{ControlAction, PlayerServer, PlayerAction}; use super::player_wrapper::{ControlAction, PlayerAction, PlayerServer};
use super::Player;
use super::PlaybackError; use super::PlaybackError;
use super::Player;
use super::PlayerError; use super::PlayerError;
/// A controller for a Player running on another thread. /// A controller for a Player running on another thread.
@ -21,7 +21,7 @@ pub struct Controller {
impl Controller { impl Controller {
pub fn create< pub fn create<
F: FnOnce() -> Player<I> + Send + 'static, F: FnOnce() -> Player<I> + Send + 'static,
I: std::iter::Iterator<Item=Result<Item, InterpreterError>>, I: std::iter::Iterator<Item = Result<Item, InterpreterError>>,
>( >(
player_gen: F, player_gen: F,
) -> Self { ) -> Self {
@ -48,7 +48,7 @@ impl Controller {
pub fn create_repl< pub fn create_repl<
F: FnOnce() -> Player<I> + Send + 'static, F: FnOnce() -> Player<I> + Send + 'static,
I: std::iter::Iterator<Item=Result<Item, InterpreterError>>, I: std::iter::Iterator<Item = Result<Item, InterpreterError>>,
>( >(
player_gen: F, player_gen: F,
) -> Self { ) -> Self {
@ -87,14 +87,15 @@ impl Controller {
Ok(()) Ok(())
} else { } else {
Err(PlaybackError { Err(PlaybackError {
msg: "Incorrect acknowledgement received for Controller control action" msg: "Incorrect acknowledgement received for Controller control action".into(),
.into(), }
}.into()) .into())
} }
} else { } else {
Err(PlaybackError { Err(PlaybackError {
msg: "Invalid acknowledgement received for Controller control action".into(), msg: "Invalid acknowledgement received for Controller control action".into(),
}.into()) }
.into())
} }
} }
@ -146,7 +147,8 @@ impl Controller {
Ok(x) => Ok(x), Ok(x) => Ok(x),
Err(_) => Err(PlaybackError { Err(_) => Err(PlaybackError {
msg: "PlayerServer did not exit correctly".into(), msg: "PlayerServer did not exit correctly".into(),
}.into()), }
.into()),
} }
} }
@ -208,10 +210,13 @@ impl Controller {
if action == to_send { if action == to_send {
break; break;
} else { } else {
result.push(PlaybackError { result.push(
PlaybackError {
msg: "Incorrect acknowledgement received for Controller control action" msg: "Incorrect acknowledgement received for Controller control action"
.into(), .into(),
}.into()); }
.into(),
);
} }
} else if let Err(e) = self.handle_event(msg) { } else if let Err(e) = self.handle_event(msg) {
result.push(e); result.push(e);

View file

@ -1,5 +1,5 @@
use std::fmt::{Debug, Display, Error, Formatter};
use std::convert::From; use std::convert::From;
use std::fmt::{Debug, Display, Error, Formatter};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum PlayerError { pub enum PlayerError {
@ -67,7 +67,7 @@ impl From<PlaybackError> for PlayerError {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum UriError { pub enum UriError {
Unsupported(String), Unsupported(String),
Message(String) Message(String),
} }
impl UriError { impl UriError {
@ -81,7 +81,7 @@ impl Display for UriError {
write!(f, "UriError: ")?; write!(f, "UriError: ")?;
match self { match self {
Self::Unsupported(scheme) => write!(f, "Unsupported URI `{}//`", scheme), Self::Unsupported(scheme) => write!(f, "Unsupported URI `{}//`", scheme),
Self::Message(msg) => write!(f, "{}", msg) Self::Message(msg) => write!(f, "{}", msg),
} }
} }
} }

View file

@ -15,10 +15,10 @@ pub(crate) mod uri;
//mod utility; //mod utility;
pub use controller::Controller; pub use controller::Controller;
pub use errors::{PlaybackError, UriError, PlayerError}; pub use errors::{PlaybackError, PlayerError, UriError};
pub use player::Player;
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
pub use player::mpd_connection; pub use player::mpd_connection;
pub use player::Player;
//pub use utility::{play_script}; //pub use utility::{play_script};
#[cfg(test)] #[cfg(test)]

View file

@ -179,9 +179,7 @@ impl SystemControlWrapper {
fn enqueued(item: Item, dbus_ctrl: &Sender<DbusControl>) { fn enqueued(item: Item, dbus_ctrl: &Sender<DbusControl>) {
//println!("Got enqueued item {}", &item.title); //println!("Got enqueued item {}", &item.title);
let file_uri = item let file_uri = item.field("filename").and_then(|x| x.to_owned().to_str());
.field("filename")
.and_then(|x| x.to_owned().to_str());
dbus_ctrl dbus_ctrl
.send(DbusControl::SetMetadata(Metadata { .send(DbusControl::SetMetadata(Metadata {
length: None, length: None,

View file

@ -6,11 +6,11 @@ use rodio::{decoder::Decoder, OutputStream, OutputStreamHandle, Sink};
use m3u8_rs::{MediaPlaylist, MediaSegment}; use m3u8_rs::{MediaPlaylist, MediaSegment};
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
use mpd::{Client, Song, error}; use mpd::{error, Client, Song};
use super::uri::Uri; use super::uri::Uri;
use muss_interpreter::{Item, InterpreterError}; use muss_interpreter::{InterpreterError, Item};
//use super::PlaybackError; //use super::PlaybackError;
use super::PlayerError; use super::PlayerError;
@ -20,7 +20,7 @@ use super::UriError;
/// Playback functionality for a script. /// Playback functionality for a script.
/// This takes the output of the runner and plays or saves it. /// This takes the output of the runner and plays or saves it.
pub struct Player<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> { pub struct Player<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> {
runner: I, runner: I,
sink: Sink, sink: Sink,
#[allow(dead_code)] #[allow(dead_code)]
@ -30,7 +30,7 @@ pub struct Player<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> {
mpd_connection: Option<Client<std::net::TcpStream>>, mpd_connection: Option<Client<std::net::TcpStream>>,
} }
impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> Player<I> { impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
pub fn new(runner: I) -> Result<Self, PlayerError> { pub fn new(runner: I) -> Result<Self, PlayerError> {
let (stream, output_handle) = let (stream, output_handle) =
OutputStream::try_default().map_err(PlayerError::from_err_playback)?; OutputStream::try_default().map_err(PlayerError::from_err_playback)?;
@ -166,9 +166,7 @@ impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> Player<I> {
for item in &mut self.runner { for item in &mut self.runner {
match item { match item {
Ok(music) => { Ok(music) => {
if let Some(filename) = if let Some(filename) = music_filename(&music) {
music_filename(&music)
{
//println!("Adding file `{}` to playlist", filename); //println!("Adding file `{}` to playlist", filename);
playlist.segments.push(MediaSegment { playlist.segments.push(MediaSegment {
uri: filename, uri: filename,
@ -215,12 +213,13 @@ impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> Player<I> {
match uri.scheme() { match uri.scheme() {
Some(s) => match &s.to_lowercase() as &str { Some(s) => match &s.to_lowercase() as &str {
"file:" => { "file:" => {
let file = fs::File::open(uri.path()).map_err(PlayerError::from_err_playback)?; let file =
fs::File::open(uri.path()).map_err(PlayerError::from_err_playback)?;
let stream = io::BufReader::new(file); let stream = io::BufReader::new(file);
let source = Decoder::new(stream).map_err(PlayerError::from_err_playback)?; let source = Decoder::new(stream).map_err(PlayerError::from_err_playback)?;
self.sink.append(source); self.sink.append(source);
Ok(()) Ok(())
}, }
#[cfg(feature = "mpd")] #[cfg(feature = "mpd")]
"mpd:" => { "mpd:" => {
if let Some(mpd_client) = &mut self.mpd_connection { if let Some(mpd_client) = &mut self.mpd_connection {
@ -229,13 +228,17 @@ impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> Player<I> {
file: uri.path().to_owned(), file: uri.path().to_owned(),
..Default::default() ..Default::default()
}; };
mpd_client.push(song).map_err(PlayerError::from_err_playback)?; mpd_client
.push(song)
.map_err(PlayerError::from_err_playback)?;
Ok(()) Ok(())
} else { } else {
Err(PlayerError::from_err_playback("Cannot play MPD song: no MPD client connected")) Err(PlayerError::from_err_playback(
"Cannot play MPD song: no MPD client connected",
))
} }
}, }
scheme => Err(UriError::Unsupported(scheme.to_owned()).into()) scheme => Err(UriError::Unsupported(scheme.to_owned()).into()),
}, },
None => { None => {
//default //default

View file

@ -2,7 +2,7 @@ use std::sync::mpsc::{Receiver, Sender};
use std::{thread, thread::JoinHandle}; use std::{thread, thread::JoinHandle};
//use muss_interpreter::tokens::TokenReader; //use muss_interpreter::tokens::TokenReader;
use muss_interpreter::{Item, InterpreterError}; use muss_interpreter::{InterpreterError, Item};
use super::Player; use super::Player;
use super::PlayerError; use super::PlayerError;
@ -11,7 +11,7 @@ use super::PlayerError;
/// This allows for message passing between the player and controller. /// This allows for message passing between the player and controller.
/// ///
/// You will probably never directly interact with this, instead using Controller to communicate. /// You will probably never directly interact with this, instead using Controller to communicate.
pub struct PlayerServer<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> { pub struct PlayerServer<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> {
player: Player<I>, player: Player<I>,
control: Receiver<ControlAction>, control: Receiver<ControlAction>,
event: Sender<PlayerAction>, event: Sender<PlayerAction>,
@ -19,7 +19,7 @@ pub struct PlayerServer<I: std::iter::Iterator<Item=Result<Item, InterpreterErro
keep_alive: bool, keep_alive: bool,
} }
impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> PlayerServer<I> { impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer<I> {
pub fn new( pub fn new(
player: Player<I>, player: Player<I>,
ctrl: Receiver<ControlAction>, ctrl: Receiver<ControlAction>,

View file

@ -16,14 +16,14 @@ impl<'a> Uri<&'a str> {
Some(end) => { Some(end) => {
// proper URI // proper URI
if let Some(query_start) = self.0.find('?') { if let Some(query_start) = self.0.find('?') {
self.0.get(end+2..query_start).unwrap() self.0.get(end + 2..query_start).unwrap()
} else if let Some(frag_start) = self.0.find('#') { } else if let Some(frag_start) = self.0.find('#') {
self.0.get(end+2..frag_start).unwrap() self.0.get(end + 2..frag_start).unwrap()
} else { } else {
self.0.get(end+2..).unwrap() self.0.get(end + 2..).unwrap()
} }
}, }
None => self.0 None => self.0,
} }
} }

View file

@ -34,7 +34,9 @@ pub fn parse() -> CliArgs {
pub fn validate(args: &CliArgs) -> Result<(), String> { pub fn validate(args: &CliArgs) -> Result<(), String> {
if let Some(mpd_addr) = &args.mpd { if let Some(mpd_addr) = &args.mpd {
let _: std::net::SocketAddr = mpd_addr.parse().map_err(|e| format!("Unrecognized MPS address `{}`: {}", mpd_addr, e))?; let _: std::net::SocketAddr = mpd_addr
.parse()
.map_err(|e| format!("Unrecognized MPS address `{}`: {}", mpd_addr, e))?;
} }
Ok(()) Ok(())
} }

View file

@ -81,7 +81,12 @@ fn main() {
// build playback controller // build playback controller
let script_file2 = script_file.clone(); let script_file2 = script_file.clone();
let volume = args.volume; let volume = args.volume;
let mpd = match args.mpd.clone().map(|a| muss_player::mpd_connection(a.parse().unwrap())).transpose() { let mpd = match args
.mpd
.clone()
.map(|a| muss_player::mpd_connection(a.parse().unwrap()))
.transpose()
{
Ok(mpd) => mpd, Ok(mpd) => mpd,
Err(e) => panic!("Abort: Cannot connect to MPD: {}", e), Err(e) => panic!("Abort: Cannot connect to MPD: {}", e),
}; };

View file

@ -1,27 +1,25 @@
//! Read, Execute, Print Loop functionality //! Read, Execute, Print Loop functionality
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
use std::sync::{RwLock};
use std::sync::mpsc::{self, Receiver};
use std::io::{self, Write}; use std::io::{self, Write};
use std::sync::mpsc::{self, Receiver};
use std::sync::RwLock;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use console::{Key, Term}; use console::{Key, Term};
use muss_interpreter::{Interpreter, Debugger, Item, InterpreterEvent, InterpreterError};
use muss_interpreter::lang::TypePrimitive; use muss_interpreter::lang::TypePrimitive;
use muss_interpreter::{Debugger, Interpreter, InterpreterError, InterpreterEvent, Item};
use muss_player::{Controller, Player}; use muss_player::{Controller, Player};
use super::channel_io::{channel_io, ChannelWriter}; use super::channel_io::{channel_io, ChannelWriter};
use super::cli::CliArgs; use super::cli::CliArgs;
lazy_static! { lazy_static! {
static ref DEBUG_STATE: RwLock<DebugState> = RwLock::new( static ref DEBUG_STATE: RwLock<DebugState> = RwLock::new(DebugState {
DebugState {
debug_flag: DebugFlag::Normal, debug_flag: DebugFlag::Normal,
verbose: false, verbose: false,
} });
);
} }
const TERMINAL_WRITE_ERROR: &str = "Failed to write to terminal output"; const TERMINAL_WRITE_ERROR: &str = "Failed to write to terminal output";
@ -55,7 +53,7 @@ struct DebugState {
enum DebugFlag { enum DebugFlag {
Skip, Skip,
List, List,
Normal Normal,
} }
impl ReplState { impl ReplState {
@ -80,59 +78,79 @@ impl ReplState {
} }
} }
fn interpreter_event_callback<T: muss_interpreter::tokens::TokenReader>(_interpreter: &mut Interpreter<'_, T>, event: InterpreterEvent) -> Result<(), InterpreterError> { fn interpreter_event_callback<T: muss_interpreter::tokens::TokenReader>(
_interpreter: &mut Interpreter<'_, T>,
event: InterpreterEvent,
) -> Result<(), InterpreterError> {
match event { match event {
InterpreterEvent::StatementComplete => { InterpreterEvent::StatementComplete => {
if let Ok(mut d_state) = DEBUG_STATE.write() { if let Ok(mut d_state) = DEBUG_STATE.write() {
d_state.debug_flag = DebugFlag::Normal; d_state.debug_flag = DebugFlag::Normal;
} }
}, }
_ => {}, _ => {}
} }
Ok(()) Ok(())
} }
#[inline] #[inline]
fn item_prompt(terminal: &mut Term, args: &CliArgs) { fn item_prompt(terminal: &mut Term, args: &CliArgs) {
write!(terminal, "*I{}", args.prompt) write!(terminal, "*I{}", args.prompt).expect(TERMINAL_WRITE_ERROR);
.expect(TERMINAL_WRITE_ERROR);
} }
fn pretty_print_item(item: &Item, terminal: &mut Term, args: &CliArgs, verbose: bool) { fn pretty_print_item(item: &Item, terminal: &mut Term, args: &CliArgs, verbose: bool) {
item_prompt(terminal, args); item_prompt(terminal, args);
if verbose { if verbose {
writeln!(terminal, "--\\/-- `{}` --\\/--", writeln!(
item.field("title").unwrap_or(&TypePrimitive::Empty).as_str() terminal,
).expect(TERMINAL_WRITE_ERROR); "--\\/-- `{}` --\\/--",
item.field("title")
.unwrap_or(&TypePrimitive::Empty)
.as_str()
)
.expect(TERMINAL_WRITE_ERROR);
let mut fields: Vec<&_> = item.iter().collect(); let mut fields: Vec<&_> = item.iter().collect();
fields.sort(); fields.sort();
for field in fields { for field in fields {
if field != "title" { if field != "title" {
writeln!(terminal, " {}: `{}`", writeln!(
terminal,
" {}: `{}`",
field, field,
item.field(field).unwrap_or(&TypePrimitive::Empty).as_str() item.field(field).unwrap_or(&TypePrimitive::Empty).as_str()
).expect(TERMINAL_WRITE_ERROR); )
.expect(TERMINAL_WRITE_ERROR);
} }
} }
} else { } else {
writeln!(terminal, "`{}` by `{}`", writeln!(
item.field("title").unwrap_or(&TypePrimitive::Empty).as_str(), terminal,
item.field("artist").unwrap_or(&TypePrimitive::Empty).as_str(), "`{}` by `{}`",
).expect(TERMINAL_WRITE_ERROR); item.field("title")
.unwrap_or(&TypePrimitive::Empty)
.as_str(),
item.field("artist")
.unwrap_or(&TypePrimitive::Empty)
.as_str(),
)
.expect(TERMINAL_WRITE_ERROR);
} }
//writeln!(terminal, "I{}----", args.prompt).expect(TERMINAL_WRITE_ERROR); //writeln!(terminal, "I{}----", args.prompt).expect(TERMINAL_WRITE_ERROR);
} }
fn handle_list_rx(state: &mut ReplState, args: &CliArgs) { fn handle_list_rx(state: &mut ReplState, args: &CliArgs) {
//let items = state.list_rx.try_iter().collect::<Vec<_>>(); //let items = state.list_rx.try_iter().collect::<Vec<_>>();
let d_state = DEBUG_STATE.read().expect("Failed to get read lock for debug state info").clone(); let d_state = DEBUG_STATE
.read()
.expect("Failed to get read lock for debug state info")
.clone();
for item in state.list_rx.try_iter() { for item in state.list_rx.try_iter() {
match item { match item {
Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose), Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose),
Err(e) => error_prompt( Err(e) => error_prompt(
muss_player::PlayerError::Playback( muss_player::PlayerError::Playback(muss_player::PlaybackError::from_err(e)),
muss_player::PlaybackError::from_err(e) args,
), args), ),
} }
} }
let flag = d_state.debug_flag; let flag = d_state.debug_flag;
@ -140,11 +158,13 @@ fn handle_list_rx(state: &mut ReplState, args: &CliArgs) {
DebugFlag::List => { DebugFlag::List => {
while let Ok(item) = state.list_rx.recv() { while let Ok(item) = state.list_rx.recv() {
match item { match item {
Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose), Ok(item) => {
pretty_print_item(&item, &mut state.terminal, args, d_state.verbose)
}
Err(e) => error_prompt( Err(e) => error_prompt(
muss_player::PlayerError::Playback( muss_player::PlayerError::Playback(muss_player::PlaybackError::from_err(e)),
muss_player::PlaybackError::from_err(e) args,
), args), ),
} }
// stop listing if no longer in list mode // stop listing if no longer in list mode
let flag = if let Ok(d_state) = DEBUG_STATE.read() { let flag = if let Ok(d_state) = DEBUG_STATE.read() {
@ -153,11 +173,11 @@ fn handle_list_rx(state: &mut ReplState, args: &CliArgs) {
DebugFlag::Normal DebugFlag::Normal
}; };
match flag { match flag {
DebugFlag::List => {}, DebugFlag::List => {}
_ => break, _ => break,
} }
} }
}, }
_ => {} _ => {}
} }
} }
@ -167,18 +187,26 @@ pub fn repl(args: CliArgs) {
term.set_title("muss"); term.set_title("muss");
let (writer, reader) = channel_io(); let (writer, reader) = channel_io();
let volume = args.volume; let volume = args.volume;
let mpd = match args.mpd.clone().map(|a| muss_player::mpd_connection(a.parse().unwrap())).transpose() { let mpd = match args
.mpd
.clone()
.map(|a| muss_player::mpd_connection(a.parse().unwrap()))
.transpose()
{
Ok(mpd) => mpd, Ok(mpd) => mpd,
Err(e) => { Err(e) => {
eprintln!("Cannot connect to MPD address `{}`: {}", args.mpd.unwrap(), e); eprintln!(
"Cannot connect to MPD address `{}`: {}",
args.mpd.unwrap(),
e
);
return; return;
} }
}; };
let (list_tx, list_rx) = mpsc::channel(); let (list_tx, list_rx) = mpsc::channel();
let mut state = ReplState::new(writer, term, list_rx); let mut state = ReplState::new(writer, term, list_rx);
let player_builder = move || { let player_builder = move || {
let runner = Interpreter::with_stream_and_callback(reader, let runner = Interpreter::with_stream_and_callback(reader, &interpreter_event_callback);
&interpreter_event_callback);
let debugger = Debugger::new(runner, move |interpretor, item| { let debugger = Debugger::new(runner, move |interpretor, item| {
let flag = if let Ok(d_state) = DEBUG_STATE.read() { let flag = if let Ok(d_state) = DEBUG_STATE.read() {
d_state.debug_flag d_state.debug_flag
@ -192,7 +220,7 @@ pub fn repl(args: CliArgs) {
// NOTE: recursion occurs here // NOTE: recursion occurs here
} }
None None
}, }
DebugFlag::List => { DebugFlag::List => {
if let Some(x) = item { if let Some(x) = item {
list_tx.send(x.map_err(|e| e.to_string())).unwrap_or(()); list_tx.send(x.map_err(|e| e.to_string())).unwrap_or(());
@ -286,7 +314,11 @@ pub fn repl(args: CliArgs) {
} }
} }
fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! { fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(
args: &CliArgs,
state: &mut ReplState,
mut execute: F,
) -> ! {
prompt(state, args); prompt(state, args);
loop { loop {
match state match state
@ -296,13 +328,11 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
{ {
Key::Char(read_c) => { Key::Char(read_c) => {
if state.cursor_rightward_position == 0 { if state.cursor_rightward_position == 0 {
write!(state.terminal, "{}", read_c) write!(state.terminal, "{}", read_c).expect(TERMINAL_WRITE_ERROR);
.expect(TERMINAL_WRITE_ERROR);
state.statement_buf.push(read_c); state.statement_buf.push(read_c);
state.current_line.push(read_c); state.current_line.push(read_c);
} else { } else {
write!(state.terminal, "{}", read_c) write!(state.terminal, "{}", read_c).expect(TERMINAL_WRITE_ERROR);
.expect(TERMINAL_WRITE_ERROR);
for i in state.current_line.len() - state.cursor_rightward_position for i in state.current_line.len() - state.cursor_rightward_position
..state.current_line.len() ..state.current_line.len()
{ {
@ -346,21 +376,22 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
} }
';' => { ';' => {
if state.in_literal.is_none() { if state.in_literal.is_none() {
state state.terminal.write_line("").expect(TERMINAL_WRITE_ERROR);
.terminal
.write_line("")
.expect(TERMINAL_WRITE_ERROR);
let statement = state.statement_buf.iter().collect::<String>(); let statement = state.statement_buf.iter().collect::<String>();
let statement_result = statement.trim(); let statement_result = statement.trim();
if !statement_result.starts_with('?') { if !statement_result.starts_with('?') {
state state
.writer .writer
.write_all(state.statement_buf.iter().collect::<String>().as_bytes()) .write_all(
state.statement_buf.iter().collect::<String>().as_bytes(),
)
.expect(INTERPRETER_WRITE_ERROR); .expect(INTERPRETER_WRITE_ERROR);
execute(state, args); execute(state, args);
state.statement_buf.clear(); state.statement_buf.clear();
} }
let last_line = state.current_line[0..state.current_line.len() - 1].iter().collect::<String>(); let last_line = state.current_line[0..state.current_line.len() - 1]
.iter()
.collect::<String>();
state.current_line.clear(); state.current_line.clear();
if !last_line.is_empty() if !last_line.is_empty()
&& ((!state.history.is_empty() && ((!state.history.is_empty()
@ -415,8 +446,7 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
.terminal .terminal
.move_cursor_left(1) .move_cursor_left(1)
.expect(TERMINAL_WRITE_ERROR); .expect(TERMINAL_WRITE_ERROR);
write!(state.terminal, " ") write!(state.terminal, " ").expect(TERMINAL_WRITE_ERROR);
.expect(TERMINAL_WRITE_ERROR);
state state
.terminal .terminal
.flush() .flush()
@ -433,9 +463,9 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
let removed_char = state let removed_char = state
.current_line .current_line
.remove(state.current_line.len() - state.cursor_rightward_position - 1); .remove(state.current_line.len() - state.cursor_rightward_position - 1);
state.statement_buf.remove( state
state.statement_buf.len() - state.cursor_rightward_position - 1, .statement_buf
); .remove(state.statement_buf.len() - state.cursor_rightward_position - 1);
// re-sync unclosed syntax tracking // re-sync unclosed syntax tracking
match removed_char { match removed_char {
'"' | '`' => { '"' | '`' => {
@ -484,9 +514,9 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
let removed_char = state let removed_char = state
.current_line .current_line
.remove(state.current_line.len() - state.cursor_rightward_position); .remove(state.current_line.len() - state.cursor_rightward_position);
state.statement_buf.remove( state
state.statement_buf.len() - state.cursor_rightward_position, .statement_buf
); .remove(state.statement_buf.len() - state.cursor_rightward_position);
// re-sync unclosed syntax tracking // re-sync unclosed syntax tracking
match removed_char { match removed_char {
'"' | '`' => { '"' | '`' => {
@ -528,10 +558,7 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
} }
} }
Key::Enter => { Key::Enter => {
state state.terminal.write_line("").expect(TERMINAL_WRITE_ERROR);
.terminal
.write_line("")
.expect(TERMINAL_WRITE_ERROR);
let statement = state.statement_buf.iter().collect::<String>(); let statement = state.statement_buf.iter().collect::<String>();
let statement_result = statement.trim(); let statement_result = statement.trim();
if statement_result.starts_with('?') { if statement_result.starts_with('?') {
@ -578,10 +605,7 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
1 => { 1 => {
state.selected_history = 0; state.selected_history = 0;
state.line_number -= 1; state.line_number -= 1;
state state.terminal.clear_line().expect(TERMINAL_WRITE_ERROR);
.terminal
.clear_line()
.expect(TERMINAL_WRITE_ERROR);
prompt(state, args); prompt(state, args);
// clear stale input buffer // clear stale input buffer
state.statement_buf.clear(); state.statement_buf.clear();
@ -589,12 +613,12 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
state.in_literal = None; state.in_literal = None;
state.bracket_depth = 0; state.bracket_depth = 0;
state.curly_depth = 0; state.curly_depth = 0;
}, }
0 => {}, 0 => {}
_ => { _ => {
state.selected_history -= 1; state.selected_history -= 1;
display_history_line(state, args); display_history_line(state, args);
}, }
} }
} }
Key::ArrowLeft => { Key::ArrowLeft => {
@ -624,8 +648,7 @@ fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut Rep
#[inline(always)] #[inline(always)]
fn prompt(state: &mut ReplState, args: &CliArgs) { fn prompt(state: &mut ReplState, args: &CliArgs) {
write!(state.terminal, "{: >2}{}", state.line_number, args.prompt) write!(state.terminal, "{: >2}{}", state.line_number, args.prompt).expect(TERMINAL_WRITE_ERROR);
.expect(TERMINAL_WRITE_ERROR);
state.line_number += 1; state.line_number += 1;
state state
.terminal .terminal
@ -637,10 +660,7 @@ fn prompt(state: &mut ReplState, args: &CliArgs) {
fn display_history_line(state: &mut ReplState, args: &CliArgs) { fn display_history_line(state: &mut ReplState, args: &CliArgs) {
// get historical line // get historical line
state.line_number -= 1; state.line_number -= 1;
state state.terminal.clear_line().expect(TERMINAL_WRITE_ERROR);
.terminal
.clear_line()
.expect(TERMINAL_WRITE_ERROR);
prompt(state, args); prompt(state, args);
let new_statement = state.history[state.history.len() - state.selected_history].trim(); let new_statement = state.history[state.history.len() - state.selected_history].trim();
state state
@ -665,42 +685,62 @@ fn error_prompt(error: muss_player::PlayerError, args: &CliArgs) {
fn repl_commands(command_str: &str, state: &mut ReplState, _args: &CliArgs) { fn repl_commands(command_str: &str, state: &mut ReplState, _args: &CliArgs) {
let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect(); let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect();
match words[0] { match words[0] {
"?help" => writeln!(state.terminal, "{}", super::help::HELP_STRING).expect(TERMINAL_WRITE_ERROR), "?help" => {
"?function" | "?functions" => writeln!(state.terminal, "{}", super::help::FUNCTIONS).expect(TERMINAL_WRITE_ERROR), writeln!(state.terminal, "{}", super::help::HELP_STRING).expect(TERMINAL_WRITE_ERROR)
"?filter" | "?filters" => writeln!(state.terminal, "{}", super::help::FILTERS).expect(TERMINAL_WRITE_ERROR), }
"?sort" | "?sorter" | "?sorters" => writeln!(state.terminal, "{}", super::help::SORTERS).expect(TERMINAL_WRITE_ERROR), "?function" | "?functions" => {
"?proc" | "?procedure" | "?procedures" => writeln!(state.terminal, "{}", super::help::PROCEDURES).expect(TERMINAL_WRITE_ERROR), writeln!(state.terminal, "{}", super::help::FUNCTIONS).expect(TERMINAL_WRITE_ERROR)
}
"?filter" | "?filters" => {
writeln!(state.terminal, "{}", super::help::FILTERS).expect(TERMINAL_WRITE_ERROR)
}
"?sort" | "?sorter" | "?sorters" => {
writeln!(state.terminal, "{}", super::help::SORTERS).expect(TERMINAL_WRITE_ERROR)
}
"?proc" | "?procedure" | "?procedures" => {
writeln!(state.terminal, "{}", super::help::PROCEDURES).expect(TERMINAL_WRITE_ERROR)
}
"?list" => { "?list" => {
{ {
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state"); let mut debug_state = DEBUG_STATE
.write()
.expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::List; debug_state.debug_flag = DebugFlag::List;
} }
writeln!(state.terminal, "Listing upcoming items").expect(TERMINAL_WRITE_ERROR); writeln!(state.terminal, "Listing upcoming items").expect(TERMINAL_WRITE_ERROR);
}
},
"?skip" => { "?skip" => {
{ {
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state"); let mut debug_state = DEBUG_STATE
.write()
.expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::Skip; debug_state.debug_flag = DebugFlag::Skip;
} }
writeln!(state.terminal, "Skipping upcoming items").expect(TERMINAL_WRITE_ERROR); writeln!(state.terminal, "Skipping upcoming items").expect(TERMINAL_WRITE_ERROR);
}, }
"?normal" => { "?normal" => {
{ {
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state"); let mut debug_state = DEBUG_STATE
.write()
.expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::Normal; debug_state.debug_flag = DebugFlag::Normal;
} }
writeln!(state.terminal, "Resuming normal operation").expect(TERMINAL_WRITE_ERROR); writeln!(state.terminal, "Resuming normal operation").expect(TERMINAL_WRITE_ERROR);
}, }
"?verbose" => { "?verbose" => {
let verbose = { let verbose = {
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state"); let mut debug_state = DEBUG_STATE
.write()
.expect("Failed to get write lock for debug state");
debug_state.verbose = !debug_state.verbose; debug_state.verbose = !debug_state.verbose;
debug_state.verbose debug_state.verbose
}; };
writeln!(state.terminal, "Verbosed toggled to {}", verbose).expect(TERMINAL_WRITE_ERROR); writeln!(state.terminal, "Verbosed toggled to {}", verbose)
}, .expect(TERMINAL_WRITE_ERROR);
"?commands" => writeln!(state.terminal, "{}", super::help::REPL_COMMANDS).expect(TERMINAL_WRITE_ERROR), }
"?commands" => {
writeln!(state.terminal, "{}", super::help::REPL_COMMANDS).expect(TERMINAL_WRITE_ERROR)
}
_ => writeln!(state.terminal, "Unknown command, try ?help").expect(TERMINAL_WRITE_ERROR), _ => writeln!(state.terminal, "Unknown command, try ?help").expect(TERMINAL_WRITE_ERROR),
} }
} }