#![feature(box_patterns, box_syntax, proc_macro_hygiene, decl_macro, iter_intersperse)] #![feature(plugin)] #[macro_use] extern crate serde_derive; extern crate includedir; extern crate phf; extern crate serde_json; mod command_tree; mod language; use self::command_tree::CommandTree; mod repl_options; use repl_options::ReplOptions; mod directive_actions; mod directives; use directives::directives_from_pass_names; mod help; mod response; use std::{collections::HashSet, sync::Arc}; use colored::*; pub use language::{ ComputationRequest, ComputationResponse, DebugAsk, DebugResponse, GlobalOutputStats, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface, }; use response::ReplResponse; include!(concat!(env!("OUT_DIR"), "/static.rs")); const VERSION_STRING: &str = "0.1.0"; const HISTORY_SAVE_FILE: &str = ".schala_history"; const OPTIONS_SAVE_FILE: &str = ".schala_repl"; type InterpreterDirectiveOutput = Option; pub struct Repl { /// If this is the first character typed by a user into the repl, the following /// will be interpreted as a directive to the REPL rather than a command in the /// running programming language. sigil: char, line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>, language_state: L, options: ReplOptions, } #[derive(Clone)] enum PromptStyle { Normal, Multiline, } impl Repl { pub fn new(initial_state: L) -> Self { use linefeed::Interface; let line_reader = Interface::new("schala-repl").unwrap(); let sigil = ':'; Repl { sigil, line_reader, language_state: initial_state, options: ReplOptions::new() } } pub fn run_repl(&mut self, config: L::Config) { println!("Schala meta-interpeter version {}", VERSION_STRING); println!("Type {} for help with the REPL", format!("{}help", self.sigil).bright_green().bold()); self.load_options(); self.handle_repl_loop(config); 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(e) => eprintln!("{}", e), } } fn handle_repl_loop(&mut self, config: L::Config) { use linefeed::ReadResult::*; 'main: loop { macro_rules! match_or_break { ($line:expr) => { match $line { Err(e) => { println!("readline IO Error: {}", e); break 'main; } Ok(Eof) | Ok(Signal(_)) => break 'main, Ok(Input(ref input)) => input, } }; } self.update_line_reader(); let line = self.line_reader.read_line(); let input: &str = match_or_break!(line); self.line_reader.add_history_unique(input.to_string()); let mut chars = input.chars().peekable(); let repl_responses = match chars.next() { Some(ch) if ch == self.sigil => if chars.peek() == Some(&'{') { let mut buf = String::new(); buf.push_str(input.get(2..).unwrap()); 'multiline: loop { self.set_prompt(PromptStyle::Multiline); let new_line = self.line_reader.read_line(); let new_input = match_or_break!(new_line); if new_input.starts_with(":}") { break 'multiline; } else { buf.push_str(new_input); buf.push('\n'); } } self.handle_input(&buf, &config) } else { if let Some(output) = self.handle_interpreter_directive(input.get(1..).unwrap()) { println!("{}", output); } continue; }, _ => self.handle_input(input, &config), }; for repl_response in repl_responses.iter() { println!("{}", repl_response); } } } fn update_line_reader(&mut self) { let tab_complete_handler = TabCompleteHandler::new(self.sigil, self.get_directives()); self.line_reader.set_completer(Arc::new(tab_complete_handler)); //TODO fix this here self.set_prompt(PromptStyle::Normal); } fn set_prompt(&mut self, prompt_style: PromptStyle) { let prompt_str = match prompt_style { PromptStyle::Normal => ">> ", PromptStyle::Multiline => ">| ", }; 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 arguments: Vec<&str> = input.split_whitespace().collect(); if arguments.is_empty() { return None; } let directives = self.get_directives(); directives.perform(self, &arguments) } fn handle_input(&mut self, input: &str, config: &L::Config) -> Vec { let mut debug_requests = HashSet::new(); for ask in self.options.debug_asks.iter() { debug_requests.insert(ask.clone()); } let request = ComputationRequest { source: input, config: config.clone(), debug_requests }; let response = self.language_state.run_computation(request); response::handle_computation_response(response, &self.options) } fn get_directives(&mut self) -> CommandTree { let pass_names = match self.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::{Completer, Completion}, 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 = matches!(command_tree, Some(CommandTree::Top(_))); let word = if top { word.get(1..).unwrap() } else { word }; for cmd in command_tree.map(|x| x.get_subcommands()).unwrap_or_default().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) } }