diff --git a/Cargo.lock b/Cargo.lock index fd715a0..ce1da09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 8de7225..f4ca441 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "muss" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = ["NGnius (Graham) "] 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 diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml index 1a04f60..f92ac5f 100644 --- a/interpreter/Cargo.toml +++ b/interpreter/Cargo.toml @@ -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" diff --git a/interpreter/src/debug.rs b/interpreter/src/debug.rs index 519adac..3e1c5ac 100644 --- a/interpreter/src/debug.rs +++ b/interpreter/src/debug.rs @@ -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, + ) -> Option, { interpreter: Interpreter<'a, T>, - transmuter: &'b dyn Fn( - &mut Interpreter<'a, T>, - Option>, - ) -> Option>, + 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, + ) -> Option, { /// 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>, - ) -> Option>, + 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, + ) -> Option, { - type Item = Result; + type Item = InterpreterItem; fn next(&mut self) -> Option { let next_item = self.interpreter.next(); diff --git a/interpreter/src/faye.rs b/interpreter/src/faye.rs index 03ed35f..e8cbb62 100644 --- a/interpreter/src/faye.rs +++ b/interpreter/src/faye.rs @@ -60,6 +60,12 @@ impl<'a, R: Read> Interpreter<'a, Tokenizer> { 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>, 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; + impl<'a, T> Iterator for Interpreter<'a, T> where T: TokenReader, { - type Item = Result; + type Item = InterpreterItem; fn next(&mut self) -> Option { loop { diff --git a/interpreter/src/lang/type_primitives.rs b/interpreter/src/lang/type_primitives.rs index 0afe760..80289b9 100644 --- a/interpreter/src/lang/type_primitives.rs +++ b/interpreter/src/lang/type_primitives.rs @@ -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(), diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index cabffce..b8f22ff 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -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; diff --git a/m3u8/Cargo.toml b/m3u8/Cargo.toml index a5bdfa1..639e309 100644 --- a/m3u8/Cargo.toml +++ b/m3u8/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "muss-m3u8" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = ["NGnius "] 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" } diff --git a/m3u8/src/main.rs b/m3u8/src/main.rs index e1da003..de8e74a 100644 --- a/m3u8/src/main.rs +++ b/m3u8/src/main.rs @@ -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(); diff --git a/ngnius.mps.desktop b/ngnius.muss.desktop similarity index 87% rename from ngnius.mps.desktop rename to ngnius.muss.desktop index a8e9136..66b808d 100644 --- a/ngnius.mps.desktop +++ b/ngnius.muss.desktop @@ -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 diff --git a/player/Cargo.toml b/player/Cargo.toml index 28afc8b..60b630e 100644 --- a/player/Cargo.toml +++ b/player/Cargo.toml @@ -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" } diff --git a/player/src/controller.rs b/player/src/controller.rs index 55caf5a..362e845 100644 --- a/player/src/controller.rs +++ b/player/src/controller.rs @@ -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 + Send + 'static, + I: std::iter::Iterator>, >( 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 + Send + 'static, + I: std::iter::Iterator>, >( player_gen: F, ) -> Self { diff --git a/player/src/os_controls.rs b/player/src/os_controls.rs index 096c957..8fd8cc4 100644 --- a/player/src/os_controls.rs +++ b/player/src/os_controls.rs @@ -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![]); diff --git a/player/src/player.rs b/player/src/player.rs index e323942..1ea2d8b 100644 --- a/player/src/player.rs +++ b/player/src/player.rs @@ -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>; + /// 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>> { + 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>, } -impl<'a, T: TokenReader + 'a> Player<'a, T> { - pub fn new(runner: Interpreter<'a, T>) -> Result { +impl>> Player { + pub fn new(runner: I) -> Result { let (stream, output_handle) = OutputStream::try_default().map_err(PlayerError::from_err_playback)?; Ok(Self { diff --git a/player/src/player_wrapper.rs b/player/src/player_wrapper.rs index 215273d..3abb42b 100644 --- a/player/src/player_wrapper.rs +++ b/player/src/player_wrapper.rs @@ -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>> { + player: Player, control: Receiver, event: Sender, playback: Sender, keep_alive: bool, } -impl<'a, T: TokenReader + 'a> PlayerServer<'a, T> { +impl>> PlayerServer { pub fn new( - player: Player<'a, T>, + player: Player, ctrl: Receiver, event: Sender, playback: Sender, @@ -146,7 +146,7 @@ impl<'a, T: TokenReader + 'a> PlayerServer<'a, T> { self.on_end(); } - pub fn spawn Player<'a, T> + Send + 'static>( + pub fn spawn Player + Send + 'static>( factory: F, ctrl_tx: Sender, ctrl_rx: Receiver, diff --git a/src/help.rs b/src/help.rs index 8514bfd..6300599 100644 --- a/src/help.rs +++ b/src/help.rs @@ -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."; diff --git a/src/repl.rs b/src/repl.rs index 711c04a..6e4a4b4 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -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 = 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; + struct ReplState { terminal: Term, line_number: usize, @@ -22,10 +40,25 @@ struct ReplState { selected_history: usize, current_line: Vec, cursor_rightward_position: usize, + //debug: Arc>, + list_rx: Receiver, +} + +#[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) -> 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::>(); + 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(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! { +fn read_loop(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! { prompt(state, args); loop { match state @@ -145,22 +289,22 @@ fn read_loop(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(args: &CliArgs, state: &mut ReplState, mut execute: F) } ';' => { if state.in_literal.is_none() { - state - .writer - .write(state.statement_buf.iter().collect::().as_bytes()) - .expect("Failed to write to MPS interpreter"); - execute(); - state.statement_buf.clear(); + let statement = state.statement_buf.iter().collect::(); + let statement_result = statement.trim(); + if !statement_result.starts_with('?') { + state + .writer + .write(state.statement_buf.iter().collect::().as_bytes()) + .expect(INTERPRETER_WRITE_ERROR); + execute(state, args); + state.statement_buf.clear(); + } } } '\n' => { @@ -207,7 +355,7 @@ fn read_loop(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(args: &CliArgs, state: &mut ReplState, mut execute: F) state .writer .write(state.statement_buf.iter().collect::().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(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(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(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(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::(); 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(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(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(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(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(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), } }