Merge 23846ecd84
into 802bfed173
This commit is contained in:
commit
b19c736d60
|
@ -1,4 +1,4 @@
|
||||||
use crate::util::eval_source;
|
use crate::util::{eval_source, evaluate_block_with_exit_code, make_main_call};
|
||||||
use log::{info, trace};
|
use log::{info, trace};
|
||||||
use nu_engine::{convert_env_values, eval_block};
|
use nu_engine::{convert_env_values, eval_block};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
|
@ -126,11 +126,19 @@ pub fn evaluate_file(
|
||||||
|
|
||||||
// Invoke the main command with arguments.
|
// Invoke the main command with arguments.
|
||||||
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
||||||
let args = format!("main {}", args.join(" "));
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
eval_source(
|
let main_call_block = make_main_call(
|
||||||
|
&mut working_set,
|
||||||
|
source_filename.to_string_lossy().to_string(),
|
||||||
|
args,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
engine_state.merge_delta(working_set.delta)?;
|
||||||
|
|
||||||
|
evaluate_block_with_exit_code(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
args.as_bytes(),
|
main_call_block,
|
||||||
"<commandline>",
|
"<commandline>",
|
||||||
input,
|
input,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
use nu_cmd_base::hook::eval_hook;
|
use nu_cmd_base::hook::eval_hook;
|
||||||
use nu_engine::{eval_block, eval_block_with_early_return};
|
use nu_engine::{eval_block, eval_block_with_early_return};
|
||||||
use nu_parser::{escape_quote_string, lex, parse, unescape_unquote_string, Token, TokenContents};
|
use nu_parser::{
|
||||||
|
escape_quote_string, find_longest_command, lex, lite_parse, parse, parse_internal_call,
|
||||||
|
unescape_unquote_string, Token, TokenContents,
|
||||||
|
};
|
||||||
|
use nu_protocol::ast::{Block, Expr, Expression, Pipeline};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
report_error, report_error_new, PipelineData, ShellError, Span, Value,
|
report_error, report_error_new, PipelineData, ShellError, Signature, Span, Value,
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use nu_utils::enable_vt_processing;
|
use nu_utils::enable_vt_processing;
|
||||||
use nu_utils::perf;
|
use nu_utils::perf;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
// This will collect environment variables from std::env and adds them to a stack.
|
// This will collect environment variables from std::env and adds them to a stack.
|
||||||
//
|
//
|
||||||
|
@ -199,17 +204,123 @@ fn gather_env_vars(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_source(
|
pub fn make_main_call(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
fname: String,
|
||||||
|
args: &[String],
|
||||||
|
redirect_env: bool,
|
||||||
|
) -> Result<Arc<Block>, ShellError> {
|
||||||
|
let source = format!("{} {}", fname, args.join(" "));
|
||||||
|
let source = source.as_bytes();
|
||||||
|
let file_id = working_set.add_file("<commandline>".to_string(), source);
|
||||||
|
let file_span = working_set.get_span_for_file(file_id);
|
||||||
|
let (tokens, err) = lex(source, file_span.start, &[], &[], false);
|
||||||
|
if let Some(err) = err {
|
||||||
|
working_set.error(err)
|
||||||
|
}
|
||||||
|
let (lite_block, err) = lite_parse(tokens.as_slice());
|
||||||
|
if let Some(err) = err {
|
||||||
|
working_set.error(err);
|
||||||
|
}
|
||||||
|
let lite_command = &lite_block.block[0].commands[0];
|
||||||
|
|
||||||
|
let (decl_id, command_len) =
|
||||||
|
find_longest_command(working_set, b"main", &lite_command.parts[1..])
|
||||||
|
.expect("make_main_call() called, but 'main' definition not found in state");
|
||||||
|
|
||||||
|
let parsed_call = parse_internal_call(
|
||||||
|
working_set,
|
||||||
|
Span::concat(&lite_command.parts[..command_len + 1]),
|
||||||
|
&lite_command.parts[(command_len + 1)..],
|
||||||
|
decl_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
let expression = Expression::new(
|
||||||
|
working_set,
|
||||||
|
Expr::Call(parsed_call.call),
|
||||||
|
Span::concat(lite_command.parts.as_slice()),
|
||||||
|
parsed_call.output,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(warning) = working_set.parse_warnings.first() {
|
||||||
|
report_error(working_set, warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any parse errors were found, report the first error and exit.
|
||||||
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
|
report_error(working_set, err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
|
report_error(working_set, err);
|
||||||
|
// Not a fatal error, for now
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut block = Block {
|
||||||
|
signature: Box::new(Signature::new("")),
|
||||||
|
pipelines: vec![Pipeline::from_vec(vec![expression])],
|
||||||
|
captures: vec![],
|
||||||
|
redirect_env,
|
||||||
|
ir_block: None,
|
||||||
|
span: Some(file_span),
|
||||||
|
};
|
||||||
|
|
||||||
|
match nu_engine::compile(working_set, &block) {
|
||||||
|
Ok(ir_block) => {
|
||||||
|
block.ir_block = Some(ir_block);
|
||||||
|
}
|
||||||
|
Err(err) => working_set.compile_errors.push(err),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Arc::new(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate_block(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
source: &[u8],
|
block: Arc<Block>,
|
||||||
|
input: PipelineData,
|
||||||
|
allow_return: bool,
|
||||||
|
) -> Result<Option<i32>, ShellError> {
|
||||||
|
let pipeline = if allow_return {
|
||||||
|
eval_block_with_early_return::<WithoutDebug>(engine_state, stack, &block, input)
|
||||||
|
} else {
|
||||||
|
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let status = if let PipelineData::ByteStream(..) = pipeline {
|
||||||
|
pipeline.print(engine_state, stack, false, false)?
|
||||||
|
} else {
|
||||||
|
if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
||||||
|
let pipeline = eval_hook(
|
||||||
|
engine_state,
|
||||||
|
stack,
|
||||||
|
Some(pipeline),
|
||||||
|
vec![],
|
||||||
|
&hook,
|
||||||
|
"display_output",
|
||||||
|
)?;
|
||||||
|
pipeline.print(engine_state, stack, false, false)
|
||||||
|
} else {
|
||||||
|
pipeline.print(engine_state, stack, true, false)
|
||||||
|
}?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(status.map(|status| status.code()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate_block_with_exit_code(
|
||||||
|
engine_state: &mut EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
block: Arc<Block>,
|
||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
allow_return: bool,
|
allow_return: bool,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
|
let exit_code = match evaluate_block(engine_state, stack, block, input, allow_return) {
|
||||||
Ok(code) => code.unwrap_or(0),
|
Ok(code) => code.unwrap_or(0),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
|
@ -237,14 +348,16 @@ pub fn eval_source(
|
||||||
exit_code
|
exit_code
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_source(
|
pub fn eval_source(
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
source: &[u8],
|
source: &[u8],
|
||||||
fname: &str,
|
fname: &str,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
allow_return: bool,
|
allow_return: bool,
|
||||||
) -> Result<Option<i32>, ShellError> {
|
) -> i32 {
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
|
||||||
let (block, delta) = {
|
let (block, delta) = {
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
let output = parse(
|
let output = parse(
|
||||||
|
@ -259,7 +372,7 @@ fn evaluate_source(
|
||||||
|
|
||||||
if let Some(err) = working_set.parse_errors.first() {
|
if let Some(err) = working_set.parse_errors.first() {
|
||||||
report_error(&working_set, err);
|
report_error(&working_set, err);
|
||||||
return Ok(Some(1));
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = working_set.compile_errors.first() {
|
if let Some(err) = working_set.compile_errors.first() {
|
||||||
|
@ -270,33 +383,19 @@ fn evaluate_source(
|
||||||
(output, working_set.render())
|
(output, working_set.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
engine_state.merge_delta(delta)?;
|
if let Err(err) = engine_state.merge_delta(delta) {
|
||||||
|
report_error_new(engine_state, &err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
let pipeline = if allow_return {
|
let exit_code =
|
||||||
eval_block_with_early_return::<WithoutDebug>(engine_state, stack, &block, input)
|
evaluate_block_with_exit_code(engine_state, stack, block, fname, input, allow_return);
|
||||||
} else {
|
perf!(
|
||||||
eval_block::<WithoutDebug>(engine_state, stack, &block, input)
|
&format!("eval_source {}", &fname),
|
||||||
}?;
|
start_time,
|
||||||
|
engine_state.get_config().use_ansi_coloring
|
||||||
let status = if let PipelineData::ByteStream(..) = pipeline {
|
);
|
||||||
pipeline.print(engine_state, stack, false, false)?
|
exit_code
|
||||||
} else {
|
|
||||||
if let Some(hook) = engine_state.get_config().hooks.display_output.clone() {
|
|
||||||
let pipeline = eval_hook(
|
|
||||||
engine_state,
|
|
||||||
stack,
|
|
||||||
Some(pipeline),
|
|
||||||
vec![],
|
|
||||||
&hook,
|
|
||||||
"display_output",
|
|
||||||
)?;
|
|
||||||
pipeline.print(engine_state, stack, false, false)
|
|
||||||
} else {
|
|
||||||
pipeline.print(engine_state, stack, true, false)
|
|
||||||
}?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(status.map(|status| status.code()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub use nu_protocol::parser_path::*;
|
||||||
pub use parse_keywords::*;
|
pub use parse_keywords::*;
|
||||||
|
|
||||||
pub use parser::{
|
pub use parser::{
|
||||||
is_math_expression_like, parse, parse_block, parse_expression, parse_external_call,
|
find_longest_command, is_math_expression_like, parse, parse_block, parse_expression,
|
||||||
parse_unit_value, trim_quotes, trim_quotes_str, unescape_unquote_string, DURATION_UNIT_GROUPS,
|
parse_external_call, parse_internal_call, parse_unit_value, trim_quotes, trim_quotes_str,
|
||||||
|
unescape_unquote_string, DURATION_UNIT_GROUPS,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ use itertools::Itertools;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_engine::DIR_VAR_PARSER_INFO;
|
use nu_engine::DIR_VAR_PARSER_INFO;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::*, engine::StateWorkingSet, eval_const::eval_constant, BlockId, DidYouMean, Flag,
|
ast::*, engine::StateWorkingSet, eval_const::eval_constant, BlockId, DeclId, DidYouMean, Flag,
|
||||||
ParseError, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, VarId, ENV_VARIABLE_ID,
|
ParseError, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, VarId, ENV_VARIABLE_ID,
|
||||||
IN_VARIABLE_ID,
|
IN_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
|
@ -1250,6 +1250,46 @@ pub fn parse_internal_call(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// finds the longest command matching a command name and a list of arguments
|
||||||
|
/// if a command is found, it returns its decl id and the number of arguments that were consumed as
|
||||||
|
/// part of the command name
|
||||||
|
pub fn find_longest_command(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
name: &[u8],
|
||||||
|
args: &[Span],
|
||||||
|
) -> Option<(DeclId, usize)> {
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut name_spans = vec![];
|
||||||
|
let mut name = name.to_owned();
|
||||||
|
|
||||||
|
// combine all the args together, separated by spaces
|
||||||
|
for arg in args {
|
||||||
|
name_spans.push(*arg);
|
||||||
|
let name_part = working_set.get_span_contents(*arg);
|
||||||
|
name.push(b' ');
|
||||||
|
name.extend(name_part);
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut maybe_decl_id = working_set.find_decl(&name);
|
||||||
|
|
||||||
|
while maybe_decl_id.is_none() && !name_spans.is_empty() {
|
||||||
|
// Find the longest command match
|
||||||
|
|
||||||
|
// name_spans length is checked in the loop condition so we can unwrap
|
||||||
|
let popped_span = name_spans
|
||||||
|
.pop()
|
||||||
|
.expect("internal error: already checked for non-empty");
|
||||||
|
pos -= 1;
|
||||||
|
|
||||||
|
let popped_len = popped_span.end - popped_span.start;
|
||||||
|
name.truncate(name.len() - popped_len - 1); // remove the length of the removed span and the whitespace
|
||||||
|
maybe_decl_id = working_set.find_decl(&name);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_decl_id.map(|id| (id, pos))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span) -> Expression {
|
pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span) -> Expression {
|
||||||
trace!("parsing: call");
|
trace!("parsing: call");
|
||||||
|
|
||||||
|
@ -1261,57 +1301,15 @@ pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span)
|
||||||
return garbage(working_set, head);
|
return garbage(working_set, head);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pos = 0;
|
let cmd_name = working_set.get_span_contents(spans[0]);
|
||||||
let cmd_start = pos;
|
|
||||||
let mut name_spans = vec![];
|
|
||||||
let mut name = vec![];
|
|
||||||
|
|
||||||
for word_span in spans[cmd_start..].iter() {
|
if let Some((decl_id, len)) = find_longest_command(working_set, cmd_name, &spans[1..]) {
|
||||||
// Find the longest group of words that could form a command
|
let cmd_spans = &spans[..len + 1];
|
||||||
|
let arg_spans = &spans[len + 1..];
|
||||||
name_spans.push(*word_span);
|
|
||||||
|
|
||||||
let name_part = working_set.get_span_contents(*word_span);
|
|
||||||
if name.is_empty() {
|
|
||||||
name.extend(name_part);
|
|
||||||
} else {
|
|
||||||
name.push(b' ');
|
|
||||||
name.extend(name_part);
|
|
||||||
}
|
|
||||||
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut maybe_decl_id = working_set.find_decl(&name);
|
|
||||||
|
|
||||||
while maybe_decl_id.is_none() {
|
|
||||||
// Find the longest command match
|
|
||||||
if name_spans.len() <= 1 {
|
|
||||||
// Keep the first word even if it does not match -- could be external command
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
name_spans.pop();
|
|
||||||
pos -= 1;
|
|
||||||
|
|
||||||
let mut name = vec![];
|
|
||||||
for name_span in &name_spans {
|
|
||||||
let name_part = working_set.get_span_contents(*name_span);
|
|
||||||
if name.is_empty() {
|
|
||||||
name.extend(name_part);
|
|
||||||
} else {
|
|
||||||
name.push(b' ');
|
|
||||||
name.extend(name_part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maybe_decl_id = working_set.find_decl(&name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(decl_id) = maybe_decl_id {
|
|
||||||
// Before the internal parsing we check if there is no let or alias declarations
|
// Before the internal parsing we check if there is no let or alias declarations
|
||||||
// that are missing their name, e.g.: let = 1 or alias = 2
|
// that are missing their name, e.g.: let = 1 or alias = 2
|
||||||
if spans.len() > 1 {
|
if !arg_spans.is_empty() {
|
||||||
let test_equal = working_set.get_span_contents(spans[1]);
|
let test_equal = working_set.get_span_contents(arg_spans[0]);
|
||||||
|
|
||||||
if test_equal == [b'='] {
|
if test_equal == [b'='] {
|
||||||
trace!("incomplete statement");
|
trace!("incomplete statement");
|
||||||
|
@ -1358,21 +1356,11 @@ pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span)
|
||||||
return expression;
|
return expression;
|
||||||
} else {
|
} else {
|
||||||
trace!("parsing: alias of internal call");
|
trace!("parsing: alias of internal call");
|
||||||
parse_internal_call(
|
parse_internal_call(working_set, Span::concat(cmd_spans), arg_spans, decl_id)
|
||||||
working_set,
|
|
||||||
Span::concat(&spans[cmd_start..pos]),
|
|
||||||
&spans[pos..],
|
|
||||||
decl_id,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trace!("parsing: internal call");
|
trace!("parsing: internal call");
|
||||||
parse_internal_call(
|
parse_internal_call(working_set, Span::concat(cmd_spans), arg_spans, decl_id)
|
||||||
working_set,
|
|
||||||
Span::concat(&spans[cmd_start..pos]),
|
|
||||||
&spans[pos..],
|
|
||||||
decl_id,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Expression::new(
|
Expression::new(
|
||||||
|
|
|
@ -324,6 +324,43 @@ fn main_script_can_have_subcommands2() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main_script_use_filename_in_error() {
|
||||||
|
Playground::setup("main_filename", |dirs, sandbox| {
|
||||||
|
sandbox.mkdir("main_filename");
|
||||||
|
sandbox.with_files(&[FileWithContent(
|
||||||
|
"script.nu",
|
||||||
|
r#"def main [--foo: string] {
|
||||||
|
print foo
|
||||||
|
}"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline("nu script.nu --foo"));
|
||||||
|
|
||||||
|
assert!(actual.err.contains("script.nu --foo"));
|
||||||
|
assert!(!actual.err.contains("main --foo"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn main_script_subcommand_use_filename_in_error() {
|
||||||
|
Playground::setup("main_filename", |dirs, sandbox| {
|
||||||
|
sandbox.mkdir("main_filename");
|
||||||
|
sandbox.with_files(&[FileWithContent(
|
||||||
|
"script.nu",
|
||||||
|
r#"def main [] {}
|
||||||
|
def 'main asdf' [--foo: string] {
|
||||||
|
print foo
|
||||||
|
}"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline("nu script.nu asdf --foo"));
|
||||||
|
|
||||||
|
assert!(actual.err.contains("script.nu asdf --foo"));
|
||||||
|
assert!(!actual.err.contains("main asdf --foo"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn source_empty_file() {
|
fn source_empty_file() {
|
||||||
Playground::setup("source_empty_file", |dirs, sandbox| {
|
Playground::setup("source_empty_file", |dirs, sandbox| {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user