246 lines
7.2 KiB
Rust
246 lines
7.2 KiB
Rust
use std::sync::Arc;
|
|
use std::collections::HashSet;
|
|
|
|
use colored::*;
|
|
use crate::language::{ProgrammingLanguageInterface,
|
|
ComputationRequest, ComputationResponse,
|
|
DebugAsk, LangMetaResponse, LangMetaRequest};
|
|
|
|
mod command_tree;
|
|
use self::command_tree::CommandTree;
|
|
mod repl_options;
|
|
use repl_options::ReplOptions;
|
|
mod directive_actions;
|
|
mod directives;
|
|
use directives::directives_from_pass_names;
|
|
|
|
const HISTORY_SAVE_FILE: &'static str = ".schala_history";
|
|
const OPTIONS_SAVE_FILE: &'static str = ".schala_repl";
|
|
|
|
type InterpreterDirectiveOutput = Option<String>;
|
|
|
|
pub struct Repl {
|
|
interpreter_directive_sigil: char,
|
|
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
|
|
language_states: Vec<Box<ProgrammingLanguageInterface>>,
|
|
options: ReplOptions,
|
|
directives: CommandTree,
|
|
}
|
|
|
|
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 handle_interpreter_directive(&mut self, input: &str) -> InterpreterDirectiveOutput {
|
|
let mut iter = input.chars();
|
|
iter.next();
|
|
let arguments: Vec<&str> = iter
|
|
.as_str()
|
|
.split_whitespace()
|
|
.collect();
|
|
|
|
if arguments.len() < 1 {
|
|
return None;
|
|
}
|
|
|
|
let directives = self.get_directives();
|
|
directives.perform(self, &arguments)
|
|
}
|
|
|
|
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) -> CommandTree {
|
|
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: CommandTree,
|
|
}
|
|
|
|
use linefeed::complete::{Completion, Completer};
|
|
use linefeed::terminal::Terminal;
|
|
|
|
impl TabCompleteHandler {
|
|
fn new(sigil: char, top_level_commands: CommandTree) -> 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<&CommandTree> = Some(&self.top_level_commands);
|
|
|
|
loop {
|
|
match words.next() {
|
|
None => {
|
|
let top = match command_tree {
|
|
Some(CommandTree::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<&CommandTree> = command_tree.and_then(|cm| match cm {
|
|
CommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
|
|
CommandTree::NonTerminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
|
|
CommandTree::Terminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
|
|
});
|
|
command_tree = new_ptr;
|
|
}
|
|
}
|
|
}
|
|
Some(completions)
|
|
}
|
|
}
|