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-03-01 02:43:11 -08:00
extern crate rustyline ;
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
use rustyline ::error ::ReadlineError ;
use rustyline ::Editor ;
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-05-07 01:42:19 -07:00
struct TabCompleteHandler {
passes : Vec < String > ,
sigil : char ,
}
impl TabCompleteHandler {
fn complete_interpreter_directive ( & self , line : & str , pos : usize ) -> rustyline ::Result < ( usize , Vec < String > ) > {
let mut iter = line . chars ( ) ;
iter . next ( ) ;
let commands : Vec < & str > = iter
. as_str ( )
. split_whitespace ( )
. collect ( ) ;
println! ( " POS {} --- " , pos ) ;
let completes = match & commands [ .. ] {
& [ " debug " , " show " ] | & [ " debug " , " hide " ] = > self . passes . clone ( ) ,
& [ " debug " ] | & [ " debug " , _ ] = > vec! [ " passes " . to_string ( ) , " show " . to_string ( ) , " hide " . to_string ( ) ] ,
& [ _cmd ] = > vec! [ " debug " . to_string ( ) ] ,
_ = > vec! [ ] ,
} ;
Ok ( ( pos , completes ) )
}
}
2018-05-06 21:29:27 -07:00
impl rustyline ::completion ::Completer for TabCompleteHandler {
fn complete ( & self , line : & str , pos : usize ) -> rustyline ::Result < ( usize , Vec < String > ) > {
2018-05-07 01:42:19 -07:00
if line . starts_with ( & format! ( " {} " , self . sigil ) ) {
self . complete_interpreter_directive ( line , pos )
} else {
Ok ( ( pos , vec! ( format! ( " tab-completion-no-done " ) , format! ( " tab-completion-still-not-done " ) ) ) )
}
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-05-06 21:29:27 -07:00
console : rustyline ::Editor < TabCompleteHandler > ,
2017-10-30 20:06:20 -07:00
}
impl Repl {
fn new ( languages : Vec < Box < ProgrammingLanguageInterface > > , initial_index : usize ) -> Repl {
let i = if initial_index < languages . len ( ) { initial_index } else { 0 } ;
2018-05-07 01:42:19 -07:00
let console = Editor ::< TabCompleteHandler > ::new ( ) ;
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-03-01 02:43:11 -08:00
console
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-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-03-01 02:49:14 -08:00
self . console . get_history ( ) . load ( " .schala_history " ) . unwrap_or ( ( ) ) ;
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-05-07 01:42:19 -07:00
let tab_complete_handler = TabCompleteHandler { sigil : self . interpreter_directive_sigil , passes : self . get_cur_language ( ) . get_passes ( ) } ;
self . console . set_completer ( Some ( tab_complete_handler ) ) ;
2017-12-07 20:08:31 -08:00
let prompt_str = format! ( " {} >> " , language_name ) ;
2018-03-01 02:43:11 -08:00
match self . console . readline ( & prompt_str ) {
Err ( ReadlineError ::Eof ) | Err ( ReadlineError ::Interrupted ) = > break ,
2017-10-30 20:06:20 -07:00
Err ( e ) = > {
println! ( " Terminal read error: {} " , e ) ;
} ,
2018-03-01 02:43:11 -08:00
Ok ( ref input ) = > {
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-04-23 19:03:32 -07:00
_ = > {
self . console . get_history ( ) . add ( input ) ;
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-03-01 02:49:14 -08:00
self . console . get_history ( ) . save ( " .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
}