Parameterize Repl over language type

This commit is contained in:
Greg Shuflin 2021-10-14 00:56:01 -07:00
parent 0f7e568341
commit 3cbe80e933
9 changed files with 116 additions and 133 deletions

View File

@ -174,11 +174,11 @@ fn stage_names() -> Vec<&'static str> {
impl ProgrammingLanguageInterface for Schala { impl ProgrammingLanguageInterface for Schala {
fn language_name(&self) -> String { fn language_name() -> String {
"Schala".to_owned() "Schala".to_owned()
} }
fn source_file_suffix(&self) -> String { fn source_file_suffix() -> String {
"schala".to_owned() "schala".to_owned()
} }

View File

@ -2,8 +2,8 @@ use std::collections::HashSet;
use std::time; use std::time;
pub trait ProgrammingLanguageInterface { pub trait ProgrammingLanguageInterface {
fn language_name(&self) -> String; fn language_name() -> String;
fn source_file_suffix(&self) -> String; fn source_file_suffix() -> String;
fn run_computation(&mut self, _request: ComputationRequest) -> ComputationResponse; fn run_computation(&mut self, _request: ComputationRequest) -> ComputationResponse;

View File

@ -7,12 +7,6 @@ extern crate includedir;
extern crate phf; extern crate phf;
extern crate serde_json; extern crate serde_json;
use std::collections::HashSet;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::process::exit;
mod language; mod language;
mod repl; mod repl;
@ -21,49 +15,7 @@ pub use language::{
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";
pub fn start_repl(langs: Vec<Box<dyn ProgrammingLanguageInterface>>) {
let mut repl = repl::Repl::new(langs);
repl.run_repl();
}
pub fn run_noninteractive(filenames: Vec<PathBuf>, languages: Vec<Box<dyn ProgrammingLanguageInterface>>) {
// for now, ony do something with the first filename
let filename = &filenames[0];
let ext = filename
.extension()
.and_then(|e| e.to_str())
.unwrap_or_else(|| {
println!("Source file `{}` has no extension.", filename.display());
exit(1);
});
let mut language = Box::new(
languages
.into_iter()
.find(|lang| lang.source_file_suffix() == ext)
.unwrap_or_else(|| {
println!("Extension .{} not recognized", ext);
exit(1);
}),
);
let mut source_file = File::open(filename).unwrap();
let mut buffer = String::new();
source_file.read_to_string(&mut buffer).unwrap();
let request = ComputationRequest {
source: &buffer,
debug_requests: HashSet::new(),
};
let response = language.run_computation(request);
match response.main_output {
Ok(s) => println!("{}", s),
Err(s) => println!("{}", s),
};
}

View File

@ -1,4 +1,5 @@
use super::{InterpreterDirectiveOutput, Repl}; use super::{InterpreterDirectiveOutput, Repl};
use crate::language::ProgrammingLanguageInterface;
use crate::repl::directive_actions::DirectiveAction; use crate::repl::directive_actions::DirectiveAction;
use colored::*; use colored::*;
@ -85,7 +86,11 @@ impl CommandTree {
self.get_children().iter().map(|x| x.get_cmd()).collect() self.get_children().iter().map(|x| x.get_cmd()).collect()
} }
pub fn perform(&self, repl: &mut Repl, arguments: &Vec<&str>) -> InterpreterDirectiveOutput { pub fn perform<L: ProgrammingLanguageInterface>(
&self,
repl: &mut Repl<L>,
arguments: &Vec<&str>,
) -> InterpreterDirectiveOutput {
let mut dir_pointer: &CommandTree = self; let mut dir_pointer: &CommandTree = self;
let mut idx = 0; let mut idx = 0;

View File

@ -1,5 +1,7 @@
use super::{InterpreterDirectiveOutput, Repl}; use super::{InterpreterDirectiveOutput, Repl};
use crate::language::{DebugAsk, DebugResponse, LangMetaRequest, LangMetaResponse}; use crate::language::{
DebugAsk, DebugResponse, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface,
};
use crate::repl::help::help; use crate::repl::help::help;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
@ -20,7 +22,11 @@ pub enum DirectiveAction {
} }
impl DirectiveAction { impl DirectiveAction {
pub fn perform(&self, repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput { pub fn perform<L: ProgrammingLanguageInterface>(
&self,
repl: &mut Repl<L>,
arguments: &[&str],
) -> InterpreterDirectiveOutput {
use DirectiveAction::*; use DirectiveAction::*;
match self { match self {
Null => None, Null => None,
@ -30,8 +36,10 @@ impl DirectiveAction {
::std::process::exit(0) ::std::process::exit(0)
} }
ListPasses => { ListPasses => {
let language_state = repl.get_cur_language_state(); let pass_names = match repl
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) { .language_state
.request_meta(LangMetaRequest::StageNames)
{
LangMetaResponse::StageNames(names) => names, LangMetaResponse::StageNames(names) => names,
_ => vec![], _ => vec![],
}; };
@ -46,7 +54,6 @@ impl DirectiveAction {
Some(buf) Some(buf)
} }
ShowImmediate => { ShowImmediate => {
let cur_state = repl.get_cur_language_state();
let stage_name = match arguments.get(0) { let stage_name = match arguments.get(0) {
Some(s) => s.to_string(), Some(s) => s.to_string(),
None => return Some(format!("Must specify a thing to debug")), None => return Some(format!("Must specify a thing to debug")),
@ -55,7 +62,7 @@ impl DirectiveAction {
stage_name: stage_name.clone(), stage_name: stage_name.clone(),
token: None, token: None,
}); });
let meta_response = cur_state.request_meta(meta); let meta_response = repl.language_state.request_meta(meta);
let response = match meta_response { let response = match meta_response {
LangMetaResponse::ImmediateDebug(DebugResponse { ask, value }) => match ask { LangMetaResponse::ImmediateDebug(DebugResponse { ask, value }) => match ask {
@ -100,43 +107,37 @@ impl DirectiveAction {
}); });
None None
} }
TotalTimeOff => total_time_off(repl, arguments), TotalTimeOff => {
TotalTimeOn => total_time_on(repl, arguments), repl.options.show_total_time = false;
StageTimeOff => stage_time_off(repl, arguments), None
StageTimeOn => stage_time_on(repl, arguments), }
TotalTimeOn => {
repl.options.show_total_time = true;
None
}
StageTimeOff => {
repl.options.show_stage_times = false;
None
}
StageTimeOn => {
repl.options.show_stage_times = true;
None
}
Doc => doc(repl, arguments), Doc => doc(repl, arguments),
} }
} }
} }
fn total_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput { fn doc<L: ProgrammingLanguageInterface>(
repl.options.show_total_time = true; repl: &mut Repl<L>,
None arguments: &[&str],
} ) -> InterpreterDirectiveOutput {
fn total_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
repl.options.show_total_time = false;
None
}
fn stage_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
repl.options.show_stage_times = true;
None
}
fn stage_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
repl.options.show_stage_times = false;
None
}
fn doc(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
arguments arguments
.get(0) .get(0)
.map(|cmd| { .map(|cmd| {
let source = cmd.to_string(); let source = cmd.to_string();
let meta = LangMetaRequest::Docs { source }; let meta = LangMetaRequest::Docs { source };
let cur_state = repl.get_cur_language_state(); match repl.language_state.request_meta(meta) {
match cur_state.request_meta(meta) {
LangMetaResponse::Docs { doc_string } => Some(doc_string), LangMetaResponse::Docs { doc_string } => Some(doc_string),
_ => Some(format!("Invalid doc response")), _ => Some(format!("Invalid doc response")),
} }

View File

@ -28,6 +28,7 @@ fn get_list(passes_directives: &Vec<CommandTree>, include_help: bool) -> Vec<Com
vec![ vec![
CommandTree::terminal("exit", Some("exit the REPL"), vec![], QuitProgram), CommandTree::terminal("exit", Some("exit the REPL"), vec![], QuitProgram),
//TODO there should be an alias for this
CommandTree::terminal("quit", Some("exit the REPL"), vec![], QuitProgram), CommandTree::terminal("quit", Some("exit the REPL"), vec![], QuitProgram),
CommandTree::terminal( CommandTree::terminal(
"help", "help",
@ -85,15 +86,6 @@ fn get_list(passes_directives: &Vec<CommandTree>, include_help: bool) -> Vec<Com
), ),
], ],
), ),
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::terminal( CommandTree::terminal(
"doc", "doc",
Some("Get language-specific help for an item"), Some("Get language-specific help for an item"),

View File

@ -2,9 +2,13 @@ use std::fmt::Write as FmtWrite;
use super::command_tree::CommandTree; use super::command_tree::CommandTree;
use super::{InterpreterDirectiveOutput, Repl}; use super::{InterpreterDirectiveOutput, Repl};
use crate::language::ProgrammingLanguageInterface;
use colored::*; use colored::*;
pub fn help(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput { pub fn help<L: ProgrammingLanguageInterface>(
repl: &mut Repl<L>,
arguments: &[&str],
) -> InterpreterDirectiveOutput {
match arguments { match arguments {
[] => return global_help(repl), [] => return global_help(repl),
commands => { commands => {
@ -46,7 +50,7 @@ fn get_directive_from_commands<'a>(
matched_directive matched_directive
} }
fn global_help(repl: &mut Repl) -> InterpreterDirectiveOutput { fn global_help<L: ProgrammingLanguageInterface>(repl: &mut Repl<L>) -> InterpreterDirectiveOutput {
let mut buf = String::new(); let mut buf = String::new();
writeln!( writeln!(
@ -69,12 +73,11 @@ fn global_help(repl: &mut Repl) -> InterpreterDirectiveOutput {
.unwrap(); .unwrap();
} }
let ref lang = repl.get_cur_language_state();
writeln!(buf, "").unwrap(); writeln!(buf, "").unwrap();
writeln!( writeln!(
buf, buf,
"Language-specific help for {}", "Language-specific help for {}",
lang.language_name() <L as ProgrammingLanguageInterface>::language_name()
) )
.unwrap(); .unwrap();
writeln!(buf, "-----------------------").unwrap(); writeln!(buf, "-----------------------").unwrap();

View File

@ -1,6 +1,6 @@
use colored::*;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use colored::*;
use crate::language::{ use crate::language::{
ComputationRequest, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface, ComputationRequest, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface,
@ -22,13 +22,13 @@ const OPTIONS_SAVE_FILE: &'static str = ".schala_repl";
type InterpreterDirectiveOutput = Option<String>; type InterpreterDirectiveOutput = Option<String>;
pub struct Repl { pub struct Repl<L: ProgrammingLanguageInterface> {
/// If this is the first character typed by a user into the repl, the following /// 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 /// will be interpreted as a directive to the REPL rather than a command in the
/// running programming language. /// running programming language.
sigil: char, sigil: char,
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>, line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
language_states: Vec<Box<dyn ProgrammingLanguageInterface>>, language_state: L,
options: ReplOptions, options: ReplOptions,
} }
@ -38,8 +38,8 @@ enum PromptStyle {
Multiline, Multiline,
} }
impl Repl { impl<L: ProgrammingLanguageInterface> Repl<L> {
pub fn new(initial_states: Vec<Box<dyn ProgrammingLanguageInterface>>) -> Repl { pub fn new(initial_state: L) -> Self {
use linefeed::Interface; use linefeed::Interface;
let line_reader = Interface::new("schala-repl").unwrap(); let line_reader = Interface::new("schala-repl").unwrap();
let sigil = ':'; let sigil = ':';
@ -47,7 +47,7 @@ impl Repl {
Repl { Repl {
sigil, sigil,
line_reader, line_reader,
language_states: initial_states, language_state: initial_state,
options: ReplOptions::new(), options: ReplOptions::new(),
} }
} }
@ -55,7 +55,8 @@ impl Repl {
pub fn run_repl(&mut self) { pub fn run_repl(&mut self) {
println!("Schala meta-interpeter version {}", crate::VERSION_STRING); println!("Schala meta-interpeter version {}", crate::VERSION_STRING);
println!( println!(
"Type {} for help with the REPL", format!("{}help", self.sigil).bright_green().bold() "Type {} for help with the REPL",
format!("{}help", self.sigil).bright_green().bold()
); );
self.load_options(); self.load_options();
self.handle_repl_loop(); self.handle_repl_loop();
@ -68,10 +69,10 @@ impl Repl {
.load_history(HISTORY_SAVE_FILE) .load_history(HISTORY_SAVE_FILE)
.unwrap_or(()); .unwrap_or(());
match ReplOptions::load_from_file(OPTIONS_SAVE_FILE) { match ReplOptions::load_from_file(OPTIONS_SAVE_FILE) {
Ok(options) => { Ok(options) => {
self.options = options; self.options = options;
} }
Err(e) => eprintln!("{}",e) Err(e) => eprintln!("{}", e),
} }
} }
@ -132,8 +133,7 @@ impl Repl {
} }
fn update_line_reader(&mut self) { fn update_line_reader(&mut self) {
let tab_complete_handler = let tab_complete_handler = TabCompleteHandler::new(self.sigil, self.get_directives());
TabCompleteHandler::new(self.sigil, self.get_directives());
self.line_reader self.line_reader
.set_completer(Arc::new(tab_complete_handler)); //TODO fix this here .set_completer(Arc::new(tab_complete_handler)); //TODO fix this here
self.set_prompt(PromptStyle::Normal); self.set_prompt(PromptStyle::Normal);
@ -166,11 +166,6 @@ impl Repl {
directives.perform(self, &arguments) directives.perform(self, &arguments)
} }
fn get_cur_language_state(&mut self) -> &mut Box<dyn ProgrammingLanguageInterface> {
//TODO this is obviously not complete
&mut self.language_states[0]
}
fn handle_input(&mut self, input: &str) -> Vec<ReplResponse> { fn handle_input(&mut self, input: &str) -> Vec<ReplResponse> {
let mut debug_requests = HashSet::new(); let mut debug_requests = HashSet::new();
for ask in self.options.debug_asks.iter() { for ask in self.options.debug_asks.iter() {
@ -181,14 +176,15 @@ impl Repl {
source: input, source: input,
debug_requests, debug_requests,
}; };
let ref mut language_state = self.get_cur_language_state(); let response = self.language_state.run_computation(request);
let response = language_state.run_computation(request);
response::handle_computation_response(response, &self.options) response::handle_computation_response(response, &self.options)
} }
fn get_directives(&mut self) -> CommandTree { fn get_directives(&mut self) -> CommandTree {
let language_state = self.get_cur_language_state(); let pass_names = match self
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) { .language_state
.request_meta(LangMetaRequest::StageNames)
{
LangMetaResponse::StageNames(names) => names, LangMetaResponse::StageNames(names) => names,
_ => vec![], _ => vec![],
}; };

View File

@ -1,8 +1,9 @@
use schala_repl::{run_noninteractive, start_repl, ProgrammingLanguageInterface}; use schala_repl::{Repl, ProgrammingLanguageInterface, ComputationRequest};
use std::path::PathBuf; use std::{fs::File, io::Read, path::PathBuf, process::exit, collections::HashSet};
use std::process::exit; use schala_lang::Schala;
//TODO specify multiple langs, and have a way to switch between them
fn main() { fn main() {
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let matches = command_line_options() let matches = command_line_options()
@ -17,17 +18,50 @@ fn main() {
exit(0); exit(0);
} }
let langs: Vec<Box<dyn ProgrammingLanguageInterface>> =
vec![Box::new(schala_lang::Schala::new())];
if matches.free.is_empty() { if matches.free.is_empty() {
start_repl(langs); let state = Schala::new();
let mut repl = Repl::new(state);
repl.run_repl();
} else { } else {
let paths = matches.free.iter().map(PathBuf::from).collect(); let paths: Vec<PathBuf> = matches.free.iter().map(PathBuf::from).collect();
run_noninteractive(paths, langs); //TODO handle more than one file
let filename = &paths[0];
let extension = filename.extension().and_then(|e| e.to_str())
.unwrap_or_else(|| {
eprintln!("Source file `{}` has no extension.", filename.display());
exit(1);
});
//TODO this proably should be a macro for every supported language
if extension == Schala::source_file_suffix() {
run_noninteractive(paths, Schala::new());
} else {
eprintln!("Extension .{} not recognized", extension);
exit(1);
}
} }
} }
pub fn run_noninteractive<L: ProgrammingLanguageInterface>(filenames: Vec<PathBuf>, mut language: L) {
// for now, ony do something with the first filename
let filename = &filenames[0];
let mut source_file = File::open(filename).unwrap();
let mut buffer = String::new();
source_file.read_to_string(&mut buffer).unwrap();
let request = ComputationRequest {
source: &buffer,
debug_requests: HashSet::new(),
};
let response = language.run_computation(request);
match response.main_output {
Ok(s) => println!("{}", s),
Err(s) => eprintln!("{}", s),
};
}
fn command_line_options() -> getopts::Options { fn command_line_options() -> getopts::Options {
let mut options = getopts::Options::new(); let mut options = getopts::Options::new();
options.optflag("h", "help", "Show help text"); options.optflag("h", "help", "Show help text");