Flatten schala-repl files

This commit is contained in:
Greg Shuflin 2021-10-14 01:33:46 -07:00
parent 3cbe80e933
commit 76c2257c7e
8 changed files with 275 additions and 282 deletions

View File

@ -1,6 +1,6 @@
use super::{InterpreterDirectiveOutput, Repl}; use crate::directive_actions::DirectiveAction;
use crate::language::ProgrammingLanguageInterface; use crate::language::ProgrammingLanguageInterface;
use crate::repl::directive_actions::DirectiveAction; use crate::{InterpreterDirectiveOutput, Repl};
use colored::*; use colored::*;
/// A CommandTree is either a `Terminal` or a `NonTerminal`. When command parsing reaches the first /// A CommandTree is either a `Terminal` or a `NonTerminal`. When command parsing reaches the first

View File

@ -1,8 +1,8 @@
use super::{InterpreterDirectiveOutput, Repl}; use crate::help::help;
use crate::language::{ use crate::language::{
DebugAsk, DebugResponse, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface, DebugAsk, DebugResponse, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface,
}; };
use crate::repl::help::help; use crate::{InterpreterDirectiveOutput, Repl};
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -1,5 +1,5 @@
use crate::repl::command_tree::CommandTree; use crate::command_tree::CommandTree;
use crate::repl::directive_actions::DirectiveAction; use crate::directive_actions::DirectiveAction;
pub fn directives_from_pass_names(pass_names: &Vec<String>) -> CommandTree { pub fn directives_from_pass_names(pass_names: &Vec<String>) -> CommandTree {
let passes_directives: Vec<CommandTree> = pass_names let passes_directives: Vec<CommandTree> = pass_names

View File

@ -1,8 +1,8 @@
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use super::command_tree::CommandTree; use crate::command_tree::CommandTree;
use super::{InterpreterDirectiveOutput, Repl};
use crate::language::ProgrammingLanguageInterface; use crate::language::ProgrammingLanguageInterface;
use crate::{InterpreterDirectiveOutput, Repl};
use colored::*; use colored::*;
pub fn help<L: ProgrammingLanguageInterface>( pub fn help<L: ProgrammingLanguageInterface>(

View File

@ -7,15 +7,278 @@ extern crate includedir;
extern crate phf; extern crate phf;
extern crate serde_json; extern crate serde_json;
mod command_tree;
mod language; 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::{ pub use language::{
ComputationRequest, ComputationResponse, DebugAsk, DebugResponse, GlobalOutputStats, ComputationRequest, ComputationResponse, DebugAsk, DebugResponse, GlobalOutputStats,
LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface,
}; };
pub use repl::Repl;
include!(concat!(env!("OUT_DIR"), "/static.rs")); include!(concat!(env!("OUT_DIR"), "/static.rs"));
const VERSION_STRING: &'static str = "0.1.0"; 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<String>;
pub struct Repl<L: ProgrammingLanguageInterface> {
/// 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<L: ProgrammingLanguageInterface> Repl<L> {
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<ReplResponse> {
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<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_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)
}
}

View File

@ -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<String>;
pub struct Repl<L: ProgrammingLanguageInterface> {
/// 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<L: ProgrammingLanguageInterface> Repl<L> {
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<ReplResponse> {
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<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_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)
}
}

View File

@ -2,8 +2,8 @@ use colored::*;
use std::fmt; use std::fmt;
use std::fmt::Write; use std::fmt::Write;
use super::ReplOptions;
use crate::language::{ComputationResponse, DebugAsk}; use crate::language::{ComputationResponse, DebugAsk};
use crate::ReplOptions;
pub struct ReplResponse { pub struct ReplResponse {
label: Option<String>, label: Option<String>,