2017-10-30 20:06:20 -07:00
#![ feature(link_args) ]
2018-04-03 23:24:13 -07:00
#![ feature(slice_patterns, box_patterns, box_syntax) ]
2017-10-30 20:06:20 -07:00
#![ feature(plugin) ]
#![ plugin(rocket_codegen) ]
extern crate getopts ;
2018-07-02 00:20:52 -07:00
extern crate linefeed ;
2017-10-30 20:06:20 -07:00
extern crate itertools ;
2018-03-24 15:14:24 -07:00
extern crate colored ;
2018-03-20 20:29:07 -07:00
2017-10-30 20:06:20 -07:00
#[ 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 ;
2018-03-25 00:09:51 -07:00
use std ::fmt ::Write as FmtWrite ;
2017-10-30 20:06:20 -07:00
2018-05-05 22:36:04 -07:00
use colored ::* ;
2018-05-01 02:24:50 -07:00
use itertools ::Itertools ;
2018-03-01 02:43:11 -08:00
2017-11-02 02:45:26 -07:00
mod language ;
2017-10-30 20:06:20 -07:00
mod webapp ;
2017-10-30 22:18:02 -07:00
pub mod llvm_wrap ;
2017-10-30 20:06:20 -07:00
2018-03-23 18:56:09 -07:00
const VERSION_STRING : & 'static str = " 0.1.0 " ;
2017-10-30 20:06:20 -07:00
include! ( concat! ( env! ( " OUT_DIR " ) , " /static.rs " ) ) ;
2018-05-02 01:59:09 -07:00
pub use language ::{ LLVMCodeString , ProgrammingLanguageInterface , EvalOptions , ExecutionMethod , TraceArtifact , FinishedComputation , UnfinishedComputation } ;
2017-10-30 22:18:02 -07:00
pub type PLIGenerator = Box < Fn ( ) -> Box < ProgrammingLanguageInterface > + Send + Sync > ;
2017-10-30 20:06:20 -07:00
2018-03-23 19:04:32 -07:00
pub fn repl_main ( generators : Vec < PLIGenerator > ) {
2017-10-30 20:06:20 -07:00
let languages : Vec < Box < ProgrammingLanguageInterface > > = 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 ) ;
}
2018-03-27 00:50:31 -07:00
let mut options = EvalOptions ::default ( ) ;
2018-05-07 02:38:44 -07:00
let debug_passes = if let Some ( opts ) = option_matches . opt_str ( " debug " ) {
let output : Vec < String > = opts . split_terminator ( " , " ) . map ( | s | s . to_string ( ) ) . collect ( ) ;
output
} else {
vec! [ ]
} ;
2018-03-27 00:50:31 -07:00
2017-10-30 20:06:20 -07:00
let language_names : Vec < String > = languages . iter ( ) . map ( | lang | { lang . get_language_name ( ) } ) . collect ( ) ;
let initial_index : usize =
option_matches . opt_str ( " lang " )
2017-12-09 18:19:07 -08:00
. and_then ( | lang | { language_names . iter ( ) . position ( | x | { x . to_lowercase ( ) = = lang . to_lowercase ( ) } ) } )
2017-10-30 20:06:20 -07:00
. unwrap_or ( 0 ) ;
2018-03-20 20:29:07 -07:00
options . execution_method = match option_matches . opt_str ( " eval-style " ) {
Some ( ref s ) if s = = " compile " = > ExecutionMethod ::Compile ,
_ = > ExecutionMethod ::Interpret ,
2017-10-30 20:06:20 -07:00
} ;
match option_matches . free [ .. ] {
[ ] | [ _ ] = > {
let mut repl = Repl ::new ( languages , initial_index ) ;
repl . run ( ) ;
}
[ _ , ref filename , _ .. ] = > {
2018-05-07 02:38:44 -07:00
run_noninteractive ( filename , languages , options , debug_passes ) ;
2017-10-30 20:06:20 -07:00
}
} ;
}
2018-05-07 02:38:44 -07:00
fn run_noninteractive ( filename : & str , languages : Vec < Box < ProgrammingLanguageInterface > > , mut options : EvalOptions , debug_passes : Vec < String > ) {
2017-10-30 20:06:20 -07:00
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 ( ) ;
2018-05-07 02:38:44 -07:00
for pass in debug_passes . into_iter ( ) {
if let Some ( _ ) = language . get_passes ( ) . iter ( ) . find ( | stage_name | * * stage_name = = pass ) {
options . debug_passes . insert ( pass ) ;
}
}
2018-03-20 20:29:07 -07:00
match options . execution_method {
ExecutionMethod ::Compile = > {
/*
2017-10-30 20:06:20 -07:00
let llvm_bytecode = language . compile ( & buffer ) ;
compilation_sequence ( llvm_bytecode , filename ) ;
2018-03-20 20:29:07 -07:00
* /
panic! ( " Not ready to go yet " ) ;
} ,
ExecutionMethod ::Interpret = > {
2018-04-29 22:53:17 -07:00
let output = language . execute_pipeline ( & buffer , & options ) ;
2018-03-20 20:29:07 -07:00
output . to_noninteractive ( ) . map ( | text | println! ( " {} " , text ) ) ;
2018-03-11 12:56:51 -07:00
}
2017-10-30 20:06:20 -07:00
}
}
2018-07-02 01:50:24 -07:00
enum CommandTree {
Terminal ( String ) ,
NonTerminal ( String , Vec < CommandTree > )
}
2018-07-02 00:20:52 -07:00
struct TabCompleteHandler {
2018-05-07 01:42:19 -07:00
passes : Vec < String > ,
sigil : char ,
2018-07-02 01:50:24 -07:00
top_level_commands : Vec < String > ,
2018-05-07 01:42:19 -07:00
}
2018-07-02 01:50:24 -07:00
use linefeed ::complete ::{ Completion , Completer } ;
use linefeed ::terminal ::Terminal ;
2018-05-07 01:42:19 -07:00
impl TabCompleteHandler {
2018-07-02 01:50:24 -07:00
fn new ( sigil : char , passes : Vec < String > ) -> TabCompleteHandler {
TabCompleteHandler {
passes ,
sigil ,
top_level_commands : vec ! [
" exit " . to_string ( ) ,
" quit " . to_string ( ) ,
" lang " . to_string ( ) ,
" debug " . to_string ( ) ,
]
}
2018-05-07 01:42:19 -07:00
}
}
2018-05-06 21:29:27 -07:00
2018-07-02 01:50:24 -07:00
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 ( ) ;
2018-05-07 01:42:19 -07:00
if line . starts_with ( & format! ( " {} " , self . sigil ) ) {
2018-07-02 01:50:24 -07:00
let mut words = line [ 1 .. ( if start = = 0 { 1 } else { start } ) ] . split_whitespace ( ) ;
let mut completions = Vec ::new ( ) ;
match words . next ( ) {
None = > {
let word = word . get ( 1 .. ) . unwrap ( ) ;
for cmd in self . top_level_commands . iter ( ) {
if cmd . starts_with ( word ) {
completions . push (
Completion {
completion : format ! ( " :{} " , cmd ) ,
display : Some ( cmd . clone ( ) ) ,
suffix : linefeed ::complete ::Suffix ::Some ( ' ' )
}
)
}
}
} ,
Some ( " debug " ) = > match words . next ( ) {
None = > for cmd in [ " show " , " hide " , " passes " ] . iter ( ) {
if cmd . starts_with ( word ) {
completions . push ( Completion ::simple ( cmd . to_string ( ) ) ) ;
}
} ,
Some ( " show " ) | Some ( " hide " ) = > for pass in self . passes . iter ( ) {
if pass . starts_with ( word ) {
completions . push ( Completion ::simple ( pass . to_string ( ) ) ) ;
}
} ,
_ = > return None ,
} ,
_ = > return None
}
Some ( completions )
2018-05-07 01:42:19 -07:00
} else {
2018-07-02 01:50:24 -07:00
None
2018-05-07 01:42:19 -07:00
}
2018-05-06 21:29:27 -07:00
}
}
2017-10-30 20:06:20 -07:00
struct Repl {
options : EvalOptions ,
languages : Vec < Box < ProgrammingLanguageInterface > > ,
current_language_index : usize ,
interpreter_directive_sigil : char ,
2018-07-02 00:20:52 -07:00
line_reader : linefeed ::interface ::Interface < linefeed ::terminal ::DefaultTerminal > ,
2017-10-30 20:06:20 -07:00
}
impl Repl {
fn new ( languages : Vec < Box < ProgrammingLanguageInterface > > , initial_index : usize ) -> Repl {
2018-07-02 00:20:52 -07:00
use linefeed ::Interface ;
2017-10-30 20:06:20 -07:00
let i = if initial_index < languages . len ( ) { initial_index } else { 0 } ;
2018-07-02 00:20:52 -07:00
let line_reader = Interface ::new ( " schala-repl " ) . unwrap ( ) ;
2018-03-01 02:43:11 -08:00
2017-10-30 20:06:20 -07:00
Repl {
options : Repl ::get_options ( ) ,
languages : languages ,
current_language_index : i ,
2018-04-23 19:45:58 -07:00
interpreter_directive_sigil : ' :' ,
2018-07-02 00:20:52 -07:00
line_reader
2017-10-30 20:06:20 -07:00
}
}
2018-05-01 02:24:50 -07:00
fn get_cur_language ( & self ) -> & ProgrammingLanguageInterface {
self . languages [ self . current_language_index ] . as_ref ( )
}
2017-10-30 20:06:20 -07:00
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 ) {
2018-07-02 00:20:52 -07:00
use linefeed ::ReadResult ;
2018-03-23 18:56:09 -07:00
println! ( " Schala MetaInterpreter version {} " , VERSION_STRING ) ;
2018-04-23 19:45:58 -07:00
println! ( " Type {} help for help with the REPL " , self . interpreter_directive_sigil ) ;
2017-12-07 20:08:31 -08:00
2018-07-02 00:20:52 -07:00
self . line_reader . load_history ( " .schala_history " ) . unwrap_or ( ( ) ) ;
2018-03-01 02:49:14 -08:00
2017-10-30 20:06:20 -07:00
loop {
2017-12-07 20:08:31 -08:00
let language_name = self . languages [ self . current_language_index ] . get_language_name ( ) ;
2018-07-02 01:50:24 -07:00
let tab_complete_handler = TabCompleteHandler ::new ( self . interpreter_directive_sigil , self . get_cur_language ( ) . get_passes ( ) ) ;
self . line_reader . set_completer ( std ::sync ::Arc ::new ( tab_complete_handler ) ) ;
2017-12-07 20:08:31 -08:00
let prompt_str = format! ( " {} >> " , language_name ) ;
2018-07-02 00:20:52 -07:00
self . line_reader . set_prompt ( & prompt_str ) ;
2017-12-07 20:08:31 -08:00
2018-07-02 00:20:52 -07:00
match self . line_reader . read_line ( ) {
//Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => break,
2017-10-30 20:06:20 -07:00
Err ( e ) = > {
println! ( " Terminal read error: {} " , e ) ;
} ,
2018-07-02 00:20:52 -07:00
Ok ( ReadResult ::Eof ) = > break ,
Ok ( ReadResult ::Signal ( _ ) ) = > break ,
Ok ( ReadResult ::Input ( ref input ) ) = > {
self . line_reader . add_history_unique ( input . to_string ( ) ) ;
2018-03-25 00:09:51 -07:00
let output = match input . chars ( ) . nth ( 0 ) {
Some ( ch ) if ch = = self . interpreter_directive_sigil = > self . handle_interpreter_directive ( input ) ,
2018-05-11 01:09:11 -07:00
_ = > Some ( self . input_handler ( input ) ) ,
2018-03-25 00:09:51 -07:00
} ;
if let Some ( o ) = output {
println! ( " => {} " , o ) ;
2017-10-30 20:06:20 -07:00
}
}
}
}
2018-07-02 00:20:52 -07:00
self . line_reader . save_history ( " .schala_history " ) . unwrap_or ( ( ) ) ;
2018-02-11 16:28:17 -08:00
self . save_options ( ) ;
2017-10-30 20:06:20 -07:00
println! ( " Exiting... " ) ;
}
fn input_handler ( & mut self , input : & str ) -> String {
let ref mut language = self . languages [ self . current_language_index ] ;
2018-04-29 00:04:31 -07:00
let interpreter_output = language . execute_pipeline ( input , & self . options ) ;
2018-03-20 20:29:07 -07:00
interpreter_output . to_repl ( )
2017-10-30 20:06:20 -07:00
}
2018-04-23 21:33:15 -07:00
fn handle_interpreter_directive ( & mut self , input : & str ) -> Option < String > {
let mut iter = input . chars ( ) ;
iter . next ( ) ;
let commands : Vec < & str > = iter
. as_str ( )
. split_whitespace ( )
. collect ( ) ;
let cmd : & str = match commands . get ( 0 ) . clone ( ) {
None = > return None ,
Some ( s ) = > s
} ;
match cmd {
" exit " | " quit " = > {
self . save_options ( ) ;
exit ( 0 )
} ,
" lang " | " language " = > match commands . get ( 1 ) {
Some ( & " show " ) = > {
let mut buf = String ::new ( ) ;
for ( i , lang ) in self . languages . iter ( ) . enumerate ( ) {
write! ( buf , " {}{} \n " , if i = = self . current_language_index { " * " } else { " " } , lang . get_language_name ( ) ) . unwrap ( ) ;
}
Some ( buf )
} ,
Some ( & " go " ) = > match commands . get ( 2 ) {
None = > Some ( format! ( " Must specify a language name " ) ) ,
Some ( & desired_name ) = > {
for ( i , _ ) in self . languages . iter ( ) . enumerate ( ) {
let lang_name = self . languages [ i ] . get_language_name ( ) ;
if lang_name . to_lowercase ( ) = = desired_name . to_lowercase ( ) {
self . current_language_index = i ;
return Some ( format! ( " Switching to {} " , self . languages [ self . current_language_index ] . get_language_name ( ) ) ) ;
}
}
Some ( format! ( " Language {} not found " , desired_name ) )
}
} ,
Some ( & " next " ) | Some ( & " n " ) = > {
self . current_language_index = ( self . current_language_index + 1 ) % self . languages . len ( ) ;
Some ( format! ( " Switching to {} " , self . languages [ self . current_language_index ] . get_language_name ( ) ) )
} ,
Some ( & " previous " ) | Some ( & " p " ) | Some ( & " prev " ) = > {
self . current_language_index = if self . current_language_index = = 0 { self . languages . len ( ) - 1 } else { self . current_language_index - 1 } ;
Some ( format! ( " Switching to {} " , self . languages [ self . current_language_index ] . get_language_name ( ) ) )
} ,
Some ( e ) = > Some ( format! ( " Bad `lang(uage)` argument: {} " , e ) ) ,
None = > Some ( format! ( " Valid arguments for `lang(uage)` are `show`, `next`|`n`, `previous`|`prev`|`n` " ) )
} ,
" help " = > {
let mut buf = String ::new ( ) ;
let ref lang = self . languages [ self . current_language_index ] ;
writeln! ( buf , " MetaInterpreter options " ) . unwrap ( ) ;
writeln! ( buf , " ----------------------- " ) . unwrap ( ) ;
writeln! ( buf , " exit | quit - exit the REPL " ) . unwrap ( ) ;
2018-05-06 20:53:27 -07:00
writeln! ( buf , " debug [show|hide] <pass_name> - show or hide debug info for a given pass " ) . unwrap ( ) ;
writeln! ( buf , " debug passes - display the names of all passes " ) . unwrap ( ) ;
2018-04-23 21:33:15 -07:00
writeln! ( buf , " lang [prev|next|go <name> |show] - toggle to previous or next language, go to a specific language by name, or show all languages " ) . unwrap ( ) ;
writeln! ( buf , " Language-specific help for {} " , lang . get_language_name ( ) ) . unwrap ( ) ;
writeln! ( buf , " ----------------------- " ) . unwrap ( ) ;
writeln! ( buf , " {} " , lang . custom_interpreter_directives_help ( ) ) . unwrap ( ) ;
Some ( buf )
} ,
2018-05-01 00:38:01 -07:00
" debug " = > self . handle_debug ( commands ) ,
e = > self . languages [ self . current_language_index ]
. handle_custom_interpreter_directives ( & commands )
. or ( Some ( format! ( " Unknown command: {} " , e ) ) )
}
}
fn handle_debug ( & mut self , commands : Vec < & str > ) -> Option < String > {
2018-05-04 01:58:43 -07:00
let passes = self . get_cur_language ( ) . get_passes ( ) ;
2018-05-01 00:38:01 -07:00
match commands . get ( 1 ) {
2018-05-05 22:36:04 -07:00
Some ( & " passes " ) = > Some (
passes . into_iter ( )
. map ( | p | {
if self . options . debug_passes . contains ( & p ) {
let color = " green " ;
format! ( " * {} " , p . color ( color ) )
} else {
p
}
} )
. intersperse ( format! ( " -> " ) )
. collect ( ) ) ,
2018-05-01 00:38:01 -07:00
b @ Some ( & " show " ) | b @ Some ( & " hide " ) = > {
let show = b = = Some ( & " show " ) ;
2018-05-03 09:40:11 -07:00
let debug_pass : String = match commands . get ( 2 ) {
2018-05-01 02:58:29 -07:00
Some ( s ) = > s . to_string ( ) ,
2018-05-01 00:38:01 -07:00
None = > return Some ( format! ( " Must specify a stage to debug " ) ) ,
} ;
2018-05-03 09:40:11 -07:00
if let Some ( stage ) = passes . iter ( ) . find ( | stage_name | * * stage_name = = debug_pass ) {
let msg = format! ( " {} debug for stage {} " , if show { " Enabling " } else { " Disabling " } , debug_pass ) ;
2018-05-01 18:10:24 -07:00
if show {
2018-05-03 09:40:11 -07:00
self . options . debug_passes . insert ( stage . clone ( ) ) ;
2018-05-01 18:10:24 -07:00
} else {
2018-05-03 09:40:11 -07:00
self . options . debug_passes . remove ( stage ) ;
2018-05-01 18:10:24 -07:00
}
Some ( msg )
} else {
2018-05-03 09:40:11 -07:00
Some ( format! ( " Couldn't find stage: {} " , debug_pass ) )
2018-05-01 02:24:50 -07:00
}
2018-05-01 00:38:01 -07:00
} ,
_ = > Some ( format! ( " Unknown debug command " ) )
}
2017-10-30 20:06:20 -07:00
}
}
2018-03-20 21:13:34 -07:00
/*
2017-10-30 20:06:20 -07:00
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 ) ) ;
}
}
2018-03-20 21:13:34 -07:00
* /
2017-10-30 20:06:20 -07:00
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 " ) ;
2018-03-27 00:50:31 -07:00
options . optopt ( " d " ,
" debug " ,
" Debug a stage (l = tokenizer, a = AST, r = parse trace, s = symbol table) " ,
" [l|a|r|s] " ) ;
2017-10-30 20:06:20 -07:00
options
}