schala/schala-repl/src/repl/mod.rs

303 lines
9.3 KiB
Rust

use std::fmt::Write as FmtWrite;
use std::sync::Arc;
use std::collections::HashSet;
use colored::*;
use crate::language::{ProgrammingLanguageInterface,
ComputationRequest, ComputationResponse,
DebugAsk, LangMetaResponse, LangMetaRequest};
mod old_command_tree;
use self::old_command_tree::{OldCommandTree, BoxedCommandFunction};
mod repl_options;
use repl_options::ReplOptions;
mod directives;
use directives::directives_from_pass_names;
const HISTORY_SAVE_FILE: &'static str = ".schala_history";
const OPTIONS_SAVE_FILE: &'static str = ".schala_repl";
pub struct Repl {
interpreter_directive_sigil: char,
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
language_states: Vec<Box<ProgrammingLanguageInterface>>,
options: ReplOptions,
directives: OldCommandTree,
}
impl Repl {
pub fn new(mut initial_states: Vec<Box<ProgrammingLanguageInterface>>) -> Repl {
use linefeed::Interface;
let line_reader = Interface::new("schala-repl").unwrap();
let interpreter_directive_sigil = ':';
let pass_names = match initial_states[0].request_meta(LangMetaRequest::StageNames) {
LangMetaResponse::StageNames(names) => names,
_ => vec![],
};
Repl {
interpreter_directive_sigil,
line_reader,
language_states: initial_states,
options: ReplOptions::new(),
directives: directives_from_pass_names(&pass_names)
}
}
pub fn run_repl(&mut self) {
println!("Schala MetaInterpreter version {}", crate::VERSION_STRING);
println!("Type {}help for help with the REPL", self.interpreter_directive_sigil);
self.load_options();
self.handle_repl_loop();
self.save_before_exit();
println!("Exiting...");
}
fn load_options(&mut self) {
self.line_reader.load_history(HISTORY_SAVE_FILE).unwrap_or(());
match ReplOptions::load_from_file(OPTIONS_SAVE_FILE) {
Ok(options) => {
self.options = options;
},
Err(()) => ()
};
}
fn handle_repl_loop(&mut self) {
use linefeed::ReadResult::*;
loop {
self.update_line_reader();
match self.line_reader.read_line() {
Err(e) => {
println!("readline IO Error: {}", e);
break;
},
Ok(Eof) | Ok(Signal(_)) => break,
Ok(Input(ref input)) => {
self.line_reader.add_history_unique(input.to_string());
let output = match input.chars().nth(0) {
Some(ch) if ch == self.interpreter_directive_sigil => self.handle_interpreter_directive(input),
_ => Some(self.handle_input(input)),
};
if let Some(o) = output {
println!("=> {}", o);
}
}
}
}
}
fn update_line_reader(&mut self) {
let tab_complete_handler = TabCompleteHandler::new(self.interpreter_directive_sigil, self.get_directives());
self.line_reader.set_completer(Arc::new(tab_complete_handler)); //TODO fix this here
let prompt_str = format!(">> ");
self.line_reader.set_prompt(&prompt_str).unwrap();
}
fn save_before_exit(&self) {
self.line_reader.save_history(HISTORY_SAVE_FILE).unwrap_or(());
self.options.save_to_file(OPTIONS_SAVE_FILE);
}
fn get_function_from_directives<'a>(directives: &'a OldCommandTree, commands: &Vec<&str>) -> Result<(&'a BoxedCommandFunction, usize), String> {
let mut dir_pointer: &OldCommandTree = &directives;
let mut idx = 0;
loop {
match dir_pointer {
OldCommandTree::Top(subcommands) | OldCommandTree::NonTerminal { children: subcommands, .. } => {
let next_command = match commands.get(idx) {
Some(cmd) => cmd,
None => break Err(format!("Command requires arguments"))
};
idx += 1;
match subcommands.iter().find(|sc| sc.get_cmd() == *next_command) {
Some(command_tree) => {
dir_pointer = command_tree;
},
None => break Err(format!("Command {} not found", next_command))
};
},
OldCommandTree::Terminal { function, .. } => {
break Ok((function, idx));
},
}
}
}
fn handle_interpreter_directive(&mut self, input: &str) -> Option<String> {
let mut iter = input.chars();
iter.next();
let commands: Vec<&str> = iter
.as_str()
.split_whitespace()
.collect();
if commands.len() < 1 {
return None;
}
let directives = self.get_directives();
let result: Result<(&BoxedCommandFunction, _), String> = Repl::get_function_from_directives(&directives, &commands);
match result {
Ok((f, idx)) => f(self, &commands[idx..]),
Err(err) => Some(err.red().to_string())
}
}
fn print_help_message(&mut self, commands_passed_to_help: &[&str] ) -> String {
let mut buf = String::new();
let directives = match self.get_directives() {
OldCommandTree::Top(children) => children,
_ => panic!("Top-level OldCommandTree not Top")
};
match commands_passed_to_help {
[] => {
writeln!(buf, "MetaInterpreter options").unwrap();
writeln!(buf, "-----------------------").unwrap();
for directive in directives {
let trailer = " ";
writeln!(buf, "{}{}- {}", directive.get_cmd(), trailer, directive.get_help()).unwrap();
}
let ref lang = self.get_cur_language_state();
writeln!(buf, "").unwrap();
writeln!(buf, "Language-specific help for {}", lang.get_language_name()).unwrap();
writeln!(buf, "-----------------------").unwrap();
},
_ => {
writeln!(buf, "Command-specific help not available yet").unwrap();
}
};
buf
}
fn get_cur_language_state(&mut self) -> &mut Box<ProgrammingLanguageInterface> {
//TODO this is obviously not complete
&mut self.language_states[0]
}
fn handle_input(&mut self, input: &str) -> String {
let mut debug_requests = HashSet::new();
for ask in self.options.debug_asks.iter() {
debug_requests.insert(ask.clone());
}
let request = ComputationRequest {
source: input,
debug_requests,
};
let ref mut language_state = self.get_cur_language_state();
let response = language_state.run_computation(request);
self.handle_computation_response(response)
}
fn handle_computation_response(&mut self, response: ComputationResponse) -> String {
let mut buf = String::new();
if self.options.show_total_time {
buf.push_str(&format!("Total duration: {:?}\n", response.global_output_stats.total_duration));
}
if self.options.show_stage_times {
buf.push_str(&format!("{:?}\n", response.global_output_stats.stage_durations));
}
for debug_resp in response.debug_responses {
let stage_name = match debug_resp.ask {
DebugAsk::ByStage { stage_name } => stage_name,
_ => continue,
};
let s = format!("{} - {}\n", stage_name, debug_resp.value);
buf.push_str(&s);
}
buf.push_str(&match response.main_output {
Ok(s) => s,
Err(e) => format!("{} {}", "Error".red(), e)
});
buf
}
fn get_directives(&mut self) -> OldCommandTree {
let language_state = self.get_cur_language_state();
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) {
LangMetaResponse::StageNames(names) => names,
_ => vec![],
};
directives_from_pass_names(&pass_names)
}
}
struct TabCompleteHandler {
sigil: char,
top_level_commands: OldCommandTree,
}
use linefeed::complete::{Completion, Completer};
use linefeed::terminal::Terminal;
impl TabCompleteHandler {
fn new(sigil: char, top_level_commands: OldCommandTree) -> TabCompleteHandler {
TabCompleteHandler {
top_level_commands,
sigil,
}
}
}
impl<T: Terminal> Completer<T> for TabCompleteHandler {
fn complete(&self, word: &str, prompter: &::linefeed::prompter::Prompter<T>, start: usize, _end: usize) -> Option<Vec<Completion>> {
let line = prompter.buffer();
if !line.starts_with(self.sigil) {
return None;
}
let mut words = line[1..(if start == 0 { 1 } else { start })].split_whitespace();
let mut completions = Vec::new();
let mut command_tree: Option<&OldCommandTree> = Some(&self.top_level_commands);
loop {
match words.next() {
None => {
let top = match command_tree {
Some(OldCommandTree::Top(_)) => true,
_ => false
};
let word = if top { word.get(1..).unwrap() } else { word };
for cmd in command_tree.map(|x| x.get_children()).unwrap_or(vec![]).into_iter() {
if cmd.starts_with(word) {
completions.push(Completion {
completion: format!("{}{}", if top { ":" } else { "" }, cmd),
display: Some(cmd.to_string()),
suffix: ::linefeed::complete::Suffix::Some(' ')
})
}
}
break;
},
Some(s) => {
let new_ptr: Option<&OldCommandTree> = command_tree.and_then(|cm| match cm {
OldCommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
OldCommandTree::NonTerminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
OldCommandTree::Terminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
});
command_tree = new_ptr;
}
}
}
Some(completions)
}
}