abandon collect as keyword command, but change $in to use a new Collect expr type instead
This commit is contained in:
parent
e982c12a2f
commit
59b217443a
|
@ -429,6 +429,14 @@ fn find_matching_block_end_in_expr(
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
Expr::Collect(_, expr) => find_matching_block_end_in_expr(
|
||||||
|
line,
|
||||||
|
working_set,
|
||||||
|
expr,
|
||||||
|
global_span_offset,
|
||||||
|
global_cursor_offset,
|
||||||
|
),
|
||||||
|
|
||||||
Expr::Block(block_id)
|
Expr::Block(block_id)
|
||||||
| Expr::Closure(block_id)
|
| Expr::Closure(block_id)
|
||||||
| Expr::RowCondition(block_id)
|
| Expr::RowCondition(block_id)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use nu_engine::{command_prelude::*, get_eval_block};
|
use nu_engine::{command_prelude::*, get_eval_block, redirect_env};
|
||||||
use nu_protocol::{engine::CommandType, DataSource, PipelineMetadata};
|
use nu_protocol::{engine::Closure, DataSource, PipelineMetadata};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Collect;
|
pub struct Collect;
|
||||||
|
@ -13,15 +13,16 @@ impl Command for Collect {
|
||||||
Signature::build("collect")
|
Signature::build("collect")
|
||||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.optional(
|
.optional(
|
||||||
"block",
|
"closure",
|
||||||
SyntaxShape::Block,
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
"The block to run once the stream is collected.",
|
"The closure to run once the stream is collected.",
|
||||||
)
|
)
|
||||||
.category(Category::Core)
|
.switch(
|
||||||
}
|
"keep-env",
|
||||||
|
"let the closure affect environment variables",
|
||||||
fn command_type(&self) -> CommandType {
|
None,
|
||||||
CommandType::Keyword
|
)
|
||||||
|
.category(Category::Filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
@ -29,7 +30,7 @@ impl Command for Collect {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
r#"If provided, run a block with the collected value as input.
|
r#"If provided, run a closure with the collected value as input.
|
||||||
|
|
||||||
The entire stream will be collected into one value in memory, so if the stream
|
The entire stream will be collected into one value in memory, so if the stream
|
||||||
is particularly large, this can cause high memory usage."#
|
is particularly large, this can cause high memory usage."#
|
||||||
|
@ -42,12 +43,7 @@ is particularly large, this can cause high memory usage."#
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
// This is compiled specially by the IR compiler. The code here is never used when
|
let closure: Option<Closure> = call.opt(engine_state, stack, 0)?;
|
||||||
// running in IR mode.
|
|
||||||
let call = call.assert_ast_call()?;
|
|
||||||
let block_id = call
|
|
||||||
.positional_nth(0)
|
|
||||||
.map(|expr| expr.as_block().expect("checked through parser"));
|
|
||||||
|
|
||||||
let metadata = match input.metadata() {
|
let metadata = match input.metadata() {
|
||||||
// Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
|
// Remove the `FilePath` metadata, because after `collect` it's no longer necessary to
|
||||||
|
@ -62,21 +58,40 @@ is particularly large, this can cause high memory usage."#
|
||||||
let input = input.into_value(call.head)?;
|
let input = input.into_value(call.head)?;
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
if let Some(block_id) = block_id {
|
if let Some(closure) = closure {
|
||||||
let block = engine_state.get_block(block_id);
|
let block = engine_state.get_block(closure.block_id);
|
||||||
|
let mut stack_captures =
|
||||||
|
stack.captures_to_stack_preserve_out_dest(closure.captures.clone());
|
||||||
|
|
||||||
|
let mut saved_positional = None;
|
||||||
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
|
if let Some(var_id) = &var.var_id {
|
||||||
|
stack_captures.add_var(*var_id, input.clone());
|
||||||
|
saved_positional = Some(*var_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let eval_block = get_eval_block(engine_state);
|
let eval_block = get_eval_block(engine_state);
|
||||||
|
|
||||||
if let Some(var_id) = block.signature.get_positional(0).and_then(|var| var.var_id) {
|
result = eval_block(
|
||||||
stack.add_var(var_id, input);
|
engine_state,
|
||||||
result = eval_block(engine_state, stack, block, PipelineData::Empty);
|
&mut stack_captures,
|
||||||
stack.remove_var(var_id);
|
block,
|
||||||
} else {
|
input.into_pipeline_data_with_metadata(metadata),
|
||||||
result = eval_block(
|
);
|
||||||
engine_state,
|
|
||||||
stack,
|
if call.has_flag(engine_state, stack, "keep-env")? {
|
||||||
block,
|
redirect_env(engine_state, stack, &stack_captures);
|
||||||
input.into_pipeline_data_with_metadata(metadata),
|
// for when we support `data | let x = $in;`
|
||||||
);
|
// remove the variables added earlier
|
||||||
|
for (var_id, _) in closure.captures {
|
||||||
|
stack_captures.remove_var(var_id);
|
||||||
|
}
|
||||||
|
if let Some(u) = saved_positional {
|
||||||
|
stack_captures.remove_var(u);
|
||||||
|
}
|
||||||
|
// add any new variables to the stack
|
||||||
|
stack.vars.extend(stack_captures.vars);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = Ok(input.into_pipeline_data_with_metadata(metadata));
|
result = Ok(input.into_pipeline_data_with_metadata(metadata));
|
||||||
|
|
|
@ -204,6 +204,7 @@ impl BlockBuilder {
|
||||||
Instruction::Drain { src } => allocate(&[*src], &[]),
|
Instruction::Drain { src } => allocate(&[*src], &[]),
|
||||||
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
|
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
|
||||||
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
|
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
|
||||||
|
Instruction::DropVariable { var_id: _ } => Ok(()),
|
||||||
Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]),
|
Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]),
|
||||||
Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]),
|
Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]),
|
||||||
Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]),
|
Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]),
|
||||||
|
|
|
@ -50,9 +50,6 @@ pub(crate) fn compile_call(
|
||||||
"let" | "mut" => {
|
"let" | "mut" => {
|
||||||
return compile_let(working_set, builder, call, redirect_modes, io_reg);
|
return compile_let(working_set, builder, call, redirect_modes, io_reg);
|
||||||
}
|
}
|
||||||
"collect" => {
|
|
||||||
return compile_collect(working_set, builder, call, redirect_modes, io_reg);
|
|
||||||
}
|
|
||||||
"try" => {
|
"try" => {
|
||||||
return compile_try(working_set, builder, call, redirect_modes, io_reg);
|
return compile_try(working_set, builder, call, redirect_modes, io_reg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,6 +171,28 @@ pub(crate) fn compile_expression(
|
||||||
Err(CompileError::UnsupportedOperatorExpression { span: op.span })
|
Err(CompileError::UnsupportedOperatorExpression { span: op.span })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
let in_reg = match in_reg {
|
||||||
|
Some(in_reg) => in_reg,
|
||||||
|
None => {
|
||||||
|
let reg_id = builder.next_register()?;
|
||||||
|
builder.load_empty(reg_id)?;
|
||||||
|
reg_id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Implicit collect
|
||||||
|
builder.push(
|
||||||
|
Instruction::StoreVariable {
|
||||||
|
var_id: *var_id,
|
||||||
|
src: in_reg,
|
||||||
|
}
|
||||||
|
.into_spanned(expr.span),
|
||||||
|
)?;
|
||||||
|
compile_expression(working_set, builder, expr, redirect_modes, None, out_reg)?;
|
||||||
|
// Clean it up afterward
|
||||||
|
builder.push(Instruction::DropVariable { var_id: *var_id }.into_spanned(expr.span))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg)
|
compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg)
|
||||||
|
|
|
@ -350,63 +350,6 @@ pub(crate) fn compile_let(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile a call to `collect`
|
|
||||||
pub(crate) fn compile_collect(
|
|
||||||
working_set: &StateWorkingSet,
|
|
||||||
builder: &mut BlockBuilder,
|
|
||||||
call: &Call,
|
|
||||||
redirect_modes: RedirectModes,
|
|
||||||
io_reg: RegId,
|
|
||||||
) -> Result<(), CompileError> {
|
|
||||||
let block_id = call
|
|
||||||
.positional_nth(0)
|
|
||||||
.map(|expr| {
|
|
||||||
expr.as_block().ok_or(CompileError::UnexpectedExpression {
|
|
||||||
expr_name: format!("{:?}", expr),
|
|
||||||
span: expr.span,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
if let Some(block_id) = block_id {
|
|
||||||
let block = working_set.get_block(block_id);
|
|
||||||
|
|
||||||
if let Some(var_id) = block.signature.get_positional(0).and_then(|var| var.var_id) {
|
|
||||||
// Pseudocode:
|
|
||||||
//
|
|
||||||
// store-variable $var, %io_reg
|
|
||||||
// ...<block>...
|
|
||||||
builder.push(
|
|
||||||
Instruction::StoreVariable {
|
|
||||||
var_id,
|
|
||||||
src: io_reg,
|
|
||||||
}
|
|
||||||
.into_spanned(block.span.unwrap_or(call.head)),
|
|
||||||
)?;
|
|
||||||
compile_block(working_set, builder, block, redirect_modes, None, io_reg)
|
|
||||||
} else {
|
|
||||||
// Pseudocode:
|
|
||||||
//
|
|
||||||
// collect %io_reg
|
|
||||||
// ...<block>...
|
|
||||||
builder.push(Instruction::Collect { src_dst: io_reg }.into_spanned(call.head))?;
|
|
||||||
compile_block(
|
|
||||||
working_set,
|
|
||||||
builder,
|
|
||||||
block,
|
|
||||||
redirect_modes,
|
|
||||||
Some(io_reg),
|
|
||||||
io_reg,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Pseudocode:
|
|
||||||
//
|
|
||||||
// collect %io_reg
|
|
||||||
builder.push(Instruction::Collect { src_dst: io_reg }.into_spanned(call.head))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compile a call to `try`, setting an error handler over the evaluated block
|
/// Compile a call to `try`, setting an error handler over the evaluated block
|
||||||
pub(crate) fn compile_try(
|
pub(crate) fn compile_try(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
|
|
|
@ -259,6 +259,10 @@ pub fn eval_expression_with_input<D: DebugContext>(
|
||||||
input = eval_external(engine_state, stack, head, args, input)?;
|
input = eval_external(engine_state, stack, head, args, input)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
input = eval_collect::<D>(engine_state, stack, *var_id, expr, input)?;
|
||||||
|
}
|
||||||
|
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
let block = engine_state.get_block(*block_id);
|
let block = engine_state.get_block(*block_id);
|
||||||
// FIXME: protect this collect with ctrl-c
|
// FIXME: protect this collect with ctrl-c
|
||||||
|
@ -605,6 +609,26 @@ pub fn eval_block<D: DebugContext>(
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn eval_collect<D: DebugContext>(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
// Evaluate the expression with the variable set to the collected input
|
||||||
|
let span = input.span().unwrap_or(Span::unknown());
|
||||||
|
|
||||||
|
stack.add_var(var_id, input.into_value(span)?);
|
||||||
|
|
||||||
|
let result = eval_expression_with_input::<D>(engine_state, stack, expr, PipelineData::empty())
|
||||||
|
.map(|(result, _failed)| result);
|
||||||
|
|
||||||
|
stack.remove_var(var_id);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eval_subexpression<D: DebugContext>(
|
pub fn eval_subexpression<D: DebugContext>(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
@ -729,6 +753,18 @@ impl Eval for EvalRuntime {
|
||||||
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
|
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eval_collect<D: DebugContext>(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
// It's a little bizarre, but the expression can still have some kind of result even with
|
||||||
|
// nothing input
|
||||||
|
eval_collect::<D>(engine_state, stack, var_id, expr, PipelineData::empty())?
|
||||||
|
.into_value(expr.span)
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_subexpression<D: DebugContext>(
|
fn eval_subexpression<D: DebugContext>(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
|
|
|
@ -336,6 +336,10 @@ fn eval_instruction<D: DebugContext>(
|
||||||
ctx.stack.add_var(*var_id, value);
|
ctx.stack.add_var(*var_id, value);
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
}
|
}
|
||||||
|
Instruction::DropVariable { var_id } => {
|
||||||
|
ctx.stack.remove_var(*var_id);
|
||||||
|
Ok(Continue)
|
||||||
|
}
|
||||||
Instruction::LoadEnv { dst, key } => {
|
Instruction::LoadEnv { dst, key } => {
|
||||||
let key = ctx.get_str(*key, *span)?;
|
let key = ctx.get_str(*key, *span)?;
|
||||||
if let Some(value) = get_env_var_case_insensitive(ctx, key) {
|
if let Some(value) = get_env_var_case_insensitive(ctx, key) {
|
||||||
|
|
|
@ -189,6 +189,9 @@ fn flatten_expression_into(
|
||||||
));
|
));
|
||||||
flatten_expression_into(working_set, not, output);
|
flatten_expression_into(working_set, not, output);
|
||||||
}
|
}
|
||||||
|
Expr::Collect(_, expr) => {
|
||||||
|
flatten_expression_into(working_set, expr, output);
|
||||||
|
}
|
||||||
Expr::Closure(block_id) => {
|
Expr::Closure(block_id) => {
|
||||||
let outer_span = expr.span;
|
let outer_span = expr.span;
|
||||||
|
|
||||||
|
|
|
@ -6220,6 +6220,10 @@ pub fn discover_captures_in_expr(
|
||||||
discover_captures_in_expr(working_set, &match_.1, seen, seen_blocks, output)?;
|
discover_captures_in_expr(working_set, &match_.1, seen, seen_blocks, output)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
seen.push(*var_id);
|
||||||
|
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?
|
||||||
|
}
|
||||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
|
@ -6305,58 +6309,20 @@ fn wrap_element_with_collect(
|
||||||
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
|
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
|
||||||
let span = expr.span;
|
let span = expr.span;
|
||||||
|
|
||||||
if let Some(decl_id) = working_set.find_decl(b"collect") {
|
// IN_VARIABLE_ID should get replaced with a unique variable, so that we don't have to
|
||||||
let mut output = vec![];
|
// execute as a closure
|
||||||
|
let var_id = working_set.add_variable(b"$in".into(), expr.span, Type::Any, false);
|
||||||
|
let mut expr = expr.clone();
|
||||||
|
expr.replace_in_variable(working_set, var_id);
|
||||||
|
|
||||||
// IN_VARIABLE_ID should get replaced with a unique variable, so that we don't have to
|
// Bind the custom `$in` variable for that particular expression
|
||||||
// execute as a closure
|
Expression::new(
|
||||||
let var_id = working_set.add_variable(b"$in".into(), expr.span, Type::Any, false);
|
working_set,
|
||||||
let mut expr = expr.clone();
|
Expr::Collect(var_id, Box::new(expr.clone())),
|
||||||
expr.replace_in_variable(working_set, var_id);
|
span,
|
||||||
|
// We can expect it to have the same result type
|
||||||
let mut signature = Signature::new("");
|
expr.ty,
|
||||||
signature.required_positional.push(PositionalArg {
|
)
|
||||||
var_id: Some(var_id),
|
|
||||||
name: "$in".into(),
|
|
||||||
desc: String::new(),
|
|
||||||
shape: SyntaxShape::Any,
|
|
||||||
default_value: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut block = Block {
|
|
||||||
pipelines: vec![Pipeline::from_vec(vec![expr.clone()])],
|
|
||||||
signature: Box::new(signature),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
compile_block(working_set, &mut block);
|
|
||||||
|
|
||||||
let block_id = working_set.add_block(Arc::new(block));
|
|
||||||
|
|
||||||
output.push(Argument::Positional(Expression::new(
|
|
||||||
working_set,
|
|
||||||
Expr::Closure(block_id),
|
|
||||||
span,
|
|
||||||
Type::Any,
|
|
||||||
)));
|
|
||||||
|
|
||||||
// The containing, synthetic call to `collect`.
|
|
||||||
// We don't want to have a real span as it will confuse flattening
|
|
||||||
// The args are where we'll get the real info
|
|
||||||
Expression::new(
|
|
||||||
working_set,
|
|
||||||
Expr::Call(Box::new(Call {
|
|
||||||
head: Span::new(0, 0),
|
|
||||||
arguments: output,
|
|
||||||
decl_id,
|
|
||||||
parser_info: HashMap::new(),
|
|
||||||
})),
|
|
||||||
span,
|
|
||||||
Type::Any,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Expression::garbage(working_set, span)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses a vector of u8 to create an AST Block. If a file name is given, then
|
// Parses a vector of u8 to create an AST Block. If a file name is given, then
|
||||||
|
|
|
@ -83,7 +83,7 @@ impl Block {
|
||||||
pub fn replace_in_variable(
|
pub fn replace_in_variable(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &mut StateWorkingSet<'_>,
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
new_var_id: usize,
|
new_var_id: VarId,
|
||||||
) {
|
) {
|
||||||
for pipeline in self.pipelines.iter_mut() {
|
for pipeline in self.pipelines.iter_mut() {
|
||||||
if let Some(element) = pipeline.elements.first_mut() {
|
if let Some(element) = pipeline.elements.first_mut() {
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub enum Expr {
|
||||||
RowCondition(BlockId),
|
RowCondition(BlockId),
|
||||||
UnaryNot(Box<Expression>),
|
UnaryNot(Box<Expression>),
|
||||||
BinaryOp(Box<Expression>, Box<Expression>, Box<Expression>), //lhs, op, rhs
|
BinaryOp(Box<Expression>, Box<Expression>, Box<Expression>), //lhs, op, rhs
|
||||||
|
Collect(VarId, Box<Expression>),
|
||||||
Subexpression(BlockId),
|
Subexpression(BlockId),
|
||||||
Block(BlockId),
|
Block(BlockId),
|
||||||
Closure(BlockId),
|
Closure(BlockId),
|
||||||
|
@ -65,11 +66,13 @@ impl Expr {
|
||||||
&self,
|
&self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
) -> (Option<OutDest>, Option<OutDest>) {
|
) -> (Option<OutDest>, Option<OutDest>) {
|
||||||
// Usages of `$in` will be wrapped by a `collect` call by the parser,
|
|
||||||
// so we do not have to worry about that when considering
|
|
||||||
// which of the expressions below may consume pipeline output.
|
|
||||||
match self {
|
match self {
|
||||||
Expr::Call(call) => working_set.get_decl(call.decl_id).pipe_redirection(),
|
Expr::Call(call) => working_set.get_decl(call.decl_id).pipe_redirection(),
|
||||||
|
Expr::Collect(_, _) => {
|
||||||
|
// A collect expression always has default redirection, it's just going to collect
|
||||||
|
// stdout unless another type of redirection is specified
|
||||||
|
(None, None)
|
||||||
|
},
|
||||||
Expr::Subexpression(block_id) | Expr::Block(block_id) => working_set
|
Expr::Subexpression(block_id) | Expr::Block(block_id) => working_set
|
||||||
.get_block(*block_id)
|
.get_block(*block_id)
|
||||||
.pipe_redirection(working_set),
|
.pipe_redirection(working_set),
|
||||||
|
|
|
@ -224,6 +224,9 @@ impl Expression {
|
||||||
Expr::Signature(_) => false,
|
Expr::Signature(_) => false,
|
||||||
Expr::String(_) => false,
|
Expr::String(_) => false,
|
||||||
Expr::RawString(_) => false,
|
Expr::RawString(_) => false,
|
||||||
|
// A `$in` variable found within a `Collect` is local, as it's already been wrapped
|
||||||
|
// This is probably unlikely to happen anyway - the expressions are wrapped depth-first
|
||||||
|
Expr::Collect(_, _) => false,
|
||||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||||
let block = working_set.get_block(*block_id);
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
|
@ -387,6 +390,7 @@ impl Expression {
|
||||||
i.replace_span(working_set, replaced, new_span)
|
i.replace_span(working_set, replaced, new_span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Collect(_, expr) => expr.replace_span(working_set, replaced, new_span),
|
||||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||||
let mut block = (**working_set.get_block(*block_id)).clone();
|
let mut block = (**working_set.get_block(*block_id)).clone();
|
||||||
|
|
||||||
|
@ -457,6 +461,8 @@ impl Expression {
|
||||||
Expr::Operator(_) => {}
|
Expr::Operator(_) => {}
|
||||||
// These have their own input
|
// These have their own input
|
||||||
Expr::Block(_) | Expr::Closure(_) => {}
|
Expr::Block(_) | Expr::Closure(_) => {}
|
||||||
|
// `$in` in `Collect` has already been handled, so we don't need to check further
|
||||||
|
Expr::Collect(_, _) => {}
|
||||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||||
let mut block = Block::clone(working_set.get_block(*block_id));
|
let mut block = Block::clone(working_set.get_block(*block_id));
|
||||||
block.replace_in_variable(working_set, new_var_id);
|
block.replace_in_variable(working_set, new_var_id);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span};
|
use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span, VarId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ impl RedirectionTarget {
|
||||||
pub fn replace_in_variable(
|
pub fn replace_in_variable(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &mut StateWorkingSet<'_>,
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
new_var_id: usize,
|
new_var_id: VarId,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
RedirectionTarget::File { expr, .. } => {
|
RedirectionTarget::File { expr, .. } => {
|
||||||
|
@ -92,7 +92,7 @@ impl PipelineRedirection {
|
||||||
pub fn replace_in_variable(
|
pub fn replace_in_variable(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &mut StateWorkingSet<'_>,
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
new_var_id: usize,
|
new_var_id: VarId,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
PipelineRedirection::Single { source: _, target } => {
|
PipelineRedirection::Single { source: _, target } => {
|
||||||
|
@ -154,7 +154,7 @@ impl PipelineElement {
|
||||||
pub fn replace_in_variable(
|
pub fn replace_in_variable(
|
||||||
&mut self,
|
&mut self,
|
||||||
working_set: &mut StateWorkingSet<'_>,
|
working_set: &mut StateWorkingSet<'_>,
|
||||||
new_var_id: usize,
|
new_var_id: VarId,
|
||||||
) {
|
) {
|
||||||
self.expr.replace_in_variable(working_set, new_var_id);
|
self.expr.replace_in_variable(working_set, new_var_id);
|
||||||
if let Some(redirection) = &mut self.redirection {
|
if let Some(redirection) = &mut self.redirection {
|
||||||
|
|
|
@ -259,6 +259,7 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String {
|
||||||
Expr::String(_) | Expr::RawString(_) => "string".to_string(),
|
Expr::String(_) | Expr::RawString(_) => "string".to_string(),
|
||||||
Expr::StringInterpolation(_) => "string interpolation".to_string(),
|
Expr::StringInterpolation(_) => "string interpolation".to_string(),
|
||||||
Expr::GlobInterpolation(_, _) => "glob interpolation".to_string(),
|
Expr::GlobInterpolation(_, _) => "glob interpolation".to_string(),
|
||||||
|
Expr::Collect(_, _) => "collect".to_string(),
|
||||||
Expr::Subexpression(_) => "subexpression".to_string(),
|
Expr::Subexpression(_) => "subexpression".to_string(),
|
||||||
Expr::Table(_) => "table".to_string(),
|
Expr::Table(_) => "table".to_string(),
|
||||||
Expr::UnaryNot(_) => "unary not".to_string(),
|
Expr::UnaryNot(_) => "unary not".to_string(),
|
||||||
|
|
|
@ -159,6 +159,9 @@ pub trait Eval {
|
||||||
Expr::ExternalCall(head, args) => {
|
Expr::ExternalCall(head, args) => {
|
||||||
Self::eval_external_call(state, mut_state, head, args, expr_span)
|
Self::eval_external_call(state, mut_state, head, args, expr_span)
|
||||||
}
|
}
|
||||||
|
Expr::Collect(var_id, expr) => {
|
||||||
|
Self::eval_collect::<D>(state, mut_state, *var_id, expr)
|
||||||
|
}
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
Self::eval_subexpression::<D>(state, mut_state, *block_id, expr_span)
|
Self::eval_subexpression::<D>(state, mut_state, *block_id, expr_span)
|
||||||
}
|
}
|
||||||
|
@ -356,6 +359,13 @@ pub trait Eval {
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Value, ShellError>;
|
) -> Result<Value, ShellError>;
|
||||||
|
|
||||||
|
fn eval_collect<D: DebugContext>(
|
||||||
|
state: Self::State<'_>,
|
||||||
|
mut_state: &mut Self::MutState,
|
||||||
|
var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Value, ShellError>;
|
||||||
|
|
||||||
fn eval_subexpression<D: DebugContext>(
|
fn eval_subexpression<D: DebugContext>(
|
||||||
state: Self::State<'_>,
|
state: Self::State<'_>,
|
||||||
mut_state: &mut Self::MutState,
|
mut_state: &mut Self::MutState,
|
||||||
|
|
|
@ -422,6 +422,15 @@ impl Eval for EvalConst {
|
||||||
Err(ShellError::NotAConstant { span })
|
Err(ShellError::NotAConstant { span })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eval_collect<D: DebugContext>(
|
||||||
|
_: &StateWorkingSet,
|
||||||
|
_: &mut (),
|
||||||
|
_var_id: VarId,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
Err(ShellError::NotAConstant { span: expr.span })
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_subexpression<D: DebugContext>(
|
fn eval_subexpression<D: DebugContext>(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
_: &mut (),
|
_: &mut (),
|
||||||
|
|
|
@ -102,6 +102,10 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
|
||||||
let var = FmtVar::new(self.engine_state, *var_id);
|
let var = FmtVar::new(self.engine_state, *var_id);
|
||||||
write!(f, "{:WIDTH$} {var}, {src}", "store-variable")
|
write!(f, "{:WIDTH$} {var}, {src}", "store-variable")
|
||||||
}
|
}
|
||||||
|
Instruction::DropVariable { var_id } => {
|
||||||
|
let var = FmtVar::new(self.engine_state, *var_id);
|
||||||
|
write!(f, "{:WIDTH$} {var}", "drop-variable")
|
||||||
|
}
|
||||||
Instruction::LoadEnv { dst, key } => {
|
Instruction::LoadEnv { dst, key } => {
|
||||||
let key = FmtData(self.data, *key);
|
let key = FmtData(self.data, *key);
|
||||||
write!(f, "{:WIDTH$} {dst}, {key}", "load-env")
|
write!(f, "{:WIDTH$} {dst}, {key}", "load-env")
|
||||||
|
|
|
@ -127,6 +127,8 @@ pub enum Instruction {
|
||||||
LoadVariable { dst: RegId, var_id: VarId },
|
LoadVariable { dst: RegId, var_id: VarId },
|
||||||
/// Store the value of a variable from the `src` register
|
/// Store the value of a variable from the `src` register
|
||||||
StoreVariable { var_id: VarId, src: RegId },
|
StoreVariable { var_id: VarId, src: RegId },
|
||||||
|
/// Remove a variable from the stack, freeing up whatever resources were associated with it
|
||||||
|
DropVariable { var_id: VarId },
|
||||||
/// Load the value of an environment variable into the `dst` register
|
/// Load the value of an environment variable into the `dst` register
|
||||||
LoadEnv { dst: RegId, key: DataSlice },
|
LoadEnv { dst: RegId, key: DataSlice },
|
||||||
/// Load the value of an environment variable into the `dst` register, or `Nothing` if it
|
/// Load the value of an environment variable into the `dst` register, or `Nothing` if it
|
||||||
|
|
|
@ -329,6 +329,12 @@ fn convert_to_value(
|
||||||
msg: "glob interpolation not supported in nuon".into(),
|
msg: "glob interpolation not supported in nuon".into(),
|
||||||
span: expr.span,
|
span: expr.span,
|
||||||
}),
|
}),
|
||||||
|
Expr::Collect(..) => Err(ShellError::OutsideSpannedLabeledError {
|
||||||
|
src: original_text.to_string(),
|
||||||
|
error: "Error when loading".into(),
|
||||||
|
msg: "`$in` not supported in nuon".into(),
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
Expr::Subexpression(..) => Err(ShellError::OutsideSpannedLabeledError {
|
Expr::Subexpression(..) => Err(ShellError::OutsideSpannedLabeledError {
|
||||||
src: original_text.to_string(),
|
src: original_text.to_string(),
|
||||||
error: "Error when loading".into(),
|
error: "Error when loading".into(),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user