2022-01-03 22:53:57 +00:00
|
|
|
//! Read, Execute, Print Loop functionality
|
|
|
|
|
2022-03-05 01:37:59 +00:00
|
|
|
use std::io::{self, Write};
|
|
|
|
|
|
|
|
use console::{Term, Key};
|
2022-01-03 22:53:57 +00:00
|
|
|
|
|
|
|
use mps_interpreter::MpsRunner;
|
2022-01-04 02:15:28 +00:00
|
|
|
use mps_player::{MpsController, MpsPlayer};
|
2022-01-03 22:53:57 +00:00
|
|
|
|
2022-01-04 01:15:19 +00:00
|
|
|
use super::channel_io::{channel_io, ChannelWriter};
|
2022-01-04 02:15:28 +00:00
|
|
|
use super::cli::CliArgs;
|
2022-01-04 01:15:19 +00:00
|
|
|
|
|
|
|
struct ReplState {
|
2022-03-05 01:37:59 +00:00
|
|
|
terminal: Term,
|
2022-01-04 01:15:19 +00:00
|
|
|
line_number: usize,
|
2022-03-05 01:37:59 +00:00
|
|
|
statement_buf: Vec<char>,
|
2022-01-04 01:15:19 +00:00
|
|
|
writer: ChannelWriter,
|
2022-01-17 02:00:00 +00:00
|
|
|
in_literal: Option<char>,
|
|
|
|
bracket_depth: usize,
|
2022-02-24 14:47:37 +00:00
|
|
|
curly_depth: usize,
|
2022-03-05 01:37:59 +00:00
|
|
|
history: Vec<String>,
|
|
|
|
selected_history: usize,
|
|
|
|
current_line: Vec<char>,
|
|
|
|
cursor_rightward_position: usize,
|
2022-01-04 01:15:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ReplState {
|
2022-03-05 01:37:59 +00:00
|
|
|
fn new(chan_writer: ChannelWriter, term: Term) -> Self {
|
2022-01-04 01:15:19 +00:00
|
|
|
Self {
|
2022-03-05 01:37:59 +00:00
|
|
|
terminal: term,
|
2022-01-04 01:15:19 +00:00
|
|
|
line_number: 0,
|
|
|
|
statement_buf: Vec::new(),
|
|
|
|
writer: chan_writer,
|
2022-01-17 02:00:00 +00:00
|
|
|
in_literal: None,
|
|
|
|
bracket_depth: 0,
|
2022-02-24 14:47:37 +00:00
|
|
|
curly_depth: 0,
|
2022-03-05 01:37:59 +00:00
|
|
|
history: Vec::new(),
|
|
|
|
selected_history: 0,
|
|
|
|
current_line: Vec::new(),
|
|
|
|
cursor_rightward_position: 0,
|
2022-01-04 01:15:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-03 22:53:57 +00:00
|
|
|
|
|
|
|
pub fn repl(args: CliArgs) {
|
2022-03-05 01:37:59 +00:00
|
|
|
let term = Term::stdout();
|
|
|
|
term.set_title("mps");
|
2022-01-04 01:15:19 +00:00
|
|
|
let (writer, reader) = channel_io();
|
2022-02-03 22:04:01 +00:00
|
|
|
let volume = args.volume.clone();
|
2022-01-03 22:53:57 +00:00
|
|
|
let player_builder = move || {
|
|
|
|
let runner = MpsRunner::with_stream(reader);
|
2022-01-31 14:22:36 +00:00
|
|
|
|
2022-02-03 22:04:01 +00:00
|
|
|
let player = MpsPlayer::new(runner).unwrap();
|
|
|
|
if let Some(vol) = volume {
|
|
|
|
player.set_volume(vol);
|
|
|
|
}
|
|
|
|
player
|
2022-01-03 22:53:57 +00:00
|
|
|
};
|
2022-03-05 01:37:59 +00:00
|
|
|
let mut state = ReplState::new(writer, term);
|
2022-01-03 22:53:57 +00:00
|
|
|
if let Some(playlist_file) = &args.playlist {
|
2022-03-05 01:37:59 +00:00
|
|
|
writeln!(state.terminal, "Playlist mode (output: `{}`)", playlist_file).expect("Failed to write to terminal output");
|
2022-01-03 22:53:57 +00:00
|
|
|
let mut player = player_builder();
|
2022-01-31 14:22:36 +00:00
|
|
|
let mut playlist_writer =
|
|
|
|
io::BufWriter::new(std::fs::File::create(playlist_file).unwrap_or_else(|_| {
|
|
|
|
panic!("Abort: Cannot create writeable file `{}`", playlist_file)
|
|
|
|
}));
|
2022-01-04 01:15:19 +00:00
|
|
|
read_loop(&args, &mut state, || {
|
|
|
|
match player.save_m3u8(&mut playlist_writer) {
|
2022-01-04 02:15:28 +00:00
|
|
|
Ok(_) => {}
|
2022-01-04 18:00:05 +00:00
|
|
|
Err(e) => {
|
2022-01-27 01:15:00 +00:00
|
|
|
error_prompt(e, &args);
|
2022-01-04 18:00:05 +00:00
|
|
|
// consume any further errors (this shouldn't actually write anything)
|
|
|
|
while let Err(e) = player.save_m3u8(&mut playlist_writer) {
|
2022-01-27 01:15:00 +00:00
|
|
|
error_prompt(e, &args);
|
2022-01-04 18:00:05 +00:00
|
|
|
}
|
2022-01-24 21:12:29 +00:00
|
|
|
}
|
2022-01-03 22:53:57 +00:00
|
|
|
}
|
2022-01-04 02:15:28 +00:00
|
|
|
playlist_writer
|
|
|
|
.flush()
|
|
|
|
.expect("Failed to flush playlist to file");
|
2022-01-04 01:15:19 +00:00
|
|
|
});
|
2022-01-03 22:53:57 +00:00
|
|
|
} else {
|
2022-03-05 01:37:59 +00:00
|
|
|
writeln!(state.terminal, "Playback mode (output: audio device)").expect("Failed to write to terminal output");
|
2022-01-03 22:53:57 +00:00
|
|
|
let ctrl = MpsController::create_repl(player_builder);
|
2022-01-04 01:15:19 +00:00
|
|
|
read_loop(&args, &mut state, || {
|
|
|
|
if args.wait {
|
|
|
|
match ctrl.wait_for_empty() {
|
2022-01-04 02:15:28 +00:00
|
|
|
Ok(_) => {}
|
2022-01-27 01:15:00 +00:00
|
|
|
Err(e) => error_prompt(e, &args),
|
2022-01-04 01:15:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-01-04 18:00:05 +00:00
|
|
|
// consume all incoming errors
|
|
|
|
let mut had_err = true;
|
|
|
|
while had_err {
|
|
|
|
let mut new_had_err = false;
|
|
|
|
for e in ctrl.check_ack() {
|
2022-01-27 01:15:00 +00:00
|
|
|
error_prompt(e, &args);
|
2022-01-04 18:00:05 +00:00
|
|
|
new_had_err = true;
|
|
|
|
}
|
|
|
|
had_err = new_had_err;
|
2022-01-04 01:15:19 +00:00
|
|
|
}
|
2022-01-03 22:53:57 +00:00
|
|
|
}
|
2022-01-04 01:15:19 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! {
|
2022-03-05 01:37:59 +00:00
|
|
|
prompt(state, args);
|
2022-01-04 01:15:19 +00:00
|
|
|
loop {
|
2022-03-05 01:37:59 +00:00
|
|
|
match state.terminal.read_key().expect("Failed to read terminal input") {
|
|
|
|
Key::Char(read_c) => {
|
|
|
|
if state.cursor_rightward_position == 0 {
|
|
|
|
write!(state.terminal, "{}", read_c).expect("Failed to write to terminal output");
|
|
|
|
state.statement_buf.push(read_c);
|
|
|
|
state.current_line.push(read_c);
|
2022-01-17 02:00:00 +00:00
|
|
|
} else {
|
2022-03-05 01:37:59 +00:00
|
|
|
write!(state.terminal, "{}", read_c).expect("Failed to write to terminal output");
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
state.terminal.move_cursor_left(state.cursor_rightward_position).expect("Failed to write to terminal output");
|
|
|
|
state.statement_buf.insert(state.statement_buf.len() - state.cursor_rightward_position, read_c);
|
|
|
|
state.current_line.insert(state.current_line.len() - state.cursor_rightward_position, read_c);
|
2022-01-17 02:00:00 +00:00
|
|
|
}
|
2022-03-05 01:37:59 +00:00
|
|
|
match read_c {
|
|
|
|
'"' | '`' => {
|
|
|
|
if let Some(c) = state.in_literal {
|
|
|
|
if c == read_c {
|
|
|
|
state.in_literal = None;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state.in_literal = Some(read_c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
'(' => state.bracket_depth += 1,
|
|
|
|
')' => if state.bracket_depth != 0 { state.bracket_depth -= 1 },
|
|
|
|
'{' => state.curly_depth += 1,
|
|
|
|
'}' => if state.curly_depth != 0 { state.curly_depth -= 1 },
|
|
|
|
';' => {
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
'\n' => {
|
|
|
|
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);
|
|
|
|
state.statement_buf.clear();
|
|
|
|
} else if state.bracket_depth == 0 && state.in_literal.is_none() && state.curly_depth == 0 {
|
|
|
|
state.statement_buf.push(';');
|
|
|
|
state
|
|
|
|
.writer
|
|
|
|
.write(state.statement_buf.iter().collect::<String>().as_bytes())
|
|
|
|
.expect("Failed to write to MPS interpreter");
|
|
|
|
execute();
|
|
|
|
state.statement_buf.clear();
|
|
|
|
}
|
|
|
|
prompt(state, args);
|
|
|
|
}
|
|
|
|
_ => {}
|
2022-01-17 02:00:00 +00:00
|
|
|
}
|
2022-03-05 01:37:59 +00:00
|
|
|
},
|
|
|
|
Key::Backspace => {
|
2022-03-11 01:52:48 +00:00
|
|
|
if state.cursor_rightward_position == 0 {
|
|
|
|
if let Some(c) = state.statement_buf.pop() {
|
2022-03-16 15:56:59 +00:00
|
|
|
// re-sync syntax tracking
|
|
|
|
match c {
|
|
|
|
'"' | '`' => {
|
|
|
|
if let Some(c2) = state.in_literal {
|
|
|
|
if c == c2 {
|
|
|
|
state.in_literal = None;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state.in_literal = Some(c);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'(' => if state.bracket_depth != 0 { state.bracket_depth -= 1 },
|
|
|
|
')' => state.bracket_depth += 1,
|
|
|
|
'{' => if state.curly_depth != 0 { state.curly_depth -= 1 },
|
|
|
|
'}' => state.curly_depth += 1,
|
|
|
|
_ => {},
|
|
|
|
}
|
2022-03-11 01:52:48 +00:00
|
|
|
match c {
|
|
|
|
'\n' | '\r' => {
|
|
|
|
// another line, cannot backspace that far
|
|
|
|
state.statement_buf.push(c);
|
2022-03-16 15:56:59 +00:00
|
|
|
},
|
2022-03-11 01:52:48 +00:00
|
|
|
_ => {
|
|
|
|
state.current_line.pop();
|
|
|
|
state.terminal.move_cursor_left(1).expect("Failed to write to terminal output");
|
|
|
|
write!(state.terminal, " ").expect("Failed to write to terminal output");
|
|
|
|
state.terminal.flush().expect("Failed to flush terminal output");
|
|
|
|
state.terminal.move_cursor_left(1).expect("Failed to write to terminal output");
|
|
|
|
}
|
2022-03-05 01:37:59 +00:00
|
|
|
}
|
2022-03-11 01:52:48 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if state.current_line.len() != state.cursor_rightward_position {
|
|
|
|
// if not at start of line
|
|
|
|
let removed_char = state.current_line.remove(state.current_line.len()-state.cursor_rightward_position-1);
|
|
|
|
state.statement_buf.remove(state.statement_buf.len()-state.cursor_rightward_position-1);
|
|
|
|
// re-sync unclosed syntax tracking
|
|
|
|
match removed_char {
|
|
|
|
'"' | '`' => {
|
2022-03-16 15:56:59 +00:00
|
|
|
if let Some(c2) = state.in_literal {
|
|
|
|
if removed_char == c2 {
|
2022-03-11 01:52:48 +00:00
|
|
|
state.in_literal = None;
|
|
|
|
}
|
2022-03-16 15:56:59 +00:00
|
|
|
} else {
|
|
|
|
state.in_literal = Some(removed_char);
|
2022-03-11 01:52:48 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'(' => if state.bracket_depth != 0 { state.bracket_depth -= 1 },
|
|
|
|
')' => state.bracket_depth += 1,
|
|
|
|
'{' => if state.curly_depth != 0 { state.curly_depth -= 1 },
|
|
|
|
'}' => state.curly_depth += 1,
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
// re-print end of line to remove character in middle
|
|
|
|
state.terminal.move_cursor_left(1).expect("Failed to write to terminal output");
|
|
|
|
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");
|
2022-03-05 01:37:59 +00:00
|
|
|
}
|
2022-03-11 01:52:48 +00:00
|
|
|
write!(state.terminal, " ").expect("Failed to write to terminal output");
|
|
|
|
state.terminal.move_cursor_left(state.cursor_rightward_position + 1).expect("Failed to write to terminal output");
|
2022-03-05 01:37:59 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-11 01:52:48 +00:00
|
|
|
|
2022-03-05 01:37:59 +00:00
|
|
|
},
|
|
|
|
Key::Enter => {
|
|
|
|
state.terminal.write_line("").expect("Failed to write to terminal output");
|
|
|
|
let statement = state.statement_buf.iter().collect::<String>();
|
|
|
|
let statement_result = statement.trim();
|
|
|
|
if statement_result.starts_with('?') {
|
2022-01-27 01:15:00 +00:00
|
|
|
//println!("Got {}", statement_result.unwrap());
|
2022-03-05 01:37:59 +00:00
|
|
|
repl_commands(statement_result);
|
2022-01-17 02:00:00 +00:00
|
|
|
state.statement_buf.clear();
|
2022-02-24 14:47:37 +00:00
|
|
|
} else if state.bracket_depth == 0 && state.in_literal.is_none() && state.curly_depth == 0 {
|
2022-03-05 01:37:59 +00:00
|
|
|
state.statement_buf.push(';');
|
|
|
|
let complete_statement = state.statement_buf.iter().collect::<String>();
|
2022-01-04 02:15:28 +00:00
|
|
|
state
|
|
|
|
.writer
|
2022-03-05 01:37:59 +00:00
|
|
|
.write(complete_statement.as_bytes())
|
2022-01-04 02:15:28 +00:00
|
|
|
.expect("Failed to write to MPS interpreter");
|
|
|
|
execute();
|
2022-01-17 02:00:00 +00:00
|
|
|
state.statement_buf.clear();
|
2022-01-04 02:15:28 +00:00
|
|
|
}
|
2022-03-05 01:37:59 +00:00
|
|
|
state.statement_buf.push('\n');
|
|
|
|
state.cursor_rightward_position = 0;
|
|
|
|
// history
|
|
|
|
let last_line = state.current_line.iter().collect::<String>();
|
|
|
|
state.current_line.clear();
|
|
|
|
if !last_line.is_empty() && ((!state.history.is_empty() && state.history[state.history.len()-1] != last_line) || state.history.is_empty()) {
|
|
|
|
state.history.push(last_line);
|
|
|
|
}
|
|
|
|
state.selected_history = 0;
|
|
|
|
|
|
|
|
prompt(state, args);
|
|
|
|
},
|
|
|
|
Key::ArrowUp => {
|
|
|
|
if state.selected_history != state.history.len() {
|
|
|
|
state.selected_history += 1;
|
|
|
|
display_history_line(state, args);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Key::ArrowDown => {
|
|
|
|
if state.selected_history > 1 {
|
|
|
|
state.selected_history -= 1;
|
|
|
|
display_history_line(state, args);
|
|
|
|
} else if state.selected_history == 1 {
|
|
|
|
state.selected_history = 0;
|
|
|
|
state.line_number -= 1;
|
|
|
|
state.terminal.clear_line().expect("Failed to write to terminal output");
|
|
|
|
prompt(state, args);
|
|
|
|
// clear stale input buffer
|
|
|
|
state.statement_buf.clear();
|
|
|
|
state.current_line.clear();
|
|
|
|
state.in_literal = None;
|
|
|
|
state.bracket_depth = 0;
|
|
|
|
state.curly_depth = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Key::ArrowLeft => {
|
|
|
|
if state.current_line.len() > state.cursor_rightward_position {
|
|
|
|
state.terminal.move_cursor_left(1).expect("Failed to write to terminal output");
|
|
|
|
state.cursor_rightward_position += 1;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Key::ArrowRight => {
|
|
|
|
if state.cursor_rightward_position != 0 {
|
|
|
|
state.terminal.move_cursor_right(1).expect("Failed to write to terminal output");
|
|
|
|
state.cursor_rightward_position -= 1;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => continue
|
2022-01-03 22:53:57 +00:00
|
|
|
}
|
2022-03-05 01:37:59 +00:00
|
|
|
|
|
|
|
//println!("Read {}", read_buf[0]);
|
|
|
|
|
2022-01-03 22:53:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
2022-03-05 01:37:59 +00:00
|
|
|
fn prompt(state: &mut ReplState, args: &CliArgs) {
|
|
|
|
write!(state.terminal, "{}{}", state.line_number, args.prompt).expect("Failed to write to terminal output");
|
|
|
|
state.line_number += 1;
|
|
|
|
state.terminal.flush().expect("Failed to flush terminal output");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
fn display_history_line(state: &mut ReplState, args: &CliArgs) {
|
|
|
|
// get historical line
|
|
|
|
state.line_number -= 1;
|
|
|
|
state.terminal.clear_line().expect("Failed to write to terminal output");
|
|
|
|
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");
|
|
|
|
// clear stale input buffer
|
|
|
|
state.statement_buf.clear();
|
|
|
|
state.current_line.clear();
|
|
|
|
state.in_literal = None;
|
|
|
|
state.bracket_depth = 0;
|
|
|
|
state.curly_depth = 0;
|
|
|
|
state.statement_buf.extend(new_statement.chars());
|
|
|
|
state.current_line.extend(new_statement.chars());
|
2022-01-03 22:53:57 +00:00
|
|
|
}
|
2022-01-04 02:15:28 +00:00
|
|
|
|
2022-01-27 01:15:00 +00:00
|
|
|
#[inline(always)]
|
|
|
|
fn error_prompt(error: mps_player::PlaybackError, args: &CliArgs) {
|
|
|
|
eprintln!("E{}{}", args.prompt, error.message());
|
|
|
|
}
|
|
|
|
|
2022-01-04 02:15:28 +00:00
|
|
|
fn repl_commands(command_str: &str) {
|
2022-01-29 21:13:52 +00:00
|
|
|
let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect();
|
2022-01-04 02:15:28 +00:00
|
|
|
match words[0] {
|
2022-01-04 02:22:22 +00:00
|
|
|
"?help" => println!("{}", super::help::HELP_STRING),
|
|
|
|
"?function" | "?functions" => println!("{}", super::help::FUNCTIONS),
|
|
|
|
"?filter" | "?filters" => println!("{}", super::help::FILTERS),
|
2022-01-25 15:03:27 +00:00
|
|
|
"?sort" | "?sorter" | "?sorters" => println!("{}", super::help::SORTERS),
|
2022-02-23 16:33:45 +00:00
|
|
|
"?proc" | "?procedure" | "?procedures" => println!("{}", super::help::PROCEDURES),
|
2022-01-04 02:15:28 +00:00
|
|
|
_ => println!("Unknown command, try ?help"),
|
|
|
|
}
|
|
|
|
}
|