diff --git a/Cargo.toml b/Cargo.toml index d617897..8c03e3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,9 @@ rocket_contrib = "*" phf = "0.7.12" includedir = "0.2.0" +schala-lib = { path = "schala-lib" } + [build-dependencies] includedir_codegen = "0.2.0" + +[workspace] diff --git a/schala-lib/Cargo.toml b/schala-lib/Cargo.toml new file mode 100644 index 0000000..23d8ead --- /dev/null +++ b/schala-lib/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "schala-lib" +version = "0.1.0" +authors = ["greg "] + +[dependencies] +llvm-sys = "*" +take_mut = "0.1.3" +itertools = "0.5.8" +getopts = "*" +linefeed = "0.3" +lazy_static = "0.2.8" +maplit = "*" +colored = "1.5" +serde = "1.0.15" +serde_derive = "1.0.15" +serde_json = "1.0.3" +rocket = "*" +rocket_codegen = "*" +rocket_contrib = "*" +phf = "0.7.12" +includedir = "0.2.0" + +[build-dependencies] +includedir_codegen = "0.2.0" diff --git a/build.rs b/schala-lib/build.rs similarity index 79% rename from build.rs rename to schala-lib/build.rs index 943ce5c..a6395ef 100644 --- a/build.rs +++ b/schala-lib/build.rs @@ -4,7 +4,7 @@ use includedir_codegen::Compression; fn main() { includedir_codegen::start("WEBFILES") - .dir("static", Compression::Gzip) + .dir("../static", Compression::Gzip) .build("static.rs") .unwrap(); } diff --git a/src/language.rs b/schala-lib/src/language.rs similarity index 100% rename from src/language.rs rename to schala-lib/src/language.rs diff --git a/schala-lib/src/lib.rs b/schala-lib/src/lib.rs new file mode 100644 index 0000000..12bc6d8 --- /dev/null +++ b/schala-lib/src/lib.rs @@ -0,0 +1,360 @@ +#![feature(link_args)] +#![feature(advanced_slice_patterns, slice_patterns, box_patterns, box_syntax)] +#![feature(plugin)] +#![plugin(rocket_codegen)] +extern crate getopts; +extern crate linefeed; +extern crate itertools; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate maplit; +#[macro_use] +extern crate serde_derive; +extern crate serde_json; +extern crate rocket; +extern crate rocket_contrib; +extern crate includedir; +extern crate phf; + +use std::path::Path; +use std::fs::File; +use std::io::{Read, Write}; +use std::process::exit; +use std::default::Default; + +mod language; +use language::{ProgrammingLanguageInterface, EvalOptions, LLVMCodeString}; +mod webapp; +mod llvm_wrap; + +include!(concat!(env!("OUT_DIR"), "/static.rs")); + +type PLIGenerator = Box Box + Send + Sync>; + +fn schala_main(generators: Vec) { + let languages: Vec> = generators.iter().map(|x| x()).collect(); + + let option_matches = program_options().parse(std::env::args()).unwrap_or_else(|e| { + println!("{:?}", e); + exit(1); + }); + + if option_matches.opt_present("list-languages") { + for lang in languages { + println!("{}", lang.get_language_name()); + } + exit(1); + } + + if option_matches.opt_present("help") { + println!("{}", program_options().usage("Schala metainterpreter")); + exit(0); + } + + if option_matches.opt_present("webapp") { + webapp::web_main(generators); + exit(0); + } + + let language_names: Vec = languages.iter().map(|lang| {lang.get_language_name()}).collect(); + let initial_index: usize = + option_matches.opt_str("lang") + .and_then(|lang| { language_names.iter().position(|x| { *x == lang }) }) + .unwrap_or(0); + + let mut options = EvalOptions::default(); + options.compile = match option_matches.opt_str("eval-style") { + Some(ref s) if s == "compile" => true, + _ => false + }; + + match option_matches.free[..] { + [] | [_] => { + let mut repl = Repl::new(languages, initial_index); + repl.options.show_llvm_ir = true; //TODO make this be configurable + repl.run(); + } + [_, ref filename, _..] => { + + run_noninteractive(filename, languages, options); + } + }; +} + +fn run_noninteractive(filename: &str, languages: Vec>, options: EvalOptions) { + let path = Path::new(filename); + let ext = path.extension().and_then(|e| e.to_str()).unwrap_or_else(|| { + println!("Source file lacks extension"); + exit(1); + }); + let mut language = Box::new(languages.into_iter().find(|lang| lang.get_source_file_suffix() == ext) + .unwrap_or_else(|| { + println!("Extension .{} not recognized", ext); + exit(1); + })); + + let mut source_file = File::open(path).unwrap(); + let mut buffer = String::new(); + + source_file.read_to_string(&mut buffer).unwrap(); + + if options.compile { + if !language.can_compile() { + panic!("Trying to compile a non-compileable language"); + } else { + let llvm_bytecode = language.compile(&buffer); + compilation_sequence(llvm_bytecode, filename); + } + } else { + let interpretor_output = language.evaluate_in_repl(&buffer, &options); + interpretor_output.print_to_screen(); + } +} + +type LineReader = linefeed::Reader; +struct Repl { + options: EvalOptions, + languages: Vec>, + current_language_index: usize, + interpreter_directive_sigil: char, + reader: LineReader, +} + +impl Repl { + fn new(languages: Vec>, initial_index: usize) -> Repl { + let mut reader: linefeed::Reader<_> = linefeed::Reader::new("Metainterpreter").unwrap(); + reader.set_prompt(">> "); + let i = if initial_index < languages.len() { initial_index } else { 0 }; + + Repl { + options: Repl::get_options(), + languages: languages, + current_language_index: i, + interpreter_directive_sigil: '.', + reader: reader, + } + } + + fn get_options() -> EvalOptions { + File::open(".schala_repl") + .and_then(|mut file| { + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + Ok(contents) + }) + .and_then(|contents| { + let options: EvalOptions = serde_json::from_str(&contents)?; + Ok(options) + }).unwrap_or(EvalOptions::default()) + } + + fn save_options(&self) { + let ref options = self.options; + let read = File::create(".schala_repl") + .and_then(|mut file| { + let buf = serde_json::to_string(options).unwrap(); + file.write_all(buf.as_bytes()) + }); + + if let Err(err) = read { + println!("Error saving .schala_repl file {}", err); + } + } + + fn run(&mut self) { + use linefeed::ReadResult::*; + println!("MetaInterpreter v 0.05"); + println!("Using language: {}", self.languages[self.current_language_index].get_language_name()); + loop { + match self.reader.read_line() { + Err(e) => { + println!("Terminal read error: {}", e); + }, + Ok(Eof) => { + break; + } + Ok(Input(ref input)) => { + self.reader.add_history(input.clone()); + if self.handle_interpreter_directive(input) { + continue; + } + let output = self.input_handler(input); + println!("=> {}", output); + } + _ => (), + } + } + println!("Exiting..."); + } + + fn input_handler(&mut self, input: &str) -> String { + let ref mut language = self.languages[self.current_language_index]; + let interpretor_output = language.evaluate_in_repl(input, &self.options); + interpretor_output.to_string() + } + + fn handle_interpreter_directive(&mut self, input: &str) -> bool { + match input.chars().nth(0) { + Some(ch) if ch == self.interpreter_directive_sigil => (), + _ => return false + } + + let mut iter = input.chars(); + iter.next(); + let trimmed_sigil: &str = iter.as_str(); + + let commands: Vec<&str> = trimmed_sigil + .split_whitespace() + .collect(); + + let cmd: &str = match commands.get(0).clone() { + None => return true, + Some(s) => s + }; + + match cmd { + "exit" | "quit" => { + self.save_options(); + exit(0) + }, + "history" => { + for item in self.reader.history() { + println!("{}", item); + } + }, + "help" => { + println!("Commands:"); + println!("exit | quit"); + println!("lang [show|next|previous]"); + println!("set [show|hide] [tokens|parse|symbols|eval|llvm]"); + } + "lang" => { + match commands.get(1) { + Some(&"show") => { + for (i, lang) in self.languages.iter().enumerate() { + if i == self.current_language_index { + println!("* {}", lang.get_language_name()); + } else { + println!("{}", lang.get_language_name()); + } + } + }, + Some(&"next") => { + self.current_language_index = (self.current_language_index + 1) % self.languages.len(); + println!("Switching to {}", self.languages[self.current_language_index].get_language_name()); + } + Some(&"prev") | Some(&"previous") => { + self.current_language_index = if self.current_language_index == 0 { self.languages.len() - 1 } else { self.current_language_index - 1 }; + println!("Switching to {}", self.languages[self.current_language_index].get_language_name()); + }, + Some(e) => println!("Bad `lang` argument: {}", e), + None => println!("`lang` - valid arguments `show`, `next`, `prev`|`previous`"), + } + }, + "set" => { + let show = match commands.get(1) { + Some(&"show") => true, + Some(&"hide") => false, + Some(e) => { + println!("Bad `set` argument: {}", e); + return true; + } + None => { + println!("`set` - valid arguments `show {{option}}`, `hide {{option}}`"); + return true; + } + }; + match commands.get(2) { + Some(&"tokens") => self.options.debug_tokens = show, + Some(&"parse") => self.options.debug_parse = show, + Some(&"symbols") => self.options.debug_symbol_table = show, + Some(&"eval") => { + //let ref mut language = self.languages[self.current_language_index]; + //language.set_option("trace_evaluation", show); + }, + Some(&"llvm") => self.options.show_llvm_ir = show, + Some(e) => { + println!("Bad `show`/`hide` argument: {}", e); + return true; + } + None => { + println!("`show`/`hide` requires an argument"); + return true; + } + } + }, + e => println!("Unknown command: {}", e) + } + return true; + } +} + +pub fn compilation_sequence(llvm_code: LLVMCodeString, sourcefile: &str) { + use std::process::Command; + + let ll_filename = "out.ll"; + let obj_filename = "out.o"; + let q: Vec<&str> = sourcefile.split('.').collect(); + let bin_filename = match &q[..] { + &[name, "maaru"] => name, + _ => panic!("Bad filename {}", sourcefile), + }; + + let LLVMCodeString(llvm_str) = llvm_code; + + println!("Compilation process finished for {}", ll_filename); + File::create(ll_filename) + .and_then(|mut f| f.write_all(llvm_str.as_bytes())) + .expect("Error writing file"); + + let llc_output = Command::new("llc") + .args(&["-filetype=obj", ll_filename, "-o", obj_filename]) + .output() + .expect("Failed to run llc"); + + + if !llc_output.status.success() { + println!("{}", String::from_utf8_lossy(&llc_output.stderr)); + } + + let gcc_output = Command::new("gcc") + .args(&["-o", bin_filename, &obj_filename]) + .output() + .expect("failed to run gcc"); + + if !gcc_output.status.success() { + println!("{}", String::from_utf8_lossy(&gcc_output.stdout)); + println!("{}", String::from_utf8_lossy(&gcc_output.stderr)); + } + + for filename in [obj_filename].iter() { + Command::new("rm") + .arg(filename) + .output() + .expect(&format!("failed to run rm {}", filename)); + } +} + +fn program_options() -> getopts::Options { + let mut options = getopts::Options::new(); + options.optopt("s", + "eval-style", + "Specify whether to compile (if supported) or interpret the language. If not specified, the default is language-specific", + "[compile|interpret]" + ); + options.optflag("", + "list-languages", + "Show a list of all supported languages"); + options.optopt("l", + "lang", + "Start up REPL in a language", + "LANGUAGE"); + options.optflag("h", + "help", + "Show help text"); + options.optflag("w", + "webapp", + "Start up web interpreter"); + options +} diff --git a/src/llvm_wrap.rs b/schala-lib/src/llvm_wrap.rs similarity index 100% rename from src/llvm_wrap.rs rename to schala-lib/src/llvm_wrap.rs diff --git a/src/webapp.rs b/schala-lib/src/webapp.rs similarity index 97% rename from src/webapp.rs rename to schala-lib/src/webapp.rs index 5d4975e..8f2f844 100644 --- a/src/webapp.rs +++ b/schala-lib/src/webapp.rs @@ -1,6 +1,7 @@ use rocket; use rocket::State; use rocket::response::Content; +use rocket::response::NamedFile; use rocket::http::ContentType; use rocket_contrib::Json; use language::{ProgrammingLanguageInterface, EvalOptions}; diff --git a/src/main.rs b/src/main.rs index 10eeb96..dd2a392 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,19 +27,12 @@ mod schala_lang; mod maaru_lang; mod robo_lang; -mod language; -use language::{ProgrammingLanguageInterface, EvalOptions, LLVMCodeString}; - -mod webapp; -mod llvm_wrap; - -include!(concat!(env!("OUT_DIR"), "/static.rs")); +extern crate schala_lib; +use schala_lib::{PLIGenerator, schala_main}; #[link_args="-ltinfo"] extern { } -type PLIGenerator = Box Box + Send + Sync>; - fn main() { let generators: Vec = vec![ Box::new(|| { let x: Box = Box::new(schala_lang::Schala::new()); x }), @@ -50,330 +43,4 @@ fn main() { schala_main(generators); } -fn schala_main(generators: Vec) { - let languages: Vec> = generators.iter().map(|x| x()).collect(); - - let option_matches = program_options().parse(std::env::args()).unwrap_or_else(|e| { - println!("{:?}", e); - exit(1); - }); - - if option_matches.opt_present("list-languages") { - for lang in languages { - println!("{}", lang.get_language_name()); - } - exit(1); - } - - if option_matches.opt_present("help") { - println!("{}", program_options().usage("Schala metainterpreter")); - exit(0); - } - - if option_matches.opt_present("webapp") { - webapp::web_main(generators); - exit(0); - } - - let language_names: Vec = languages.iter().map(|lang| {lang.get_language_name()}).collect(); - let initial_index: usize = - option_matches.opt_str("lang") - .and_then(|lang| { language_names.iter().position(|x| { *x == lang }) }) - .unwrap_or(0); - - let mut options = EvalOptions::default(); - options.compile = match option_matches.opt_str("eval-style") { - Some(ref s) if s == "compile" => true, - _ => false - }; - - match option_matches.free[..] { - [] | [_] => { - let mut repl = Repl::new(languages, initial_index); - repl.options.show_llvm_ir = true; //TODO make this be configurable - repl.run(); - } - [_, ref filename, _..] => { - - run_noninteractive(filename, languages, options); - } - }; -} - -fn run_noninteractive(filename: &str, languages: Vec>, options: EvalOptions) { - let path = Path::new(filename); - let ext = path.extension().and_then(|e| e.to_str()).unwrap_or_else(|| { - println!("Source file lacks extension"); - exit(1); - }); - let mut language = Box::new(languages.into_iter().find(|lang| lang.get_source_file_suffix() == ext) - .unwrap_or_else(|| { - println!("Extension .{} not recognized", ext); - exit(1); - })); - - let mut source_file = File::open(path).unwrap(); - let mut buffer = String::new(); - - source_file.read_to_string(&mut buffer).unwrap(); - - if options.compile { - if !language.can_compile() { - panic!("Trying to compile a non-compileable language"); - } else { - let llvm_bytecode = language.compile(&buffer); - compilation_sequence(llvm_bytecode, filename); - } - } else { - let interpretor_output = language.evaluate_in_repl(&buffer, &options); - interpretor_output.print_to_screen(); - } -} - -type LineReader = linefeed::Reader; -struct Repl { - options: EvalOptions, - languages: Vec>, - current_language_index: usize, - interpreter_directive_sigil: char, - reader: LineReader, -} - -impl Repl { - fn new(languages: Vec>, initial_index: usize) -> Repl { - let mut reader: linefeed::Reader<_> = linefeed::Reader::new("Metainterpreter").unwrap(); - reader.set_prompt(">> "); - let i = if initial_index < languages.len() { initial_index } else { 0 }; - - Repl { - options: Repl::get_options(), - languages: languages, - current_language_index: i, - interpreter_directive_sigil: '.', - reader: reader, - } - } - - fn get_options() -> EvalOptions { - File::open(".schala_repl") - .and_then(|mut file| { - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - Ok(contents) - }) - .and_then(|contents| { - let options: EvalOptions = serde_json::from_str(&contents)?; - Ok(options) - }).unwrap_or(EvalOptions::default()) - } - - fn save_options(&self) { - let ref options = self.options; - let read = File::create(".schala_repl") - .and_then(|mut file| { - let buf = serde_json::to_string(options).unwrap(); - file.write_all(buf.as_bytes()) - }); - - if let Err(err) = read { - println!("Error saving .schala_repl file {}", err); - } - } - - fn run(&mut self) { - use linefeed::ReadResult::*; - println!("MetaInterpreter v 0.05"); - println!("Using language: {}", self.languages[self.current_language_index].get_language_name()); - loop { - match self.reader.read_line() { - Err(e) => { - println!("Terminal read error: {}", e); - }, - Ok(Eof) => { - break; - } - Ok(Input(ref input)) => { - self.reader.add_history(input.clone()); - if self.handle_interpreter_directive(input) { - continue; - } - let output = self.input_handler(input); - println!("=> {}", output); - } - _ => (), - } - } - println!("Exiting..."); - } - - fn input_handler(&mut self, input: &str) -> String { - let ref mut language = self.languages[self.current_language_index]; - let interpretor_output = language.evaluate_in_repl(input, &self.options); - interpretor_output.to_string() - } - - fn handle_interpreter_directive(&mut self, input: &str) -> bool { - match input.chars().nth(0) { - Some(ch) if ch == self.interpreter_directive_sigil => (), - _ => return false - } - - let mut iter = input.chars(); - iter.next(); - let trimmed_sigil: &str = iter.as_str(); - - let commands: Vec<&str> = trimmed_sigil - .split_whitespace() - .collect(); - - let cmd: &str = match commands.get(0).clone() { - None => return true, - Some(s) => s - }; - - match cmd { - "exit" | "quit" => { - self.save_options(); - exit(0) - }, - "history" => { - for item in self.reader.history() { - println!("{}", item); - } - }, - "help" => { - println!("Commands:"); - println!("exit | quit"); - println!("lang [show|next|previous]"); - println!("set [show|hide] [tokens|parse|symbols|eval|llvm]"); - } - "lang" => { - match commands.get(1) { - Some(&"show") => { - for (i, lang) in self.languages.iter().enumerate() { - if i == self.current_language_index { - println!("* {}", lang.get_language_name()); - } else { - println!("{}", lang.get_language_name()); - } - } - }, - Some(&"next") => { - self.current_language_index = (self.current_language_index + 1) % self.languages.len(); - println!("Switching to {}", self.languages[self.current_language_index].get_language_name()); - } - Some(&"prev") | Some(&"previous") => { - self.current_language_index = if self.current_language_index == 0 { self.languages.len() - 1 } else { self.current_language_index - 1 }; - println!("Switching to {}", self.languages[self.current_language_index].get_language_name()); - }, - Some(e) => println!("Bad `lang` argument: {}", e), - None => println!("`lang` - valid arguments `show`, `next`, `prev`|`previous`"), - } - }, - "set" => { - let show = match commands.get(1) { - Some(&"show") => true, - Some(&"hide") => false, - Some(e) => { - println!("Bad `set` argument: {}", e); - return true; - } - None => { - println!("`set` - valid arguments `show {{option}}`, `hide {{option}}`"); - return true; - } - }; - match commands.get(2) { - Some(&"tokens") => self.options.debug_tokens = show, - Some(&"parse") => self.options.debug_parse = show, - Some(&"symbols") => self.options.debug_symbol_table = show, - Some(&"eval") => { - //let ref mut language = self.languages[self.current_language_index]; - //language.set_option("trace_evaluation", show); - }, - Some(&"llvm") => self.options.show_llvm_ir = show, - Some(e) => { - println!("Bad `show`/`hide` argument: {}", e); - return true; - } - None => { - println!("`show`/`hide` requires an argument"); - return true; - } - } - }, - e => println!("Unknown command: {}", e) - } - return true; - } -} - -pub fn compilation_sequence(llvm_code: LLVMCodeString, sourcefile: &str) { - use std::process::Command; - - let ll_filename = "out.ll"; - let obj_filename = "out.o"; - let q: Vec<&str> = sourcefile.split('.').collect(); - let bin_filename = match &q[..] { - &[name, "maaru"] => name, - _ => panic!("Bad filename {}", sourcefile), - }; - - let LLVMCodeString(llvm_str) = llvm_code; - - println!("Compilation process finished for {}", ll_filename); - File::create(ll_filename) - .and_then(|mut f| f.write_all(llvm_str.as_bytes())) - .expect("Error writing file"); - - let llc_output = Command::new("llc") - .args(&["-filetype=obj", ll_filename, "-o", obj_filename]) - .output() - .expect("Failed to run llc"); - - - if !llc_output.status.success() { - println!("{}", String::from_utf8_lossy(&llc_output.stderr)); - } - - let gcc_output = Command::new("gcc") - .args(&["-o", bin_filename, &obj_filename]) - .output() - .expect("failed to run gcc"); - - if !gcc_output.status.success() { - println!("{}", String::from_utf8_lossy(&gcc_output.stdout)); - println!("{}", String::from_utf8_lossy(&gcc_output.stderr)); - } - - for filename in [obj_filename].iter() { - Command::new("rm") - .arg(filename) - .output() - .expect(&format!("failed to run rm {}", filename)); - } -} - -fn program_options() -> getopts::Options { - let mut options = getopts::Options::new(); - options.optopt("s", - "eval-style", - "Specify whether to compile (if supported) or interpret the language. If not specified, the default is language-specific", - "[compile|interpret]" - ); - options.optflag("", - "list-languages", - "Show a list of all supported languages"); - options.optopt("l", - "lang", - "Start up REPL in a language", - "LANGUAGE"); - options.optflag("h", - "help", - "Show help text"); - options.optflag("w", - "webapp", - "Start up web interpreter"); - options -}