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 {
fn language_name(&self) -> String {
fn language_name() -> String {
"Schala".to_owned()
}
fn source_file_suffix(&self) -> String {
fn source_file_suffix() -> String {
"schala".to_owned()
}

View File

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

View File

@ -7,12 +7,6 @@ extern crate includedir;
extern crate phf;
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 repl;
@ -21,49 +15,7 @@ pub use language::{
LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface,
};
pub use repl::Repl;
include!(concat!(env!("OUT_DIR"), "/static.rs"));
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 crate::language::ProgrammingLanguageInterface;
use crate::repl::directive_actions::DirectiveAction;
use colored::*;
@ -85,7 +86,11 @@ impl CommandTree {
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 idx = 0;

View File

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

View File

@ -28,6 +28,7 @@ fn get_list(passes_directives: &Vec<CommandTree>, include_help: bool) -> Vec<Com
vec![
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(
"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(
"doc",
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::{InterpreterDirectiveOutput, Repl};
use crate::language::ProgrammingLanguageInterface;
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 {
[] => return global_help(repl),
commands => {
@ -46,7 +50,7 @@ fn get_directive_from_commands<'a>(
matched_directive
}
fn global_help(repl: &mut Repl) -> InterpreterDirectiveOutput {
fn global_help<L: ProgrammingLanguageInterface>(repl: &mut Repl<L>) -> InterpreterDirectiveOutput {
let mut buf = String::new();
writeln!(
@ -69,12 +73,11 @@ fn global_help(repl: &mut Repl) -> InterpreterDirectiveOutput {
.unwrap();
}
let ref lang = repl.get_cur_language_state();
writeln!(buf, "").unwrap();
writeln!(
buf,
"Language-specific help for {}",
lang.language_name()
<L as ProgrammingLanguageInterface>::language_name()
)
.unwrap();
writeln!(buf, "-----------------------").unwrap();

View File

@ -1,6 +1,6 @@
use colored::*;
use std::collections::HashSet;
use std::sync::Arc;
use colored::*;
use crate::language::{
ComputationRequest, LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface,
@ -22,13 +22,13 @@ const OPTIONS_SAVE_FILE: &'static str = ".schala_repl";
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
/// 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.
sigil: char,
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
language_states: Vec<Box<dyn ProgrammingLanguageInterface>>,
language_state: L,
options: ReplOptions,
}
@ -38,8 +38,8 @@ enum PromptStyle {
Multiline,
}
impl Repl {
pub fn new(initial_states: Vec<Box<dyn ProgrammingLanguageInterface>>) -> Repl {
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 = ':';
@ -47,7 +47,7 @@ impl Repl {
Repl {
sigil,
line_reader,
language_states: initial_states,
language_state: initial_state,
options: ReplOptions::new(),
}
}
@ -55,7 +55,8 @@ impl Repl {
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()
"Type {} for help with the REPL",
format!("{}help", self.sigil).bright_green().bold()
);
self.load_options();
self.handle_repl_loop();
@ -68,10 +69,10 @@ impl Repl {
.load_history(HISTORY_SAVE_FILE)
.unwrap_or(());
match ReplOptions::load_from_file(OPTIONS_SAVE_FILE) {
Ok(options) => {
self.options = options;
}
Err(e) => eprintln!("{}",e)
Ok(options) => {
self.options = options;
}
Err(e) => eprintln!("{}", e),
}
}
@ -132,8 +133,7 @@ impl Repl {
}
fn update_line_reader(&mut self) {
let tab_complete_handler =
TabCompleteHandler::new(self.sigil, self.get_directives());
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);
@ -166,11 +166,6 @@ impl Repl {
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> {
let mut debug_requests = HashSet::new();
for ask in self.options.debug_asks.iter() {
@ -181,14 +176,15 @@ impl Repl {
source: input,
debug_requests,
};
let ref mut language_state = self.get_cur_language_state();
let response = language_state.run_computation(request);
let response = self.language_state.run_computation(request);
response::handle_computation_response(response, &self.options)
}
fn get_directives(&mut self) -> CommandTree {
let language_state = self.get_cur_language_state();
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) {
let pass_names = match self
.language_state
.request_meta(LangMetaRequest::StageNames)
{
LangMetaResponse::StageNames(names) => names,
_ => 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::process::exit;
use std::{fs::File, io::Read, path::PathBuf, process::exit, collections::HashSet};
use schala_lang::Schala;
//TODO specify multiple langs, and have a way to switch between them
fn main() {
let args: Vec<String> = std::env::args().collect();
let matches = command_line_options()
@ -17,17 +18,50 @@ fn main() {
exit(0);
}
let langs: Vec<Box<dyn ProgrammingLanguageInterface>> =
vec![Box::new(schala_lang::Schala::new())];
if matches.free.is_empty() {
start_repl(langs);
let state = Schala::new();
let mut repl = Repl::new(state);
repl.run_repl();
} else {
let paths = matches.free.iter().map(PathBuf::from).collect();
run_noninteractive(paths, langs);
let paths: Vec<PathBuf> = matches.free.iter().map(PathBuf::from).collect();
//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 {
let mut options = getopts::Options::new();
options.optflag("h", "help", "Show help text");