2020-04-06 07:16:14 +00:00
use std ::path ::Path ;
2020-04-18 01:50:58 +00:00
use log ::trace ;
2020-04-13 07:59:57 +00:00
use nu_errors ::{ ArgumentError , ParseError } ;
use nu_protocol ::hir ::{
2020-04-20 06:41:51 +00:00
self , Binary , Block , ClassifiedBlock , ClassifiedCommand , ClassifiedPipeline , Commands ,
2020-08-07 04:53:37 +00:00
Expression , ExternalRedirection , Flag , FlagKind , InternalCommand , Member , NamedArguments ,
Operator , SpannedExpression , Unit ,
2020-04-13 07:59:57 +00:00
} ;
2020-04-06 07:16:14 +00:00
use nu_protocol ::{ NamedType , PositionalType , Signature , SyntaxShape , UnspannedPathMember } ;
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
use nu_source ::{ Span , Spanned , SpannedItem } ;
2020-04-06 07:16:14 +00:00
use num_bigint ::BigInt ;
2020-08-01 18:39:55 +00:00
//use crate::errors::{ParseError, ParseResult};
use crate ::lite_parse ::{ lite_parse , LiteBlock , LiteCommand , LitePipeline } ;
use crate ::path ::expand_path ;
use crate ::signature ::SignatureRegistry ;
2020-04-06 07:16:14 +00:00
/// Parses a simple column path, one without a variable (implied or explicit) at the head
fn parse_simple_column_path ( lite_arg : & Spanned < String > ) -> ( SpannedExpression , Option < ParseError > ) {
let mut delimiter = '.' ;
let mut inside_delimiter = false ;
let mut output = vec! [ ] ;
let mut current_part = String ::new ( ) ;
let mut start_index = 0 ;
let mut last_index = 0 ;
for ( idx , c ) in lite_arg . item . char_indices ( ) {
last_index = idx ;
if inside_delimiter {
if c = = delimiter {
inside_delimiter = false ;
}
2020-05-19 19:27:26 +00:00
} else if c = = '\'' | | c = = '"' | | c = = '`' {
2020-04-06 07:16:14 +00:00
inside_delimiter = true ;
delimiter = c ;
} else if c = = '.' {
let part_span = Span ::new (
lite_arg . span . start ( ) + start_index ,
lite_arg . span . start ( ) + idx ,
) ;
if let Ok ( row_number ) = current_part . parse ::< u64 > ( ) {
output . push ( Member ::Int ( BigInt ::from ( row_number ) , part_span ) ) ;
} else {
let trimmed = trim_quotes ( & current_part ) ;
output . push ( Member ::Bare ( trimmed . clone ( ) . spanned ( part_span ) ) ) ;
}
current_part . clear ( ) ;
// Note: I believe this is safe because of the delimiter we're using, but if we get fancy with
// unicode we'll need to change this
start_index = idx + '.' . len_utf8 ( ) ;
continue ;
}
current_part . push ( c ) ;
}
if ! current_part . is_empty ( ) {
let part_span = Span ::new (
lite_arg . span . start ( ) + start_index ,
lite_arg . span . start ( ) + last_index + 1 ,
) ;
if let Ok ( row_number ) = current_part . parse ::< u64 > ( ) {
output . push ( Member ::Int ( BigInt ::from ( row_number ) , part_span ) ) ;
} else {
let current_part = trim_quotes ( & current_part ) ;
output . push ( Member ::Bare ( current_part . spanned ( part_span ) ) ) ;
}
}
(
SpannedExpression ::new ( Expression ::simple_column_path ( output ) , lite_arg . span ) ,
None ,
)
}
/// Parses a column path, adding in the preceding reference to $it if it's elided
2020-05-19 04:20:09 +00:00
pub fn parse_full_column_path (
2020-05-16 03:18:24 +00:00
lite_arg : & Spanned < String > ,
registry : & dyn SignatureRegistry ,
) -> ( SpannedExpression , Option < ParseError > ) {
2020-04-06 07:16:14 +00:00
let mut delimiter = '.' ;
let mut inside_delimiter = false ;
let mut output = vec! [ ] ;
let mut current_part = String ::new ( ) ;
let mut start_index = 0 ;
let mut last_index = 0 ;
2020-05-16 03:18:24 +00:00
let mut error = None ;
2020-04-06 07:16:14 +00:00
let mut head = None ;
for ( idx , c ) in lite_arg . item . char_indices ( ) {
last_index = idx ;
if inside_delimiter {
if c = = delimiter {
inside_delimiter = false ;
}
2020-05-16 07:25:18 +00:00
} else if c = = '(' {
inside_delimiter = true ;
delimiter = ')' ;
2020-04-06 07:16:14 +00:00
} else if c = = '\'' | | c = = '"' {
inside_delimiter = true ;
delimiter = c ;
} else if c = = '.' {
let part_span = Span ::new (
lite_arg . span . start ( ) + start_index ,
lite_arg . span . start ( ) + idx ,
) ;
2020-05-16 03:18:24 +00:00
if head . is_none ( ) & & current_part . starts_with ( " $( " ) & & current_part . ends_with ( ')' ) {
// We have a command invocation
let string : String = current_part
. chars ( )
. skip ( 2 )
. take ( current_part . len ( ) - 3 )
. collect ( ) ;
// We haven't done much with the inner string, so let's go ahead and work with it
let lite_block = match lite_parse ( & string , lite_arg . span . start ( ) + 2 ) {
Ok ( lp ) = > lp ,
2020-08-01 18:39:55 +00:00
Err ( e ) = > return ( garbage ( lite_arg . span ) , Some ( e . cause ) ) ,
2020-05-16 03:18:24 +00:00
} ;
let classified_block = classify_block ( & lite_block , registry ) ;
let err = classified_block . failed ;
if error . is_none ( ) {
error = err ;
}
head = Some ( Expression ::Invocation ( classified_block . block ) )
} else if head . is_none ( ) & & current_part . starts_with ( '$' ) {
2020-04-06 07:16:14 +00:00
// We have the variable head
head = Some ( Expression ::variable ( current_part . clone ( ) , part_span ) )
} else if let Ok ( row_number ) = current_part . parse ::< u64 > ( ) {
output . push (
UnspannedPathMember ::Int ( BigInt ::from ( row_number ) ) . into_path_member ( part_span ) ,
) ;
} else {
let current_part = trim_quotes ( & current_part ) ;
output . push (
UnspannedPathMember ::String ( current_part . clone ( ) ) . into_path_member ( part_span ) ,
) ;
}
current_part . clear ( ) ;
// Note: I believe this is safe because of the delimiter we're using, but if we get fancy with
// unicode we'll need to change this
start_index = idx + '.' . len_utf8 ( ) ;
continue ;
}
current_part . push ( c ) ;
}
if ! current_part . is_empty ( ) {
let part_span = Span ::new (
lite_arg . span . start ( ) + start_index ,
lite_arg . span . start ( ) + last_index + 1 ,
) ;
if head . is_none ( ) {
2020-05-16 03:18:24 +00:00
if current_part . starts_with ( " $( " ) & & current_part . ends_with ( ')' ) {
// We have a command invocation
let string : String = current_part
. chars ( )
. skip ( 2 )
. take ( current_part . len ( ) - 3 )
. collect ( ) ;
// We haven't done much with the inner string, so let's go ahead and work with it
let lite_block = match lite_parse ( & string , lite_arg . span . start ( ) + 2 ) {
Ok ( lp ) = > lp ,
2020-08-01 18:39:55 +00:00
Err ( e ) = > return ( garbage ( lite_arg . span ) , Some ( e . cause ) ) ,
2020-05-16 03:18:24 +00:00
} ;
let classified_block = classify_block ( & lite_block , registry ) ;
let err = classified_block . failed ;
if error . is_none ( ) {
error = err ;
}
head = Some ( Expression ::Invocation ( classified_block . block ) ) ;
} else if current_part . starts_with ( '$' ) {
2020-04-06 17:30:54 +00:00
// We have the variable head
2020-04-06 07:16:14 +00:00
head = Some ( Expression ::variable ( current_part , lite_arg . span ) ) ;
} else if let Ok ( row_number ) = current_part . parse ::< u64 > ( ) {
output . push (
UnspannedPathMember ::Int ( BigInt ::from ( row_number ) ) . into_path_member ( part_span ) ,
) ;
} else {
let current_part = trim_quotes ( & current_part ) ;
output . push ( UnspannedPathMember ::String ( current_part ) . into_path_member ( part_span ) ) ;
}
} else if let Ok ( row_number ) = current_part . parse ::< u64 > ( ) {
output . push (
UnspannedPathMember ::Int ( BigInt ::from ( row_number ) ) . into_path_member ( part_span ) ,
) ;
} else {
let current_part = trim_quotes ( & current_part ) ;
output . push ( UnspannedPathMember ::String ( current_part ) . into_path_member ( part_span ) ) ;
}
}
if let Some ( head ) = head {
(
SpannedExpression ::new (
Expression ::path ( SpannedExpression ::new ( head , lite_arg . span ) , output ) ,
lite_arg . span ,
) ,
2020-05-16 03:18:24 +00:00
error ,
2020-04-06 07:16:14 +00:00
)
} else {
(
SpannedExpression ::new (
Expression ::path (
SpannedExpression ::new (
Expression ::variable ( " $it " . into ( ) , lite_arg . span ) ,
lite_arg . span ,
) ,
output ,
) ,
lite_arg . span ,
) ,
2020-05-16 03:18:24 +00:00
error ,
2020-04-06 07:16:14 +00:00
)
}
}
fn trim_quotes ( input : & str ) -> String {
let mut chars = input . chars ( ) ;
match ( chars . next ( ) , chars . next_back ( ) ) {
( Some ( '\'' ) , Some ( '\'' ) ) = > chars . collect ( ) ,
( Some ( '"' ) , Some ( '"' ) ) = > chars . collect ( ) ,
2020-05-19 19:27:26 +00:00
( Some ( '`' ) , Some ( '`' ) ) = > chars . collect ( ) ,
2020-04-06 07:16:14 +00:00
_ = > input . to_string ( ) ,
}
}
2020-04-18 17:30:40 +00:00
/// Parse a numeric range
2020-04-06 07:16:14 +00:00
fn parse_range ( lite_arg : & Spanned < String > ) -> ( SpannedExpression , Option < ParseError > ) {
let numbers : Vec < _ > = lite_arg . item . split ( " .. " ) . collect ( ) ;
if numbers . len ( ) ! = 2 {
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " range " , lite_arg . clone ( ) ) ) ,
)
} else if let Ok ( lhs ) = numbers [ 0 ] . parse ::< i64 > ( ) {
if let Ok ( rhs ) = numbers [ 1 ] . parse ::< i64 > ( ) {
(
SpannedExpression ::new (
Expression ::range (
SpannedExpression ::new ( Expression ::integer ( lhs ) , lite_arg . span ) ,
lite_arg . span ,
SpannedExpression ::new ( Expression ::integer ( rhs ) , lite_arg . span ) ,
) ,
lite_arg . span ,
) ,
None ,
)
} else {
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " range " , lite_arg . clone ( ) ) ) ,
)
}
} else {
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " range " , lite_arg . clone ( ) ) ) ,
)
}
}
2020-04-18 17:30:40 +00:00
/// Parse any allowed operator, including word-based operators
2020-04-06 07:16:14 +00:00
fn parse_operator ( lite_arg : & Spanned < String > ) -> ( SpannedExpression , Option < ParseError > ) {
2020-04-30 00:59:50 +00:00
let operator = match & lite_arg . item [ .. ] {
" == " = > Operator ::Equal ,
" != " = > Operator ::NotEqual ,
" < " = > Operator ::LessThan ,
" <= " = > Operator ::LessThanOrEqual ,
" > " = > Operator ::GreaterThan ,
" >= " = > Operator ::GreaterThanOrEqual ,
" =~ " = > Operator ::Contains ,
" !~ " = > Operator ::NotContains ,
" + " = > Operator ::Plus ,
" - " = > Operator ::Minus ,
" * " = > Operator ::Multiply ,
" / " = > Operator ::Divide ,
" in: " = > Operator ::In ,
" not-in: " = > Operator ::NotIn ,
" && " = > Operator ::And ,
" || " = > Operator ::Or ,
_ = > {
return (
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " operator " , lite_arg . clone ( ) ) ) ,
) ;
}
2020-04-06 07:16:14 +00:00
} ;
(
SpannedExpression ::new ( Expression ::operator ( operator ) , lite_arg . span ) ,
None ,
)
}
2020-04-18 17:30:40 +00:00
/// Parse a unit type, eg '10kb'
2020-04-06 07:16:14 +00:00
fn parse_unit ( lite_arg : & Spanned < String > ) -> ( SpannedExpression , Option < ParseError > ) {
let unit_groups = [
2020-04-18 01:50:58 +00:00
( Unit ::Byte , vec! [ " b " , " B " ] ) ,
2020-05-02 18:14:07 +00:00
( Unit ::Kilobyte , vec! [ " kb " , " KB " , " Kb " , " kB " ] ) ,
( Unit ::Megabyte , vec! [ " mb " , " MB " , " Mb " , " mB " ] ) ,
( Unit ::Gigabyte , vec! [ " gb " , " GB " , " Gb " , " gB " ] ) ,
( Unit ::Terabyte , vec! [ " tb " , " TB " , " Tb " , " tB " ] ) ,
( Unit ::Petabyte , vec! [ " pb " , " PB " , " Pb " , " pB " ] ) ,
2020-07-10 17:48:11 +00:00
( Unit ::Nanosecond , vec! [ " ns " ] ) ,
( Unit ::Microsecond , vec! [ " us " ] ) ,
( Unit ::Millisecond , vec! [ " ms " ] ) ,
2020-08-15 19:03:28 +00:00
( Unit ::Second , vec! [ " sec " ] ) ,
( Unit ::Minute , vec! [ " min " ] ) ,
( Unit ::Hour , vec! [ " hr " ] ) ,
( Unit ::Day , vec! [ " day " ] ) ,
( Unit ::Week , vec! [ " wk " ] ) ,
( Unit ::Month , vec! [ " mon " ] ) ,
( Unit ::Year , vec! [ " yr " ] ) ,
2020-04-06 07:16:14 +00:00
] ;
for unit_group in unit_groups . iter ( ) {
2020-04-18 01:50:58 +00:00
for unit in unit_group . 1. iter ( ) {
2020-04-06 07:16:14 +00:00
if lite_arg . item . ends_with ( unit ) {
let mut lhs = lite_arg . item . clone ( ) ;
for _ in 0 .. unit . len ( ) {
lhs . pop ( ) ;
}
2020-08-21 17:51:29 +00:00
// these units are allowed to be signed
2020-04-18 01:50:58 +00:00
if let Ok ( x ) = lhs . parse ::< i64 > ( ) {
let lhs_span =
Span ::new ( lite_arg . span . start ( ) , lite_arg . span . start ( ) + lhs . len ( ) ) ;
let unit_span =
Span ::new ( lite_arg . span . start ( ) + lhs . len ( ) , lite_arg . span . end ( ) ) ;
return (
SpannedExpression ::new (
Expression ::unit ( x . spanned ( lhs_span ) , unit_group . 0. spanned ( unit_span ) ) ,
lite_arg . span ,
) ,
None ,
) ;
2020-04-06 07:16:14 +00:00
}
}
}
}
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " unit " , lite_arg . clone ( ) ) ) ,
)
}
2020-05-19 19:27:26 +00:00
#[ derive(Debug) ]
enum FormatCommand {
Text ( Spanned < String > ) ,
Column ( Spanned < String > ) ,
}
fn format ( input : & str , start : usize ) -> ( Vec < FormatCommand > , Option < ParseError > ) {
let original_start = start ;
let mut output = vec! [ ] ;
let mut error = None ;
let mut loop_input = input . chars ( ) . peekable ( ) ;
let mut start = start ;
let mut end = start ;
loop {
let mut before = String ::new ( ) ;
let mut found_start = false ;
while let Some ( c ) = loop_input . next ( ) {
end + = 1 ;
if c = = '{' {
if let Some ( x ) = loop_input . peek ( ) {
if * x = = '{' {
found_start = true ;
end + = 1 ;
let _ = loop_input . next ( ) ;
break ;
}
}
}
before . push ( c ) ;
}
if ! before . is_empty ( ) {
if found_start {
output . push ( FormatCommand ::Text (
before . to_string ( ) . spanned ( Span ::new ( start , end - 2 ) ) ,
) ) ;
} else {
output . push ( FormatCommand ::Text ( before . spanned ( Span ::new ( start , end ) ) ) ) ;
break ;
}
}
// Look for column as we're now at one
let mut column = String ::new ( ) ;
start = end ;
let mut previous_c = ' ' ;
let mut found_end = false ;
while let Some ( c ) = loop_input . next ( ) {
end + = 1 ;
if c = = '}' & & previous_c = = '}' {
let _ = column . pop ( ) ;
found_end = true ;
break ;
}
previous_c = c ;
column . push ( c ) ;
}
if ! column . is_empty ( ) {
if found_end {
output . push ( FormatCommand ::Column (
column . to_string ( ) . spanned ( Span ::new ( start , end - 2 ) ) ,
) ) ;
} else {
output . push ( FormatCommand ::Column (
column . to_string ( ) . spanned ( Span ::new ( start , end ) ) ,
) ) ;
if error . is_none ( ) {
error = Some ( ParseError ::argument_error (
input . spanned ( Span ::new ( original_start , end ) ) ,
ArgumentError ::MissingValueForName ( " unclosed {{ }} " . to_string ( ) ) ,
) ) ;
}
}
}
if found_start & & ! found_end {
error = Some ( ParseError ::argument_error (
input . spanned ( Span ::new ( original_start , end ) ) ,
ArgumentError ::MissingValueForName ( " unclosed {{ }} " . to_string ( ) ) ,
) ) ;
}
if before . is_empty ( ) & & column . is_empty ( ) {
break ;
}
start = end ;
}
( output , error )
}
/// Parses an interpolated string, one that has expressions inside of it
fn parse_interpolated_string (
registry : & dyn SignatureRegistry ,
lite_arg : & Spanned < String > ,
) -> ( SpannedExpression , Option < ParseError > ) {
let inner_string = trim_quotes ( & lite_arg . item ) ;
let mut error = None ;
let ( format_result , err ) = format ( & inner_string , lite_arg . span . start ( ) + 1 ) ;
if error . is_none ( ) {
error = err ;
}
let mut output = vec! [ ] ;
for f in format_result {
match f {
FormatCommand ::Text ( t ) = > {
output . push ( SpannedExpression {
expr : Expression ::Literal ( hir ::Literal ::String ( t . item ) ) ,
span : t . span ,
} ) ;
}
FormatCommand ::Column ( c ) = > {
let ( o , err ) = parse_full_column_path ( & c , registry ) ;
if error . is_none ( ) {
error = err ;
}
output . push ( o ) ;
}
}
}
let block = vec! [ Commands {
span : lite_arg . span ,
list : vec ! [ ClassifiedCommand ::Internal ( InternalCommand {
name : " build-string " . to_owned ( ) ,
name_span : lite_arg . span ,
args : hir ::Call {
head : Box ::new ( SpannedExpression {
expr : Expression ::Synthetic ( hir ::Synthetic ::String ( " build-string " . to_owned ( ) ) ) ,
span : lite_arg . span ,
} ) ,
2020-08-07 04:53:37 +00:00
external_redirection : ExternalRedirection ::Stdout ,
2020-05-19 19:27:26 +00:00
named : None ,
positional : Some ( output ) ,
span : lite_arg . span ,
} ,
} ) ] ,
} ] ;
let call = SpannedExpression {
expr : Expression ::Invocation ( Block {
block ,
span : lite_arg . span ,
} ) ,
span : lite_arg . span ,
} ;
( call , error )
}
2020-07-02 23:29:28 +00:00
/// Parses the given argument using the shape as a guide for how to correctly parse the argument
fn parse_external_arg (
registry : & dyn SignatureRegistry ,
lite_arg : & Spanned < String > ,
) -> ( SpannedExpression , Option < ParseError > ) {
if lite_arg . item . starts_with ( '$' ) {
return parse_full_column_path ( & lite_arg , registry ) ;
}
if lite_arg . item . starts_with ( '`' ) & & lite_arg . item . len ( ) > 1 & & lite_arg . item . ends_with ( '`' ) {
// This is an interpolated string
parse_interpolated_string ( registry , & lite_arg )
} else {
(
SpannedExpression ::new ( Expression ::string ( lite_arg . item . clone ( ) ) , lite_arg . span ) ,
None ,
)
}
}
2020-04-06 07:16:14 +00:00
/// Parses the given argument using the shape as a guide for how to correctly parse the argument
fn parse_arg (
expected_type : SyntaxShape ,
registry : & dyn SignatureRegistry ,
lite_arg : & Spanned < String > ,
) -> ( SpannedExpression , Option < ParseError > ) {
if lite_arg . item . starts_with ( '$' ) {
2020-05-16 03:18:24 +00:00
return parse_full_column_path ( & lite_arg , registry ) ;
2020-04-06 07:16:14 +00:00
}
match expected_type {
SyntaxShape ::Number = > {
if let Ok ( x ) = lite_arg . item . parse ::< i64 > ( ) {
(
SpannedExpression ::new ( Expression ::integer ( x ) , lite_arg . span ) ,
None ,
)
} else if let Ok ( x ) = lite_arg . item . parse ::< f64 > ( ) {
(
SpannedExpression ::new ( Expression ::decimal ( x ) , lite_arg . span ) ,
None ,
)
} else {
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " number " , lite_arg . clone ( ) ) ) ,
)
}
}
SyntaxShape ::Int = > {
if let Ok ( x ) = lite_arg . item . parse ::< i64 > ( ) {
(
SpannedExpression ::new ( Expression ::integer ( x ) , lite_arg . span ) ,
None ,
)
} else {
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " number " , lite_arg . clone ( ) ) ) ,
)
}
}
SyntaxShape ::String = > {
2020-05-19 19:27:26 +00:00
if lite_arg . item . starts_with ( '`' )
& & lite_arg . item . len ( ) > 1
& & lite_arg . item . ends_with ( '`' )
{
// This is an interpolated string
parse_interpolated_string ( registry , & lite_arg )
} else {
let trimmed = trim_quotes ( & lite_arg . item ) ;
(
SpannedExpression ::new ( Expression ::string ( trimmed ) , lite_arg . span ) ,
None ,
)
}
2020-04-06 07:16:14 +00:00
}
SyntaxShape ::Pattern = > {
let trimmed = trim_quotes ( & lite_arg . item ) ;
2020-04-23 04:17:38 +00:00
let expanded = expand_path ( & trimmed ) . to_string ( ) ;
2020-04-06 07:16:14 +00:00
(
SpannedExpression ::new ( Expression ::pattern ( expanded ) , lite_arg . span ) ,
None ,
)
}
SyntaxShape ::Range = > parse_range ( & lite_arg ) ,
SyntaxShape ::Operator = > parse_operator ( & lite_arg ) ,
SyntaxShape ::Unit = > parse_unit ( & lite_arg ) ,
SyntaxShape ::Path = > {
let trimmed = trim_quotes ( & lite_arg . item ) ;
2020-04-23 04:17:38 +00:00
let expanded = expand_path ( & trimmed ) . to_string ( ) ;
2020-04-06 07:16:14 +00:00
let path = Path ::new ( & expanded ) ;
(
SpannedExpression ::new ( Expression ::FilePath ( path . to_path_buf ( ) ) , lite_arg . span ) ,
None ,
)
}
SyntaxShape ::ColumnPath = > parse_simple_column_path ( lite_arg ) ,
2020-05-16 03:18:24 +00:00
SyntaxShape ::FullColumnPath = > parse_full_column_path ( lite_arg , registry ) ,
2020-04-06 07:16:14 +00:00
SyntaxShape ::Any = > {
let shapes = vec! [
SyntaxShape ::Int ,
SyntaxShape ::Number ,
SyntaxShape ::Range ,
SyntaxShape ::Unit ,
SyntaxShape ::Block ,
SyntaxShape ::Table ,
SyntaxShape ::String ,
] ;
for shape in shapes . iter ( ) {
if let ( s , None ) = parse_arg ( * shape , registry , lite_arg ) {
return ( s , None ) ;
}
}
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " any shape " , lite_arg . clone ( ) ) ) ,
)
}
SyntaxShape ::Table = > {
let mut chars = lite_arg . item . chars ( ) ;
match ( chars . next ( ) , chars . next_back ( ) ) {
( Some ( '[' ) , Some ( ']' ) ) = > {
// We have a literal row
let string : String = chars . collect ( ) ;
let mut error = None ;
// We haven't done much with the inner string, so let's go ahead and work with it
2020-04-20 06:41:51 +00:00
let lite_block = match lite_parse ( & string , lite_arg . span . start ( ) + 1 ) {
Ok ( lb ) = > lb ,
2020-08-01 18:39:55 +00:00
Err ( e ) = > return ( garbage ( lite_arg . span ) , Some ( e . cause ) ) ,
2020-04-06 07:16:14 +00:00
} ;
2020-04-20 07:44:19 +00:00
if lite_block . block . is_empty ( ) {
return (
SpannedExpression ::new ( Expression ::List ( vec! [ ] ) , lite_arg . span ) ,
error ,
) ;
}
if lite_block . block . len ( ) > 1 {
2020-04-20 06:41:51 +00:00
return (
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " table " , lite_arg . clone ( ) ) ) ,
) ;
}
let lite_pipeline = lite_block . block [ 0 ] . clone ( ) ;
2020-04-06 07:16:14 +00:00
let mut output = vec! [ ] ;
for lite_inner in & lite_pipeline . commands {
let ( arg , err ) = parse_arg ( SyntaxShape ::Any , registry , & lite_inner . name ) ;
output . push ( arg ) ;
if error . is_none ( ) {
error = err ;
}
for arg in & lite_inner . args {
let ( arg , err ) = parse_arg ( SyntaxShape ::Any , registry , & arg ) ;
output . push ( arg ) ;
if error . is_none ( ) {
error = err ;
}
}
}
(
SpannedExpression ::new ( Expression ::List ( output ) , lite_arg . span ) ,
error ,
)
}
_ = > (
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " table " , lite_arg . clone ( ) ) ) ,
) ,
}
}
2020-04-18 01:50:58 +00:00
SyntaxShape ::Block | SyntaxShape ::Math = > {
2020-04-06 07:16:14 +00:00
// Blocks have one of two forms: the literal block and the implied block
// To parse a literal block, we need to detect that what we have is itself a block
let mut chars = lite_arg . item . chars ( ) ;
match ( chars . next ( ) , chars . next_back ( ) ) {
( Some ( '{' ) , Some ( '}' ) ) = > {
// We have a literal block
let string : String = chars . collect ( ) ;
// We haven't done much with the inner string, so let's go ahead and work with it
2020-04-20 06:41:51 +00:00
let lite_block = match lite_parse ( & string , lite_arg . span . start ( ) + 1 ) {
2020-04-06 07:16:14 +00:00
Ok ( lp ) = > lp ,
2020-08-01 18:39:55 +00:00
Err ( e ) = > return ( garbage ( lite_arg . span ) , Some ( e . cause ) ) ,
2020-04-06 07:16:14 +00:00
} ;
2020-04-20 06:41:51 +00:00
let classified_block = classify_block ( & lite_block , registry ) ;
2020-04-13 07:59:57 +00:00
let error = classified_block . failed ;
(
SpannedExpression ::new (
2020-04-20 06:41:51 +00:00
Expression ::Block ( classified_block . block ) ,
2020-04-13 07:59:57 +00:00
lite_arg . span ,
) ,
error ,
)
2020-04-06 07:16:14 +00:00
}
_ = > {
// We have an implied block, but we can't parse this here
// it needed to have been parsed up higher where we have control over more than one arg
(
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " block " , lite_arg . clone ( ) ) ) ,
)
}
}
}
}
}
2020-07-19 07:12:56 +00:00
#[ cfg(test) ]
mod test {
use super ::* ;
#[ derive(Clone, Debug) ]
struct MockRegistry { }
impl MockRegistry {
fn new ( ) -> Self {
MockRegistry { }
}
}
impl SignatureRegistry for MockRegistry {
fn has ( & self , _name : & str ) -> bool {
false
}
fn get ( & self , _name : & str ) -> Option < nu_protocol ::Signature > {
None
}
fn clone_box ( & self ) -> Box < dyn SignatureRegistry > {
Box ::new ( self . clone ( ) )
}
}
#[ test ]
fn parse_integer ( ) -> Result < ( ) , ParseError > {
let raw = " 32 " . to_string ( ) ;
let input = raw . clone ( ) . spanned ( Span ::new ( 0 , raw . len ( ) ) ) ;
let registry = MockRegistry ::new ( ) ;
let result = parse_arg ( SyntaxShape ::Int , & registry , & input ) ;
assert_eq! ( result . 1 , None ) ;
assert_eq! ( result . 0. expr , Expression ::integer ( 32 ) ) ;
Ok ( ( ) )
}
#[ test ]
fn parse_number ( ) -> Result < ( ) , ParseError > {
let raw = " -32.2 " . to_string ( ) ;
let input = raw . clone ( ) . spanned ( Span ::new ( 0 , raw . len ( ) ) ) ;
let registry = MockRegistry ::new ( ) ;
let result = parse_arg ( SyntaxShape ::Number , & registry , & input ) ;
assert_eq! ( result . 1 , None ) ;
assert_eq! ( result . 0. expr , Expression ::decimal ( - 32.2 ) ) ;
let raw = " 32.2 " . to_string ( ) ;
let input = raw . clone ( ) . spanned ( Span ::new ( 0 , raw . len ( ) ) ) ;
let registry = MockRegistry ::new ( ) ;
let result = parse_arg ( SyntaxShape ::Number , & registry , & input ) ;
assert_eq! ( result . 1 , None ) ;
assert_eq! ( result . 0. expr , Expression ::decimal ( 32.2 ) ) ;
let raw = " -34 " . to_string ( ) ;
let input = raw . clone ( ) . spanned ( Span ::new ( 0 , raw . len ( ) ) ) ;
let registry = MockRegistry ::new ( ) ;
let result = parse_arg ( SyntaxShape ::Number , & registry , & input ) ;
assert_eq! ( result . 1 , None ) ;
assert_eq! ( result . 0. expr , Expression ::integer ( - 34 ) ) ;
let raw = " 34 " . to_string ( ) ;
let input = raw . clone ( ) . spanned ( Span ::new ( 0 , raw . len ( ) ) ) ;
let registry = MockRegistry ::new ( ) ;
let result = parse_arg ( SyntaxShape ::Number , & registry , & input ) ;
assert_eq! ( result . 1 , None ) ;
assert_eq! ( result . 0. expr , Expression ::integer ( 34 ) ) ;
Ok ( ( ) )
}
}
2020-08-01 18:30:45 +00:00
/// Match the available flags in a signature with what the user provided. This will check both long-form flags (--long) and shorthand flags (-l)
/// This also allows users to provide a group of shorthand flags (-la) that correspond to multiple shorthand flags at once.
2020-04-06 07:16:14 +00:00
fn get_flags_from_flag (
signature : & nu_protocol ::Signature ,
cmd : & Spanned < String > ,
arg : & Spanned < String > ,
) -> ( Vec < ( String , NamedType ) > , Option < ParseError > ) {
if arg . item . starts_with ( '-' ) {
// It's a flag (or set of flags)
let mut output = vec! [ ] ;
let mut error = None ;
let remainder : String = arg . item . chars ( ) . skip ( 1 ) . collect ( ) ;
if remainder . starts_with ( '-' ) {
// Long flag expected
let remainder : String = remainder . chars ( ) . skip ( 1 ) . collect ( ) ;
if let Some ( ( named_type , _ ) ) = signature . named . get ( & remainder ) {
output . push ( ( remainder . clone ( ) , named_type . clone ( ) ) ) ;
} else {
error = Some ( ParseError ::argument_error (
cmd . clone ( ) ,
ArgumentError ::UnexpectedFlag ( arg . clone ( ) ) ,
) ) ;
}
} else {
// Short flag(s) expected
let mut starting_pos = arg . span . start ( ) + 1 ;
for c in remainder . chars ( ) {
let mut found = false ;
for ( full_name , named_arg ) in signature . named . iter ( ) {
if Some ( c ) = = named_arg . 0. get_short ( ) {
found = true ;
output . push ( ( full_name . clone ( ) , named_arg . 0. clone ( ) ) ) ;
break ;
}
}
if ! found {
error = Some ( ParseError ::argument_error (
cmd . clone ( ) ,
ArgumentError ::UnexpectedFlag (
arg . item
. clone ( )
. spanned ( Span ::new ( starting_pos , starting_pos + c . len_utf8 ( ) ) ) ,
) ,
) ) ;
}
starting_pos + = c . len_utf8 ( ) ;
}
}
( output , error )
} else {
// It's not a flag, so don't bother with it
( vec! [ ] , None )
}
}
2020-04-18 17:30:40 +00:00
/// This is a bit of a "fix-up" of previously parsed areas. In cases where we're in shorthand mode (eg in the `where` command), we need
/// to use the original source to parse a column path. Without it, we'll lose a little too much information to parse it correctly. As we'll
/// only know we were on the left-hand side of an expression after we do the full math parse, we need to do this step after rather than during
/// the initial parse.
2020-04-18 01:50:58 +00:00
fn shorthand_reparse (
left : SpannedExpression ,
orig_left : Option < Spanned < String > > ,
registry : & dyn SignatureRegistry ,
shorthand_mode : bool ,
) -> ( SpannedExpression , Option < ParseError > ) {
2020-04-18 17:30:40 +00:00
// If we're in shorthand mode, we need to reparse the left-hand side if possible
2020-04-18 01:50:58 +00:00
if shorthand_mode {
if let Some ( orig_left ) = orig_left {
parse_arg ( SyntaxShape ::FullColumnPath , registry , & orig_left )
} else {
( left , None )
}
} else {
( left , None )
}
}
2020-04-18 18:39:06 +00:00
fn parse_parenthesized_expression (
lite_arg : & Spanned < String > ,
registry : & dyn SignatureRegistry ,
shorthand_mode : bool ,
) -> ( SpannedExpression , Option < ParseError > ) {
let mut chars = lite_arg . item . chars ( ) ;
match ( chars . next ( ) , chars . next_back ( ) ) {
( Some ( '(' ) , Some ( ')' ) ) = > {
// We have a literal row
let string : String = chars . collect ( ) ;
// We haven't done much with the inner string, so let's go ahead and work with it
2020-04-20 06:41:51 +00:00
let lite_block = match lite_parse ( & string , lite_arg . span . start ( ) + 1 ) {
Ok ( lb ) = > lb ,
2020-08-01 18:39:55 +00:00
Err ( e ) = > return ( garbage ( lite_arg . span ) , Some ( e . cause ) ) ,
2020-04-18 18:39:06 +00:00
} ;
2020-04-20 06:41:51 +00:00
if lite_block . block . len ( ) ! = 1 {
return (
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " math expression " , lite_arg . clone ( ) ) ) ,
) ;
}
let mut lite_pipeline = lite_block . block [ 0 ] . clone ( ) ;
2020-04-18 18:39:06 +00:00
let mut collection = vec! [ ] ;
for lite_cmd in lite_pipeline . commands . iter_mut ( ) {
collection . push ( lite_cmd . name . clone ( ) ) ;
collection . append ( & mut lite_cmd . args ) ;
}
let ( _ , expr , err ) =
parse_math_expression ( 0 , & collection [ .. ] , registry , shorthand_mode ) ;
( expr , err )
}
_ = > (
garbage ( lite_arg . span ) ,
Some ( ParseError ::mismatch ( " table " , lite_arg . clone ( ) ) ) ,
) ,
}
}
fn parse_possibly_parenthesized (
lite_arg : & Spanned < String > ,
registry : & dyn SignatureRegistry ,
shorthand_mode : bool ,
) -> (
( Option < Spanned < String > > , SpannedExpression ) ,
Option < ParseError > ,
) {
if lite_arg . item . starts_with ( '(' ) {
let ( lhs , err ) = parse_parenthesized_expression ( lite_arg , registry , shorthand_mode ) ;
( ( None , lhs ) , err )
} else {
let ( lhs , err ) = parse_arg ( SyntaxShape ::Any , registry , lite_arg ) ;
( ( Some ( lite_arg . clone ( ) ) , lhs ) , err )
}
}
2020-04-18 17:30:40 +00:00
/// Handle parsing math expressions, complete with working with the precedence of the operators
2020-04-18 01:50:58 +00:00
fn parse_math_expression (
incoming_idx : usize ,
lite_args : & [ Spanned < String > ] ,
registry : & dyn SignatureRegistry ,
shorthand_mode : bool ,
) -> ( usize , SpannedExpression , Option < ParseError > ) {
// Precedence parsing is included
2020-06-11 17:17:08 +00:00
// Short_hand mode means that the left-hand side of an expression can point to a column-path. To make this possible,
// we parse as normal, but then go back and when we detect a left-hand side, reparse that value if it's a string
2020-04-18 01:50:58 +00:00
let mut idx = 0 ;
let mut error = None ;
let mut working_exprs = vec! [ ] ;
let mut prec = vec! [ ] ;
2020-04-18 18:39:06 +00:00
let ( lhs_working_expr , err ) =
parse_possibly_parenthesized ( & lite_args [ idx ] , registry , shorthand_mode ) ;
2020-04-18 01:50:58 +00:00
if error . is_none ( ) {
error = err ;
}
2020-04-18 18:39:06 +00:00
working_exprs . push ( lhs_working_expr ) ;
2020-04-18 01:50:58 +00:00
idx + = 1 ;
prec . push ( 0 ) ;
while idx < lite_args . len ( ) {
let ( op , err ) = parse_arg ( SyntaxShape ::Operator , registry , & lite_args [ idx ] ) ;
if error . is_none ( ) {
error = err ;
}
idx + = 1 ;
if idx < lite_args . len ( ) {
trace! (
" idx: {} working_exprs: {:#?} prec: {:?} " ,
idx ,
working_exprs ,
prec
) ;
2020-04-18 18:39:06 +00:00
let ( rhs_working_expr , err ) =
parse_possibly_parenthesized ( & lite_args [ idx ] , registry , shorthand_mode ) ;
2020-04-18 01:50:58 +00:00
if error . is_none ( ) {
error = err ;
}
let next_prec = op . precedence ( ) ;
if ! prec . is_empty ( ) & & next_prec > * prec . last ( ) . expect ( " this shouldn't happen " ) {
prec . push ( next_prec ) ;
working_exprs . push ( ( None , op ) ) ;
2020-04-18 18:39:06 +00:00
working_exprs . push ( rhs_working_expr ) ;
2020-04-18 01:50:58 +00:00
} else {
while ! prec . is_empty ( )
& & * prec . last ( ) . expect ( " This shouldn't happen " ) > = next_prec
& & next_prec > 0 // Not garbage
& & working_exprs . len ( ) > = 3
{
// Pop 3 and create and expression, push and repeat
trace! (
" idx: {} working_exprs: {:#?} prec: {:?} " ,
idx ,
working_exprs ,
prec
) ;
let ( _ , right ) = working_exprs . pop ( ) . expect ( " This shouldn't be possible " ) ;
let ( _ , op ) = working_exprs . pop ( ) . expect ( " This shouldn't be possible " ) ;
let ( orig_left , left ) =
working_exprs . pop ( ) . expect ( " This shouldn't be possible " ) ;
// If we're in shorthand mode, we need to reparse the left-hand side if possibe
let ( left , err ) = shorthand_reparse ( left , orig_left , registry , shorthand_mode ) ;
if error . is_none ( ) {
error = err ;
}
let span = Span ::new ( left . span . start ( ) , right . span . end ( ) ) ;
working_exprs . push ( (
None ,
SpannedExpression {
expr : Expression ::Binary ( Box ::new ( Binary { left , op , right } ) ) ,
span ,
} ,
) ) ;
prec . pop ( ) ;
}
working_exprs . push ( ( None , op ) ) ;
2020-04-18 18:39:06 +00:00
working_exprs . push ( rhs_working_expr ) ;
2020-06-11 17:17:08 +00:00
prec . push ( next_prec ) ;
2020-04-18 01:50:58 +00:00
}
idx + = 1 ;
} else {
if error . is_none ( ) {
error = Some ( ParseError ::argument_error (
lite_args [ idx - 1 ] . clone ( ) ,
ArgumentError ::MissingMandatoryPositional ( " right hand side " . into ( ) ) ,
) ) ;
}
working_exprs . push ( ( None , garbage ( op . span ) ) ) ;
working_exprs . push ( ( None , garbage ( op . span ) ) ) ;
prec . push ( 0 ) ;
}
}
while working_exprs . len ( ) > = 3 {
// Pop 3 and create and expression, push and repeat
let ( _ , right ) = working_exprs . pop ( ) . expect ( " This shouldn't be possible " ) ;
let ( _ , op ) = working_exprs . pop ( ) . expect ( " This shouldn't be possible " ) ;
let ( orig_left , left ) = working_exprs . pop ( ) . expect ( " This shouldn't be possible " ) ;
let ( left , err ) = shorthand_reparse ( left , orig_left , registry , shorthand_mode ) ;
if error . is_none ( ) {
error = err ;
}
let span = Span ::new ( left . span . start ( ) , right . span . end ( ) ) ;
working_exprs . push ( (
None ,
SpannedExpression {
expr : Expression ::Binary ( Box ::new ( Binary { left , op , right } ) ) ,
span ,
} ,
) ) ;
}
let ( orig_left , left ) = working_exprs . pop ( ) . expect ( " This shouldn't be possible " ) ;
let ( left , err ) = shorthand_reparse ( left , orig_left , registry , shorthand_mode ) ;
if error . is_none ( ) {
error = err ;
}
( incoming_idx + idx , left , error )
}
2020-04-18 17:30:40 +00:00
/// Handles parsing the positional arguments as a batch
/// This allows us to check for times where multiple arguments are treated as one shape, as is the case with SyntaxShape::Math
fn parse_positional_argument (
2020-04-06 07:16:14 +00:00
idx : usize ,
lite_cmd : & LiteCommand ,
positional_type : & PositionalType ,
2020-07-04 19:40:04 +00:00
remaining_positionals : usize ,
2020-04-06 07:16:14 +00:00
registry : & dyn SignatureRegistry ,
) -> ( usize , SpannedExpression , Option < ParseError > ) {
let mut idx = idx ;
let mut error = None ;
let arg = match positional_type {
2020-04-18 01:50:58 +00:00
PositionalType ::Mandatory ( _ , SyntaxShape ::Math )
| PositionalType ::Optional ( _ , SyntaxShape ::Math ) = > {
2020-04-13 07:59:57 +00:00
// A condition can take up multiple arguments, as we build the operation as <arg> <operator> <arg>
// We need to do this here because in parse_arg, we have access to only one arg at a time
2020-04-18 01:50:58 +00:00
if idx < lite_cmd . args . len ( ) {
if lite_cmd . args [ idx ] . item . starts_with ( '{' ) {
// It's an explicit math expression, so parse it deeper in
let ( arg , err ) = parse_arg ( SyntaxShape ::Math , registry , & lite_cmd . args [ idx ] ) ;
if error . is_none ( ) {
error = err ;
}
arg
} else {
2020-07-04 20:56:54 +00:00
let end_idx = if lite_cmd . args . len ( ) > remaining_positionals {
lite_cmd . args . len ( ) - remaining_positionals
} else {
lite_cmd . args . len ( )
} ;
let ( new_idx , arg , err ) =
parse_math_expression ( idx , & lite_cmd . args [ idx .. end_idx ] , registry , true ) ;
2020-04-18 01:50:58 +00:00
let span = arg . span ;
let mut commands = hir ::Commands ::new ( span ) ;
commands . push ( ClassifiedCommand ::Expr ( Box ::new ( arg ) ) ) ;
2020-04-20 06:41:51 +00:00
let mut block = hir ::Block ::new ( span ) ;
block . push ( commands ) ;
2020-04-18 01:50:58 +00:00
2020-04-20 06:41:51 +00:00
let arg = SpannedExpression ::new ( Expression ::Block ( block ) , span ) ;
2020-04-18 01:50:58 +00:00
2020-07-04 19:40:04 +00:00
idx = new_idx - 1 ;
2020-04-18 01:50:58 +00:00
if error . is_none ( ) {
error = err ;
}
arg
2020-04-13 07:59:57 +00:00
}
2020-04-06 07:16:14 +00:00
} else {
if error . is_none ( ) {
2020-04-13 18:59:33 +00:00
error = Some ( ParseError ::argument_error (
lite_cmd . name . clone ( ) ,
ArgumentError ::MissingMandatoryPositional ( " condition " . into ( ) ) ,
) )
2020-04-06 07:16:14 +00:00
}
2020-04-13 18:59:33 +00:00
garbage ( lite_cmd . span ( ) )
2020-04-06 07:16:14 +00:00
}
}
2020-04-18 17:30:40 +00:00
PositionalType ::Mandatory ( _ , shape ) | PositionalType ::Optional ( _ , shape ) = > {
2020-04-06 07:16:14 +00:00
let ( arg , err ) = parse_arg ( * shape , registry , & lite_cmd . args [ idx ] ) ;
if error . is_none ( ) {
error = err ;
}
arg
}
} ;
( idx , arg , error )
}
2020-04-18 17:30:40 +00:00
/// Does a full parse of an internal command using the lite-ly parse command as a starting point
/// This main focus at this level is to understand what flags were passed in, what positional arguments were passed in, what rest arguments were passed in
/// and to ensure that the basic requirements in terms of number of each were met.
fn parse_internal_command (
2020-04-06 07:16:14 +00:00
lite_cmd : & LiteCommand ,
registry : & dyn SignatureRegistry ,
signature : & Signature ,
2020-05-04 08:44:33 +00:00
mut idx : usize ,
2020-04-06 07:16:14 +00:00
) -> ( InternalCommand , Option < ParseError > ) {
// This is a known internal command, so we need to work with the arguments and parse them according to the expected types
2020-05-04 08:44:33 +00:00
let ( name , name_span ) = if idx = = 0 {
( lite_cmd . name . item . clone ( ) , lite_cmd . name . span )
} else {
(
format! ( " {} {} " , lite_cmd . name . item , lite_cmd . args [ 0 ] . item ) ,
Span ::new ( lite_cmd . name . span . start ( ) , lite_cmd . args [ 0 ] . span . end ( ) ) ,
)
} ;
let mut internal_command = InternalCommand ::new ( name , name_span , lite_cmd . span ( ) ) ;
2020-04-06 07:16:14 +00:00
internal_command . args . set_initial_flags ( & signature ) ;
let mut current_positional = 0 ;
let mut named = NamedArguments ::new ( ) ;
let mut positional = vec! [ ] ;
let mut error = None ;
while idx < lite_cmd . args . len ( ) {
if lite_cmd . args [ idx ] . item . starts_with ( '-' ) & & lite_cmd . args [ idx ] . item . len ( ) > 1 {
let ( named_types , err ) =
get_flags_from_flag ( & signature , & lite_cmd . name , & lite_cmd . args [ idx ] ) ;
if err . is_none ( ) {
for ( full_name , named_type ) in & named_types {
match named_type {
NamedType ::Mandatory ( _ , shape ) | NamedType ::Optional ( _ , shape ) = > {
if idx = = lite_cmd . args . len ( ) {
// Oops, we're missing the argument to our named argument
if error . is_none ( ) {
error = Some ( ParseError ::argument_error (
lite_cmd . name . clone ( ) ,
ArgumentError ::MissingValueForName ( format! ( " {:?} " , shape ) ) ,
) ) ;
}
} else {
idx + = 1 ;
if lite_cmd . args . len ( ) > idx {
let ( arg , err ) =
parse_arg ( * shape , registry , & lite_cmd . args [ idx ] ) ;
named . insert_mandatory (
full_name . clone ( ) ,
lite_cmd . args [ idx - 1 ] . span ,
arg ,
) ;
if error . is_none ( ) {
error = err ;
}
} else if error . is_none ( ) {
error = Some ( ParseError ::argument_error (
lite_cmd . name . clone ( ) ,
ArgumentError ::MissingValueForName ( full_name . to_owned ( ) ) ,
) ) ;
}
}
}
NamedType ::Switch ( _ ) = > {
named . insert_switch (
full_name . clone ( ) ,
Some ( Flag ::new ( FlagKind ::Longhand , lite_cmd . args [ idx ] . span ) ) ,
) ;
}
}
}
} else {
positional . push ( garbage ( lite_cmd . args [ idx ] . span ) ) ;
if error . is_none ( ) {
error = err ;
}
}
} else if signature . positional . len ( ) > current_positional {
let arg = {
2020-04-18 17:30:40 +00:00
let ( new_idx , expr , err ) = parse_positional_argument (
2020-04-06 07:16:14 +00:00
idx ,
& lite_cmd ,
& signature . positional [ current_positional ] . 0 ,
2020-07-04 19:40:04 +00:00
signature . positional . len ( ) - current_positional - 1 ,
2020-04-06 07:16:14 +00:00
registry ,
) ;
idx = new_idx ;
if error . is_none ( ) {
error = err ;
}
expr
} ;
positional . push ( arg ) ;
current_positional + = 1 ;
} else if let Some ( ( rest_type , _ ) ) = & signature . rest_positional {
let ( arg , err ) = parse_arg ( * rest_type , registry , & lite_cmd . args [ idx ] ) ;
if error . is_none ( ) {
error = err ;
}
positional . push ( arg ) ;
current_positional + = 1 ;
} else {
positional . push ( garbage ( lite_cmd . args [ idx ] . span ) ) ;
if error . is_none ( ) {
error = Some ( ParseError ::argument_error (
lite_cmd . name . clone ( ) ,
ArgumentError ::UnexpectedArgument ( lite_cmd . args [ idx ] . clone ( ) ) ,
) ) ;
}
}
idx + = 1 ;
}
// Count the required positional arguments and ensure these have been met
let mut required_arg_count = 0 ;
for positional_arg in & signature . positional {
if let PositionalType ::Mandatory ( _ , _ ) = positional_arg . 0 {
required_arg_count + = 1 ;
}
}
if positional . len ( ) < required_arg_count & & error . is_none ( ) {
2020-04-25 16:45:45 +00:00
// to make "command -h" work even if required arguments are missing
if ! named . named . contains_key ( " help " ) {
let ( _ , name ) = & signature . positional [ positional . len ( ) ] ;
error = Some ( ParseError ::argument_error (
lite_cmd . name . clone ( ) ,
ArgumentError ::MissingMandatoryPositional ( name . to_owned ( ) ) ,
) ) ;
}
2020-04-06 07:16:14 +00:00
}
if ! named . is_empty ( ) {
internal_command . args . named = Some ( named ) ;
}
if ! positional . is_empty ( ) {
internal_command . args . positional = Some ( positional ) ;
}
( internal_command , error )
}
/// Convert a lite-ly parsed pipeline into a fully classified pipeline, ready to be evaluated.
/// This conversion does error-recovery, so the result is allowed to be lossy. A lossy unit is designated as garbage.
/// Errors are returned as part of a side-car error rather than a Result to allow both error and lossy result simultaneously.
2020-04-20 06:41:51 +00:00
fn classify_pipeline (
2020-04-06 07:16:14 +00:00
lite_pipeline : & LitePipeline ,
registry : & dyn SignatureRegistry ,
2020-04-20 06:41:51 +00:00
) -> ( ClassifiedPipeline , Option < ParseError > ) {
2020-08-28 08:51:27 +00:00
let mut commands = Commands ::new ( lite_pipeline . span ( ) ) ;
2020-04-06 07:16:14 +00:00
let mut error = None ;
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
let mut iter = lite_pipeline . commands . iter ( ) . peekable ( ) ;
while let Some ( lite_cmd ) = iter . next ( ) {
2020-04-06 07:16:14 +00:00
if lite_cmd . name . item . starts_with ( '^' ) {
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
let name = lite_cmd
. name
. clone ( )
. map ( | v | v . chars ( ) . skip ( 1 ) . collect ::< String > ( ) ) ;
// TODO this is the same as the `else` branch below, only the name differs. Find a way
// to share this functionality.
2020-04-20 21:45:11 +00:00
let mut args = vec! [ ] ;
let ( name , err ) = parse_arg ( SyntaxShape ::String , registry , & name ) ;
let name_span = name . span ;
if error . is_none ( ) {
error = err ;
}
args . push ( name ) ;
for lite_arg in & lite_cmd . args {
2020-07-02 23:29:28 +00:00
let ( expr , err ) = parse_external_arg ( registry , lite_arg ) ;
2020-04-20 21:45:11 +00:00
if error . is_none ( ) {
error = err ;
}
args . push ( expr ) ;
}
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
commands . push ( ClassifiedCommand ::Internal ( InternalCommand {
name : " run_external " . to_string ( ) ,
2020-04-20 07:44:19 +00:00
name_span ,
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
args : hir ::Call {
head : Box ::new ( SpannedExpression {
expr : Expression ::string ( " run_external " . to_string ( ) ) ,
2020-04-20 07:44:19 +00:00
span : name_span ,
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
} ) ,
positional : Some ( args ) ,
named : None ,
2020-08-21 19:37:51 +00:00
span : name_span ,
2020-08-07 04:53:37 +00:00
external_redirection : if iter . peek ( ) . is_none ( ) {
ExternalRedirection ::None
} else {
ExternalRedirection ::Stdout
} ,
2020-04-06 07:16:14 +00:00
} ,
} ) )
2020-04-13 18:59:33 +00:00
} else if lite_cmd . name . item = = " = " {
2020-04-18 01:50:58 +00:00
let expr = if ! lite_cmd . args . is_empty ( ) {
let ( _ , expr , err ) = parse_math_expression ( 0 , & lite_cmd . args [ 0 .. ] , registry , false ) ;
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
error = error . or ( err ) ;
2020-04-18 01:50:58 +00:00
expr
2020-04-13 18:59:33 +00:00
} else {
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
error = error . or_else ( | | {
Some ( ParseError ::argument_error (
2020-04-13 18:59:33 +00:00
lite_cmd . name . clone ( ) ,
ArgumentError ::MissingMandatoryPositional ( " an expression " . into ( ) ) ,
) )
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
} ) ;
2020-04-13 18:59:33 +00:00
garbage ( lite_cmd . span ( ) )
} ;
commands . push ( ClassifiedCommand ::Expr ( Box ::new ( expr ) ) )
2020-04-06 07:16:14 +00:00
} else {
2020-05-04 08:44:33 +00:00
if ! lite_cmd . args . is_empty ( ) {
// Check if it's a sub-command
if let Some ( signature ) =
registry . get ( & format! ( " {} {} " , lite_cmd . name . item , lite_cmd . args [ 0 ] . item ) )
{
2020-07-19 17:49:40 +00:00
let ( mut internal_command , err ) =
2020-05-04 08:44:33 +00:00
parse_internal_command ( & lite_cmd , registry , & signature , 1 ) ;
error = error . or ( err ) ;
2020-08-07 04:53:37 +00:00
internal_command . args . external_redirection = if iter . peek ( ) . is_none ( ) {
ExternalRedirection ::None
} else {
ExternalRedirection ::Stdout
} ;
2020-05-04 08:44:33 +00:00
commands . push ( ClassifiedCommand ::Internal ( internal_command ) ) ;
continue ;
}
}
// Check if it's an internal command
if let Some ( signature ) = registry . get ( & lite_cmd . name . item ) {
2020-07-19 17:49:40 +00:00
let ( mut internal_command , err ) =
2020-05-04 08:44:33 +00:00
parse_internal_command ( & lite_cmd , registry , & signature , 0 ) ;
error = error . or ( err ) ;
2020-08-07 04:53:37 +00:00
internal_command . args . external_redirection = if iter . peek ( ) . is_none ( ) {
ExternalRedirection ::None
} else {
ExternalRedirection ::Stdout
} ;
2020-05-04 08:44:33 +00:00
commands . push ( ClassifiedCommand ::Internal ( internal_command ) ) ;
continue ;
}
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
let name = lite_cmd . name . clone ( ) . map ( | v | {
let trimmed = trim_quotes ( & v ) ;
2020-04-23 04:17:38 +00:00
expand_path ( & trimmed ) . to_string ( )
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
} ) ;
2020-04-20 21:45:11 +00:00
let mut args = vec! [ ] ;
let ( name , err ) = parse_arg ( SyntaxShape ::String , registry , & name ) ;
2020-04-20 07:44:19 +00:00
let name_span = name . span ;
2020-04-20 21:45:11 +00:00
if error . is_none ( ) {
error = err ;
}
args . push ( name ) ;
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
2020-04-20 21:45:11 +00:00
for lite_arg in & lite_cmd . args {
2020-07-02 23:29:28 +00:00
let ( expr , err ) = parse_external_arg ( registry , lite_arg ) ;
2020-04-20 21:45:11 +00:00
if error . is_none ( ) {
error = err ;
}
args . push ( expr ) ;
}
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
commands . push ( ClassifiedCommand ::Internal ( InternalCommand {
name : " run_external " . to_string ( ) ,
2020-04-20 07:44:19 +00:00
name_span ,
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
args : hir ::Call {
head : Box ::new ( SpannedExpression {
expr : Expression ::string ( " run_external " . to_string ( ) ) ,
2020-04-20 07:44:19 +00:00
span : name_span ,
Move external closer to internal (#1611)
* Refactor InputStream and affected commands.
First, making `values` private and leaning on the `Stream` implementation makes
consumes of `InputStream` less likely to have to change in the future, if we
change what an `InputStream` is internally.
Second, we're dropping `Option<InputStream>` as the input to pipelines,
internals, and externals. Instead, `InputStream.is_empty` can be used to check
for "emptiness". Empty streams are typically only ever used as the first input
to a pipeline.
* Add run_external internal command.
We want to push external commands closer to internal commands, eventually
eliminating the concept of "external" completely. This means we can consolidate
a couple of things:
- Variable evaluation (for example, `$it`, `$nu`, alias vars)
- Behaviour of whole stream vs per-item external execution
It should also make it easier for us to start introducing argument signatures
for external commands,
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
* Update run_external.rs
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2020-04-20 03:30:44 +00:00
} ) ,
positional : Some ( args ) ,
named : None ,
2020-08-21 19:37:51 +00:00
span : name_span ,
2020-08-07 04:53:37 +00:00
external_redirection : if iter . peek ( ) . is_none ( ) {
ExternalRedirection ::None
} else {
ExternalRedirection ::Stdout
} ,
2020-04-06 07:16:14 +00:00
} ,
} ) )
}
}
2020-04-20 06:41:51 +00:00
( ClassifiedPipeline ::new ( commands ) , error )
}
2020-05-06 06:57:37 +00:00
type SpannedKeyValue = ( Spanned < String > , Spanned < String > ) ;
fn expand_shorthand_forms (
lite_pipeline : & LitePipeline ,
) -> ( LitePipeline , Option < SpannedKeyValue > , Option < ParseError > ) {
if ! lite_pipeline . commands . is_empty ( ) {
if lite_pipeline . commands [ 0 ] . name . item = = " = " {
( lite_pipeline . clone ( ) , None , None )
} else if lite_pipeline . commands [ 0 ] . name . contains ( '=' ) {
let assignment : Vec < _ > = lite_pipeline . commands [ 0 ] . name . split ( '=' ) . collect ( ) ;
if assignment . len ( ) ! = 2 {
(
lite_pipeline . clone ( ) ,
None ,
Some ( ParseError ::mismatch (
" environment variable assignment " ,
lite_pipeline . commands [ 0 ] . name . clone ( ) ,
) ) ,
)
} else {
let original_span = lite_pipeline . commands [ 0 ] . name . span ;
2020-05-26 19:03:55 +00:00
let env_value = trim_quotes ( assignment [ 1 ] ) ;
let ( variable_name , value ) = ( assignment [ 0 ] , env_value ) ;
2020-05-06 06:57:37 +00:00
let mut lite_pipeline = lite_pipeline . clone ( ) ;
if ! lite_pipeline . commands [ 0 ] . args . is_empty ( ) {
let new_lite_command_name = lite_pipeline . commands [ 0 ] . args [ 0 ] . clone ( ) ;
let mut new_lite_command_args = lite_pipeline . commands [ 0 ] . args . clone ( ) ;
2020-05-24 23:26:27 +00:00
new_lite_command_args . remove ( 0 ) ;
2020-05-06 06:57:37 +00:00
lite_pipeline . commands [ 0 ] . name = new_lite_command_name ;
lite_pipeline . commands [ 0 ] . args = new_lite_command_args ;
(
lite_pipeline ,
Some ( (
variable_name . to_string ( ) . spanned ( original_span ) ,
2020-05-26 19:03:55 +00:00
value . spanned ( original_span ) ,
2020-05-06 06:57:37 +00:00
) ) ,
None ,
)
} else {
(
lite_pipeline . clone ( ) ,
None ,
Some ( ParseError ::mismatch (
" a command following variable " ,
lite_pipeline . commands [ 0 ] . name . clone ( ) ,
) ) ,
)
}
}
} else {
( lite_pipeline . clone ( ) , None , None )
}
} else {
( lite_pipeline . clone ( ) , None , None )
}
}
2020-04-20 06:41:51 +00:00
pub fn classify_block ( lite_block : & LiteBlock , registry : & dyn SignatureRegistry ) -> ClassifiedBlock {
2020-08-28 08:51:27 +00:00
let mut block = Block ::new ( lite_block . span ( ) ) ;
2020-04-20 06:41:51 +00:00
let mut error = None ;
for lite_pipeline in & lite_block . block {
2020-05-06 06:57:37 +00:00
let ( lite_pipeline , vars , err ) = expand_shorthand_forms ( lite_pipeline ) ;
if error . is_none ( ) {
error = err ;
}
let ( pipeline , err ) = classify_pipeline ( & lite_pipeline , registry ) ;
let pipeline = if let Some ( vars ) = vars {
let span = pipeline . commands . span ;
let block = hir ::Block {
block : vec ! [ pipeline . commands . clone ( ) ] ,
span ,
} ;
let mut call = hir ::Call ::new (
Box ::new ( SpannedExpression {
expr : Expression ::string ( " with-env " . to_string ( ) ) ,
span ,
} ) ,
span ,
) ;
call . positional = Some ( vec! [
SpannedExpression {
expr : Expression ::List ( vec! [
SpannedExpression {
expr : Expression ::string ( vars . 0. item ) ,
span : vars . 0. span ,
} ,
SpannedExpression {
expr : Expression ::string ( vars . 1. item ) ,
span : vars . 1. span ,
} ,
] ) ,
span : Span ::new ( vars . 0. span . start ( ) , vars . 1. span . end ( ) ) ,
} ,
SpannedExpression {
expr : Expression ::Block ( block ) ,
span ,
} ,
] ) ;
let classified_with_env = ClassifiedCommand ::Internal ( InternalCommand {
name : " with-env " . to_string ( ) ,
name_span : Span ::unknown ( ) ,
args : call ,
} ) ;
ClassifiedPipeline {
commands : Commands {
list : vec ! [ classified_with_env ] ,
span ,
} ,
}
} else {
pipeline
} ;
2020-04-20 06:41:51 +00:00
block . push ( pipeline . commands ) ;
if error . is_none ( ) {
error = err ;
}
}
ClassifiedBlock ::new ( block , error )
2020-04-06 07:16:14 +00:00
}
/// Easy shorthand function to create a garbage expression at the given span
pub fn garbage ( span : Span ) -> SpannedExpression {
SpannedExpression ::new ( Expression ::Garbage , span )
}
2020-05-02 18:14:07 +00:00
#[ test ]
fn unit_parse_byte_units ( ) -> Result < ( ) , ParseError > {
struct TestCase {
string : String ,
value : i64 ,
unit : Unit ,
} ;
let cases = [
TestCase {
string : String ::from ( " 108b " ) ,
value : 108 ,
unit : Unit ::Byte ,
} ,
TestCase {
string : String ::from ( " 0B " ) ,
value : 0 ,
unit : Unit ::Byte ,
} ,
TestCase {
string : String ::from ( " 10kb " ) ,
value : 10 ,
unit : Unit ::Kilobyte ,
} ,
TestCase {
string : String ::from ( " 16KB " ) ,
value : 16 ,
unit : Unit ::Kilobyte ,
} ,
TestCase {
string : String ::from ( " 99kB " ) ,
value : 99 ,
unit : Unit ::Kilobyte ,
} ,
TestCase {
string : String ::from ( " 27Kb " ) ,
value : 27 ,
unit : Unit ::Kilobyte ,
} ,
TestCase {
string : String ::from ( " 11Mb " ) ,
value : 11 ,
unit : Unit ::Megabyte ,
} ,
TestCase {
string : String ::from ( " 27mB " ) ,
value : 27 ,
unit : Unit ::Megabyte ,
} ,
TestCase {
string : String ::from ( " 811Gb " ) ,
value : 811 ,
unit : Unit ::Gigabyte ,
} ,
TestCase {
string : String ::from ( " 27gB " ) ,
value : 27 ,
unit : Unit ::Gigabyte ,
} ,
TestCase {
string : String ::from ( " 11Tb " ) ,
value : 11 ,
unit : Unit ::Terabyte ,
} ,
TestCase {
string : String ::from ( " 1027tB " ) ,
value : 1027 ,
unit : Unit ::Terabyte ,
} ,
TestCase {
string : String ::from ( " 11Pb " ) ,
value : 11 ,
unit : Unit ::Petabyte ,
} ,
TestCase {
string : String ::from ( " 27pB " ) ,
value : 27 ,
unit : Unit ::Petabyte ,
} ,
] ;
for case in cases . iter ( ) {
let input_len = case . string . len ( ) ;
let value_len = case . value . to_string ( ) . len ( ) ;
let input = case . string . clone ( ) . spanned ( Span ::new ( 0 , input_len ) ) ;
let result = parse_unit ( & input ) ;
assert_eq! ( result . 1 , None ) ;
assert_eq! (
result . 0. expr ,
Expression ::unit (
Spanned {
span : Span ::new ( 0 , value_len ) ,
item : case . value
} ,
Spanned {
span : Span ::new ( value_len , input_len ) ,
item : case . unit
}
)
) ;
}
Ok ( ( ) )
}