Add ?skip and ?list REPL commands and fix some missed names

This commit is contained in:
NGnius (Graham) 2022-07-08 21:24:46 -04:00
parent c76562fa84
commit 0202c28385
17 changed files with 322 additions and 102 deletions

9
Cargo.lock generated
View file

@ -1230,17 +1230,18 @@ dependencies = [
[[package]]
name = "muss"
version = "0.8.0"
version = "0.9.0"
dependencies = [
"clap 3.2.8",
"console",
"lazy_static 1.4.0",
"muss-interpreter",
"muss-player",
]
[[package]]
name = "muss-interpreter"
version = "0.8.0"
version = "0.9.0"
dependencies = [
"bliss-audio-symphonia",
"criterion",
@ -1256,7 +1257,7 @@ dependencies = [
[[package]]
name = "muss-m3u8"
version = "0.8.0"
version = "0.9.0"
dependencies = [
"clap 3.2.8",
"m3u8-rs",
@ -1265,7 +1266,7 @@ dependencies = [
[[package]]
name = "muss-player"
version = "0.8.0"
version = "0.9.0"
dependencies = [
"fluent-uri",
"m3u8-rs",

View file

@ -1,6 +1,6 @@
[package]
name = "muss"
version = "0.8.0"
version = "0.9.0"
edition = "2021"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
description = "Music Set Script language (MuSS)"
@ -19,17 +19,18 @@ members = [
[dependencies]
# local
muss-interpreter = { version = "0.8.0", path = "./interpreter" }
muss-interpreter = { version = "0.9.0", path = "./interpreter" }
# external
clap = { version = "3.0", features = ["derive"] }
console = { version = "0.15" }
lazy_static = { version = "1.4" }
[target.'cfg(not(target_os = "linux"))'.dependencies]
muss-player = { version = "0.8.0", path = "./player", default-features = false, features = ["mpd"] }
muss-player = { version = "0.9.0", path = "./player", default-features = false, features = ["mpd"] }
[target.'cfg(target_os = "linux")'.dependencies]
# TODO fix need to specify OS-specific dependency of mps-player
muss-player = { version = "0.8.0", path = "./player", features = ["mpris-player", "mpd"] }
muss-player = { version = "0.9.0", path = "./player", features = ["mpris-player", "mpd"] }
[profile.release]
debug = false

View file

@ -1,6 +1,6 @@
[package]
name = "muss-interpreter"
version = "0.8.0"
version = "0.9.0"
edition = "2021"
license = "LGPL-2.1-only OR GPL-3.0-only"
readme = "README.md"

View file

@ -1,31 +1,33 @@
use std::iter::Iterator;
use super::tokens::TokenReader;
use super::{InterpreterError, Interpreter, Item};
use super::{Interpreter, InterpreterItem};
/// Wrapper for InterpreterError with a built-in callback function for every iteration of the interpreter.
pub struct Debugger<'a, 'b, T>
pub struct Debugger<'a, T, F>
where
T: TokenReader,
F: Fn(
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{
interpreter: Interpreter<'a, T>,
transmuter: &'b dyn Fn(
&mut Interpreter<'a, T>,
Option<Result<Item, InterpreterError>>,
) -> Option<Result<Item, InterpreterError>>,
transmuter: F,
}
impl<'a, 'b, T> Debugger<'a, 'b, T>
impl<'a, T, F> Debugger<'a, T, F>
where
T: TokenReader,
F: Fn(
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{
/// Create a new instance of Debugger using the provided interpreter and callback.
pub fn new(
faye: Interpreter<'a, T>,
item_handler: &'b dyn Fn(
&mut Interpreter<'a, T>,
Option<Result<Item, InterpreterError>>,
) -> Option<Result<Item, InterpreterError>>,
item_handler: F,
) -> Self {
Self {
interpreter: faye,
@ -34,11 +36,15 @@ where
}
}
impl<'a, 'b, T> Iterator for Debugger<'a, 'b, T>
impl<'a, T, F> Iterator for Debugger<'a, T, F>
where
T: TokenReader,
F: Fn(
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{
type Item = Result<Item, InterpreterError>;
type Item = InterpreterItem;
fn next(&mut self) -> Option<Self::Item> {
let next_item = self.interpreter.next();

View file

@ -60,6 +60,12 @@ impl<'a, R: Read> Interpreter<'a, Tokenizer<R>> {
let tokenizer = Tokenizer::new(stream);
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 {
let tokenizer = Tokenizer::new(stream);
let vocab = LanguageDictionary::standard();
Self::with(vocab, tokenizer, callback)
}
}
impl<'a, T> Interpreter<'a, T>
@ -127,11 +133,13 @@ where
}
}
pub type InterpreterItem = Result<Item, InterpreterError>;
impl<'a, T> Iterator for Interpreter<'a, T>
where
T: TokenReader,
{
type Item = Result<Item, InterpreterError>;
type Item = InterpreterItem;
fn next(&mut self) -> Option<Self::Item> {
loop {

View file

@ -33,6 +33,7 @@ impl TypePrimitive {
}
}
/// Pretty-print the value, without type info
pub fn as_str(&self) -> String {
match self {
Self::String(s) => s.clone(),

View file

@ -263,7 +263,7 @@ pub mod tokens;
pub use context::Context;
pub use debug::Debugger;
pub use errors::InterpreterError;
pub use faye::{Interpreter, InterpreterEvent};
pub use faye::{Interpreter, InterpreterEvent, InterpreterItem};
//pub use interpretor::{interpretor, Interpretor};
pub use item::Item;
//pub(crate) use item::ItemRuntimeUtil;

View file

@ -1,6 +1,6 @@
[package]
name = "muss-m3u8"
version = "0.8.0"
version = "0.9.0"
edition = "2021"
authors = ["NGnius <ngniusness@gmail.com>"]
description = "Minimal playlist generator for MPS files"
@ -13,7 +13,7 @@ readme = "README.md"
[dependencies]
# local
muss-interpreter = { version = "0.8.0", path = "../interpreter" }
muss-interpreter = { version = "0.9.0", path = "../interpreter" }
# external
clap = { version = "3.0", features = ["derive"] }
m3u8-rs = { version = "^3.0.0" }

View file

@ -11,7 +11,7 @@ use std::path::Path;
use m3u8_rs::{MediaPlaylist, MediaSegment};
use mps_interpreter::{Interpreter, Item};
use muss_interpreter::{Interpreter, Item};
fn main() {
let args = cli::parse();

View file

@ -1,10 +1,10 @@
[Desktop Entry]
Categories=AudioVideo;Player;
Comment=
Exec=mps %U
Exec=muss %U
GenericName=Music Player
Icon=utilities-terminal
Name=MPS
Name=Muss
StartupNotify=false
Terminal=true
Type=Application

View file

@ -1,6 +1,6 @@
[package]
name = "muss-player"
version = "0.8.0"
version = "0.9.0"
edition = "2021"
license = "LGPL-2.1-only OR GPL-3.0-only"
readme = "README.md"
@ -12,7 +12,7 @@ fluent-uri = { version = "^0.1" }
mpd = { version = "0.0.12", optional = true }
# local
muss-interpreter = { path = "../interpreter", version = "0.8.0" }
muss-interpreter = { path = "../interpreter", version = "0.9.0" }
[target.'cfg(target_os = "linux")'.dependencies]
#dbus = { version = "^0.9" }

View file

@ -1,7 +1,7 @@
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::JoinHandle;
use muss_interpreter::tokens::TokenReader;
use muss_interpreter::{Item, InterpreterError};
use super::os_controls::SystemControlWrapper;
use super::player_wrapper::{ControlAction, PlayerServer, PlayerAction};
@ -20,9 +20,8 @@ pub struct Controller {
impl Controller {
pub fn create<
'a,
F: FnOnce() -> Player<'a, T> + Send + 'static,
T: TokenReader + 'static,
F: FnOnce() -> Player<I> + Send + 'static,
I: std::iter::Iterator<Item=Result<Item, InterpreterError>>,
>(
player_gen: F,
) -> Self {
@ -48,9 +47,8 @@ impl Controller {
}
pub fn create_repl<
'a,
F: FnOnce() -> Player<'a, T> + Send + 'static,
T: TokenReader + 'static,
F: FnOnce() -> Player<I> + Send + 'static,
I: std::iter::Iterator<Item=Result<Item, InterpreterError>>,
>(
player_gen: F,
) -> Self {

View file

@ -59,7 +59,7 @@ impl SystemControlWrapper {
self.dbus_ctrl = Some(tx);
let control_clone1 = self.control.clone();
self.dbus_handle = Some(std::thread::spawn(move || {
let dbus_conn = MprisPlayer::new("mps".into(), "mps".into(), "ngnius.mps".into());
let dbus_conn = MprisPlayer::new("muss".into(), "muss".into(), "ngnius.muss".into());
//let (msg_tx, msg_rx) = channel();
// dbus setup
//self.dbus_conn.set_supported_mime_types(vec![]);

View file

@ -10,16 +10,18 @@ use mpd::{Client, Song, error};
use super::uri::Uri;
use muss_interpreter::{tokens::TokenReader, Interpreter, Item};
use muss_interpreter::{Item, InterpreterError};
//use super::PlaybackError;
use super::PlayerError;
use super::UriError;
//type Interpreter = std::iter::Iterator<Item=Result<Item, InterpreterError>>;
/// Playback functionality for a script.
/// This takes the output of the runner and plays or saves it.
pub struct Player<'a, T: TokenReader + 'a> {
runner: Interpreter<'a, T>,
pub struct Player<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> {
runner: I,
sink: Sink,
#[allow(dead_code)]
output_stream: OutputStream, // this is required for playback, so it must live as long as this struct instance
@ -28,8 +30,8 @@ pub struct Player<'a, T: TokenReader + 'a> {
mpd_connection: Option<Client<std::net::TcpStream>>,
}
impl<'a, T: TokenReader + 'a> Player<'a, T> {
pub fn new(runner: Interpreter<'a, T>) -> Result<Self, PlayerError> {
impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> Player<I> {
pub fn new(runner: I) -> Result<Self, PlayerError> {
let (stream, output_handle) =
OutputStream::try_default().map_err(PlayerError::from_err_playback)?;
Ok(Self {

View file

@ -1,8 +1,8 @@
use std::sync::mpsc::{Receiver, Sender};
use std::{thread, thread::JoinHandle};
use muss_interpreter::tokens::TokenReader;
use muss_interpreter::Item;
//use muss_interpreter::tokens::TokenReader;
use muss_interpreter::{Item, InterpreterError};
use super::Player;
use super::PlayerError;
@ -11,17 +11,17 @@ use super::PlayerError;
/// This allows for message passing between the player and controller.
///
/// You will probably never directly interact with this, instead using Controller to communicate.
pub struct PlayerServer<'a, T: TokenReader + 'a> {
player: Player<'a, T>,
pub struct PlayerServer<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> {
player: Player<I>,
control: Receiver<ControlAction>,
event: Sender<PlayerAction>,
playback: Sender<PlaybackAction>,
keep_alive: bool,
}
impl<'a, T: TokenReader + 'a> PlayerServer<'a, T> {
impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> PlayerServer<I> {
pub fn new(
player: Player<'a, T>,
player: Player<I>,
ctrl: Receiver<ControlAction>,
event: Sender<PlayerAction>,
playback: Sender<PlaybackAction>,
@ -146,7 +146,7 @@ impl<'a, T: TokenReader + 'a> PlayerServer<'a, T> {
self.on_end();
}
pub fn spawn<F: FnOnce() -> Player<'a, T> + Send + 'static>(
pub fn spawn<F: FnOnce() -> Player<I> + Send + 'static>(
factory: F,
ctrl_tx: Sender<ControlAction>,
ctrl_rx: Receiver<ControlAction>,

View file

@ -2,7 +2,8 @@
pub const HELP_STRING: &str =
"This language is all about iteration. Almost everything is an iterator or operates on iterators. By default, any operation which is not an assignment will cause the script runner to handle (play/save) the items which that statement contains.
To view the currently-supported operations, try ?functions, ?filters, ?procedures, or ?sorters";
To view Muss syntax, try ?functions, ?filters, ?procedures, or ?sorters
To view REPL commands, try ?commands";
pub const FUNCTIONS: &str =
"FUNCTIONS (?functions)
@ -161,3 +162,26 @@ Comma-separated procedure steps will be executed sequentially (like a for loop i
file(filepath) -- e.g. file(`~/Music/Romantic Traffic.flac`)
Load an item from file, populating the item with the song's tags.";
pub const REPL_COMMANDS: &str =
"COMMANDS (?commands)
REPL-specific operations to help with writing Muss scripts: ?command
help
filters
functions
procedures
sorters
Show various help strings for the Muss language and syntax.
list
List remaining items to be played instead of playing them. If no items are about to be played, list all items in the next statement. In playlist mode, only the latter functionality applies.
skip
Skip remaining items to be played. If no items are about to be played, skip all items in the next statement. This is a no-output version of ?list.
normal
Cancel any active ?skip or ?list operation.
verbose
Toggle verbose messages, like extra information on items in ?list and other goodies.";

View file

@ -1,15 +1,33 @@
//! Read, Execute, Print Loop functionality
use std::sync::{RwLock};
use std::sync::mpsc::{self, Receiver};
use std::io::{self, Write};
use lazy_static::lazy_static;
use console::{Key, Term};
use muss_interpreter::Interpreter;
use muss_interpreter::{Interpreter, Debugger, Item, InterpreterEvent, InterpreterError};
use muss_interpreter::lang::TypePrimitive;
use muss_player::{Controller, Player};
use super::channel_io::{channel_io, ChannelWriter};
use super::cli::CliArgs;
lazy_static! {
static ref DEBUG_STATE: RwLock<DebugState> = RwLock::new(
DebugState {
debug_flag: DebugFlag::Normal,
verbose: false,
}
);
}
const TERMINAL_WRITE_ERROR: &str = "Failed to write to terminal output";
const INTERPRETER_WRITE_ERROR: &str = "Failed to write to interpreter";
type DebugItem = Result<Item, String>;
struct ReplState {
terminal: Term,
line_number: usize,
@ -22,10 +40,25 @@ struct ReplState {
selected_history: usize,
current_line: Vec<char>,
cursor_rightward_position: usize,
//debug: Arc<RwLock<DebugState>>,
list_rx: Receiver<DebugItem>,
}
#[derive(Clone)]
struct DebugState {
debug_flag: DebugFlag,
verbose: bool,
}
#[derive(Copy, Clone)]
enum DebugFlag {
Skip,
List,
Normal
}
impl ReplState {
fn new(chan_writer: ChannelWriter, term: Term) -> Self {
fn new(chan_writer: ChannelWriter, term: Term, debug_list: Receiver<DebugItem>) -> Self {
Self {
terminal: term,
line_number: 0,
@ -38,13 +71,92 @@ impl ReplState {
selected_history: 0,
current_line: Vec::new(),
cursor_rightward_position: 0,
/*debug: Arc::new(RwLock::new(DebugState {
debug_flag: DebugFlag::Normal,
})),*/
list_rx: debug_list,
}
}
}
fn interpreter_event_callback<'a, T: muss_interpreter::tokens::TokenReader>(_interpreter: &mut Interpreter<'a, T>, event: InterpreterEvent) -> Result<(), InterpreterError> {
match event {
InterpreterEvent::StatementComplete => {
if let Ok(mut d_state) = DEBUG_STATE.write() {
d_state.debug_flag = DebugFlag::Normal;
}
},
_ => {},
}
Ok(())
}
fn pretty_print_item(item: &Item, terminal: &mut Term, args: &CliArgs, verbose: bool) {
if verbose {
writeln!(terminal, "I{}--\\/-- `{}` --\\/--", args.prompt,
item.field("title").unwrap_or(&TypePrimitive::Empty).as_str()
).expect(TERMINAL_WRITE_ERROR);
let mut fields: Vec<&_> = item.iter().collect();
fields.sort();
for field in fields {
if field != "title" {
writeln!(terminal, "I{} {}: `{}`",
args.prompt, field,
item.field(field).unwrap_or(&TypePrimitive::Empty).as_str()
).expect(TERMINAL_WRITE_ERROR);
}
}
} else {
writeln!(terminal, "I{}`{}` by `{}`", args.prompt,
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);
}
fn handle_list_rx(state: &mut ReplState, args: &CliArgs) {
//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();
for item in state.list_rx.try_iter() {
match item {
Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose),
Err(e) => error_prompt(
muss_player::PlayerError::Playback(
muss_player::PlaybackError::from_err(e)
), args),
}
}
let flag = d_state.debug_flag;
match flag {
DebugFlag::List => {
while let Ok(item) = state.list_rx.recv() {
match item {
Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose),
Err(e) => error_prompt(
muss_player::PlayerError::Playback(
muss_player::PlaybackError::from_err(e)
), args),
}
// stop listing if no longer in list mode
let flag = if let Ok(d_state) = DEBUG_STATE.read() {
d_state.debug_flag
} else {
DebugFlag::Normal
};
match flag {
DebugFlag::List => {},
_ => break,
}
}
},
_ => {}
}
}
pub fn repl(args: CliArgs) {
let term = Term::stdout();
term.set_title("mps");
term.set_title("muss");
let (writer, reader) = channel_io();
let volume = args.volume.clone();
let mpd = match args.mpd.clone().map(|a| muss_player::mpd_connection(a.parse().unwrap())).transpose() {
@ -54,10 +166,41 @@ pub fn repl(args: CliArgs) {
return;
}
};
let (list_tx, list_rx) = mpsc::channel();
let mut state = ReplState::new(writer, term, list_rx);
let player_builder = move || {
let runner = Interpreter::with_stream(reader);
let runner = Interpreter::with_stream_and_callback(reader,
&interpreter_event_callback);
let debugger = Debugger::new(runner, move |interpretor, item| {
let flag = if let Ok(d_state) = DEBUG_STATE.read() {
d_state.debug_flag
} else {
DebugFlag::Normal
};
match flag {
DebugFlag::Normal => item,
DebugFlag::Skip => {
while let Some(_) = interpretor.next() {
// NOTE: recursion occurs here
}
None
},
DebugFlag::List => {
if let Some(x) = item {
list_tx.send(x.map_err(|e| e.to_string())).unwrap_or(());
while let Some(x) = interpretor.next() {
// NOTE: recursion occurs here
// in most cases this will never be a case of Some(...) because
// recursive calls to this function intercept it first and return None
list_tx.send(x.map_err(|e| e.to_string())).unwrap_or(());
}
}
None
}
}
});
let mut player = Player::new(runner).unwrap();
let mut player = Player::new(debugger).unwrap();
if let Some(vol) = volume {
player.set_volume(vol);
}
@ -66,7 +209,6 @@ pub fn repl(args: CliArgs) {
}
player
};
let mut state = ReplState::new(writer, term);
if let Some(playlist_file) = &args.playlist {
if args.mpd.is_some() {
writeln!(
@ -74,21 +216,21 @@ pub fn repl(args: CliArgs) {
"Playlist mode (output: `{}` & MPD)",
playlist_file
)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
} else {
writeln!(
state.terminal,
"Playlist mode (output: `{}`)",
playlist_file
)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
}
let mut player = player_builder();
let mut playlist_writer =
io::BufWriter::new(std::fs::File::create(playlist_file).unwrap_or_else(|_| {
panic!("Abort: Cannot create writeable file `{}`", playlist_file)
}));
read_loop(&args, &mut state, || {
read_loop(&args, &mut state, |state, args| {
match player.save_m3u8(&mut playlist_writer) {
Ok(_) => {}
Err(e) => {
@ -102,17 +244,18 @@ pub fn repl(args: CliArgs) {
playlist_writer
.flush()
.expect("Failed to flush playlist to file");
handle_list_rx(state, args);
});
} else {
if args.mpd.is_some() {
writeln!(state.terminal, "Playback mode (output: audio device & MPD)")
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
} else {
writeln!(state.terminal, "Playback mode (output: audio device)")
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
}
let ctrl = Controller::create_repl(player_builder);
read_loop(&args, &mut state, || {
read_loop(&args, &mut state, |state, args| {
if args.wait {
match ctrl.wait_for_empty() {
Ok(_) => {}
@ -130,11 +273,12 @@ pub fn repl(args: CliArgs) {
had_err = new_had_err;
}
}
handle_list_rx(state, args);
});
}
}
fn read_loop<F: FnMut()>(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);
loop {
match state
@ -145,22 +289,22 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
Key::Char(read_c) => {
if state.cursor_rightward_position == 0 {
write!(state.terminal, "{}", read_c)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
state.statement_buf.push(read_c);
state.current_line.push(read_c);
} else {
write!(state.terminal, "{}", read_c)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
for i in state.current_line.len() - state.cursor_rightward_position
..state.current_line.len()
{
write!(state.terminal, "{}", state.current_line[i])
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
}
state
.terminal
.move_cursor_left(state.cursor_rightward_position)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
state.statement_buf.insert(
state.statement_buf.len() - state.cursor_rightward_position,
read_c,
@ -194,12 +338,16 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
}
';' => {
if state.in_literal.is_none() {
state
.writer
.write(state.statement_buf.iter().collect::<String>().as_bytes())
.expect("Failed to write to MPS interpreter");
execute();
state.statement_buf.clear();
let statement = state.statement_buf.iter().collect::<String>();
let statement_result = statement.trim();
if !statement_result.starts_with('?') {
state
.writer
.write(state.statement_buf.iter().collect::<String>().as_bytes())
.expect(INTERPRETER_WRITE_ERROR);
execute(state, args);
state.statement_buf.clear();
}
}
}
'\n' => {
@ -207,7 +355,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
let statement_result = statement.trim();
if statement_result.starts_with('?') {
//println!("Got {}", statement_result.unwrap());
repl_commands(statement_result);
repl_commands(statement_result, state, args);
state.statement_buf.clear();
} else if state.bracket_depth == 0
&& state.in_literal.is_none()
@ -217,8 +365,8 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.writer
.write(state.statement_buf.iter().collect::<String>().as_bytes())
.expect("Failed to write to MPS interpreter");
execute();
.expect(INTERPRETER_WRITE_ERROR);
execute(state, args);
state.statement_buf.clear();
}
prompt(state, args);
@ -264,9 +412,9 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.terminal
.move_cursor_left(1)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
write!(state.terminal, " ")
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
state
.terminal
.flush()
@ -274,7 +422,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.terminal
.move_cursor_left(1)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
}
}
}
@ -316,18 +464,18 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.terminal
.move_cursor_left(1)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
for i in state.current_line.len() - state.cursor_rightward_position
..state.current_line.len()
{
write!(state.terminal, "{}", state.current_line[i])
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
}
write!(state.terminal, " ").expect("Failed to write to terminal output");
write!(state.terminal, " ").expect(TERMINAL_WRITE_ERROR);
state
.terminal
.move_cursor_left(state.cursor_rightward_position + 1)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
}
}
}
@ -335,12 +483,12 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.terminal
.write_line("")
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
let statement = state.statement_buf.iter().collect::<String>();
let statement_result = statement.trim();
if statement_result.starts_with('?') {
//println!("Got {}", statement_result.unwrap());
repl_commands(statement_result);
repl_commands(statement_result, state, args);
state.statement_buf.clear();
} else if state.bracket_depth == 0
&& state.in_literal.is_none()
@ -352,7 +500,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
.writer
.write(complete_statement.as_bytes())
.expect("Failed to write to MPS interpreter");
execute();
execute(state, args);
state.statement_buf.clear();
}
state.statement_buf.push('\n');
@ -387,7 +535,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.terminal
.clear_line()
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
prompt(state, args);
// clear stale input buffer
state.statement_buf.clear();
@ -402,7 +550,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.terminal
.move_cursor_left(1)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
state.cursor_rightward_position += 1;
}
}
@ -411,7 +559,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state
.terminal
.move_cursor_right(1)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
state.cursor_rightward_position -= 1;
}
}
@ -425,7 +573,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
#[inline(always)]
fn prompt(state: &mut ReplState, args: &CliArgs) {
write!(state.terminal, "{}{}", state.line_number, args.prompt)
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
state.line_number += 1;
state
.terminal
@ -440,13 +588,13 @@ fn display_history_line(state: &mut ReplState, args: &CliArgs) {
state
.terminal
.clear_line()
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
prompt(state, args);
let new_statement = state.history[state.history.len() - state.selected_history].trim();
state
.terminal
.write(new_statement.as_bytes())
.expect("Failed to write to terminal output");
.expect(TERMINAL_WRITE_ERROR);
// clear stale input buffer
state.statement_buf.clear();
state.current_line.clear();
@ -462,14 +610,45 @@ fn error_prompt(error: muss_player::PlayerError, args: &CliArgs) {
eprintln!("E{}{}", args.prompt, error);
}
fn repl_commands(command_str: &str) {
fn repl_commands(command_str: &str, state: &mut ReplState, _args: &CliArgs) {
let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect();
match words[0] {
"?help" => println!("{}", super::help::HELP_STRING),
"?function" | "?functions" => println!("{}", super::help::FUNCTIONS),
"?filter" | "?filters" => println!("{}", super::help::FILTERS),
"?sort" | "?sorter" | "?sorters" => println!("{}", super::help::SORTERS),
"?proc" | "?procedure" | "?procedures" => println!("{}", super::help::PROCEDURES),
_ => println!("Unknown command, try ?help"),
"?help" => writeln!(state.terminal, "{}", super::help::HELP_STRING).expect(TERMINAL_WRITE_ERROR),
"?function" | "?functions" => 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" => {
{
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::List;
}
writeln!(state.terminal, "Listing upcoming items").expect(TERMINAL_WRITE_ERROR);
},
"?skip" => {
{
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::Skip;
}
writeln!(state.terminal, "Skipping upcoming items").expect(TERMINAL_WRITE_ERROR);
},
"?normal" => {
{
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::Normal;
}
writeln!(state.terminal, "Resuming normal operation").expect(TERMINAL_WRITE_ERROR);
},
"?verbose" => {
let verbose = {
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
};
writeln!(state.terminal, "Verbosed toggled to {}", verbose).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),
}
}