add explicit numbering for allocated files, fix some err redirection stuff

This commit is contained in:
Devyn Cairns 2024-07-08 13:58:41 -07:00
parent 2807717245
commit 38884eec73
7 changed files with 128 additions and 46 deletions

View File

@ -11,6 +11,7 @@ pub(crate) struct BlockBuilder {
pub(crate) data: Vec<u8>,
pub(crate) ast: Vec<Option<IrAstRef>>,
pub(crate) register_allocation_state: Vec<bool>,
pub(crate) file_count: u32,
}
impl BlockBuilder {
@ -22,6 +23,7 @@ impl BlockBuilder {
data: vec![],
ast: vec![],
register_allocation_state: vec![true],
file_count: 0,
}
}
@ -170,9 +172,13 @@ impl BlockBuilder {
Instruction::PushParserInfo { name: _, info: _ } => Ok(()),
Instruction::RedirectOut { mode: _ } => Ok(()),
Instruction::RedirectErr { mode: _ } => Ok(()),
Instruction::OpenFile { path, append: _ } => allocate(&[*path], &[]),
Instruction::WriteFile { src } => allocate(&[*src], &[]),
Instruction::CloseFile => Ok(()),
Instruction::OpenFile {
file_num: _,
path,
append: _,
} => allocate(&[*path], &[]),
Instruction::WriteFile { file_num: _, src } => allocate(&[*src], &[]),
Instruction::CloseFile { file_num: _ } => Ok(()),
Instruction::Call {
decl_id: _,
src_dst,
@ -402,6 +408,16 @@ impl BlockBuilder {
self.instructions.len()
}
/// Allocate a new file number, for redirection.
pub(crate) fn next_file_num(&mut self) -> Result<u32, CompileError> {
let next = self.file_count;
self.file_count = self
.file_count
.checked_add(1)
.ok_or_else(|| CompileError::FileOverflow)?;
Ok(next)
}
/// Consume the builder and produce the final [`IrBlock`].
pub(crate) fn finish(self) -> IrBlock {
IrBlock {
@ -409,7 +425,12 @@ impl BlockBuilder {
spans: self.spans,
data: self.data.into(),
ast: self.ast,
register_count: self.register_allocation_state.len(),
register_count: self
.register_allocation_state
.len()
.try_into()
.expect("register count overflowed in finish() despite previous checks"),
file_count: self.file_count,
}
}
}

View File

@ -113,9 +113,20 @@ fn compile_pipeline(
// element, then it's from whatever is passed in as the mode to use.
let next_redirect_modes = if let Some(next_element) = iter.peek() {
// If there's a next element we always pipe out
redirect_modes_of_expression(working_set, &next_element.expr, span)?
.with_pipe_out(next_element.pipe.unwrap_or(next_element.expr.span))
// If there's a next element we always pipe out *unless* this is a single redirection
// to stderr (e>|)
let modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?;
if matches!(
element.redirection,
Some(PipelineRedirection::Single {
source: RedirectionSource::Stderr,
..
})
) {
modes
} else {
modes.with_pipe_out(next_element.pipe.unwrap_or(next_element.expr.span))
}
} else {
redirect_modes
.take()

View File

@ -55,6 +55,7 @@ pub(crate) fn redirection_target_to_mode(
append,
span: redir_span,
} => {
let file_num = builder.next_file_num()?;
let path_reg = builder.next_register()?;
compile_expression(
working_set,
@ -66,12 +67,13 @@ pub(crate) fn redirection_target_to_mode(
)?;
builder.push(
Instruction::OpenFile {
file_num,
path: path_reg,
append: *append,
}
.into_spanned(*redir_span),
)?;
RedirectMode::File.into_spanned(*redir_span)
RedirectMode::File { file_num }.into_spanned(*redir_span)
}
RedirectionTarget::Pipe { span } => (if separate {
RedirectMode::Capture
@ -108,22 +110,28 @@ pub(crate) fn finish_redirection(
) -> Result<(), CompileError> {
match modes.out {
Some(Spanned {
item: RedirectMode::File,
item: RedirectMode::File { file_num },
span,
}) => {
builder.push(Instruction::WriteFile { src: out_reg }.into_spanned(span))?;
builder.push(
Instruction::WriteFile {
file_num,
src: out_reg,
}
.into_spanned(span),
)?;
builder.load_empty(out_reg)?;
builder.push(Instruction::CloseFile.into_spanned(span))?;
builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?;
}
_ => (),
}
match modes.err {
Some(Spanned {
item: RedirectMode::File,
item: RedirectMode::File { file_num },
span,
}) => {
builder.push(Instruction::CloseFile.into_spanned(span))?;
builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?;
}
_ => (),
}

View File

@ -41,9 +41,13 @@ pub fn eval_ir_block<D: DebugContext>(
// Allocate and initialize registers. I've found that it's not really worth trying to avoid
// the heap allocation here by reusing buffers - our allocator is fast enough
let mut registers = Vec::with_capacity(ir_block.register_count);
let empty = std::iter::repeat_with(|| PipelineData::Empty);
registers.extend(empty.take(ir_block.register_count));
let mut registers = Vec::with_capacity(ir_block.register_count as usize);
for _ in 0..ir_block.register_count {
registers.push(PipelineData::Empty);
}
// Initialize file storage.
let mut files = vec![None; ir_block.file_count as usize];
let result = eval_ir_block_impl::<D>(
&mut EvalContext {
@ -55,9 +59,9 @@ pub fn eval_ir_block<D: DebugContext>(
error_handler_base,
redirect_out: None,
redirect_err: None,
file_stack: vec![],
matches: vec![],
registers: &mut registers[..],
files: &mut files[..],
},
ir_block,
input,
@ -96,11 +100,12 @@ 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)>,
/// Intermediate pipeline data storage used by instructions, indexed by RegId
registers: &'a mut [PipelineData],
/// Holds open files used by redirections
files: &'a mut [Option<Arc<File>>],
}
impl<'a> EvalContext<'a> {
@ -389,20 +394,25 @@ fn eval_instruction<D: DebugContext>(
ctx.redirect_err = eval_redirection(ctx, mode, *span, RedirectionStream::Err)?;
Ok(Continue)
}
Instruction::OpenFile { path, append } => {
Instruction::OpenFile {
file_num,
path,
append,
} => {
let path = ctx.collect_reg(*path, *span)?;
let file = open_file(ctx, &path, *append)?;
ctx.file_stack.push(file);
ctx.files[*file_num as usize] = Some(file);
Ok(Continue)
}
Instruction::WriteFile { src } => {
Instruction::WriteFile { file_num, src } => {
let src = ctx.take_reg(*src);
let file = ctx
.file_stack
.last()
.files
.get(*file_num as usize)
.cloned()
.flatten()
.ok_or_else(|| ShellError::IrEvalError {
msg: "Tried to write file without opening a file first".into(),
msg: format!("Tried to write to file #{file_num}, but it is not open"),
span: Some(*span),
})?;
let mut stack = ctx
@ -411,12 +421,12 @@ fn eval_instruction<D: DebugContext>(
src.write_to_out_dests(ctx.engine_state, &mut stack)?;
Ok(Continue)
}
Instruction::CloseFile => {
if ctx.file_stack.pop().is_some() {
Instruction::CloseFile { file_num } => {
if ctx.files[*file_num as usize].take().is_some() {
Ok(Continue)
} else {
Err(ShellError::IrEvalError {
msg: "Tried to close file without opening a file first".into(),
msg: format!("Tried to close file #{file_num}, but it is not open"),
span: Some(*span),
})
}
@ -1153,13 +1163,14 @@ 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 => {
RedirectMode::File { file_num } => {
let file = ctx
.file_stack
.last()
.files
.get(*file_num as usize)
.cloned()
.flatten()
.ok_or_else(|| ShellError::IrEvalError {
msg: "Tried to redirect to file without opening a file first".into(),
msg: format!("Tried to redirect to file #{file_num}, but it is not open"),
span: Some(span),
})?;
Ok(Some(Redirection::File(file)))

View File

@ -41,6 +41,13 @@ pub enum CompileError {
)]
DataOverflow,
#[error("Block contains too many files.")]
#[diagnostic(
code(nu::compile::register_overflow),
help("try using fewer file redirections")
)]
FileOverflow,
#[error("Invalid redirect mode: File should not be specified by commands.")]
#[diagnostic(code(nu::compile::invalid_redirect_mode), help("this is a command bug. Please report it at https://github.com/nushell/nushell/issues/new"))]
InvalidRedirectMode,

View File

@ -16,12 +16,20 @@ impl<'a> fmt::Display for FmtIrBlock<'a> {
f,
"# {} register{}, {} instruction{}, {} byte{} of data",
self.ir_block.register_count,
plural(self.ir_block.register_count),
plural(self.ir_block.register_count as usize),
self.ir_block.instructions.len(),
plural(self.ir_block.instructions.len()),
self.ir_block.data.len(),
plural(self.ir_block.data.len()),
)?;
if self.ir_block.file_count > 0 {
writeln!(
f,
"# {} file{} used for redirection",
self.ir_block.file_count,
plural(self.ir_block.file_count as usize)
)?;
}
for (index, instruction) in self.ir_block.instructions.iter().enumerate() {
writeln!(
f,
@ -119,14 +127,22 @@ 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::OpenFile {
file_num,
path,
append,
} => {
write!(
f,
"{:WIDTH$} file({file_num}), {path}, append = {append:?}",
"open-file"
)
}
Instruction::WriteFile { src } => {
write!(f, "{:WIDTH$} {src}", "write-file")
Instruction::WriteFile { file_num, src } => {
write!(f, "{:WIDTH$} file({file_num}), {src}", "write-file")
}
Instruction::CloseFile => {
write!(f, "{:WIDTH$}", "close-file")
Instruction::CloseFile { file_num } => {
write!(f, "{:WIDTH$} file({file_num})", "close-file")
}
Instruction::Call { decl_id, src_dst } => {
let decl = FmtDecl::new(self.engine_state, *decl_id);
@ -262,7 +278,7 @@ impl fmt::Display for RedirectMode {
RedirectMode::Capture => write!(f, "capture"),
RedirectMode::Null => write!(f, "null"),
RedirectMode::Inherit => write!(f, "inherit"),
RedirectMode::File => write!(f, "file"),
RedirectMode::File { file_num } => write!(f, "file({file_num})"),
RedirectMode::Caller => write!(f, "caller"),
}
}

View File

@ -22,7 +22,8 @@ pub struct IrBlock {
#[serde(with = "serde_arc_u8_array")]
pub data: Arc<[u8]>,
pub ast: Vec<Option<IrAstRef>>,
pub register_count: usize,
pub register_count: u32,
pub file_count: u32,
}
impl fmt::Debug for IrBlock {
@ -33,6 +34,7 @@ impl fmt::Debug for IrBlock {
.field("spans", &self.spans)
.field("data", &self.data)
.field("register_count", &self.register_count)
.field("file_count", &self.register_count)
.finish_non_exhaustive()
}
}
@ -143,12 +145,16 @@ pub enum Instruction {
/// 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 },
OpenFile {
file_num: u32,
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 },
WriteFile { file_num: u32, src: RegId },
/// Pop a file used for redirection from the file stack.
CloseFile,
CloseFile { file_num: u32 },
/// 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 },
@ -306,8 +312,10 @@ pub enum RedirectMode {
Capture,
Null,
Inherit,
/// Use the file on the top of the file stack.
File,
/// Use the given numbered file.
File {
file_num: u32,
},
/// Use the redirection mode requested by the caller, for a pre-return call.
Caller,
}