Merge 9ed15ac877
into 1c37f4b958
This commit is contained in:
commit
0d6d3fb6d3
|
@ -1,12 +1,13 @@
|
|||
use crate::util::eval_source;
|
||||
use crate::util::{eval_source, evaluate_block_with_exit_code};
|
||||
use log::{info, trace};
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_parser::{find_longest_command, lex, lite_parse, parse, parse_internal_call};
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::ast::{Block, Expr, Expression, Pipeline};
|
||||
use nu_protocol::{
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
report_error, PipelineData, ShellError, Span, Value,
|
||||
report_error, PipelineData, ShellError, Signature, Span, Value,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -104,9 +105,24 @@ pub fn evaluate_file(
|
|||
// Merge the changes into the engine state.
|
||||
engine_state.merge_delta(working_set.delta)?;
|
||||
|
||||
// Check if the file contains a main command.
|
||||
let exit_code = if engine_state.find_decl(b"main", &[]).is_some() {
|
||||
// Evaluate the file, but don't run main yet.
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let source = format!("{} {}", source_filename.to_string_lossy(), 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 exit_code = if let Some((decl_id, command_len)) =
|
||||
find_longest_command(&working_set, b"main", &lite_command.parts[1..])
|
||||
{
|
||||
let pipeline =
|
||||
match eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty()) {
|
||||
Ok(data) => data,
|
||||
|
@ -124,13 +140,57 @@ pub fn evaluate_file(
|
|||
}
|
||||
}
|
||||
|
||||
// Invoke the main command with arguments.
|
||||
// Arguments with whitespaces are quoted, thus can be safely concatenated by whitespace.
|
||||
let args = format!("main {}", args.join(" "));
|
||||
eval_source(
|
||||
let parsed_call = parse_internal_call(
|
||||
&mut working_set,
|
||||
Span::concat(&lite_command.parts[..command_len + 1]),
|
||||
&lite_command.parts[(command_len + 1)..],
|
||||
decl_id,
|
||||
);
|
||||
|
||||
let expression = Expression::new(
|
||||
&mut 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 main_call = Block {
|
||||
signature: Box::new(Signature::new("")),
|
||||
pipelines: vec![Pipeline::from_vec(vec![expression])],
|
||||
captures: vec![],
|
||||
redirect_env: true,
|
||||
ir_block: None,
|
||||
span: Some(file_span),
|
||||
};
|
||||
|
||||
match nu_engine::compile(&working_set, &main_call) {
|
||||
Ok(ir_block) => {
|
||||
main_call.ir_block = Some(ir_block);
|
||||
}
|
||||
Err(err) => working_set.compile_errors.push(err),
|
||||
}
|
||||
|
||||
engine_state.merge_delta(working_set.delta)?;
|
||||
|
||||
evaluate_block_with_exit_code(
|
||||
engine_state,
|
||||
stack,
|
||||
args.as_bytes(),
|
||||
Arc::new(main_call),
|
||||
"<commandline>",
|
||||
input,
|
||||
true,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use nu_cmd_base::hook::eval_hook;
|
||||
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_protocol::ast::Block;
|
||||
use nu_protocol::{
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
|
@ -10,6 +11,7 @@ use nu_protocol::{
|
|||
use nu_utils::enable_vt_processing;
|
||||
use nu_utils::perf;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
// This will collect environment variables from std::env and adds them to a stack.
|
||||
//
|
||||
|
@ -199,17 +201,51 @@ fn gather_env_vars(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn eval_source(
|
||||
pub fn evaluate_block(
|
||||
engine_state: &mut EngineState,
|
||||
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,
|
||||
input: PipelineData,
|
||||
allow_return: bool,
|
||||
) -> i32 {
|
||||
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),
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
|
@ -237,14 +273,16 @@ pub fn eval_source(
|
|||
exit_code
|
||||
}
|
||||
|
||||
fn evaluate_source(
|
||||
pub fn eval_source(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
source: &[u8],
|
||||
fname: &str,
|
||||
input: PipelineData,
|
||||
allow_return: bool,
|
||||
) -> Result<Option<i32>, ShellError> {
|
||||
) -> i32 {
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let output = parse(
|
||||
|
@ -259,7 +297,7 @@ fn evaluate_source(
|
|||
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_error(&working_set, err);
|
||||
return Ok(Some(1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if let Some(err) = working_set.compile_errors.first() {
|
||||
|
@ -270,33 +308,19 @@ fn evaluate_source(
|
|||
(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 {
|
||||
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()))
|
||||
let exit_code =
|
||||
evaluate_block_with_exit_code(engine_state, stack, block, fname, input, allow_return);
|
||||
perf!(
|
||||
&format!("eval_source {}", &fname),
|
||||
start_time,
|
||||
engine_state.get_config().use_ansi_coloring
|
||||
);
|
||||
exit_code
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -22,6 +22,7 @@ pub use nu_protocol::parser_path::*;
|
|||
pub use parse_keywords::*;
|
||||
|
||||
pub use parser::{
|
||||
is_math_expression_like, parse, parse_block, parse_expression, parse_external_call,
|
||||
parse_unit_value, trim_quotes, trim_quotes_str, unescape_unquote_string, DURATION_UNIT_GROUPS,
|
||||
find_longest_command, is_math_expression_like, parse, parse_block, parse_expression,
|
||||
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 nu_engine::DIR_VAR_PARSER_INFO;
|
||||
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,
|
||||
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 {
|
||||
trace!("parsing: call");
|
||||
|
||||
|
@ -1261,57 +1301,15 @@ pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span)
|
|||
return garbage(working_set, head);
|
||||
}
|
||||
|
||||
let mut pos = 0;
|
||||
let cmd_start = pos;
|
||||
let mut name_spans = vec![];
|
||||
let mut name = vec![];
|
||||
let cmd_name = working_set.get_span_contents(spans[0]);
|
||||
|
||||
for word_span in spans[cmd_start..].iter() {
|
||||
// Find the longest group of words that could form a command
|
||||
|
||||
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 {
|
||||
if let Some((decl_id, len)) = find_longest_command(working_set, cmd_name, &spans[1..]) {
|
||||
let cmd_spans = &spans[..len + 1];
|
||||
let arg_spans = &spans[len + 1..];
|
||||
// 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
|
||||
if spans.len() > 1 {
|
||||
let test_equal = working_set.get_span_contents(spans[1]);
|
||||
if !arg_spans.is_empty() {
|
||||
let test_equal = working_set.get_span_contents(arg_spans[0]);
|
||||
|
||||
if test_equal == [b'='] {
|
||||
trace!("incomplete statement");
|
||||
|
@ -1358,21 +1356,11 @@ pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span)
|
|||
return expression;
|
||||
} else {
|
||||
trace!("parsing: alias of internal call");
|
||||
parse_internal_call(
|
||||
working_set,
|
||||
Span::concat(&spans[cmd_start..pos]),
|
||||
&spans[pos..],
|
||||
decl_id,
|
||||
)
|
||||
parse_internal_call(working_set, Span::concat(cmd_spans), arg_spans, decl_id)
|
||||
}
|
||||
} else {
|
||||
trace!("parsing: internal call");
|
||||
parse_internal_call(
|
||||
working_set,
|
||||
Span::concat(&spans[cmd_start..pos]),
|
||||
&spans[pos..],
|
||||
decl_id,
|
||||
)
|
||||
parse_internal_call(working_set, Span::concat(cmd_spans), arg_spans, decl_id)
|
||||
};
|
||||
|
||||
Expression::new(
|
||||
|
|
|
@ -324,6 +324,60 @@ fn main_script_can_have_subcommands2() {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_script_can_have_subcommands3() {
|
||||
Playground::setup("main_subcommands", |dirs, sandbox| {
|
||||
sandbox.mkdir("main_subcommands");
|
||||
sandbox.with_files(&[FileWithContent(
|
||||
"script.nu",
|
||||
r#"def "main foo" [x: int] {
|
||||
print ($x + 100)
|
||||
}"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), pipeline("nu script.nu foo 23"));
|
||||
|
||||
assert!(actual.out.eq("123"));
|
||||
})
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn source_empty_file() {
|
||||
Playground::setup("source_empty_file", |dirs, sandbox| {
|
||||
|
|
Loading…
Reference in New Issue
Block a user