muss/src/repl.rs

452 lines
18 KiB
Rust
Raw Normal View History

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};
2022-03-16 20:27:08 +00:00
use console::{Key, Term};
2022-01-03 22:53:57 +00:00
use mps_interpreter::MpsRunner;
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};
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,
in_literal: Option<char>,
bracket_depth: usize,
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,
in_literal: None,
bracket_depth: 0,
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-16 20:27:08 +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) {
Ok(_) => {}
Err(e) => {
error_prompt(e, &args);
// consume any further errors (this shouldn't actually write anything)
while let Err(e) = player.save_m3u8(&mut playlist_writer) {
error_prompt(e, &args);
}
2022-01-24 21:12:29 +00:00
}
2022-01-03 22:53:57 +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-16 20:27:08 +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() {
Ok(_) => {}
Err(e) => error_prompt(e, &args),
2022-01-04 01:15:19 +00:00
}
} else {
// consume all incoming errors
let mut had_err = true;
while had_err {
let mut new_had_err = false;
for e in ctrl.check_ack() {
error_prompt(e, &args);
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-16 20:27:08 +00:00
match state
.terminal
.read_key()
.expect("Failed to read terminal input")
{
2022-03-05 01:37:59 +00:00
Key::Char(read_c) => {
if state.cursor_rightward_position == 0 {
2022-03-16 20:27:08 +00:00
write!(state.terminal, "{}", read_c)
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
state.statement_buf.push(read_c);
state.current_line.push(read_c);
} else {
2022-03-16 20:27:08 +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");
2022-03-05 01:37:59 +00:00
}
2022-03-16 20:27:08 +00:00
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-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,
2022-03-16 20:27:08 +00:00
')' => {
if state.bracket_depth != 0 {
state.bracket_depth -= 1
}
}
2022-03-05 01:37:59 +00:00
'{' => state.curly_depth += 1,
2022-03-16 20:27:08 +00:00
'}' => {
if state.curly_depth != 0 {
state.curly_depth -= 1
}
}
2022-03-05 01:37:59 +00:00
';' => {
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();
2022-03-16 20:27:08 +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(';');
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-03-16 20:27:08 +00:00
}
2022-03-05 01:37:59 +00:00
Key::Backspace => {
if state.cursor_rightward_position == 0 {
if let Some(c) = state.statement_buf.pop() {
// 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);
}
2022-03-16 20:27:08 +00:00
}
'(' => {
if state.bracket_depth != 0 {
state.bracket_depth -= 1
}
}
')' => state.bracket_depth += 1,
2022-03-16 20:27:08 +00:00
'{' => {
if state.curly_depth != 0 {
state.curly_depth -= 1
}
}
'}' => state.curly_depth += 1,
2022-03-16 20:27:08 +00:00
_ => {}
}
match c {
'\n' | '\r' => {
// another line, cannot backspace that far
state.statement_buf.push(c);
2022-03-16 20:27:08 +00:00
}
_ => {
state.current_line.pop();
2022-03-16 20:27:08 +00:00
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
}
}
} else {
if state.current_line.len() != state.cursor_rightward_position {
// if not at start of line
2022-03-16 20:27:08 +00:00
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 {
'"' | '`' => {
if let Some(c2) = state.in_literal {
if removed_char == c2 {
state.in_literal = None;
}
} else {
state.in_literal = Some(removed_char);
}
2022-03-16 20:27:08 +00:00
}
'(' => {
if state.bracket_depth != 0 {
state.bracket_depth -= 1
}
}
')' => state.bracket_depth += 1,
2022-03-16 20:27:08 +00:00
'{' => {
if state.curly_depth != 0 {
state.curly_depth -= 1
}
}
'}' => state.curly_depth += 1,
2022-03-16 20:27:08 +00:00
_ => {}
}
// re-print end of line to remove character in middle
2022-03-16 20:27:08 +00:00
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
}
write!(state.terminal, " ").expect("Failed to write to terminal output");
2022-03-16 20:27:08 +00:00
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-16 20:27:08 +00:00
}
2022-03-05 01:37:59 +00:00
Key::Enter => {
2022-03-16 20:27:08 +00:00
state
.terminal
.write_line("")
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
let statement = state.statement_buf.iter().collect::<String>();
let statement_result = statement.trim();
if statement_result.starts_with('?') {
//println!("Got {}", statement_result.unwrap());
2022-03-05 01:37:59 +00:00
repl_commands(statement_result);
state.statement_buf.clear();
2022-03-16 20:27:08 +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>();
state
.writer
2022-03-05 01:37:59 +00:00
.write(complete_statement.as_bytes())
.expect("Failed to write to MPS interpreter");
execute();
state.statement_buf.clear();
}
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();
2022-03-16 20:27:08 +00:00
if !last_line.is_empty()
&& ((!state.history.is_empty()
&& state.history[state.history.len() - 1] != last_line)
|| state.history.is_empty())
{
2022-03-05 01:37:59 +00:00
state.history.push(last_line);
}
state.selected_history = 0;
prompt(state, args);
2022-03-16 20:27:08 +00:00
}
2022-03-05 01:37:59 +00:00
Key::ArrowUp => {
if state.selected_history != state.history.len() {
state.selected_history += 1;
display_history_line(state, args);
}
2022-03-16 20:27:08 +00:00
}
2022-03-05 01:37:59 +00:00
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;
2022-03-16 20:27:08 +00:00
state
.terminal
.clear_line()
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
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;
}
2022-03-16 20:27:08 +00:00
}
2022-03-05 01:37:59 +00:00
Key::ArrowLeft => {
if state.current_line.len() > state.cursor_rightward_position {
2022-03-16 20:27:08 +00:00
state
.terminal
.move_cursor_left(1)
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
state.cursor_rightward_position += 1;
}
2022-03-16 20:27:08 +00:00
}
2022-03-05 01:37:59 +00:00
Key::ArrowRight => {
if state.cursor_rightward_position != 0 {
2022-03-16 20:27:08 +00:00
state
.terminal
.move_cursor_right(1)
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
state.cursor_rightward_position -= 1;
}
2022-03-16 20:27:08 +00:00
}
_ => continue,
2022-01-03 22:53:57 +00:00
}
2022-03-16 20:27:08 +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) {
2022-03-16 20:27:08 +00:00
write!(state.terminal, "{}{}", state.line_number, args.prompt)
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
state.line_number += 1;
2022-03-16 20:27:08 +00:00
state
.terminal
.flush()
.expect("Failed to flush terminal output");
2022-03-05 01:37:59 +00:00
}
#[inline(always)]
fn display_history_line(state: &mut ReplState, args: &CliArgs) {
// get historical line
state.line_number -= 1;
2022-03-16 20:27:08 +00:00
state
.terminal
.clear_line()
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
prompt(state, args);
let new_statement = state.history[state.history.len() - state.selected_history].trim();
2022-03-16 20:27:08 +00:00
state
.terminal
.write(new_statement.as_bytes())
.expect("Failed to write to terminal output");
2022-03-05 01:37:59 +00:00
// 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
}
#[inline(always)]
fn error_prompt(error: mps_player::PlaybackError, args: &CliArgs) {
eprintln!("E{}{}", args.prompt, error.message());
}
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();
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),
"?proc" | "?procedure" | "?procedures" => println!("{}", super::help::PROCEDURES),
_ => println!("Unknown command, try ?help"),
}
}