diff --git a/schala-repl/src/repl/command_tree.rs b/schala-repl/src/command_tree.rs similarity index 97% rename from schala-repl/src/repl/command_tree.rs rename to schala-repl/src/command_tree.rs index bf853a9..d84fc7d 100644 --- a/schala-repl/src/repl/command_tree.rs +++ b/schala-repl/src/command_tree.rs @@ -1,6 +1,6 @@ -use super::{InterpreterDirectiveOutput, Repl}; +use crate::directive_actions::DirectiveAction; use crate::language::ProgrammingLanguageInterface; -use crate::repl::directive_actions::DirectiveAction; +use crate::{InterpreterDirectiveOutput, Repl}; use colored::*; /// A CommandTree is either a `Terminal` or a `NonTerminal`. When command parsing reaches the first diff --git a/schala-repl/src/repl/directive_actions.rs b/schala-repl/src/directive_actions.rs similarity index 98% rename from schala-repl/src/repl/directive_actions.rs rename to schala-repl/src/directive_actions.rs index fc36b53..a1fb5d9 100644 --- a/schala-repl/src/repl/directive_actions.rs +++ b/schala-repl/src/directive_actions.rs @@ -1,8 +1,8 @@ -use super::{InterpreterDirectiveOutput, Repl}; +use crate::help::help; use crate::language::{ DebugAsk, DebugResponse, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface, }; -use crate::repl::help::help; +use crate::{InterpreterDirectiveOutput, Repl}; use std::fmt::Write as FmtWrite; #[derive(Debug, Clone)] diff --git a/schala-repl/src/repl/directives.rs b/schala-repl/src/directives.rs similarity index 97% rename from schala-repl/src/repl/directives.rs rename to schala-repl/src/directives.rs index 0e6bb0d..8fa073f 100644 --- a/schala-repl/src/repl/directives.rs +++ b/schala-repl/src/directives.rs @@ -1,5 +1,5 @@ -use crate::repl::command_tree::CommandTree; -use crate::repl::directive_actions::DirectiveAction; +use crate::command_tree::CommandTree; +use crate::directive_actions::DirectiveAction; pub fn directives_from_pass_names(pass_names: &Vec) -> CommandTree { let passes_directives: Vec = pass_names diff --git a/schala-repl/src/repl/help.rs b/schala-repl/src/help.rs similarity index 96% rename from schala-repl/src/repl/help.rs rename to schala-repl/src/help.rs index 6a3e060..7a8974d 100644 --- a/schala-repl/src/repl/help.rs +++ b/schala-repl/src/help.rs @@ -1,8 +1,8 @@ use std::fmt::Write as FmtWrite; -use super::command_tree::CommandTree; -use super::{InterpreterDirectiveOutput, Repl}; +use crate::command_tree::CommandTree; use crate::language::ProgrammingLanguageInterface; +use crate::{InterpreterDirectiveOutput, Repl}; use colored::*; pub fn help( diff --git a/schala-repl/src/lib.rs b/schala-repl/src/lib.rs index 435b54e..d73318d 100644 --- a/schala-repl/src/lib.rs +++ b/schala-repl/src/lib.rs @@ -7,15 +7,278 @@ extern crate includedir; extern crate phf; extern crate serde_json; +mod command_tree; mod language; -mod repl; +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 response::ReplResponse; + +use colored::*; +use std::collections::HashSet; +use std::sync::Arc; pub use language::{ ComputationRequest, ComputationResponse, DebugAsk, DebugResponse, GlobalOutputStats, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface, }; -pub use repl::Repl; - include!(concat!(env!("OUT_DIR"), "/static.rs")); const VERSION_STRING: &'static str = "0.1.0"; + +const HISTORY_SAVE_FILE: &'static str = ".schala_history"; +const OPTIONS_SAVE_FILE: &'static 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) { + 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(); + 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) { + 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.nth(0) { + 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_str("\n"); + } + } + self.handle_input(&buf) + } else { + match self.handle_interpreter_directive(input.get(1..).unwrap()) { + Some(directive_output) => println!("{}", directive_output), + None => (), + } + continue; + } + } + _ => self.handle_input(input), + }; + + 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.len() < 1 { + return None; + } + + let directives = self.get_directives(); + directives.perform(self, &arguments) + } + + fn handle_input(&mut self, input: &str) -> 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, + 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}; +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_subcommands()) + .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) + } +} diff --git a/schala-repl/src/repl/mod.rs b/schala-repl/src/repl/mod.rs deleted file mode 100644 index 2fa9d85..0000000 --- a/schala-repl/src/repl/mod.rs +++ /dev/null @@ -1,270 +0,0 @@ -use colored::*; -use std::collections::HashSet; -use std::sync::Arc; - -use crate::language::{ - ComputationRequest, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface, -}; - -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; -mod help; -mod response; -use response::ReplResponse; - -const HISTORY_SAVE_FILE: &'static str = ".schala_history"; -const OPTIONS_SAVE_FILE: &'static 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) { - println!("Schala meta-interpeter version {}", crate::VERSION_STRING); - println!( - "Type {} for help with the REPL", - format!("{}help", self.sigil).bright_green().bold() - ); - 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(e) => eprintln!("{}", e), - } - } - - fn handle_repl_loop(&mut self) { - 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.nth(0) { - 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_str("\n"); - } - } - self.handle_input(&buf) - } else { - match self.handle_interpreter_directive(input.get(1..).unwrap()) { - Some(directive_output) => println!("{}", directive_output), - None => (), - } - continue; - } - } - _ => self.handle_input(input), - }; - - 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.len() < 1 { - return None; - } - - let directives = self.get_directives(); - directives.perform(self, &arguments) - } - - fn handle_input(&mut self, input: &str) -> 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, - 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}; -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_subcommands()) - .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) - } -} diff --git a/schala-repl/src/repl/repl_options.rs b/schala-repl/src/repl_options.rs similarity index 100% rename from schala-repl/src/repl/repl_options.rs rename to schala-repl/src/repl_options.rs diff --git a/schala-repl/src/repl/response.rs b/schala-repl/src/response.rs similarity index 98% rename from schala-repl/src/repl/response.rs rename to schala-repl/src/response.rs index 2414e58..44ad59d 100644 --- a/schala-repl/src/repl/response.rs +++ b/schala-repl/src/response.rs @@ -2,8 +2,8 @@ use colored::*; use std::fmt; use std::fmt::Write; -use super::ReplOptions; use crate::language::{ComputationResponse, DebugAsk}; +use crate::ReplOptions; pub struct ReplResponse { label: Option,