2016-10-02 22:30:28 -07:00
#[ cfg(test) ]
2016-10-28 19:38:03 -07:00
mod unit ;
#[ cfg(test) ]
mod integration ;
2016-10-02 22:30:28 -07:00
2016-10-23 16:43:52 -07:00
mod app ;
pub use app ::app ;
2016-10-22 23:18:26 -07:00
#[ macro_use ]
extern crate lazy_static ;
2016-10-02 22:30:28 -07:00
extern crate regex ;
2016-10-07 17:56:52 -07:00
extern crate tempdir ;
2016-10-02 22:30:28 -07:00
use std ::io ::prelude ::* ;
2016-10-05 13:58:18 -07:00
use std ::{ fs , fmt , process , io } ;
2016-10-27 00:13:10 -07:00
use std ::collections ::{ BTreeMap , HashSet } ;
2016-10-02 22:30:28 -07:00
use std ::fmt ::Display ;
use regex ::Regex ;
2016-10-07 17:56:52 -07:00
use std ::os ::unix ::fs ::PermissionsExt ;
2016-10-02 22:30:28 -07:00
macro_rules ! warn {
( $( $arg :tt ) * ) = > { {
extern crate std ;
use std ::io ::prelude ::* ;
let _ = writeln! ( & mut std ::io ::stderr ( ) , $( $arg ) * ) ;
} } ;
}
2016-10-28 00:06:36 -07:00
2016-10-02 22:30:28 -07:00
macro_rules ! die {
( $( $arg :tt ) * ) = > { {
extern crate std ;
warn! ( $( $arg ) * ) ;
std ::process ::exit ( - 1 )
} } ;
}
2016-10-23 16:43:52 -07:00
trait Slurp {
2016-10-02 22:30:28 -07:00
fn slurp ( & mut self ) -> Result < String , std ::io ::Error > ;
}
impl Slurp for fs ::File {
fn slurp ( & mut self ) -> Result < String , std ::io ::Error > {
let mut destination = String ::new ( ) ;
try ! ( self . read_to_string ( & mut destination ) ) ;
Ok ( destination )
}
}
fn re ( pattern : & str ) -> Regex {
Regex ::new ( pattern ) . unwrap ( )
}
2016-10-23 16:43:52 -07:00
#[ derive(PartialEq, Debug) ]
struct Recipe < ' a > {
2016-10-06 17:43:30 -07:00
line_number : usize ,
2016-10-02 22:30:28 -07:00
name : & ' a str ,
2016-10-27 09:44:07 -07:00
lines : Vec < Vec < Fragment < ' a > > > ,
evaluated_lines : Vec < String > ,
2016-10-09 00:30:33 -07:00
dependencies : Vec < & ' a str > ,
2016-10-23 16:43:52 -07:00
dependency_tokens : Vec < Token < ' a > > ,
arguments : Vec < & ' a str > ,
argument_tokens : Vec < Token < ' a > > ,
2016-10-06 17:43:30 -07:00
shebang : bool ,
2016-10-02 22:30:28 -07:00
}
2016-10-23 23:38:49 -07:00
#[ derive(PartialEq, Debug) ]
2016-10-27 09:44:07 -07:00
enum Fragment < ' a > {
2016-10-27 00:13:10 -07:00
Text { text : Token < ' a > } ,
2016-10-27 18:01:07 -07:00
Expression { expression : Expression < ' a > , value : Option < String > } ,
2016-10-16 18:59:49 -07:00
}
2016-10-27 00:13:10 -07:00
#[ derive(PartialEq, Debug) ]
2016-10-25 19:11:58 -07:00
enum Expression < ' a > {
2016-10-26 20:54:44 -07:00
Variable { name : & ' a str , token : Token < ' a > } ,
2016-10-28 00:06:36 -07:00
String { raw : & ' a str , cooked : String } ,
2016-10-29 01:58:30 -07:00
Backtick { raw : & ' a str } ,
2016-10-25 19:11:58 -07:00
Concatination { lhs : Box < Expression < ' a > > , rhs : Box < Expression < ' a > > } ,
}
2016-10-27 00:13:10 -07:00
impl < ' a > Expression < ' a > {
fn variables ( & ' a self ) -> Variables < ' a > {
Variables {
stack : vec ! [ self ] ,
}
}
}
struct Variables < ' a > {
stack : Vec < & ' a Expression < ' a > > ,
}
impl < ' a > Iterator for Variables < ' a > {
type Item = & ' a Token < ' a > ;
fn next ( & mut self ) -> Option < & ' a Token < ' a > > {
match self . stack . pop ( ) {
2016-10-29 01:58:30 -07:00
None | Some ( & Expression ::String { .. } ) | Some ( & Expression ::Backtick { .. } ) = > None ,
2016-10-27 00:13:10 -07:00
Some ( & Expression ::Variable { ref token , .. } ) = > Some ( token ) ,
Some ( & Expression ::Concatination { ref lhs , ref rhs } ) = > {
self . stack . push ( lhs ) ;
self . stack . push ( rhs ) ;
self . next ( )
}
}
}
}
2016-10-25 19:11:58 -07:00
impl < ' a > Display for Expression < ' a > {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
match * self {
2016-10-29 01:58:30 -07:00
Expression ::Backtick { raw , .. } = > try ! ( write! ( f , " `{}` " , raw ) ) ,
2016-10-25 19:11:58 -07:00
Expression ::Concatination { ref lhs , ref rhs } = > try ! ( write! ( f , " {} + {} " , lhs , rhs ) ) ,
2016-10-29 01:58:30 -07:00
Expression ::String { raw , .. } = > try ! ( write! ( f , " \" {} \" " , raw ) ) ,
Expression ::Variable { name , .. } = > try ! ( write! ( f , " {} " , name ) ) ,
2016-10-25 19:11:58 -07:00
}
Ok ( ( ) )
}
}
2016-10-05 13:58:18 -07:00
#[ cfg(unix) ]
2016-10-23 20:39:50 -07:00
fn error_from_signal ( recipe : & str , exit_status : process ::ExitStatus ) -> RunError {
2016-10-05 13:58:18 -07:00
use std ::os ::unix ::process ::ExitStatusExt ;
match exit_status . signal ( ) {
Some ( signal ) = > RunError ::Signal { recipe : recipe , signal : signal } ,
None = > RunError ::UnknownFailure { recipe : recipe } ,
}
}
#[ cfg(windows) ]
2016-10-23 20:39:50 -07:00
fn error_from_signal ( recipe : & str , exit_status : process ::ExitStatus ) -> RunError {
2016-10-05 13:58:18 -07:00
RunError ::UnknownFailure { recipe : recipe }
}
2016-10-03 23:55:55 -07:00
impl < ' a > Recipe < ' a > {
2016-10-29 21:51:39 -07:00
fn run (
& self ,
arguments : & [ & ' a str ] ,
scope : & BTreeMap < & str , String >
) -> Result < ( ) , RunError < ' a > > {
2016-10-29 00:55:47 -07:00
let mut arg_map = BTreeMap ::new ( ) ;
for ( i , argument ) in arguments . iter ( ) . enumerate ( ) {
2016-10-29 21:51:39 -07:00
arg_map . insert ( * self . arguments . get ( i ) . unwrap ( ) , * argument ) ;
2016-10-29 00:55:47 -07:00
}
let evaluated_lines ;
match evaluate_lines ( & self . lines , & scope , & arg_map ) {
Err ( error ) = > {
return Err ( RunError ::InternalError {
message : format ! ( " deferred evaluation failed {} " , error ) ,
} ) ;
2016-10-29 00:14:41 -07:00
}
2016-10-29 00:55:47 -07:00
Ok ( None ) = > {
return Err ( RunError ::InternalError {
message : " deferred evaluation returned None " . to_string ( ) ,
} ) ;
}
Ok ( Some ( lines ) ) = > evaluated_lines = lines ,
2016-10-29 00:14:41 -07:00
}
2016-10-07 17:56:52 -07:00
if self . shebang {
let tmp = try ! (
tempdir ::TempDir ::new ( " j " )
. map_err ( | error | RunError ::TmpdirIoError { recipe : self . name , io_error : error } )
) ;
let mut path = tmp . path ( ) . to_path_buf ( ) ;
path . push ( self . name ) ;
{
let mut f = try ! (
fs ::File ::create ( & path )
. map_err ( | error | RunError ::TmpdirIoError { recipe : self . name , io_error : error } )
) ;
let mut text = String ::new ( ) ;
// add the shebang
2016-10-29 00:14:41 -07:00
text + = & evaluated_lines [ 0 ] ;
2016-10-07 17:56:52 -07:00
text + = " \n " ;
// add blank lines so that lines in the generated script
// have the same line number as the corresponding lines
// in the justfile
for _ in 1 .. ( self . line_number + 2 ) {
text + = " \n "
}
2016-10-29 00:14:41 -07:00
for line in & evaluated_lines [ 1 .. ] {
2016-10-28 00:06:36 -07:00
text + = line ;
2016-10-07 17:56:52 -07:00
text + = " \n " ;
}
try ! (
f . write_all ( text . as_bytes ( ) )
. map_err ( | error | RunError ::TmpdirIoError { recipe : self . name , io_error : error } )
) ;
2016-10-05 13:58:18 -07:00
}
2016-10-07 17:56:52 -07:00
// get current permissions
let mut perms = try ! (
fs ::metadata ( & path )
. map_err ( | error | RunError ::TmpdirIoError { recipe : self . name , io_error : error } )
) . permissions ( ) ;
// make the script executable
let current_mode = perms . mode ( ) ;
perms . set_mode ( current_mode | 0o100 ) ;
try ! ( fs ::set_permissions ( & path , perms ) . map_err ( | error | RunError ::TmpdirIoError { recipe : self . name , io_error : error } ) ) ;
// run it!
let status = process ::Command ::new ( path ) . status ( ) ;
2016-10-05 13:58:18 -07:00
try ! ( match status {
Ok ( exit_status ) = > if let Some ( code ) = exit_status . code ( ) {
if code = = 0 {
Ok ( ( ) )
} else {
Err ( RunError ::Code { recipe : self . name , code : code } )
}
} else {
Err ( error_from_signal ( self . name , exit_status ) )
} ,
2016-10-07 17:56:52 -07:00
Err ( io_error ) = > Err ( RunError ::TmpdirIoError { recipe : self . name , io_error : io_error } )
2016-10-05 13:58:18 -07:00
} ) ;
2016-10-07 17:56:52 -07:00
} else {
2016-10-29 00:14:41 -07:00
for command in & evaluated_lines {
2016-10-27 00:13:10 -07:00
let mut command = & command [ 0 .. ] ;
2016-10-23 20:39:50 -07:00
if ! command . starts_with ( '@' ) {
2016-10-07 17:56:52 -07:00
warn! ( " {} " , command ) ;
} else {
command = & command [ 1 .. ] ;
}
let status = process ::Command ::new ( " sh " )
. arg ( " -cu " )
. arg ( command )
. status ( ) ;
try ! ( match status {
Ok ( exit_status ) = > if let Some ( code ) = exit_status . code ( ) {
if code = = 0 {
Ok ( ( ) )
} else {
Err ( RunError ::Code { recipe : self . name , code : code } )
}
} else {
Err ( error_from_signal ( self . name , exit_status ) )
} ,
Err ( io_error ) = > Err ( RunError ::IoError { recipe : self . name , io_error : io_error } )
} ) ;
}
2016-10-03 23:55:55 -07:00
}
Ok ( ( ) )
}
}
2016-10-23 16:43:52 -07:00
impl < ' a > Display for Recipe < ' a > {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
try ! ( write! ( f , " {} " , self . name ) ) ;
for argument in & self . arguments {
try ! ( write! ( f , " {} " , argument ) ) ;
}
try ! ( write! ( f , " : " ) ) ;
for dependency in & self . dependencies {
try ! ( write! ( f , " {} " , dependency ) )
}
2016-10-23 23:38:49 -07:00
2016-10-27 09:44:07 -07:00
for ( i , pieces ) in self . lines . iter ( ) . enumerate ( ) {
2016-10-23 16:43:52 -07:00
if i = = 0 {
try ! ( writeln! ( f , " " ) ) ;
}
2016-10-27 00:13:10 -07:00
for ( j , piece ) in pieces . iter ( ) . enumerate ( ) {
2016-10-23 23:38:49 -07:00
if j = = 0 {
try ! ( write! ( f , " " ) ) ;
}
2016-10-29 21:51:39 -07:00
match * piece {
Fragment ::Text { ref text } = > try ! ( write! ( f , " {} " , text . lexeme ) ) ,
Fragment ::Expression { ref expression , .. } = > try ! ( write! ( f , " {}{}{} " , " {{ " , expression , " }} " ) ) ,
2016-10-23 23:38:49 -07:00
}
}
2016-10-27 09:44:07 -07:00
if i + 1 < self . lines . len ( ) {
2016-10-23 23:38:49 -07:00
try ! ( write! ( f , " \n " ) ) ;
2016-10-23 16:43:52 -07:00
}
}
Ok ( ( ) )
}
}
2016-10-29 20:39:21 -07:00
fn resolve_recipes < ' a > (
recipes : & BTreeMap < & ' a str , Recipe < ' a > > ,
assignments : & BTreeMap < & ' a str , Expression < ' a > > ,
text : & ' a str ,
) -> Result < ( ) , Error < ' a > > {
2016-10-25 19:11:58 -07:00
let mut resolver = Resolver {
2016-10-29 20:39:21 -07:00
seen : HashSet ::new ( ) ,
stack : vec ! [ ] ,
resolved : HashSet ::new ( ) ,
recipes : recipes ,
2016-10-25 19:11:58 -07:00
} ;
for recipe in recipes . values ( ) {
try ! ( resolver . resolve ( & recipe ) ) ;
}
2016-10-29 20:39:21 -07:00
for recipe in recipes . values ( ) {
for line in & recipe . lines {
for fragment in line {
if let Fragment ::Expression { ref expression , .. } = * fragment {
for variable in expression . variables ( ) {
let name = variable . lexeme ;
if ! ( assignments . contains_key ( name ) | | recipe . arguments . contains ( & name ) ) {
// There's a borrow issue here that seems too difficult to solve.
// The error derived from the variable token has too short a lifetime,
// so we create a new error from its contents, which do live long
// enough.
//
// I suspect the solution here is to give recipes, pieces, and expressions
// two lifetime parameters instead of one, with one being the lifetime
// of the struct, and the second being the lifetime of the tokens
// that it contains
let error = variable . error ( ErrorKind ::UnknownVariable { variable : name } ) ;
return Err ( Error {
text : text ,
index : error . index ,
line : error . line ,
column : error . column ,
width : error . width ,
kind : ErrorKind ::UnknownVariable {
variable : & text [ error . index .. error . index + error . width . unwrap ( ) ] ,
}
} ) ;
}
}
}
}
}
}
2016-10-25 19:11:58 -07:00
Ok ( ( ) )
}
struct Resolver < ' a : ' b , ' b > {
stack : Vec < & ' a str > ,
seen : HashSet < & ' a str > ,
resolved : HashSet < & ' a str > ,
2016-10-29 20:39:21 -07:00
recipes : & ' b BTreeMap < & ' a str , Recipe < ' a > > ,
2016-10-25 19:11:58 -07:00
}
impl < ' a , ' b > Resolver < ' a , ' b > {
fn resolve ( & mut self , recipe : & Recipe < ' a > ) -> Result < ( ) , Error < ' a > > {
if self . resolved . contains ( recipe . name ) {
return Ok ( ( ) )
}
self . stack . push ( recipe . name ) ;
self . seen . insert ( recipe . name ) ;
for dependency_token in & recipe . dependency_tokens {
match self . recipes . get ( dependency_token . lexeme ) {
Some ( dependency ) = > if ! self . resolved . contains ( dependency . name ) {
if self . seen . contains ( dependency . name ) {
let first = self . stack [ 0 ] ;
self . stack . push ( first ) ;
return Err ( dependency_token . error ( ErrorKind ::CircularRecipeDependency {
recipe : recipe . name ,
circle : self . stack . iter ( )
. skip_while ( | name | * * name ! = dependency . name )
. cloned ( ) . collect ( )
} ) ) ;
}
return self . resolve ( dependency ) ;
} ,
None = > return Err ( dependency_token . error ( ErrorKind ::UnknownDependency {
recipe : recipe . name ,
unknown : dependency_token . lexeme
} ) ) ,
}
}
self . resolved . insert ( recipe . name ) ;
self . stack . pop ( ) ;
Ok ( ( ) )
}
}
2016-10-29 20:39:21 -07:00
fn resolve_assignments < ' a > (
assignments : & BTreeMap < & ' a str , Expression < ' a > > ,
assignment_tokens : & BTreeMap < & ' a str , Token < ' a > > ,
) -> Result < ( ) , Error < ' a > > {
let mut resolver = AssignmentResolver {
assignments : assignments ,
assignment_tokens : assignment_tokens ,
stack : vec ! [ ] ,
seen : HashSet ::new ( ) ,
evaluated : HashSet ::new ( ) ,
} ;
for name in assignments . keys ( ) {
try ! ( resolver . resolve_assignment ( name ) ) ;
}
Ok ( ( ) )
}
struct AssignmentResolver < ' a : ' b , ' b > {
assignments : & ' b BTreeMap < & ' a str , Expression < ' a > > ,
assignment_tokens : & ' b BTreeMap < & ' a str , Token < ' a > > ,
stack : Vec < & ' a str > ,
seen : HashSet < & ' a str > ,
evaluated : HashSet < & ' a str > ,
}
impl < ' a : ' b , ' b > AssignmentResolver < ' a , ' b > {
fn resolve_assignment ( & mut self , name : & ' a str ) -> Result < ( ) , Error < ' a > > {
if self . evaluated . contains ( name ) {
return Ok ( ( ) ) ;
}
self . seen . insert ( name ) ;
self . stack . push ( name ) ;
if let Some ( expression ) = self . assignments . get ( name ) {
try ! ( self . resolve_expression ( expression ) ) ;
self . evaluated . insert ( name ) ;
} else {
panic! ( ) ;
}
Ok ( ( ) )
}
fn resolve_expression ( & mut self , expression : & Expression < ' a > ) -> Result < ( ) , Error < ' a > > {
match * expression {
Expression ::Variable { name , ref token } = > {
if self . evaluated . contains ( name ) {
return Ok ( ( ) ) ;
} else if self . seen . contains ( name ) {
let token = self . assignment_tokens . get ( name ) . unwrap ( ) ;
self . stack . push ( name ) ;
return Err ( token . error ( ErrorKind ::CircularVariableDependency {
variable : name ,
circle : self . stack . clone ( ) ,
} ) ) ;
} else if self . assignments . contains_key ( name ) {
try ! ( self . resolve_assignment ( name ) ) ;
} else {
return Err ( token . error ( ErrorKind ::UnknownVariable { variable : name } ) ) ;
}
}
Expression ::String { .. } = > { }
Expression ::Backtick { .. } = > { }
Expression ::Concatination { ref lhs , ref rhs } = > {
try ! ( self . resolve_expression ( lhs ) ) ;
try ! ( self . resolve_expression ( rhs ) ) ;
}
}
Ok ( ( ) )
}
}
2016-10-29 21:51:39 -07:00
fn evaluate_assignments < ' a > (
assignments : & BTreeMap < & ' a str , Expression < ' a > > ,
) -> Result < BTreeMap < & ' a str , String > , RunError < ' a > > {
let mut evaluator = Evaluator {
evaluated : BTreeMap ::new ( ) ,
scope : & BTreeMap ::new ( ) ,
assignments : assignments ,
} ;
2016-10-29 00:14:41 -07:00
2016-10-29 21:51:39 -07:00
for name in assignments . keys ( ) {
try ! ( evaluator . evaluate_assignment ( name ) ) ;
2016-10-29 00:14:41 -07:00
}
2016-10-29 21:51:39 -07:00
Ok ( evaluator . evaluated )
2016-10-29 00:14:41 -07:00
}
2016-10-29 00:55:47 -07:00
fn evaluate_lines < ' a > (
lines : & Vec < Vec < Fragment < ' a > > > ,
scope : & BTreeMap < & ' a str , String > ,
2016-10-29 21:51:39 -07:00
arguments : & BTreeMap < & str , & str >
) -> Result < Option < Vec < String > > , RunError < ' a > > {
let mut evaluator = Evaluator {
evaluated : BTreeMap ::new ( ) ,
scope : scope ,
assignments : & BTreeMap ::new ( ) ,
2016-10-25 19:11:58 -07:00
} ;
2016-10-29 00:55:47 -07:00
let mut evaluated_lines = vec! [ ] ;
for fragments in lines {
let mut line = String ::new ( ) ;
for fragment in fragments . iter ( ) {
match * fragment {
Fragment ::Text { ref text } = > line + = text . lexeme ,
Fragment ::Expression { value : Some ( ref value ) , .. } = > {
line + = & value ;
}
Fragment ::Expression { ref expression , value : None } = > {
2016-10-29 21:51:39 -07:00
line + = & try ! ( evaluator . evaluate_expression ( expression , & arguments ) ) ;
2016-10-29 00:55:47 -07:00
}
}
}
evaluated_lines . push ( line ) ;
2016-10-27 09:44:07 -07:00
}
2016-10-29 00:55:47 -07:00
Ok ( Some ( evaluated_lines ) )
2016-10-25 19:11:58 -07:00
}
struct Evaluator < ' a : ' b , ' b > {
2016-10-29 21:51:39 -07:00
evaluated : BTreeMap < & ' a str , String > ,
scope : & ' b BTreeMap < & ' a str , String > ,
assignments : & ' b BTreeMap < & ' a str , Expression < ' a > > ,
2016-10-25 19:11:58 -07:00
}
impl < ' a , ' b > Evaluator < ' a , ' b > {
2016-10-29 21:51:39 -07:00
fn evaluate_assignment ( & mut self , name : & ' a str ) -> Result < ( ) , RunError < ' a > > {
2016-10-25 19:11:58 -07:00
if self . evaluated . contains_key ( name ) {
return Ok ( ( ) ) ;
}
if let Some ( expression ) = self . assignments . get ( name ) {
2016-10-29 21:51:39 -07:00
let value = try ! ( self . evaluate_expression ( expression , & BTreeMap ::new ( ) ) ) ;
2016-10-25 19:11:58 -07:00
self . evaluated . insert ( name , value ) ;
} else {
2016-10-29 21:51:39 -07:00
panic! ( " internal error: unknown assigment " ) ;
2016-10-25 19:11:58 -07:00
}
2016-10-29 00:14:41 -07:00
Ok ( ( ) )
}
fn evaluate_expression (
& mut self ,
expression : & Expression < ' a > ,
2016-10-29 21:51:39 -07:00
arguments : & BTreeMap < & str , & str >
) -> Result < String , RunError < ' a > > {
2016-10-25 19:11:58 -07:00
Ok ( match * expression {
2016-10-29 21:51:39 -07:00
Expression ::Variable { name , .. } = > {
2016-10-25 19:11:58 -07:00
if self . evaluated . contains_key ( name ) {
2016-10-29 21:51:39 -07:00
self . evaluated . get ( name ) . unwrap ( ) . clone ( )
2016-10-29 00:14:41 -07:00
} else if self . scope . contains_key ( name ) {
2016-10-29 21:51:39 -07:00
self . scope . get ( name ) . unwrap ( ) . clone ( )
2016-10-29 00:14:41 -07:00
} else if self . assignments . contains_key ( name ) {
2016-10-25 19:11:58 -07:00
try ! ( self . evaluate_assignment ( name ) ) ;
2016-10-29 21:51:39 -07:00
self . evaluated . get ( name ) . unwrap ( ) . clone ( )
2016-10-29 00:14:41 -07:00
} else if arguments . contains_key ( name ) {
2016-10-29 21:51:39 -07:00
arguments . get ( name ) . unwrap ( ) . to_string ( )
2016-10-29 00:14:41 -07:00
} else {
2016-10-29 21:51:39 -07:00
panic! ( " internal error: unknown error " ) ;
2016-10-03 23:55:55 -07:00
}
2016-10-25 19:11:58 -07:00
}
2016-10-28 00:06:36 -07:00
Expression ::String { ref cooked , .. } = > {
2016-10-29 21:51:39 -07:00
cooked . clone ( )
2016-10-25 19:11:58 -07:00
}
2016-10-29 01:58:30 -07:00
Expression ::Backtick { raw , .. } = > {
2016-10-29 21:51:39 -07:00
raw . to_string ( )
2016-10-29 01:58:30 -07:00
}
2016-10-25 19:11:58 -07:00
Expression ::Concatination { ref lhs , ref rhs } = > {
2016-10-29 21:51:39 -07:00
try ! ( self . evaluate_expression ( lhs , arguments ) )
+
& try ! ( self . evaluate_expression ( rhs , arguments ) )
2016-10-25 19:11:58 -07:00
}
} )
2016-10-03 23:55:55 -07:00
}
}
2016-10-22 23:18:26 -07:00
#[ derive(Debug, PartialEq) ]
2016-10-23 16:43:52 -07:00
struct Error < ' a > {
2016-10-22 23:18:26 -07:00
text : & ' a str ,
index : usize ,
line : usize ,
column : usize ,
2016-10-23 16:43:52 -07:00
width : Option < usize > ,
2016-10-22 23:18:26 -07:00
kind : ErrorKind < ' a > ,
2016-10-02 22:30:28 -07:00
}
#[ derive(Debug, PartialEq) ]
enum ErrorKind < ' a > {
2016-10-28 00:06:36 -07:00
ArgumentShadowsVariable { argument : & ' a str } ,
2016-10-23 16:43:52 -07:00
BadName { name : & ' a str } ,
2016-10-25 19:11:58 -07:00
CircularRecipeDependency { recipe : & ' a str , circle : Vec < & ' a str > } ,
CircularVariableDependency { variable : & ' a str , circle : Vec < & ' a str > } ,
2016-10-29 01:58:30 -07:00
DependencyHasArguments { recipe : & ' a str , dependency : & ' a str } ,
2016-10-23 16:43:52 -07:00
DuplicateArgument { recipe : & ' a str , argument : & ' a str } ,
2016-10-28 00:06:36 -07:00
DuplicateDependency { recipe : & ' a str , dependency : & ' a str } ,
2016-10-23 16:43:52 -07:00
DuplicateRecipe { recipe : & ' a str , first : usize } ,
2016-10-25 19:11:58 -07:00
DuplicateVariable { variable : & ' a str } ,
2016-10-23 16:43:52 -07:00
ExtraLeadingWhitespace ,
2016-10-02 22:30:28 -07:00
InconsistentLeadingWhitespace { expected : & ' a str , found : & ' a str } ,
2016-10-28 00:06:36 -07:00
InternalError { message : String } ,
InvalidEscapeSequence { character : char } ,
MixedLeadingWhitespace { whitespace : & ' a str } ,
2016-10-06 17:43:30 -07:00
OuterShebang ,
2016-10-28 00:06:36 -07:00
UnexpectedToken { expected : Vec < TokenKind > , found : TokenKind } ,
2016-10-23 16:43:52 -07:00
UnknownDependency { recipe : & ' a str , unknown : & ' a str } ,
2016-10-16 18:59:49 -07:00
UnknownStartOfToken ,
2016-10-28 00:06:36 -07:00
UnknownVariable { variable : & ' a str } ,
UnterminatedString ,
2016-10-02 22:30:28 -07:00
}
fn show_whitespace ( text : & str ) -> String {
text . chars ( ) . map ( | c | match c { '\t' = > 't' , ' ' = > 's' , _ = > c } ) . collect ( )
}
2016-10-23 16:43:52 -07:00
fn mixed_whitespace ( text : & str ) -> bool {
2016-10-02 22:30:28 -07:00
! ( text . chars ( ) . all ( | c | c = = ' ' ) | | text . chars ( ) . all ( | c | c = = '\t' ) )
}
2016-10-23 16:43:52 -07:00
struct Or < ' a , T : ' a + Display > ( & ' a [ T ] ) ;
impl < ' a , T : Display > Display for Or < ' a , T > {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
match self . 0. len ( ) {
0 = > { } ,
1 = > try ! ( write! ( f , " {} " , self . 0 [ 0 ] ) ) ,
2 = > try ! ( write! ( f , " {} or {} " , self . 0 [ 0 ] , self . 0 [ 1 ] ) ) ,
_ = > for ( i , item ) in self . 0. iter ( ) . enumerate ( ) {
try ! ( write! ( f , " {} " , item ) ) ;
if i = = self . 0. len ( ) - 1 {
} else if i = = self . 0. len ( ) - 2 {
try ! ( write! ( f , " , or " ) ) ;
} else {
try ! ( write! ( f , " , " ) )
}
2016-10-02 22:30:28 -07:00
} ,
}
2016-10-23 16:43:52 -07:00
Ok ( ( ) )
2016-10-02 22:30:28 -07:00
}
}
impl < ' a > Display for Error < ' a > {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
2016-10-28 20:34:25 -07:00
try ! ( write! ( f , " error: " ) ) ;
2016-10-28 19:56:33 -07:00
2016-10-02 22:30:28 -07:00
match self . kind {
2016-10-23 16:43:52 -07:00
ErrorKind ::BadName { name } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " name `{}` did not match /[a-z](-?[a-z0-9])*/ " , name ) ) ;
2016-10-23 16:43:52 -07:00
}
2016-10-25 19:11:58 -07:00
ErrorKind ::CircularRecipeDependency { recipe , ref circle } = > {
if circle . len ( ) = = 2 {
2016-10-28 20:40:16 -07:00
try ! ( write! ( f , " recipe `{}` depends on itself " , recipe ) ) ;
2016-10-25 19:11:58 -07:00
} else {
2016-10-28 20:40:16 -07:00
try ! ( write! ( f , " recipe `{}` has circular dependency `{}` " , recipe , circle . join ( " -> " ) ) ) ;
2016-10-25 19:11:58 -07:00
}
return Ok ( ( ) ) ;
}
ErrorKind ::CircularVariableDependency { variable , ref circle } = > {
2016-10-28 20:40:16 -07:00
try ! ( write! ( f , " assignment to `{}` has circular dependency: `{}` " , variable , circle . join ( " -> " ) ) ) ;
2016-10-23 16:43:52 -07:00
return Ok ( ( ) ) ;
}
2016-10-28 00:06:36 -07:00
ErrorKind ::InvalidEscapeSequence { character } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " ` \\ {}` is not a valid escape sequence " , character . escape_default ( ) . collect ::< String > ( ) ) ) ;
2016-10-28 00:06:36 -07:00
}
2016-10-23 16:43:52 -07:00
ErrorKind ::DuplicateArgument { recipe , argument } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " recipe `{}` has duplicate argument `{}` " , recipe , argument ) ) ;
2016-10-23 16:43:52 -07:00
}
2016-10-25 19:11:58 -07:00
ErrorKind ::DuplicateVariable { variable } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " variable `{}` is has multiple definitions " , variable ) ) ;
2016-10-25 19:11:58 -07:00
}
2016-10-23 16:43:52 -07:00
ErrorKind ::UnexpectedToken { ref expected , found } = > {
try ! ( writeln! ( f , " expected {} but found {} " , Or ( expected ) , found ) ) ;
}
ErrorKind ::DuplicateDependency { recipe , dependency } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " recipe `{}` has duplicate dependency `{}` " , recipe , dependency ) ) ;
2016-10-23 16:43:52 -07:00
}
ErrorKind ::DuplicateRecipe { recipe , first } = > {
2016-10-28 20:40:16 -07:00
try ! ( write! ( f , " recipe `{}` first defined on line {} is redefined on line {} " ,
2016-10-23 16:43:52 -07:00
recipe , first , self . line ) ) ;
return Ok ( ( ) ) ;
}
2016-10-29 00:14:41 -07:00
ErrorKind ::DependencyHasArguments { recipe , dependency } = > {
try ! ( writeln! ( f , " recipe `{}` depends on `{}` which takes arguments. dependencies may not take arguments " , recipe , dependency ) ) ;
}
2016-10-25 19:11:58 -07:00
ErrorKind ::ArgumentShadowsVariable { argument } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " argument `{}` shadows variable of the same name " , argument ) ) ;
2016-10-25 19:11:58 -07:00
}
2016-10-23 16:43:52 -07:00
ErrorKind ::MixedLeadingWhitespace { whitespace } = > {
try ! ( writeln! ( f ,
2016-10-28 20:40:16 -07:00
" found a mix of tabs and spaces in leading whitespace: `{}` \n leading whitespace may consist of tabs or spaces, but not both " ,
2016-10-23 16:43:52 -07:00
show_whitespace ( whitespace )
) ) ;
}
ErrorKind ::ExtraLeadingWhitespace = > {
try ! ( writeln! ( f , " recipe line has extra leading whitespace " ) ) ;
}
2016-10-02 22:30:28 -07:00
ErrorKind ::InconsistentLeadingWhitespace { expected , found } = > {
try ! ( writeln! ( f ,
2016-10-28 20:40:16 -07:00
" inconsistant leading whitespace: recipe started with `{}` but found line with `{}`: " ,
2016-10-02 22:30:28 -07:00
show_whitespace ( expected ) , show_whitespace ( found )
) ) ;
}
2016-10-06 17:43:30 -07:00
ErrorKind ::OuterShebang = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " a shebang `#!` is reserved syntax outside of recipes " ) )
2016-10-06 17:43:30 -07:00
}
2016-10-23 16:43:52 -07:00
ErrorKind ::UnknownDependency { recipe , unknown } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " recipe `{}` has unknown dependency `{}` " , recipe , unknown ) ) ;
2016-10-23 16:43:52 -07:00
}
2016-10-25 19:11:58 -07:00
ErrorKind ::UnknownVariable { variable } = > {
2016-10-28 20:40:16 -07:00
try ! ( writeln! ( f , " variable `{}` is unknown " , variable ) ) ;
2016-10-25 19:11:58 -07:00
}
2016-10-16 18:59:49 -07:00
ErrorKind ::UnknownStartOfToken = > {
2016-10-25 19:11:58 -07:00
try ! ( writeln! ( f , " unknown start of token: " ) ) ;
2016-10-16 18:59:49 -07:00
}
2016-10-28 00:06:36 -07:00
ErrorKind ::UnterminatedString = > {
try ! ( writeln! ( f , " unterminated string " ) ) ;
}
2016-10-22 23:18:26 -07:00
ErrorKind ::InternalError { ref message } = > {
try ! ( writeln! ( f , " internal error, this may indicate a bug in j: {} \n consider filing an issue: https://github.com/casey/j/issues/new " , message ) ) ;
}
2016-10-02 22:30:28 -07:00
}
match self . text . lines ( ) . nth ( self . line ) {
2016-10-28 20:34:25 -07:00
Some ( line ) = > {
let line_number_width = self . line . to_string ( ) . len ( ) ;
try ! ( write! ( f , " {0:1$} | \n " , " " , line_number_width ) ) ;
try ! ( write! ( f , " {} | {} \n " , self . line + 1 , line ) ) ;
try ! ( write! ( f , " {0:1$} | " , " " , line_number_width ) ) ;
try ! ( write! ( f , " {0:1$}{2:^<3$} " , " " , self . column , " " , self . width . unwrap_or ( 0 ) ) ) ;
} ,
2016-10-23 16:43:52 -07:00
None = > if self . index ! = self . text . len ( ) {
2016-10-28 20:34:25 -07:00
try ! ( write! ( f , " internal error: Error has invalid line number: {} " , self . line + 1 ) )
2016-10-23 16:43:52 -07:00
} ,
2016-10-07 17:56:52 -07:00
} ;
2016-10-02 22:30:28 -07:00
Ok ( ( ) )
}
}
2016-10-23 16:43:52 -07:00
struct Justfile < ' a > {
2016-10-25 19:11:58 -07:00
recipes : BTreeMap < & ' a str , Recipe < ' a > > ,
assignments : BTreeMap < & ' a str , Expression < ' a > > ,
2016-10-02 22:30:28 -07:00
}
2016-10-29 00:14:41 -07:00
impl < ' a , ' b > Justfile < ' a > where ' a : ' b {
2016-10-23 16:43:52 -07:00
fn first ( & self ) -> Option < & ' a str > {
2016-10-02 22:30:28 -07:00
let mut first : Option < & Recipe < ' a > > = None ;
2016-10-23 20:39:50 -07:00
for recipe in self . recipes . values ( ) {
2016-10-02 22:30:28 -07:00
if let Some ( first_recipe ) = first {
2016-10-06 17:43:30 -07:00
if recipe . line_number < first_recipe . line_number {
2016-10-02 22:30:28 -07:00
first = Some ( recipe )
}
} else {
first = Some ( recipe ) ;
}
}
first . map ( | recipe | recipe . name )
}
2016-10-23 16:43:52 -07:00
fn count ( & self ) -> usize {
2016-10-03 23:55:55 -07:00
self . recipes . len ( )
}
2016-10-23 16:43:52 -07:00
fn recipes ( & self ) -> Vec < & ' a str > {
2016-10-03 23:55:55 -07:00
self . recipes . keys ( ) . cloned ( ) . collect ( )
}
2016-10-29 00:14:41 -07:00
fn run ( & ' a self , arguments : & [ & ' b str ] ) -> Result < ( ) , RunError < ' b > > {
2016-10-29 21:51:39 -07:00
let scope = try ! ( evaluate_assignments ( & self . assignments ) ) ;
let mut ran = HashSet ::new ( ) ;
2016-10-29 00:14:41 -07:00
for ( i , argument ) in arguments . iter ( ) . enumerate ( ) {
if let Some ( recipe ) = self . recipes . get ( argument ) {
if ! recipe . arguments . is_empty ( ) {
if i ! = 0 {
return Err ( RunError ::NonLeadingRecipeWithArguments { recipe : recipe . name } ) ;
}
let rest = & arguments [ 1 .. ] ;
if recipe . arguments . len ( ) ! = rest . len ( ) {
return Err ( RunError ::ArgumentCountMismatch {
recipe : recipe . name ,
found : rest . len ( ) ,
expected : recipe . arguments . len ( ) ,
} ) ;
}
2016-10-29 21:51:39 -07:00
try ! ( self . run_recipe ( recipe , rest , & scope , & mut ran ) ) ;
2016-10-29 00:14:41 -07:00
return Ok ( ( ) ) ;
}
} else {
break ;
}
}
2016-10-03 23:55:55 -07:00
let mut missing = vec! [ ] ;
2016-10-29 00:14:41 -07:00
for recipe in arguments {
2016-10-03 23:55:55 -07:00
if ! self . recipes . contains_key ( recipe ) {
missing . push ( * recipe ) ;
2016-10-02 22:30:28 -07:00
}
}
2016-10-23 20:39:50 -07:00
if ! missing . is_empty ( ) {
2016-10-03 23:55:55 -07:00
return Err ( RunError ::UnknownRecipes { recipes : missing } ) ;
}
2016-10-29 21:51:39 -07:00
for recipe in arguments . iter ( ) . map ( | name | self . recipes . get ( name ) . unwrap ( ) ) {
try ! ( self . run_recipe ( recipe , & [ ] , & scope , & mut ran ) ) ;
2016-10-03 23:55:55 -07:00
}
Ok ( ( ) )
2016-10-02 22:30:28 -07:00
}
2016-10-05 16:03:11 -07:00
2016-10-29 21:51:39 -07:00
fn run_recipe (
& self ,
recipe : & Recipe < ' a > ,
arguments : & [ & ' a str ] ,
scope : & BTreeMap < & str , String > ,
ran : & mut HashSet < & ' a str >
) -> Result < ( ) , RunError > {
for dependency_name in & recipe . dependencies {
if ! ran . contains ( dependency_name ) {
try ! ( self . run_recipe ( & self . recipes [ dependency_name ] , & [ ] , scope , ran ) ) ;
}
}
try ! ( recipe . run ( arguments , & scope ) ) ;
ran . insert ( recipe . name ) ;
Ok ( ( ) )
}
2016-10-23 16:43:52 -07:00
fn get ( & self , name : & str ) -> Option < & Recipe < ' a > > {
2016-10-05 16:03:11 -07:00
self . recipes . get ( name )
}
2016-10-03 23:55:55 -07:00
}
2016-10-02 22:30:28 -07:00
2016-10-25 19:11:58 -07:00
impl < ' a > Display for Justfile < ' a > {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
let mut items = self . recipes . len ( ) + self . assignments . len ( ) ;
for ( name , expression ) in & self . assignments {
2016-10-29 21:51:39 -07:00
try ! ( write! ( f , " {} = {} " , name , expression ) ) ;
2016-10-25 19:11:58 -07:00
items - = 1 ;
if items ! = 0 {
2016-10-28 16:41:46 -07:00
try ! ( write! ( f , " \n \n " ) ) ;
2016-10-25 19:11:58 -07:00
}
}
for recipe in self . recipes . values ( ) {
2016-10-29 21:51:39 -07:00
try ! ( write! ( f , " {} " , recipe ) ) ;
2016-10-25 19:11:58 -07:00
items - = 1 ;
if items ! = 0 {
2016-10-28 16:41:46 -07:00
try ! ( write! ( f , " \n \n " ) ) ;
2016-10-25 19:11:58 -07:00
}
}
Ok ( ( ) )
}
}
2016-10-05 13:58:18 -07:00
#[ derive(Debug) ]
2016-10-23 16:43:52 -07:00
enum RunError < ' a > {
2016-10-29 00:14:41 -07:00
ArgumentCountMismatch { recipe : & ' a str , found : usize , expected : usize } ,
2016-10-03 23:55:55 -07:00
Code { recipe : & ' a str , code : i32 } ,
2016-10-29 01:58:30 -07:00
InternalError { message : String } ,
2016-10-05 13:58:18 -07:00
IoError { recipe : & ' a str , io_error : io ::Error } ,
2016-10-29 01:58:30 -07:00
NonLeadingRecipeWithArguments { recipe : & ' a str } ,
Signal { recipe : & ' a str , signal : i32 } ,
2016-10-07 17:56:52 -07:00
TmpdirIoError { recipe : & ' a str , io_error : io ::Error } ,
2016-10-29 01:58:30 -07:00
UnknownFailure { recipe : & ' a str } ,
UnknownRecipes { recipes : Vec < & ' a str > } ,
2016-10-03 23:55:55 -07:00
}
impl < ' a > Display for RunError < ' a > {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
2016-10-23 20:39:50 -07:00
match * self {
RunError ::UnknownRecipes { ref recipes } = > {
2016-10-03 23:55:55 -07:00
if recipes . len ( ) = = 1 {
try ! ( write! ( f , " Justfile does not contain recipe: {} " , recipes [ 0 ] ) ) ;
} else {
try ! ( write! ( f , " Justfile does not contain recipes: {} " , recipes . join ( " " ) ) ) ;
} ;
} ,
2016-10-29 00:14:41 -07:00
RunError ::NonLeadingRecipeWithArguments { recipe } = > {
try ! ( write! ( f , " Recipe `{}` takes arguments and so must be the first and only recipe specified on the command line " , recipe ) ) ;
} ,
RunError ::ArgumentCountMismatch { recipe , found , expected } = > {
try ! ( write! ( f , " Recipe `{}` takes {} argument{}, but {}{} were found " ,
recipe , expected , if expected = = 1 { " " } else { " s " } ,
if found < expected { " only " } else { " " } , found ) ) ;
2016-10-27 00:31:50 -07:00
} ,
2016-10-23 20:39:50 -07:00
RunError ::Code { recipe , code } = > {
2016-10-28 15:59:50 -07:00
try ! ( write! ( f , " Recipe \" {} \" failed with exit code {} " , recipe , code ) ) ;
2016-10-03 23:55:55 -07:00
} ,
2016-10-23 20:39:50 -07:00
RunError ::Signal { recipe , signal } = > {
2016-10-05 13:58:18 -07:00
try ! ( write! ( f , " Recipe \" {} \" wast terminated by signal {} " , recipe , signal ) ) ;
}
2016-10-23 20:39:50 -07:00
RunError ::UnknownFailure { recipe } = > {
2016-10-05 13:58:18 -07:00
try ! ( write! ( f , " Recipe \" {} \" failed for an unknown reason " , recipe ) ) ;
} ,
2016-10-23 20:39:50 -07:00
RunError ::IoError { recipe , ref io_error } = > {
2016-10-05 13:58:18 -07:00
try ! ( match io_error . kind ( ) {
io ::ErrorKind ::NotFound = > write! ( f , " Recipe \" {} \" could not be run because j could not find `sh` the command: \n {} " , recipe , io_error ) ,
io ::ErrorKind ::PermissionDenied = > write! ( f , " Recipe \" {} \" could not be run because j could not run `sh`: \n {} " , recipe , io_error ) ,
_ = > write! ( f , " Recipe \" {} \" could not be run because of an IO error while launching the `sh`: \n {} " , recipe , io_error ) ,
} ) ;
} ,
2016-10-23 20:39:50 -07:00
RunError ::TmpdirIoError { recipe , ref io_error } = >
2016-10-07 17:56:52 -07:00
try ! ( write! ( f , " Recipe \" {} \" could not be run because of an IO error while trying to create a temporary directory or write a file to that directory`: \n {} " , recipe , io_error ) ) ,
2016-10-29 00:55:47 -07:00
RunError ::InternalError { ref message } = > {
try ! ( writeln! ( f , " internal error, this may indicate a bug in j: {} \n consider filing an issue: https://github.com/casey/j/issues/new " , message ) ) ;
}
2016-10-03 23:55:55 -07:00
}
Ok ( ( ) )
2016-10-02 22:30:28 -07:00
}
}
2016-10-23 16:43:52 -07:00
#[ derive(Debug, PartialEq) ]
2016-10-16 18:59:49 -07:00
struct Token < ' a > {
2016-10-22 23:18:26 -07:00
index : usize ,
2016-10-16 18:59:49 -07:00
line : usize ,
2016-10-22 23:18:26 -07:00
column : usize ,
2016-10-23 16:43:52 -07:00
text : & ' a str ,
2016-10-16 18:59:49 -07:00
prefix : & ' a str ,
lexeme : & ' a str ,
2016-10-27 18:48:55 -07:00
kind : TokenKind ,
2016-10-16 18:59:49 -07:00
}
2016-10-22 23:18:26 -07:00
impl < ' a > Token < ' a > {
2016-10-23 16:43:52 -07:00
fn error ( & self , kind : ErrorKind < ' a > ) -> Error < ' a > {
2016-10-22 23:18:26 -07:00
Error {
2016-10-23 16:43:52 -07:00
text : self . text ,
index : self . index + self . prefix . len ( ) ,
2016-10-22 23:18:26 -07:00
line : self . line ,
2016-10-23 16:43:52 -07:00
column : self . column + self . prefix . len ( ) ,
width : Some ( self . lexeme . len ( ) ) ,
2016-10-22 23:18:26 -07:00
kind : kind ,
}
}
}
2016-10-16 18:59:49 -07:00
#[ derive(Debug, PartialEq, Clone, Copy) ]
2016-10-23 18:46:04 -07:00
enum TokenKind {
2016-10-29 01:58:30 -07:00
Backtick ,
2016-10-16 18:59:49 -07:00
Colon ,
Comment ,
Dedent ,
2016-10-29 01:58:30 -07:00
Eof ,
Eol ,
Equals ,
Indent ,
2016-10-26 20:54:44 -07:00
InterpolationEnd ,
2016-10-29 01:58:30 -07:00
InterpolationStart ,
2016-10-26 20:54:44 -07:00
Line ,
2016-10-29 01:58:30 -07:00
Name ,
Plus ,
StringToken ,
Text ,
2016-10-16 18:59:49 -07:00
}
2016-10-23 18:46:04 -07:00
impl Display for TokenKind {
2016-10-23 16:43:52 -07:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
try ! ( write! ( f , " {} " , match * self {
2016-10-29 01:58:30 -07:00
Backtick = > " backtick " ,
2016-10-26 20:54:44 -07:00
Colon = > " \" : \" " ,
Comment = > " comment " ,
Dedent = > " dedent " ,
Eof = > " end of file " ,
2016-10-29 01:58:30 -07:00
Eol = > " end of line " ,
Equals = > " \" = \" " ,
Indent = > " indent " ,
InterpolationEnd = > " }} " ,
InterpolationStart = > " {{ " ,
Line = > " command " ,
Name = > " name " ,
Plus = > " \" + \" " ,
StringToken = > " string " ,
Text = > " command text " ,
2016-10-23 16:43:52 -07:00
} ) ) ;
Ok ( ( ) )
}
}
2016-10-23 18:46:04 -07:00
use TokenKind ::* ;
2016-10-16 18:59:49 -07:00
fn token ( pattern : & str ) -> Regex {
let mut s = String ::new ( ) ;
s + = r "^(?m)([ \t]*)(" ;
s + = pattern ;
s + = " ) " ;
re ( & s )
}
2016-10-28 00:06:36 -07:00
fn tokenize ( text : & str ) -> Result < Vec < Token > , Error > {
2016-10-22 23:18:26 -07:00
lazy_static! {
2016-10-29 01:58:30 -07:00
static ref BACKTICK : Regex = token ( r "`[^`\n\r]*`" ) ;
2016-10-27 09:44:07 -07:00
static ref COLON : Regex = token ( r ":" ) ;
static ref COMMENT : Regex = token ( r "#([^!].*)?$" ) ;
2016-10-29 01:58:30 -07:00
static ref EOF : Regex = token ( r "(?-m)$" ) ;
2016-10-27 09:44:07 -07:00
static ref EOL : Regex = token ( r "\n|\r\n" ) ;
2016-10-29 01:58:30 -07:00
static ref EQUALS : Regex = token ( r "=" ) ;
2016-10-27 09:44:07 -07:00
static ref INTERPOLATION_END : Regex = token ( r "[}][}]" ) ;
static ref INTERPOLATION_START_TOKEN : Regex = token ( r "[{][{]" ) ;
2016-10-29 01:58:30 -07:00
static ref NAME : Regex = token ( r "([a-zA-Z0-9_-]+)" ) ;
static ref PLUS : Regex = token ( r "[+]" ) ;
static ref STRING : Regex = token ( " \" " ) ;
2016-10-27 09:44:07 -07:00
static ref INDENT : Regex = re ( r "^([ \t]*)[^ \t\n\r]" ) ;
static ref INTERPOLATION_START : Regex = re ( r "^[{][{]" ) ;
static ref LEADING_TEXT : Regex = re ( r "^(?m)(.+?)[{][{]" ) ;
2016-10-29 01:58:30 -07:00
static ref LINE : Regex = re ( r "^(?m)[ \t]+[^ \t\n\r].*$" ) ;
2016-10-27 09:44:07 -07:00
static ref TEXT : Regex = re ( r "^(?m)(.+)" ) ;
2016-10-26 20:54:44 -07:00
}
#[ derive(PartialEq) ]
enum State < ' a > {
Start ,
Indent ( & ' a str ) ,
Text ,
Interpolation ,
}
2016-10-16 18:59:49 -07:00
fn indentation ( text : & str ) -> Option < & str > {
2016-10-22 23:18:26 -07:00
INDENT . captures ( text ) . map ( | captures | captures . at ( 1 ) . unwrap ( ) )
2016-10-16 18:59:49 -07:00
}
2016-10-27 09:44:07 -07:00
let mut tokens = vec! [ ] ;
let mut rest = text ;
let mut index = 0 ;
let mut line = 0 ;
let mut column = 0 ;
let mut state = vec! [ State ::Start ] ;
2016-10-22 23:18:26 -07:00
macro_rules ! error {
( $kind :expr ) = > { {
Err ( Error {
text : text ,
index : index ,
line : line ,
column : column ,
2016-10-23 16:43:52 -07:00
width : None ,
2016-10-22 23:18:26 -07:00
kind : $kind ,
} )
} } ;
}
2016-10-16 18:59:49 -07:00
loop {
2016-10-22 23:18:26 -07:00
if column = = 0 {
2016-10-27 18:48:55 -07:00
if let Some ( kind ) = match ( state . last ( ) . unwrap ( ) , indentation ( rest ) ) {
2016-10-22 23:18:26 -07:00
// ignore: was no indentation and there still isn't
2016-10-23 20:39:50 -07:00
// or current line is blank
2016-10-26 20:54:44 -07:00
( & State ::Start , Some ( " " ) ) | ( _ , None ) = > {
2016-10-22 23:18:26 -07:00
None
}
// indent: was no indentation, now there is
2016-10-26 20:54:44 -07:00
( & State ::Start , Some ( current ) ) = > {
2016-10-23 16:43:52 -07:00
if mixed_whitespace ( current ) {
return error! ( ErrorKind ::MixedLeadingWhitespace { whitespace : current } )
}
2016-10-26 20:54:44 -07:00
//indent = Some(current);
state . push ( State ::Indent ( current ) ) ;
2016-10-16 18:59:49 -07:00
Some ( Indent )
}
2016-10-22 23:18:26 -07:00
// dedent: there was indentation and now there isn't
2016-10-26 20:54:44 -07:00
( & State ::Indent ( _ ) , Some ( " " ) ) = > {
// indent = None;
state . pop ( ) ;
2016-10-22 23:18:26 -07:00
Some ( Dedent )
}
// was indentation and still is, check if the new indentation matches
2016-10-26 20:54:44 -07:00
( & State ::Indent ( previous ) , Some ( current ) ) = > {
2016-10-16 18:59:49 -07:00
if ! current . starts_with ( previous ) {
2016-10-22 23:18:26 -07:00
return error! ( ErrorKind ::InconsistentLeadingWhitespace {
expected : previous ,
found : current
} ) ;
2016-10-16 18:59:49 -07:00
}
None
}
2016-10-26 20:54:44 -07:00
// at column 0 in some other state: this should never happen
( & State ::Text , _ ) | ( & State ::Interpolation , _ ) = > {
return error! ( ErrorKind ::InternalError {
message : " unexpected state at column 0 " . to_string ( )
} ) ;
}
2016-10-16 18:59:49 -07:00
} {
tokens . push ( Token {
2016-10-22 23:18:26 -07:00
index : index ,
2016-10-16 18:59:49 -07:00
line : line ,
2016-10-22 23:18:26 -07:00
column : column ,
2016-10-23 16:43:52 -07:00
text : text ,
2016-10-16 18:59:49 -07:00
prefix : " " ,
lexeme : " " ,
2016-10-27 18:48:55 -07:00
kind : kind ,
2016-10-16 18:59:49 -07:00
} ) ;
}
}
2016-10-23 16:43:52 -07:00
// insert a dedent if we're indented and we hit the end of the file
2016-10-28 00:06:36 -07:00
if & State ::Start ! = state . last ( ) . unwrap ( ) & & EOF . is_match ( rest ) {
tokens . push ( Token {
index : index ,
line : line ,
column : column ,
text : text ,
prefix : " " ,
lexeme : " " ,
kind : Dedent ,
} ) ;
2016-10-23 16:43:52 -07:00
}
2016-10-27 18:48:55 -07:00
let ( prefix , lexeme , kind ) =
2016-10-26 20:54:44 -07:00
if let ( 0 , & State ::Indent ( indent ) , Some ( captures ) ) = ( column , state . last ( ) . unwrap ( ) , LINE . captures ( rest ) ) {
2016-10-16 18:59:49 -07:00
let line = captures . at ( 0 ) . unwrap ( ) ;
if ! line . starts_with ( indent ) {
2016-10-22 23:18:26 -07:00
return error! ( ErrorKind ::InternalError { message : " unexpected indent " . to_string ( ) } ) ;
2016-10-16 18:59:49 -07:00
}
2016-10-26 20:54:44 -07:00
state . push ( State ::Text ) ;
( & line [ 0 .. indent . len ( ) ] , " " , Line )
} else if let Some ( captures ) = EOF . captures ( rest ) {
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Eof )
2016-10-28 00:06:36 -07:00
} else if let State ::Text = * state . last ( ) . unwrap ( ) {
2016-10-26 20:54:44 -07:00
if let Some ( captures ) = INTERPOLATION_START . captures ( rest ) {
state . push ( State ::Interpolation ) ;
( " " , captures . at ( 0 ) . unwrap ( ) , InterpolationStart )
} else if let Some ( captures ) = LEADING_TEXT . captures ( rest ) {
( " " , captures . at ( 1 ) . unwrap ( ) , Text )
} else if let Some ( captures ) = TEXT . captures ( rest ) {
( " " , captures . at ( 1 ) . unwrap ( ) , Text )
} else if let Some ( captures ) = EOL . captures ( rest ) {
state . pop ( ) ;
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Eol )
} else {
return error! ( ErrorKind ::InternalError {
message : format ! ( " Could not match token in text state: \" {} \" " , rest )
} ) ;
}
2016-10-27 09:44:07 -07:00
} else if let Some ( captures ) = INTERPOLATION_START_TOKEN . captures ( rest ) {
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , InterpolationStart )
2016-10-26 20:54:44 -07:00
} else if let Some ( captures ) = INTERPOLATION_END . captures ( rest ) {
2016-10-27 09:44:07 -07:00
if state . last ( ) . unwrap ( ) = = & State ::Interpolation {
state . pop ( ) ;
2016-10-26 20:54:44 -07:00
}
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , InterpolationEnd )
2016-10-22 23:18:26 -07:00
} else if let Some ( captures ) = NAME . captures ( rest ) {
2016-10-16 18:59:49 -07:00
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Name )
2016-10-22 23:18:26 -07:00
} else if let Some ( captures ) = EOL . captures ( rest ) {
2016-10-26 20:54:44 -07:00
if state . last ( ) . unwrap ( ) = = & State ::Interpolation {
panic! ( " interpolation must be closed at end of line " ) ;
}
2016-10-16 18:59:49 -07:00
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Eol )
2016-10-29 01:58:30 -07:00
} else if let Some ( captures ) = BACKTICK . captures ( rest ) {
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Backtick )
2016-10-22 23:18:26 -07:00
} else if let Some ( captures ) = COLON . captures ( rest ) {
2016-10-16 18:59:49 -07:00
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Colon )
2016-10-25 19:11:58 -07:00
} else if let Some ( captures ) = PLUS . captures ( rest ) {
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Plus )
2016-10-22 23:18:26 -07:00
} else if let Some ( captures ) = EQUALS . captures ( rest ) {
2016-10-16 18:59:49 -07:00
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Equals )
2016-10-22 23:18:26 -07:00
} else if let Some ( captures ) = COMMENT . captures ( rest ) {
2016-10-16 18:59:49 -07:00
( captures . at ( 1 ) . unwrap ( ) , captures . at ( 2 ) . unwrap ( ) , Comment )
2016-10-25 19:11:58 -07:00
} else if let Some ( captures ) = STRING . captures ( rest ) {
2016-10-28 00:06:36 -07:00
let prefix = captures . at ( 1 ) . unwrap ( ) ;
let contents = & rest [ prefix . len ( ) + 1 .. ] ;
if contents . is_empty ( ) {
return error! ( ErrorKind ::UnterminatedString ) ;
}
let mut len = 0 ;
let mut escape = false ;
for c in contents . chars ( ) {
if c = = '\n' | | c = = '\r' {
return error! ( ErrorKind ::UnterminatedString ) ;
} else if ! escape & & c = = '"' {
break ;
} else if ! escape & & c = = '\\' {
escape = true ;
} else if escape {
escape = false ;
}
len + = c . len_utf8 ( ) ;
}
let start = prefix . len ( ) ;
let content_end = start + len + 1 ;
if escape | | content_end > = rest . len ( ) {
return error! ( ErrorKind ::UnterminatedString ) ;
}
( prefix , & rest [ start .. content_end + 1 ] , StringToken )
2016-10-25 19:11:58 -07:00
} else if rest . starts_with ( " #! " ) {
return error! ( ErrorKind ::OuterShebang )
2016-10-16 18:59:49 -07:00
} else {
2016-10-25 19:11:58 -07:00
return error! ( ErrorKind ::UnknownStartOfToken )
2016-10-16 18:59:49 -07:00
} ;
let len = prefix . len ( ) + lexeme . len ( ) ;
tokens . push ( Token {
2016-10-22 23:18:26 -07:00
index : index ,
line : line ,
column : column ,
2016-10-16 18:59:49 -07:00
prefix : prefix ,
2016-10-23 16:43:52 -07:00
text : text ,
2016-10-16 18:59:49 -07:00
lexeme : lexeme ,
2016-10-27 18:48:55 -07:00
kind : kind ,
2016-10-16 18:59:49 -07:00
} ) ;
2016-10-26 20:54:44 -07:00
if len = = 0 {
2016-10-27 18:48:55 -07:00
match tokens . last ( ) . unwrap ( ) . kind {
2016-10-26 20:54:44 -07:00
Eof = > { } ,
_ = > return Err ( tokens . last ( ) . unwrap ( ) . error (
ErrorKind ::InternalError { message : format ! ( " zero length token: {:?} " , tokens . last ( ) . unwrap ( ) ) } ) ) ,
}
}
2016-10-27 18:48:55 -07:00
match tokens . last ( ) . unwrap ( ) . kind {
2016-10-16 18:59:49 -07:00
Eol = > {
line + = 1 ;
2016-10-22 23:18:26 -07:00
column = 0 ;
2016-10-16 18:59:49 -07:00
} ,
Eof = > {
break ;
} ,
_ = > {
2016-10-22 23:18:26 -07:00
column + = len ;
2016-10-16 18:59:49 -07:00
}
}
rest = & rest [ len .. ] ;
2016-10-22 23:18:26 -07:00
index + = len ;
2016-10-16 18:59:49 -07:00
}
Ok ( tokens )
}
2016-10-23 20:39:50 -07:00
fn parse ( text : & str ) -> Result < Justfile , Error > {
2016-10-22 23:18:26 -07:00
let tokens = try ! ( tokenize ( text ) ) ;
2016-10-27 18:48:55 -07:00
let filtered : Vec < _ > = tokens . into_iter ( ) . filter ( | token | token . kind ! = Comment ) . collect ( ) ;
2016-10-23 16:43:52 -07:00
if let Some ( token ) = filtered . iter ( ) . find ( | token | {
lazy_static! {
static ref GOOD_NAME : Regex = re ( " ^[a-z](-?[a-z0-9])*$ " ) ;
}
2016-10-27 18:48:55 -07:00
token . kind = = Name & & ! GOOD_NAME . is_match ( token . lexeme )
2016-10-23 16:43:52 -07:00
} ) {
return Err ( token . error ( ErrorKind ::BadName { name : token . lexeme } ) ) ;
}
2016-10-22 23:18:26 -07:00
let parser = Parser {
text : text ,
tokens : filtered . into_iter ( ) . peekable ( )
} ;
let justfile = try ! ( parser . file ( ) ) ;
Ok ( justfile )
2016-10-16 18:59:49 -07:00
}
2016-10-22 23:18:26 -07:00
struct Parser < ' a > {
text : & ' a str ,
tokens : std ::iter ::Peekable < std ::vec ::IntoIter < Token < ' a > > >
2016-10-16 18:59:49 -07:00
}
2016-10-22 23:18:26 -07:00
impl < ' a > Parser < ' a > {
2016-10-27 18:48:55 -07:00
fn peek ( & mut self , kind : TokenKind ) -> bool {
self . tokens . peek ( ) . unwrap ( ) . kind = = kind
2016-10-23 16:43:52 -07:00
}
2016-10-27 18:48:55 -07:00
fn accept ( & mut self , kind : TokenKind ) -> Option < Token < ' a > > {
if self . peek ( kind ) {
2016-10-22 23:18:26 -07:00
self . tokens . next ( )
2016-10-16 18:59:49 -07:00
} else {
None
}
}
2016-10-27 18:48:55 -07:00
fn accepted ( & mut self , kind : TokenKind ) -> bool {
self . accept ( kind ) . is_some ( )
2016-10-16 18:59:49 -07:00
}
2016-10-27 18:48:55 -07:00
fn expect ( & mut self , kind : TokenKind ) -> Option < Token < ' a > > {
if self . peek ( kind ) {
2016-10-23 16:43:52 -07:00
self . tokens . next ( ) ;
None
} else {
self . tokens . next ( )
}
2016-10-22 23:18:26 -07:00
}
2016-10-23 16:43:52 -07:00
fn expect_eol ( & mut self ) -> Option < Token < ' a > > {
if self . peek ( Eol ) {
self . accept ( Eol ) ;
None
} else if self . peek ( Eof ) {
None
} else {
self . tokens . next ( )
2016-10-16 18:59:49 -07:00
}
}
2016-10-25 19:11:58 -07:00
fn unexpected_token ( & self , found : & Token < ' a > , expected : & [ TokenKind ] ) -> Error < ' a > {
found . error ( ErrorKind ::UnexpectedToken {
expected : expected . to_vec ( ) ,
2016-10-27 18:48:55 -07:00
found : found . kind ,
2016-10-25 19:11:58 -07:00
} )
}
2016-10-23 16:43:52 -07:00
fn recipe ( & mut self , name : & ' a str , line_number : usize ) -> Result < Recipe < ' a > , Error < ' a > > {
2016-10-16 18:59:49 -07:00
let mut arguments = vec! [ ] ;
2016-10-23 16:43:52 -07:00
let mut argument_tokens = vec! [ ] ;
while let Some ( argument ) = self . accept ( Name ) {
if arguments . contains ( & argument . lexeme ) {
return Err ( argument . error ( ErrorKind ::DuplicateArgument {
recipe : name , argument : argument . lexeme
} ) ) ;
2016-10-16 18:59:49 -07:00
}
2016-10-23 16:43:52 -07:00
arguments . push ( argument . lexeme ) ;
argument_tokens . push ( argument ) ;
2016-10-16 18:59:49 -07:00
}
2016-10-23 16:43:52 -07:00
if let Some ( token ) = self . expect ( Colon ) {
2016-10-25 19:11:58 -07:00
// if we haven't accepted any arguments, an equals
2016-10-26 20:54:44 -07:00
// would have been fine as part of an assignment
2016-10-25 19:11:58 -07:00
if arguments . is_empty ( ) {
return Err ( self . unexpected_token ( & token , & [ Name , Colon , Equals ] ) ) ;
} else {
return Err ( self . unexpected_token ( & token , & [ Name , Colon ] ) ) ;
}
2016-10-23 16:43:52 -07:00
}
2016-10-16 18:59:49 -07:00
let mut dependencies = vec! [ ] ;
2016-10-23 16:43:52 -07:00
let mut dependency_tokens = vec! [ ] ;
while let Some ( dependency ) = self . accept ( Name ) {
if dependencies . contains ( & dependency . lexeme ) {
return Err ( dependency . error ( ErrorKind ::DuplicateDependency {
recipe : name ,
dependency : dependency . lexeme
} ) ) ;
2016-10-16 18:59:49 -07:00
}
2016-10-23 16:43:52 -07:00
dependencies . push ( dependency . lexeme ) ;
dependency_tokens . push ( dependency ) ;
}
if let Some ( token ) = self . expect_eol ( ) {
return Err ( self . unexpected_token ( & token , & [ Name , Eol , Eof ] ) ) ;
2016-10-16 18:59:49 -07:00
}
2016-10-27 09:44:07 -07:00
let mut lines = vec! [ ] ;
2016-10-27 00:13:10 -07:00
let mut shebang = false ;
2016-10-26 22:04:12 -07:00
if self . accepted ( Indent ) {
while ! self . accepted ( Dedent ) {
2016-10-27 00:13:10 -07:00
if self . accepted ( Eol ) {
continue ;
}
2016-10-26 22:04:12 -07:00
if let Some ( token ) = self . expect ( Line ) {
return Err ( token . error ( ErrorKind ::InternalError {
2016-10-27 18:48:55 -07:00
message : format ! ( " Expected a line but got {} " , token . kind )
2016-10-26 22:04:12 -07:00
} ) )
}
let mut pieces = vec! [ ] ;
2016-10-27 00:13:10 -07:00
while ! ( self . accepted ( Eol ) | | self . peek ( Dedent ) ) {
2016-10-26 22:04:12 -07:00
if let Some ( token ) = self . accept ( Text ) {
2016-10-27 00:13:10 -07:00
if pieces . is_empty ( ) {
2016-10-27 09:44:07 -07:00
if lines . is_empty ( ) {
2016-10-27 00:13:10 -07:00
if token . lexeme . starts_with ( " #! " ) {
shebang = true ;
}
2016-10-28 00:06:36 -07:00
} else if ! shebang & & token . lexeme . starts_with ( ' ' ) | | token . lexeme . starts_with ( '\t' ) {
2016-10-27 00:13:10 -07:00
return Err ( token . error ( ErrorKind ::ExtraLeadingWhitespace ) ) ;
}
}
2016-10-27 09:44:07 -07:00
pieces . push ( Fragment ::Text { text : token } ) ;
2016-10-26 22:04:12 -07:00
} else if let Some ( token ) = self . expect ( InterpolationStart ) {
return Err ( self . unexpected_token ( & token , & [ Text , InterpolationStart , Eol ] ) ) ;
} else {
2016-10-27 18:01:07 -07:00
pieces . push ( Fragment ::Expression {
expression : try ! ( self . expression ( true ) ) , value : None
} ) ;
2016-10-26 22:04:12 -07:00
if let Some ( token ) = self . expect ( InterpolationEnd ) {
return Err ( self . unexpected_token ( & token , & [ InterpolationEnd ] ) ) ;
}
}
}
2016-10-27 09:44:07 -07:00
lines . push ( pieces ) ;
2016-10-26 22:04:12 -07:00
}
}
2016-10-23 16:43:52 -07:00
Ok ( Recipe {
line_number : line_number ,
name : name ,
dependencies : dependencies ,
dependency_tokens : dependency_tokens ,
arguments : arguments ,
argument_tokens : argument_tokens ,
2016-10-27 18:48:55 -07:00
evaluated_lines : vec ! [ ] ,
lines : lines ,
2016-10-23 16:43:52 -07:00
shebang : shebang ,
} )
2016-10-16 18:59:49 -07:00
}
2016-10-22 23:18:26 -07:00
2016-10-26 22:04:12 -07:00
fn expression ( & mut self , interpolation : bool ) -> Result < Expression < ' a > , Error < ' a > > {
2016-10-25 19:11:58 -07:00
let first = self . tokens . next ( ) . unwrap ( ) ;
2016-10-27 18:48:55 -07:00
let lhs = match first . kind {
2016-10-26 20:54:44 -07:00
Name = > Expression ::Variable { name : first . lexeme , token : first } ,
2016-10-29 01:58:30 -07:00
Backtick = > Expression ::Backtick { raw : & first . lexeme [ 1 .. first . lexeme . len ( ) - 1 ] } ,
2016-10-28 00:06:36 -07:00
StringToken = > {
let raw = & first . lexeme [ 1 .. first . lexeme . len ( ) - 1 ] ;
let mut cooked = String ::new ( ) ;
let mut escape = false ;
for c in raw . chars ( ) {
if escape {
match c {
'n' = > cooked . push ( '\n' ) ,
'r' = > cooked . push ( '\r' ) ,
't' = > cooked . push ( '\t' ) ,
'\\' = > cooked . push ( '\\' ) ,
'"' = > cooked . push ( '"' ) ,
other = > return Err ( first . error ( ErrorKind ::InvalidEscapeSequence {
character : other ,
} ) ) ,
}
escape = false ;
continue ;
}
if c = = '\\' {
escape = true ;
continue ;
}
cooked . push ( c ) ;
}
Expression ::String { raw : raw , cooked : cooked }
}
_ = > return Err ( self . unexpected_token ( & first , & [ Name , StringToken ] ) ) ,
2016-10-25 19:11:58 -07:00
} ;
if self . accepted ( Plus ) {
2016-10-26 22:04:12 -07:00
let rhs = try ! ( self . expression ( interpolation ) ) ;
2016-10-25 19:11:58 -07:00
Ok ( Expression ::Concatination { lhs : Box ::new ( lhs ) , rhs : Box ::new ( rhs ) } )
2016-10-26 22:04:12 -07:00
} else if interpolation & & self . peek ( InterpolationEnd ) {
Ok ( lhs )
2016-10-25 19:11:58 -07:00
} else if let Some ( token ) = self . expect_eol ( ) {
2016-10-26 22:04:12 -07:00
if interpolation {
2016-10-27 00:13:10 -07:00
return Err ( self . unexpected_token ( & token , & [ Plus , Eol , InterpolationEnd ] ) )
2016-10-26 22:04:12 -07:00
} else {
Err ( self . unexpected_token ( & token , & [ Plus , Eol ] ) )
}
2016-10-25 19:11:58 -07:00
} else {
Ok ( lhs )
}
2016-10-22 23:18:26 -07:00
}
2016-10-16 18:59:49 -07:00
2016-10-22 23:18:26 -07:00
fn file ( mut self ) -> Result < Justfile < ' a > , Error < ' a > > {
2016-10-23 16:43:52 -07:00
let mut recipes = BTreeMap ::< & str , Recipe > ::new ( ) ;
2016-10-25 19:11:58 -07:00
let mut assignments = BTreeMap ::< & str , Expression > ::new ( ) ;
let mut assignment_tokens = BTreeMap ::< & str , Token < ' a > > ::new ( ) ;
2016-10-22 23:18:26 -07:00
loop {
match self . tokens . next ( ) {
2016-10-27 18:48:55 -07:00
Some ( token ) = > match token . kind {
2016-10-22 23:18:26 -07:00
Eof = > break ,
Eol = > continue ,
2016-10-25 19:11:58 -07:00
Name = > if self . accepted ( Equals ) {
if assignments . contains_key ( token . lexeme ) {
return Err ( token . error ( ErrorKind ::DuplicateVariable {
variable : token . lexeme ,
} ) ) ;
}
2016-10-26 22:04:12 -07:00
assignments . insert ( token . lexeme , try ! ( self . expression ( false ) ) ) ;
2016-10-25 19:11:58 -07:00
assignment_tokens . insert ( token . lexeme , token ) ;
2016-10-23 16:43:52 -07:00
} else {
if let Some ( recipe ) = recipes . remove ( token . lexeme ) {
return Err ( token . error ( ErrorKind ::DuplicateRecipe {
recipe : recipe . name ,
first : recipe . line_number
} ) ) ;
}
recipes . insert ( token . lexeme , try ! ( self . recipe ( token . lexeme , token . line ) ) ) ;
} ,
Comment = > return Err ( token . error ( ErrorKind ::InternalError {
message : " found comment in token stream " . to_string ( )
} ) ) ,
2016-10-27 09:44:07 -07:00
_ = > return return Err ( self . unexpected_token ( & token , & [ Name ] ) ) ,
2016-10-22 23:18:26 -07:00
} ,
None = > return Err ( Error {
text : self . text ,
index : 0 ,
line : 0 ,
column : 0 ,
2016-10-23 16:43:52 -07:00
width : None ,
2016-10-22 23:18:26 -07:00
kind : ErrorKind ::InternalError {
message : " unexpected end of token stream " . to_string ( )
}
} ) ,
}
}
2016-10-16 18:59:49 -07:00
2016-10-23 16:43:52 -07:00
if let Some ( token ) = self . tokens . next ( ) {
return Err ( token . error ( ErrorKind ::InternalError {
2016-10-27 18:48:55 -07:00
message : format ! ( " unexpected token remaining after parsing completed: {:?} " , token . kind )
2016-10-22 23:18:26 -07:00
} ) )
2016-10-06 17:43:30 -07:00
}
2016-10-29 20:39:21 -07:00
try ! ( resolve_recipes ( & recipes , & assignments , self . text ) ) ;
2016-10-23 16:43:52 -07:00
for recipe in recipes . values ( ) {
2016-10-25 19:11:58 -07:00
for argument in & recipe . argument_tokens {
if assignments . contains_key ( argument . lexeme ) {
return Err ( argument . error ( ErrorKind ::ArgumentShadowsVariable {
argument : argument . lexeme
} ) ) ;
}
}
2016-10-27 00:13:10 -07:00
2016-10-29 00:14:41 -07:00
for dependency in & recipe . dependency_tokens {
if ! recipes . get ( dependency . lexeme ) . unwrap ( ) . arguments . is_empty ( ) {
return Err ( dependency . error ( ErrorKind ::DependencyHasArguments {
recipe : recipe . name ,
dependency : dependency . lexeme ,
} ) ) ;
}
}
2016-10-23 16:43:52 -07:00
}
2016-10-29 20:39:21 -07:00
try ! ( resolve_assignments ( & assignments , & assignment_tokens ) ) ;
2016-10-27 18:48:55 -07:00
Ok ( Justfile {
2016-10-25 19:11:58 -07:00
recipes : recipes ,
assignments : assignments ,
} )
2016-10-02 22:30:28 -07:00
}
}