Debugger experiments (#11441)

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

This PR adds a new evaluator path with callbacks to a mutable trait
object implementing a Debugger trait. The trait object can do anything,
e.g., profiling, code coverage, step debugging. Currently,
entering/leaving a block and a pipeline element is marked with
callbacks, but more callbacks can be added as necessary. Not all
callbacks need to be used by all debuggers; unused ones are simply empty
calls. A simple profiler is implemented as a proof of concept.

The debugging support is implementing by making `eval_xxx()` functions
generic depending on whether we're debugging or not. This has zero
computational overhead, but makes the binary slightly larger (see
benchmarks below). `eval_xxx()` variants called from commands (like
`eval_block_with_early_return()` in `each`) are chosen with a dynamic
dispatch for two reasons: to not grow the binary size due to duplicating
the code of many commands, and for the fact that it isn't possible
because it would make Command trait objects object-unsafe.

In the future, I hope it will be possible to allow plugin callbacks such
that users would be able to implement their profiler plugins instead of
having to recompile Nushell.
[DAP](https://microsoft.github.io/debug-adapter-protocol/) would also be
interesting to explore.

Try `help debug profile`.

## Screenshots

Basic output:

![profiler_new](https://github.com/nushell/nushell/assets/25571562/418b9df0-b659-4dcb-b023-2d5fcef2c865)

To profile with more granularity, increase the profiler depth (you'll
see that repeated `is-windows` calls take a large chunk of total time,
making it a good candidate for optimizing):

![profiler_new_m3](https://github.com/nushell/nushell/assets/25571562/636d756d-5d56-460c-a372-14716f65f37f)

## Benchmarks

### Binary size

Binary size increase vs. main: **+40360 bytes**. _(Both built with
`--release --features=extra,dataframe`.)_

### Time

```nushell
# bench_debug.nu
use std bench

let test = {
    1..100
    | each {
        ls | each {|row| $row.name | str length }
    }
    | flatten
    | math avg
}

print 'debug:'
let res2 = bench { debug profile $test } --pretty
print $res2
```

```nushell
# bench_nodebug.nu
use std bench

let test = {
    1..100
    | each {
        ls | each {|row| $row.name | str length }
    }
    | flatten
    | math avg
}

print 'no debug:'
let res1 = bench { do $test } --pretty
print $res1
```

`cargo run --release -- bench_debug.nu` is consistently 1--2 ms slower
than `cargo run --release -- bench_nodebug.nu` due to the collection
overhead + gathering the report. This is expected. When gathering more
stuff, the overhead is obviously higher.

`cargo run --release -- bench_nodebug.nu` vs. `nu bench_nodebug.nu` I
didn't measure any difference. Both benchmarks report times between 97
and 103 ms randomly, without one being consistently higher than the
other. This suggests that at least in this particular case, when not
running any debugger, there is no runtime overhead.

## API changes

This PR adds a generic parameter to all `eval_xxx` functions that forces
you to specify whether you use the debugger. You can resolve it in two
ways:
* Use a provided helper that will figure it out for you. If you wanted
to use `eval_block(&engine_state, ...)`, call `let eval_block =
get_eval_block(&engine_state); eval_block(&engine_state, ...)`
* If you know you're in an evaluation path that doesn't need debugger
support, call `eval_block::<WithoutDebug>(&engine_state, ...)` (this is
the case of hooks, for example).

I tried to add more explanation in the docstring of `debugger_trait.rs`.

## TODO

- [x] Better profiler output to reduce spam of iterative commands like
`each`
- [x] Resolve `TODO: DEBUG` comments
- [x] Resolve unwraps
- [x] Add doc comments
- [x] Add usage and extra usage for `debug profile`, explaining all
columns

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Hopefully none.

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Jakub Žádník
2024-03-08 20:21:35 +02:00
committed by GitHub
parent a9ddc58f21
commit 14d1c67863
81 changed files with 1297 additions and 205 deletions

View File

@ -1,5 +1,6 @@
use nu_engine::eval_expression;
use nu_engine::get_eval_expression;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
@ -48,7 +49,10 @@ impl Command for BytesBuild {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut output = vec![];
for val in call.rest_iter_flattened(0, |expr| eval_expression(engine_state, stack, expr))? {
for val in call.rest_iter_flattened(0, |expr| {
let eval_expression = get_eval_expression(engine_state);
eval_expression(engine_state, stack, expr)
})? {
match val {
Value::Binary { mut val, .. } => output.append(&mut val),
// Explicitly propagate errors instead of dropping them.

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_expression, CallExt};
use nu_engine::{get_eval_expression, CallExt};
use nu_protocol::ast::{Argument, Block, Call, Expr, Expression};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature,
@ -43,7 +44,7 @@ impl Command for Explain {
let ctrlc = engine_state.ctrlc.clone();
let mut stack = stack.captures_to_stack(capture_block.captures);
let elements = get_pipeline_elements(engine_state, &mut stack, block)?;
let elements = get_pipeline_elements(engine_state, &mut stack, block, call.head)?;
Ok(elements.into_pipeline_data(ctrlc))
}
@ -62,9 +63,11 @@ pub fn get_pipeline_elements(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
span: Span,
) -> Result<Vec<Value>, ShellError> {
let mut element_values = vec![];
let span = Span::test_data();
let eval_expression = get_eval_expression(engine_state);
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
let mut i = 0;
@ -80,7 +83,7 @@ pub fn get_pipeline_elements(
let command = engine_state.get_decl(call.decl_id);
(
command.name().to_string(),
get_arguments(engine_state, stack, *call),
get_arguments(engine_state, stack, *call, eval_expression),
)
} else {
("no-op".to_string(), vec![])
@ -106,7 +109,12 @@ pub fn get_pipeline_elements(
Ok(element_values)
}
fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> Vec<Value> {
fn get_arguments(
engine_state: &EngineState,
stack: &mut Stack,
call: Call,
eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
) -> Vec<Value> {
let mut arg_value = vec![];
let span = Span::test_data();
for arg in &call.arguments {
@ -145,8 +153,12 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
};
if let Some(expression) = opt_expr {
let evaluated_expression =
get_expression_as_value(engine_state, stack, expression);
let evaluated_expression = get_expression_as_value(
engine_state,
stack,
expression,
eval_expression_fn,
);
let arg_type = "expr";
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
@ -166,7 +178,8 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
}
Argument::Positional(inner_expr) => {
let arg_type = "positional";
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
let evaluated_expression =
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
let evaled_span = evaluated_expression.span();
@ -184,7 +197,8 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
}
Argument::Unknown(inner_expr) => {
let arg_type = "unknown";
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
let evaluated_expression =
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
let evaled_span = evaluated_expression.span();
@ -202,7 +216,8 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
}
Argument::Spread(inner_expr) => {
let arg_type = "spread";
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
let evaluated_expression =
get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
let evaled_span = evaluated_expression.span();
@ -228,8 +243,9 @@ fn get_expression_as_value(
engine_state: &EngineState,
stack: &mut Stack,
inner_expr: &Expression,
eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
) -> Value {
match eval_expression(engine_state, stack, inner_expr) {
match eval_expression_fn(engine_state, stack, inner_expr) {
Ok(v) => v,
Err(error) => Value::error(error, inner_expr.span),
}

View File

@ -5,6 +5,7 @@ mod info;
mod inspect;
mod inspect_table;
mod metadata;
mod profile;
mod timeit;
mod view;
mod view_files;
@ -18,6 +19,7 @@ pub use info::DebugInfo;
pub use inspect::Inspect;
pub use inspect_table::build_table;
pub use metadata::Metadata;
pub use profile::DebugProfile;
pub use timeit::TimeIt;
pub use view::View;
pub use view_files::ViewFiles;

View File

@ -0,0 +1,169 @@
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::debugger::{Profiler, WithDebug};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
};
#[derive(Clone)]
pub struct DebugProfile;
impl Command for DebugProfile {
fn name(&self) -> &str {
"debug profile"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("debug profile")
.required(
"closure",
SyntaxShape::Closure(None),
"The closure to profile.",
)
.switch("spans", "Collect spans of profiled elements", Some('s'))
.switch(
"expand-source",
"Collect full source fragments of profiled elements",
Some('e'),
)
.switch(
"values",
"Collect pipeline element output values",
Some('v'),
)
.switch("expr", "Collect expression types", Some('x'))
.named(
"max-depth",
SyntaxShape::Int,
"How many blocks/closures deep to step into (default 2)",
Some('m'),
)
.input_output_types(vec![(Type::Any, Type::Table(vec![]))])
.category(Category::Debug)
}
fn usage(&self) -> &str {
"Profile pipeline elements in a closure."
}
fn extra_usage(&self) -> &str {
r#"The profiler profiles every evaluated pipeline element inside a closure, stepping into all
commands calls and other blocks/closures.
The output can be heavily customized. By default, the following columns are included:
- depth : Depth of the pipeline element. Each entered block adds one level of depth. How many
blocks deep to step into is controlled with the --max-depth option.
- id : ID of the pipeline element
- parent_id : ID of the parent element
- source : Source code of the pipeline element. If the element has multiple lines, only the
first line is used and `...` is appended to the end. Full source code can be shown
with the --expand-source flag.
- duration_ms : How long it took to run the pipeline element in milliseconds.
- (optional) span : Span of the element. Can be viewed via the `view span` command. Enabled with
the --spans flag.
- (optional) expr : The type of expression of the pipeline element. Enabled with the --expr flag.
- (optional) output : The output value of the pipeline element. Enabled with the --values flag.
To illustrate the depth and IDs, consider `debug profile { if true { echo 'spam' } }`. There are
three pipeline elements:
depth id parent_id
0 0 0 debug profile { do { if true { 'spam' } } }
1 1 0 if true { 'spam' }
2 2 1 'spam'
Each block entered increments depth by 1 and each block left decrements it by one. This way you can
control the profiling granularity. Passing --max-depth=1 to the above would stop at
`if true { 'spam' }`. The id is used to identify each element. The parent_id tells you that 'spam'
was spawned from `if true { 'spam' }` which was spawned from the root `debug profile { ... }`.
Note: In some cases, the ordering of piepeline elements might not be intuitive. For example,
`[ a bb cc ] | each { $in | str length }` involves some implicit collects and lazy evaluation
confusing the id/parent_id hierarchy. The --expr flag is helpful for investigating these issues."#
}
fn run(
&self,
engine_state: &EngineState,
caller_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let closure: Closure = call.req(engine_state, caller_stack, 0)?;
let mut callee_stack = caller_stack.captures_to_stack(closure.captures);
let block = engine_state.get_block(closure.block_id);
let default_max_depth = 2;
let collect_spans = call.has_flag(engine_state, caller_stack, "spans")?;
let collect_expanded_source =
call.has_flag(engine_state, caller_stack, "expanded-source")?;
let collect_values = call.has_flag(engine_state, caller_stack, "values")?;
let collect_exprs = call.has_flag(engine_state, caller_stack, "expr")?;
let max_depth = call
.get_flag(engine_state, caller_stack, "max-depth")?
.unwrap_or(default_max_depth);
let profiler = Profiler::new(
max_depth,
collect_spans,
true,
collect_expanded_source,
collect_values,
collect_exprs,
call.span(),
);
let lock_err = {
|_| ShellError::GenericError {
error: "Profiler Error".to_string(),
msg: "could not lock debugger, poisoned mutex".to_string(),
span: Some(call.head),
help: None,
inner: vec![],
}
};
engine_state
.activate_debugger(Box::new(profiler))
.map_err(lock_err)?;
let result = eval_block_with_early_return::<WithDebug>(
engine_state,
&mut callee_stack,
block,
input,
call.redirect_stdout,
call.redirect_stdout,
);
// TODO: See eval_source()
match result {
Ok(pipeline_data) => {
let _ = pipeline_data.into_value(call.span());
// pipeline_data.print(engine_state, caller_stack, true, false)
}
Err(_e) => (), // TODO: Report error
}
let debugger = engine_state.deactivate_debugger().map_err(lock_err)?;
let res = debugger.report(engine_state, call.span());
res.map(|val| val.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Profile config evaluation",
example: "debug profile { source $nu.config-path }",
result: None,
},
Example {
description: "Profile config evaluation with more granularity",
example: "debug profile { source $nu.config-path } --max-depth 4",
result: None,
},
]
}
}

View File

@ -1,4 +1,5 @@
use nu_engine::{eval_block, eval_expression_with_input};
use nu_engine::{get_eval_block, get_eval_expression_with_input};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
@ -52,6 +53,7 @@ impl Command for TimeIt {
if let Some(command_to_run) = command_to_run {
if let Some(block_id) = command_to_run.as_block() {
let eval_block = get_eval_block(engine_state);
let block = engine_state.get_block(block_id);
eval_block(
engine_state,
@ -62,6 +64,7 @@ impl Command for TimeIt {
call.redirect_stderr,
)?
} else {
let eval_expression_with_input = get_eval_expression_with_input(engine_state);
eval_expression_with_input(
engine_state,
stack,

View File

@ -140,6 +140,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Ast,
Debug,
DebugInfo,
DebugProfile,
Explain,
Inspect,
Metadata,

View File

@ -1,4 +1,5 @@
use nu_engine::{eval_block, redirect_env, CallExt};
use nu_engine::{get_eval_block, redirect_env, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -39,6 +40,8 @@ impl Command for ExportEnv {
let block = engine_state.get_block(capture_block.block_id);
let mut callee_stack = caller_stack.captures_to_stack(capture_block.captures);
let eval_block = get_eval_block(engine_state);
let _ = eval_block(
engine_state,
&mut callee_stack,

View File

@ -1,9 +1,11 @@
use std::path::PathBuf;
use nu_engine::{
eval_block_with_early_return, find_in_dirs_env, get_dirs_var_from_call, redirect_env, CallExt,
find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return, redirect_env,
CallExt,
};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
@ -76,6 +78,8 @@ impl Command for SourceEnv {
let block = engine_state.get_block(block_id as usize).clone();
let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let result = eval_block_with_early_return(
engine_state,
&mut callee_stack,

View File

@ -1,6 +1,7 @@
use std::collections::HashMap;
use nu_engine::{eval_block, CallExt};
use nu_protocol::debugger::WithoutDebug;
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -144,7 +145,7 @@ fn with_env(
stack.add_env_var(k, v);
}
eval_block(
eval_block::<WithoutDebug>(
engine_state,
&mut stack,
block,

View File

@ -1,5 +1,5 @@
use super::util::get_rest_for_glob_pattern;
use nu_engine::{current_dir, eval_block, CallExt};
use nu_engine::{current_dir, get_eval_block, CallExt};
use nu_path::expand_to_real_path;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
@ -64,6 +64,7 @@ impl Command for Open {
let ctrlc = engine_state.ctrlc.clone();
let cwd = current_dir(engine_state, stack)?;
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let eval_block = get_eval_block(engine_state);
if paths.is_empty() && call.rest_iter(0).next().is_none() {
// try to use path from pipeline input if there were no positional or spread args

View File

@ -1,5 +1,5 @@
use dialoguer::Input;
use nu_engine::eval_expression;
use nu_engine::get_eval_expression;
use nu_protocol::ast::Expr;
use nu_protocol::{
ast::Call,
@ -221,6 +221,7 @@ pub fn get_rest_for_glob_pattern(
starting_pos: usize,
) -> Result<Vec<Spanned<NuGlob>>, ShellError> {
let mut output = vec![];
let eval_expression = get_eval_expression(engine_state);
for result in call.rest_iter_flattened(starting_pos, |expr| {
let result = eval_expression(engine_state, stack, expr);
@ -261,6 +262,7 @@ pub fn opt_for_glob_pattern(
pos: usize,
) -> Result<Option<Spanned<NuGlob>>, ShellError> {
if let Some(expr) = call.positional_nth(pos) {
let eval_expression = get_eval_expression(engine_state);
let result = eval_expression(engine_state, stack, expr)?;
let result_span = result.span();
let result = match result {

View File

@ -9,8 +9,9 @@ use notify_debouncer_full::{
EventKind, RecursiveMode, Watcher,
},
};
use nu_engine::{current_dir, eval_block, CallExt};
use nu_engine::{current_dir, get_eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack, StateWorkingSet};
use nu_protocol::{
format_error, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature,
@ -168,6 +169,8 @@ impl Command for Watch {
eprintln!("Now watching files at {path:?}. Press ctrl+c to abort.");
let eval_block = get_eval_block(engine_state);
let event_handler =
|operation: &str, path: PathBuf, new_path: Option<PathBuf>| -> Result<(), ShellError> {
let glob_pattern = glob_pattern.clone();

View File

@ -1,12 +1,14 @@
use super::utils::chain_error_with_input;
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Type, Value,
};
use super::utils::chain_error_with_input;
#[derive(Clone)]
pub struct Each;
@ -112,6 +114,8 @@ with 'transpose' first."#
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let keep_empty = call.has_flag(engine_state, stack, "keep-empty")?;
@ -155,6 +159,8 @@ with 'transpose' first."#
x.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
// WithoutDebug,
// &None,
) {
Ok(v) => Some(v.into_value(span)),
Err(ShellError::Continue { span }) => Some(Value::nothing(span)),

View File

@ -1,6 +1,7 @@
use super::utils::chain_error_with_input;
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
@ -63,6 +64,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."#
let span = call.head;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block = get_eval_block(&engine_state);
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
@ -162,6 +164,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."#
stack.add_var(*var_id, x.clone());
}
}
Ok(match eval_block(
&engine_state,
&mut stack,

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
@ -238,12 +238,14 @@ fn group_closure(
) -> Result<IndexMap<String, Vec<Value>>, ShellError> {
let error_key = "error";
let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new();
let eval_block = get_eval_block(engine_state);
if let Some(capture_block) = &block {
let block = engine_state.get_block(capture_block.block_id);
for value in values {
let mut stack = stack.captures_to_stack(capture_block.captures.clone());
let pipeline = eval_block(
engine_state,
&mut stack,

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt, EvalBlockFn};
use nu_protocol::ast::{Block, Call, CellPath, PathMember};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData,
@ -134,6 +135,8 @@ fn insert(
let ctrlc = engine_state.ctrlc.clone();
let eval_block = get_eval_block(engine_state);
match input {
PipelineData::Value(mut value, metadata) => {
if replacement.coerce_block().is_ok() {
@ -155,6 +158,7 @@ fn insert(
block,
&cell_path.members,
false,
eval_block,
)?;
}
}
@ -168,6 +172,7 @@ fn insert(
redirect_stderr,
&cell_path.members,
matches!(first, Some(PathMember::Int { .. })),
eval_block,
)?;
}
}
@ -242,6 +247,7 @@ fn insert(
redirect_stderr,
path,
true,
eval_block,
)?;
} else {
value.insert_data_at_cell_path(path, replacement, span)?;
@ -281,6 +287,7 @@ fn insert(
&block,
&cell_path.members,
false,
eval_block,
);
if let Err(e) = err {
@ -328,6 +335,7 @@ fn insert_value_by_closure(
block: &Block,
cell_path: &[PathMember],
first_path_member_int: bool,
eval_block_fn: EvalBlockFn,
) -> Result<(), ShellError> {
let input_at_path = value.clone().follow_cell_path(cell_path, false);
@ -348,7 +356,7 @@ fn insert_value_by_closure(
.map(IntoPipelineData::into_pipeline_data)
.unwrap_or(PipelineData::Empty);
let output = eval_block(
let output = eval_block_fn(
engine_state,
stack,
block,
@ -370,6 +378,7 @@ fn insert_single_value_by_closure(
redirect_stderr: bool,
cell_path: &[PathMember],
first_path_member_int: bool,
eval_block_fn: EvalBlockFn,
) -> Result<(), ShellError> {
let span = replacement.span();
let capture_block = Closure::from_value(replacement)?;
@ -386,6 +395,7 @@ fn insert_single_value_by_closure(
block,
cell_path,
first_path_member_int,
eval_block_fn,
)
}

View File

@ -1,6 +1,6 @@
use std::{sync::mpsc, thread};
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -118,6 +118,7 @@ interleave
let (tx, rx) = mpsc::sync_channel(buffer_size);
let closures: Vec<Closure> = call.rest(engine_state, stack, 0)?;
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
// Spawn the threads for the input and closure outputs
(!input.is_nothing())

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
@ -54,6 +55,7 @@ impl Command for Items {
let orig_env_hidden = stack.env_hidden.clone();
let span = call.head;
let redirect_stderr = call.redirect_stderr;
let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state);
let input_span = input.span().unwrap_or(call.head);
let run_for_each_item = move |keyval: (String, Value)| -> Option<Value> {

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
@ -142,6 +143,8 @@ impl Command for ParEach {
vec.into_iter().map(|(_, val)| val)
};
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(create_pool(max_threads)?

View File

@ -1,6 +1,7 @@
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
@ -101,6 +102,7 @@ impl Command for Reduce {
let mut stack = stack.captures_to_stack(capture_block.captures);
let block = engine_state.get_block(capture_block.block_id);
let ctrlc = engine_state.ctrlc.clone();
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();

View File

@ -1,6 +1,7 @@
use indexmap::IndexMap;
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature,
@ -156,6 +157,8 @@ fn rename(
let columns: Vec<String> = call.rest(engine_state, stack, 0)?;
let metadata = input.metadata();
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let head_span = call.head;
input
.map(
@ -176,6 +179,7 @@ fn rename(
stack.add_var(*var_id, Value::string(c.clone(), span))
}
}
let eval_result = eval_block_with_early_return(
&engine_state,
&mut stack,
@ -184,6 +188,7 @@ fn rename(
redirect_stdout,
redirect_stderr,
);
match eval_result {
Err(e) => return Value::error(e, span),
Ok(res) => match res.collect_string_strict(span) {

View File

@ -1,4 +1,5 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -94,6 +95,8 @@ impl Command for SkipUntil {
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block = get_eval_block(&engine_state);
Ok(input
.into_iter_strict(span)?
.skip_while(move |value| {

View File

@ -1,4 +1,5 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -99,6 +100,8 @@ impl Command for SkipWhile {
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block = get_eval_block(&engine_state);
Ok(input
.into_iter_strict(span)?
.skip_while(move |value| {

View File

@ -1,4 +1,5 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -91,6 +92,8 @@ impl Command for TakeUntil {
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block = get_eval_block(&engine_state);
Ok(input
.into_iter_strict(span)?
.take_while(move |value| {

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -91,6 +91,8 @@ impl Command for TakeWhile {
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block = get_eval_block(&engine_state);
Ok(input
.into_iter_strict(span)?
.take_while(move |value| {

View File

@ -1,6 +1,6 @@
use std::{sync::mpsc, thread};
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -83,6 +83,8 @@ use it in your pipeline."#
let metadata = input.metadata();
let metadata_clone = metadata.clone();
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
match input {
// Handle external streams specially, to make sure they pass through
PipelineData::ExternalStream {

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt, EvalBlockFn};
use nu_protocol::ast::{Block, Call, CellPath, PathMember};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData,
@ -116,6 +117,8 @@ fn update(
let ctrlc = engine_state.ctrlc.clone();
let eval_block = get_eval_block(engine_state);
match input {
PipelineData::Value(mut value, metadata) => {
if replacement.coerce_block().is_ok() {
@ -137,6 +140,7 @@ fn update(
block,
&cell_path.members,
false,
eval_block,
)?;
}
}
@ -150,6 +154,7 @@ fn update(
redirect_stderr,
&cell_path.members,
matches!(first, Some(PathMember::Int { .. })),
eval_block,
)?;
}
}
@ -196,6 +201,7 @@ fn update(
redirect_stderr,
path,
true,
eval_block,
)?;
} else {
value.update_data_at_cell_path(path, replacement)?;
@ -228,6 +234,7 @@ fn update(
&block,
&cell_path.members,
false,
eval_block,
);
if let Err(e) = err {
@ -273,6 +280,7 @@ fn update_value_by_closure(
block: &Block,
cell_path: &[PathMember],
first_path_member_int: bool,
eval_block_fn: EvalBlockFn,
) -> Result<(), ShellError> {
let input_at_path = value.clone().follow_cell_path(cell_path, false)?;
@ -289,7 +297,7 @@ fn update_value_by_closure(
}
}
let output = eval_block(
let output = eval_block_fn(
engine_state,
stack,
block,
@ -311,6 +319,7 @@ fn update_single_value_by_closure(
redirect_stderr: bool,
cell_path: &[PathMember],
first_path_member_int: bool,
eval_block_fn: EvalBlockFn,
) -> Result<(), ShellError> {
let span = replacement.span();
let capture_block = Closure::from_value(replacement)?;
@ -327,6 +336,7 @@ fn update_single_value_by_closure(
block,
cell_path,
first_path_member_int,
eval_block_fn,
)
}

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt, EvalBlockFn};
use nu_protocol::ast::{Block, Call, CellPath, PathMember};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData,
@ -158,6 +159,7 @@ fn upsert(
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block = get_eval_block(engine_state);
let ctrlc = engine_state.ctrlc.clone();
@ -182,6 +184,7 @@ fn upsert(
block,
&cell_path.members,
false,
eval_block,
)?;
}
}
@ -195,6 +198,7 @@ fn upsert(
redirect_stderr,
&cell_path.members,
matches!(first, Some(PathMember::Int { .. })),
eval_block,
)?;
}
}
@ -264,6 +268,7 @@ fn upsert(
redirect_stderr,
path,
true,
eval_block,
)?;
} else {
value.upsert_data_at_cell_path(path, replacement)?;
@ -303,6 +308,7 @@ fn upsert(
&block,
&cell_path.members,
false,
eval_block,
);
if let Err(e) = err {
@ -348,6 +354,7 @@ fn upsert_value_by_closure(
block: &Block,
cell_path: &[PathMember],
first_path_member_int: bool,
eval_block_fn: EvalBlockFn,
) -> Result<(), ShellError> {
let input_at_path = value.clone().follow_cell_path(cell_path, false);
@ -368,7 +375,7 @@ fn upsert_value_by_closure(
.map(IntoPipelineData::into_pipeline_data)
.unwrap_or(PipelineData::Empty);
let output = eval_block(
let output = eval_block_fn(
engine_state,
stack,
block,
@ -390,6 +397,7 @@ fn upsert_single_value_by_closure(
redirect_stderr: bool,
cell_path: &[PathMember],
first_path_member_int: bool,
eval_block_fn: EvalBlockFn,
) -> Result<(), ShellError> {
let span = replacement.span();
let capture_block = Closure::from_value(replacement)?;
@ -406,6 +414,7 @@ fn upsert_single_value_by_closure(
block,
cell_path,
first_path_member_int,
eval_block_fn,
)
}

View File

@ -1,4 +1,5 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, EngineState, Stack},
@ -40,6 +41,10 @@ pub fn boolean_fold(
let ctrlc = engine_state.ctrlc.clone();
// TODO: This Clippy lint is incorrectly triggered in our CI for come reason
#[allow(clippy::needless_borrow)]
let eval_block = get_eval_block(&engine_state);
for value in input.into_interruptible_iter(ctrlc) {
// with_env() is used here to ensure that each iteration uses
// a different set of environment variables.

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_block, CallExt};
use nu_engine::{get_eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
@ -70,6 +71,9 @@ not supported."#
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block = get_eval_block(&engine_state);
Ok(input
.into_iter_strict(span)?
.filter_map(move |value| {
@ -80,6 +84,7 @@ not supported."#
stack.add_var(*var_id, value.clone());
}
}
let result = eval_block(
&engine_state,
&mut stack,

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
@ -102,6 +102,7 @@ impl Command for Zip {
let head = call.head;
let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata();
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let other: PipelineData = match call.req(engine_state, stack, 0)? {
// If a closure was provided, evaluate it and consume its stream output

View File

@ -1,5 +1,6 @@
use itertools::unfold;
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
@ -107,6 +108,7 @@ used as the next argument to the closure, otherwise generation stops.
let orig_env_hidden = stack.env_hidden.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state);
// A type of Option<S> is used to represent state. Invocation
// will stop on None. Using Option<S> allows functions to output

View File

@ -1,5 +1,6 @@
use nu_engine::{eval_block_with_early_return, CallExt};
use nu_engine::{get_eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
@ -46,8 +47,10 @@ impl Command for Source {
// Note: this hidden positional is the block_id that corresponded to the 0th position
// it is put here by the parser
let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?;
let block = engine_state.get_block(block_id as usize).clone();
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
eval_block_with_early_return(
engine_state,
stack,

View File

@ -1,6 +1,6 @@
use nu_cmd_base::hook::eval_hook;
use nu_engine::env_to_strings;
use nu_engine::eval_expression;
use nu_engine::get_eval_expression;
use nu_engine::CallExt;
use nu_protocol::IntoSpanned;
use nu_protocol::NuGlob;
@ -133,6 +133,8 @@ pub fn create_external_command(
})
}
let eval_expression = get_eval_expression(engine_state);
let mut spanned_args = vec![];
let mut arg_keep_raw = vec![];
for (arg, spread) in call.rest_iter(1) {