use std::fmt::Write as FmtWrite; use std::io::{Read, Write}; use std::fs::File; use std::sync::Arc; use std::collections::HashSet; use colored::*; use itertools::Itertools; use crate::language::{ProgrammingLanguageInterface, ComputationRequest, ComputationResponse, LangMetaRequest, LangMetaResponse, DebugRequest, DebugAsk, DebugResponse}; mod command_tree; use self::command_tree::{CommandTree, BoxedCommandFunction}; 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>, debug_asks: HashSet, } impl Repl { pub fn new(initial_states: Vec>) -> Repl { use linefeed::Interface; let line_reader = Interface::new("schala-repl").unwrap(); let interpreter_directive_sigil = ':'; let debug_asks = HashSet::new(); Repl { interpreter_directive_sigil, line_reader, language_states: initial_states, debug_asks } } pub fn run_repl(&mut self) { self.line_reader.load_history(HISTORY_SAVE_FILE).unwrap_or(()); println!("Schala MetaInterpreter version {}", crate::VERSION_STRING); println!("Type {}help for help with the REPL", self.interpreter_directive_sigil); self.handle_repl_loop(); self.save_before_exit(); println!("Exiting..."); } 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)); 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.save_options() { let ref options = self.options; let read = File::create(OPTIONS_SAVE_FILE) .and_then(|mut file| { let buf = ::serde_json::to_string(options).unwrap(); file.write_all(buf.as_bytes()) }); if let Err(err) = read { println!("Error saving {} file {}", OPTIONS_SAVE_FILE, err); } } */ } fn get_function_from_directives<'a>(directives: &'a CommandTree, commands: &Vec<&str>) -> Result<(&'a BoxedCommandFunction, usize), String> { let mut dir_pointer: &CommandTree = &directives; let mut idx = 0; loop { match dir_pointer { CommandTree::Top(subcommands) | CommandTree::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)) }; }, CommandTree::Terminal { name, function, .. } => { break Ok((function, idx)); }, } } } fn handle_interpreter_directive(&mut self, input: &str) -> Option { 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() { CommandTree::Top(children) => children, _ => panic!("Top-level CommandTree 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 { //TODO this is obviously not complete &mut self.language_states[0] } fn handle_input(&mut self, input: &str) -> String { let mut debug_requests = vec![]; for ask in self.debug_asks.iter() { debug_requests.push(DebugRequest { ask: ask.clone() }); } let request = ComputationRequest { source: input, debug_requests, }; let ref mut language_state = self.get_cur_language_state(); let ComputationResponse { main_output, global_output_stats, debug_responses } = language_state.run_computation(request); match main_output { Ok(s) => s, Err(e) => format!("{} {}", "Error".red(), e) } } 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![], }; let passes_directives: Vec = pass_names.iter() .map(|pass_name| { CommandTree::nonterm_no_further_tab_completions(pass_name, None) }) .collect(); CommandTree::Top(vec![ CommandTree::term_with_function("exit", Some("exit the REPL"), vec![], Box::new(|repl: &mut Repl, _cmds: &[&str]| { repl.save_before_exit(); ::std::process::exit(0) })), CommandTree::term_with_function("quit", Some("exit the REPL"), vec![], Box::new(|repl: &mut Repl, _cmds: &[&str]| { repl.save_before_exit(); ::std::process::exit(0) })), CommandTree::term_with_function("help", Some("Print this help message"), vec![], Box::new(|repl: &mut Repl, cmds: &[&str]| { Some(repl.print_help_message(cmds)) })), CommandTree::term_with_function("debug", Some("Configure debug information"), vec![ CommandTree::term_with_function("list-passes", Some("List all registered compiler passes"), vec![], Box::new(|repl: &mut Repl, cmds: &[&str]| { let language_state = repl.get_cur_language_state(); let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) { LangMetaResponse::StageNames(names) => names, _ => vec![], }; let mut buf = String::new(); for pass in pass_names.iter().map(|name| Some(name)).intersperse(None) { match pass { Some(pass) => write!(buf, "{}", pass).unwrap(), None => write!(buf, " -> ").unwrap(), } } Some(buf) })), CommandTree::nonterm("show-immediate", None, passes_directives.clone()), CommandTree::nonterm("show", None, passes_directives.clone()), CommandTree::nonterm("hide", None, passes_directives.clone()), CommandTree::nonterm("timing", None, vec![ CommandTree::nonterm_no_further_tab_completions("on", None), CommandTree::nonterm_no_further_tab_completions("off", None), ]) ], Box::new(|repl: &mut Repl, cmds: &[&str]| { let mut cur_state = repl.get_cur_language_state(); match cmds.get(0) { Some(&"show-immediate") => { let stage_name = match cmds.get(1) { Some(s) => s.to_string(), None => return Some(format!("Must specify a thing to debug")), }; let meta = LangMetaRequest::ImmediateDebug( DebugRequest { ask: DebugAsk::ByStage { stage_name: stage_name.clone() } } ); let response = match cur_state.request_meta(meta) { LangMetaResponse::ImmediateDebug(DebugResponse { ask, value }) => { if (ask != DebugAsk::ByStage { stage_name: stage_name }) { return Some(format!("Didn't get debug stage requested")); } value }, _ => return Some(format!("Invalid language meta response")), }; Some(response) }, cmd @ Some(&"show") | cmd @ Some(&"hide") => { let stage_name = match cmds.get(1) { Some(s) => s.to_string(), None => return Some(format!("Must specify a stage to show or hide")), }; let ask = DebugAsk::ByStage { stage_name }; if cmd == Some(&"show") { repl.debug_asks.insert(ask); } else { repl.debug_asks.remove(&ask); } None }, Some(&"timing") => { match cmds.get(1) { Some(&"on") => repl.debug_asks.insert(DebugAsk::Timing), Some(&"off") => repl.debug_asks.remove(&DebugAsk::Timing), _ => return Some(format!("Must specify 'on' or 'off'")), }; None }, e => Some(format!("Unsupported command: {:?}", e)), } }) ), CommandTree::nonterm("lang", Some("switch between languages, or go directly to a langauge by name"), vec![ CommandTree::nonterm_no_further_tab_completions("next", None), CommandTree::nonterm_no_further_tab_completions("prev", None), CommandTree::nonterm("go", None, vec![]), ] ), CommandTree::nonterm_no_further_tab_completions("doc", Some("Get language-specific help for an item")), ]) } } /* --------------------------------------------- */ /* fn get_options() -> EvalOptions { File::open(OPTIONS_SAVE_FILE) .and_then(|mut file| { let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }) .and_then(|contents| { let options: EvalOptions = ::serde_json::from_str(&contents)?; Ok(options) }).unwrap_or(EvalOptions::default()) } fn save_options(&self) { let ref options = self.options; let read = File::create(OPTIONS_SAVE_FILE) .and_then(|mut file| { let buf = ::serde_json::to_string(options).unwrap(); file.write_all(buf.as_bytes()) }); if let Err(err) = read { println!("Error saving {} file {}", OPTIONS_SAVE_FILE, err); } } */ 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 Completer for TabCompleteHandler { fn complete(&self, word: &str, prompter: &::linefeed::prompter::Prompter, start: usize, _end: usize) -> Option> { 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) } }