attempt to use a file stack for redirection, but realized that both out and err can be separate so need to be able to reference both
This commit is contained in:
parent
689cc49663
commit
2807717245
|
@ -1,5 +1,5 @@
|
|||
use nu_protocol::{
|
||||
ir::{DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
|
||||
ir::{DataSlice, Instruction, IrAstRef, IrBlock, Literal},
|
||||
CompileError, IntoSpanned, RegId, Span, Spanned,
|
||||
};
|
||||
|
||||
|
@ -168,10 +168,11 @@ impl BlockBuilder {
|
|||
Instruction::PushFlag { name: _ } => Ok(()),
|
||||
Instruction::PushNamed { name: _, src } => allocate(&[*src], &[]),
|
||||
Instruction::PushParserInfo { name: _, info: _ } => Ok(()),
|
||||
Instruction::RedirectOut { mode } | Instruction::RedirectErr { mode } => match mode {
|
||||
RedirectMode::File { path, .. } => allocate(&[*path], &[]),
|
||||
_ => Ok(()),
|
||||
},
|
||||
Instruction::RedirectOut { mode: _ } => Ok(()),
|
||||
Instruction::RedirectErr { mode: _ } => Ok(()),
|
||||
Instruction::OpenFile { path, append: _ } => allocate(&[*path], &[]),
|
||||
Instruction::WriteFile { src } => allocate(&[*src], &[]),
|
||||
Instruction::CloseFile => Ok(()),
|
||||
Instruction::Call {
|
||||
decl_id: _,
|
||||
src_dst,
|
||||
|
|
|
@ -154,21 +154,23 @@ fn compile_pipeline(
|
|||
},
|
||||
};
|
||||
|
||||
let out_mode = spec_redirect_modes.out.or(next_redirect_modes.out);
|
||||
let err_mode = spec_redirect_modes.err.or(next_redirect_modes.err);
|
||||
let redirect_modes = RedirectModes {
|
||||
out: spec_redirect_modes.out.or(next_redirect_modes.out),
|
||||
err: spec_redirect_modes.err.or(next_redirect_modes.err),
|
||||
};
|
||||
|
||||
compile_expression(
|
||||
working_set,
|
||||
builder,
|
||||
&element.expr,
|
||||
RedirectModes {
|
||||
out: out_mode,
|
||||
err: err_mode,
|
||||
},
|
||||
redirect_modes.clone(),
|
||||
in_reg,
|
||||
out_reg,
|
||||
)?;
|
||||
|
||||
// Clean up the redirection
|
||||
finish_redirection(builder, redirect_modes, out_reg)?;
|
||||
|
||||
// The next pipeline element takes input from this output
|
||||
in_reg = Some(out_reg);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use nu_protocol::{
|
||||
ast::{Expression, RedirectionTarget},
|
||||
engine::StateWorkingSet,
|
||||
ir::RedirectMode,
|
||||
IntoSpanned, OutDest, Span, Spanned,
|
||||
ir::{Instruction, RedirectMode},
|
||||
IntoSpanned, OutDest, RegId, Span, Spanned,
|
||||
};
|
||||
|
||||
use super::{compile_expression, BlockBuilder, CompileError};
|
||||
|
@ -64,11 +64,14 @@ pub(crate) fn redirection_target_to_mode(
|
|||
None,
|
||||
path_reg,
|
||||
)?;
|
||||
RedirectMode::File {
|
||||
path: path_reg,
|
||||
append: *append,
|
||||
}
|
||||
.into_spanned(*redir_span)
|
||||
builder.push(
|
||||
Instruction::OpenFile {
|
||||
path: path_reg,
|
||||
append: *append,
|
||||
}
|
||||
.into_spanned(*redir_span),
|
||||
)?;
|
||||
RedirectMode::File.into_spanned(*redir_span)
|
||||
}
|
||||
RedirectionTarget::Pipe { span } => (if separate {
|
||||
RedirectMode::Capture
|
||||
|
@ -97,6 +100,37 @@ pub(crate) fn redirect_modes_of_expression(
|
|||
})
|
||||
}
|
||||
|
||||
/// Finish the redirection for an expression, writing to and closing files as necessary
|
||||
pub(crate) fn finish_redirection(
|
||||
builder: &mut BlockBuilder,
|
||||
modes: RedirectModes,
|
||||
out_reg: RegId,
|
||||
) -> Result<(), CompileError> {
|
||||
match modes.out {
|
||||
Some(Spanned {
|
||||
item: RedirectMode::File,
|
||||
span,
|
||||
}) => {
|
||||
builder.push(Instruction::WriteFile { src: out_reg }.into_spanned(span))?;
|
||||
builder.load_empty(out_reg)?;
|
||||
builder.push(Instruction::CloseFile.into_spanned(span))?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match modes.err {
|
||||
Some(Spanned {
|
||||
item: RedirectMode::File,
|
||||
span,
|
||||
}) => {
|
||||
builder.push(Instruction::CloseFile.into_spanned(span))?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn out_dest_to_redirect_mode(out_dest: OutDest) -> Result<RedirectMode, CompileError> {
|
||||
match out_dest {
|
||||
OutDest::Pipe => Ok(RedirectMode::Pipe),
|
||||
|
|
|
@ -6,9 +6,9 @@ use nu_protocol::{
|
|||
debugger::DebugContext,
|
||||
engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack},
|
||||
ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode},
|
||||
record, DeclId, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest, PipelineData,
|
||||
PositionalArg, Range, Record, RegId, ShellError, Signature, Span, Spanned, Value, VarId,
|
||||
ENV_VARIABLE_ID,
|
||||
record, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest,
|
||||
PipelineData, PositionalArg, Range, Record, RegId, ShellError, Signature, Span, Spanned, Value,
|
||||
VarId, ENV_VARIABLE_ID,
|
||||
};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
|
||||
|
@ -55,6 +55,7 @@ pub fn eval_ir_block<D: DebugContext>(
|
|||
error_handler_base,
|
||||
redirect_out: None,
|
||||
redirect_err: None,
|
||||
file_stack: vec![],
|
||||
matches: vec![],
|
||||
registers: &mut registers[..],
|
||||
},
|
||||
|
@ -95,6 +96,8 @@ struct EvalContext<'a> {
|
|||
redirect_out: Option<Redirection>,
|
||||
/// State set by redirect-err
|
||||
redirect_err: Option<Redirection>,
|
||||
/// Files used for redirection
|
||||
file_stack: Vec<Arc<File>>,
|
||||
/// Scratch space to use for `match`
|
||||
matches: Vec<(VarId, Value)>,
|
||||
registers: &'a mut [PipelineData],
|
||||
|
@ -115,6 +118,28 @@ impl<'a> EvalContext<'a> {
|
|||
std::mem::replace(&mut self.registers[reg_id.0 as usize], PipelineData::Empty)
|
||||
}
|
||||
|
||||
/// Clone data from a register. Must be collected first.
|
||||
fn clone_reg(&mut self, reg_id: RegId, error_span: Span) -> Result<PipelineData, ShellError> {
|
||||
match &self.registers[reg_id.0 as usize] {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(val, meta) => Ok(PipelineData::Value(val.clone(), meta.clone())),
|
||||
_ => Err(ShellError::IrEvalError {
|
||||
msg: "Must collect to value before using instruction that clones from a register"
|
||||
.into(),
|
||||
span: Some(error_span),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone a value from a register. Must be collected first.
|
||||
fn clone_reg_value(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
|
||||
match self.clone_reg(reg_id, fallback_span)? {
|
||||
PipelineData::Empty => Ok(Value::nothing(fallback_span)),
|
||||
PipelineData::Value(val, _) => Ok(val),
|
||||
_ => unreachable!("clone_reg should never return stream data"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Take and implicitly collect a register to a value
|
||||
fn collect_reg(&mut self, reg_id: RegId, fallback_span: Span) -> Result<Value, ShellError> {
|
||||
let data = self.take_reg(reg_id);
|
||||
|
@ -234,22 +259,8 @@ fn eval_instruction<D: DebugContext>(
|
|||
Ok(Continue)
|
||||
}
|
||||
Instruction::Clone { dst, src } => {
|
||||
let data1 = ctx.take_reg(*src);
|
||||
let data2 = match &data1 {
|
||||
PipelineData::Empty => PipelineData::Empty,
|
||||
PipelineData::Value(val, meta) => PipelineData::Value(val.clone(), meta.clone()),
|
||||
_ => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "IR error: must collect before clone if a stream is expected".into(),
|
||||
msg: "error occurred here".into(),
|
||||
span: Some(*span),
|
||||
help: Some("this is a compiler bug".into()),
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
};
|
||||
ctx.put_reg(*src, data1);
|
||||
ctx.put_reg(*dst, data2);
|
||||
let data = ctx.clone_reg(*src, *span)?;
|
||||
ctx.put_reg(*dst, data);
|
||||
Ok(Continue)
|
||||
}
|
||||
Instruction::Collect { src_dst } => {
|
||||
|
@ -378,6 +389,38 @@ fn eval_instruction<D: DebugContext>(
|
|||
ctx.redirect_err = eval_redirection(ctx, mode, *span, RedirectionStream::Err)?;
|
||||
Ok(Continue)
|
||||
}
|
||||
Instruction::OpenFile { path, append } => {
|
||||
let path = ctx.collect_reg(*path, *span)?;
|
||||
let file = open_file(ctx, &path, *append)?;
|
||||
ctx.file_stack.push(file);
|
||||
Ok(Continue)
|
||||
}
|
||||
Instruction::WriteFile { src } => {
|
||||
let src = ctx.take_reg(*src);
|
||||
let file = ctx
|
||||
.file_stack
|
||||
.last()
|
||||
.cloned()
|
||||
.ok_or_else(|| ShellError::IrEvalError {
|
||||
msg: "Tried to write file without opening a file first".into(),
|
||||
span: Some(*span),
|
||||
})?;
|
||||
let mut stack = ctx
|
||||
.stack
|
||||
.push_redirection(Some(Redirection::File(file)), None);
|
||||
src.write_to_out_dests(ctx.engine_state, &mut stack)?;
|
||||
Ok(Continue)
|
||||
}
|
||||
Instruction::CloseFile => {
|
||||
if ctx.file_stack.pop().is_some() {
|
||||
Ok(Continue)
|
||||
} else {
|
||||
Err(ShellError::IrEvalError {
|
||||
msg: "Tried to close file without opening a file first".into(),
|
||||
span: Some(*span),
|
||||
})
|
||||
}
|
||||
}
|
||||
Instruction::Call { decl_id, src_dst } => {
|
||||
let input = ctx.take_reg(*src_dst);
|
||||
let result = eval_call::<D>(ctx, *decl_id, *span, input)?;
|
||||
|
@ -498,25 +541,17 @@ fn eval_instruction<D: DebugContext>(
|
|||
}
|
||||
}
|
||||
Instruction::CloneCellPath { dst, src, path } => {
|
||||
let data = ctx.take_reg(*src);
|
||||
let value = ctx.clone_reg_value(*src, *span)?;
|
||||
let path = ctx.take_reg(*path);
|
||||
if let PipelineData::Value(value, _) = &data {
|
||||
if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
|
||||
// TODO: make follow_cell_path() not have to take ownership, probably using Cow
|
||||
let value = value.clone().follow_cell_path(&path.members, true)?;
|
||||
ctx.put_reg(*src, data);
|
||||
ctx.put_reg(*dst, value.into_pipeline_data());
|
||||
Ok(Continue)
|
||||
} else {
|
||||
Err(ShellError::TypeMismatch {
|
||||
err_message: "cell path".into(),
|
||||
span: path.span().unwrap_or(*span),
|
||||
})
|
||||
}
|
||||
if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
|
||||
// TODO: make follow_cell_path() not have to take ownership, probably using Cow
|
||||
let value = value.follow_cell_path(&path.members, true)?;
|
||||
ctx.put_reg(*dst, value.into_pipeline_data());
|
||||
Ok(Continue)
|
||||
} else {
|
||||
Err(ShellError::IrEvalError {
|
||||
msg: "must collect value before clone-cell-path".into(),
|
||||
span: Some(*span),
|
||||
Err(ShellError::TypeMismatch {
|
||||
err_message: "cell path".into(),
|
||||
span: path.span().unwrap_or(*span),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -577,13 +612,7 @@ fn eval_instruction<D: DebugContext>(
|
|||
src,
|
||||
index,
|
||||
} => {
|
||||
let data = ctx.take_reg(*src);
|
||||
let PipelineData::Value(value, metadata) = data else {
|
||||
return Err(ShellError::IrEvalError {
|
||||
msg: "must collect value before match".into(),
|
||||
span: Some(*span),
|
||||
});
|
||||
};
|
||||
let value = ctx.clone_reg_value(*src, *span)?;
|
||||
ctx.matches.clear();
|
||||
if pattern.match_value(&value, &mut ctx.matches) {
|
||||
// Match succeeded: set variables and branch
|
||||
|
@ -594,7 +623,6 @@ fn eval_instruction<D: DebugContext>(
|
|||
} else {
|
||||
// Failed to match, put back original value
|
||||
ctx.matches.clear();
|
||||
ctx.put_reg(*src, PipelineData::Value(value, metadata));
|
||||
Ok(Continue)
|
||||
}
|
||||
}
|
||||
|
@ -1096,6 +1124,23 @@ enum RedirectionStream {
|
|||
Err,
|
||||
}
|
||||
|
||||
/// Open a file for redirection
|
||||
fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result<Arc<File>, ShellError> {
|
||||
let path_expanded =
|
||||
expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true);
|
||||
let mut options = File::options();
|
||||
if append {
|
||||
options.append(true);
|
||||
} else {
|
||||
options.write(true).truncate(true);
|
||||
}
|
||||
let file = options
|
||||
.create(true)
|
||||
.open(path_expanded)
|
||||
.err_span(path.span())?;
|
||||
Ok(Arc::new(file))
|
||||
}
|
||||
|
||||
/// Set up a [`Redirection`] from a [`RedirectMode`]
|
||||
fn eval_redirection(
|
||||
ctx: &mut EvalContext<'_>,
|
||||
|
@ -1108,21 +1153,16 @@ fn eval_redirection(
|
|||
RedirectMode::Capture => Ok(Some(Redirection::Pipe(OutDest::Capture))),
|
||||
RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
|
||||
RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
|
||||
RedirectMode::File { path, append } => {
|
||||
let path = ctx.collect_reg(*path, span)?;
|
||||
let path_expanded =
|
||||
expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true);
|
||||
let mut options = File::options();
|
||||
if *append {
|
||||
options.append(true);
|
||||
} else {
|
||||
options.write(true).truncate(true);
|
||||
}
|
||||
let file = options
|
||||
.create(true)
|
||||
.open(path_expanded)
|
||||
.map_err(|err| err.into_spanned(span))?;
|
||||
Ok(Some(Redirection::File(file.into())))
|
||||
RedirectMode::File => {
|
||||
let file = ctx
|
||||
.file_stack
|
||||
.last()
|
||||
.cloned()
|
||||
.ok_or_else(|| ShellError::IrEvalError {
|
||||
msg: "Tried to redirect to file without opening a file first".into(),
|
||||
span: Some(span),
|
||||
})?;
|
||||
Ok(Some(Redirection::File(file)))
|
||||
}
|
||||
RedirectMode::Caller => Ok(match which {
|
||||
RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe),
|
||||
|
|
|
@ -119,6 +119,15 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
|
|||
Instruction::RedirectErr { mode } => {
|
||||
write!(f, "{:WIDTH$} {mode}", "redirect-err")
|
||||
}
|
||||
Instruction::OpenFile { path, append } => {
|
||||
write!(f, "{:WIDTH$} {path}, append = {append:?}", "open-file")
|
||||
}
|
||||
Instruction::WriteFile { src } => {
|
||||
write!(f, "{:WIDTH$} {src}", "write-file")
|
||||
}
|
||||
Instruction::CloseFile => {
|
||||
write!(f, "{:WIDTH$}", "close-file")
|
||||
}
|
||||
Instruction::Call { decl_id, src_dst } => {
|
||||
let decl = FmtDecl::new(self.engine_state, *decl_id);
|
||||
write!(f, "{:WIDTH$} {decl}, {src_dst}", "call")
|
||||
|
@ -253,7 +262,7 @@ impl fmt::Display for RedirectMode {
|
|||
RedirectMode::Capture => write!(f, "capture"),
|
||||
RedirectMode::Null => write!(f, "null"),
|
||||
RedirectMode::Inherit => write!(f, "inherit"),
|
||||
RedirectMode::File { path, append } => write!(f, "file({path}, append={append})"),
|
||||
RedirectMode::File => write!(f, "file"),
|
||||
RedirectMode::Caller => write!(f, "caller"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,10 +134,21 @@ pub enum Instruction {
|
|||
name: DataSlice,
|
||||
info: Box<Expression>,
|
||||
},
|
||||
/// Set the redirection for stdout for the next call (only)
|
||||
/// Set the redirection for stdout for the next call (only).
|
||||
///
|
||||
/// The register for a file redirection is not consumed.
|
||||
RedirectOut { mode: RedirectMode },
|
||||
/// Set the redirection for stderr for the next call (only)
|
||||
/// Set the redirection for stderr for the next call (only).
|
||||
///
|
||||
/// The register for a file redirection is not consumed.
|
||||
RedirectErr { mode: RedirectMode },
|
||||
/// Open a file for redirection, pushing it onto the file stack.
|
||||
OpenFile { path: RegId, append: bool },
|
||||
/// Write data from the register to a file. This is done to finish a file redirection, in case
|
||||
/// an internal command or expression was evaluated rather than an external one.
|
||||
WriteFile { src: RegId },
|
||||
/// Pop a file used for redirection from the file stack.
|
||||
CloseFile,
|
||||
/// Make a call. The input is taken from `src_dst`, and the output is placed in `src_dst`,
|
||||
/// overwriting it. The argument stack is used implicitly and cleared when the call ends.
|
||||
Call { decl_id: DeclId, src_dst: RegId },
|
||||
|
@ -295,11 +306,8 @@ pub enum RedirectMode {
|
|||
Capture,
|
||||
Null,
|
||||
Inherit,
|
||||
/// File path to be used in register.
|
||||
File {
|
||||
path: RegId,
|
||||
append: bool,
|
||||
},
|
||||
/// Use the file on the top of the file stack.
|
||||
File,
|
||||
/// Use the redirection mode requested by the caller, for a pre-return call.
|
||||
Caller,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user