fix all of the error handling tests, mostly by making drain
handle external exit codes properly
This commit is contained in:
parent
6e683cd9f5
commit
ea3849da17
|
@ -115,6 +115,7 @@ impl BlockBuilder {
|
||||||
};
|
};
|
||||||
|
|
||||||
let allocate_result = match &instruction.item {
|
let allocate_result = match &instruction.item {
|
||||||
|
Instruction::Unreachable => Ok(()),
|
||||||
Instruction::LoadLiteral { dst, lit } => {
|
Instruction::LoadLiteral { dst, lit } => {
|
||||||
allocate(&[], &[*dst]).and(
|
allocate(&[], &[*dst]).and(
|
||||||
// Free any registers on the literal
|
// Free any registers on the literal
|
||||||
|
@ -485,6 +486,11 @@ impl BlockBuilder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark an unreachable code path. Produces an error at runtime if executed.
|
||||||
|
pub(crate) fn unreachable(&mut self, span: Span) -> Result<usize, CompileError> {
|
||||||
|
self.push(Instruction::Unreachable.into_spanned(span))
|
||||||
|
}
|
||||||
|
|
||||||
/// Consume the builder and produce the final [`IrBlock`].
|
/// Consume the builder and produce the final [`IrBlock`].
|
||||||
pub(crate) fn finish(self) -> IrBlock {
|
pub(crate) fn finish(self) -> IrBlock {
|
||||||
IrBlock {
|
IrBlock {
|
||||||
|
|
|
@ -335,29 +335,33 @@ pub(crate) fn compile_try(
|
||||||
) -> Result<(), CompileError> {
|
) -> Result<(), CompileError> {
|
||||||
// Pseudocode (literal block):
|
// Pseudocode (literal block):
|
||||||
//
|
//
|
||||||
// on-error-with ERR, %io_reg // or without
|
// on-error-into ERR, %io_reg // or without
|
||||||
// %io_reg <- <...block...> <- %io_reg
|
// %io_reg <- <...block...> <- %io_reg
|
||||||
// pop-error-handler
|
|
||||||
// check-external-failed %failed_reg, %io_reg
|
// check-external-failed %failed_reg, %io_reg
|
||||||
// branch-if %failed_reg, FAIL
|
// branch-if %failed_reg, FAIL
|
||||||
|
// pop-error-handler
|
||||||
// jump END
|
// jump END
|
||||||
// FAIL: drain %io_reg
|
// FAIL: drain %io_reg
|
||||||
// ERR: store-variable $err_var, %io_reg // or without
|
// unreachable
|
||||||
// %io_reg <- <...catch block...> // set to empty if no catch block
|
// ERR: clone %err_reg, %io_reg
|
||||||
|
// store-variable $err_var, %err_reg // or without
|
||||||
|
// %io_reg <- <...catch block...> <- %io_reg // set to empty if no catch block
|
||||||
// END:
|
// END:
|
||||||
//
|
//
|
||||||
// with expression that can't be inlined:
|
// with expression that can't be inlined:
|
||||||
//
|
//
|
||||||
// %closure_reg <- <catch_expr>
|
// %closure_reg <- <catch_expr>
|
||||||
// on-error-with ERR, %io_reg
|
// on-error-into ERR, %io_reg
|
||||||
// %io_reg <- <...block...> <- %io_reg
|
// %io_reg <- <...block...> <- %io_reg
|
||||||
// pop-error-handler
|
|
||||||
// check-external-failed %failed_reg, %io_reg
|
// check-external-failed %failed_reg, %io_reg
|
||||||
// branch-if %failed_reg, FAIL
|
// branch-if %failed_reg, FAIL
|
||||||
|
// pop-error-handler
|
||||||
// jump END
|
// jump END
|
||||||
// FAIL: drain %io_reg
|
// FAIL: drain %io_reg
|
||||||
// ERR: push-positional %closure_reg
|
// unreachable
|
||||||
// push-positional %io_reg
|
// ERR: clone %err_reg, %io_reg
|
||||||
|
// push-positional %closure_reg
|
||||||
|
// push-positional %err_reg
|
||||||
// call "do", %io_reg
|
// call "do", %io_reg
|
||||||
// END:
|
// END:
|
||||||
let invalid = || CompileError::InvalidKeywordCall {
|
let invalid = || CompileError::InvalidKeywordCall {
|
||||||
|
@ -410,18 +414,9 @@ pub(crate) fn compile_try(
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
// Put the error handler placeholder. If the catch argument is a non-block expression or a block
|
// Put the error handler placeholder. If we have a catch expression then we should capture the
|
||||||
// that takes an argument, we should capture the error into `io_reg` since we safely don't need
|
// error.
|
||||||
// that.
|
let error_handler_index = if catch_type.is_some() {
|
||||||
let error_handler_index = if matches!(
|
|
||||||
catch_type,
|
|
||||||
Some(
|
|
||||||
CatchType::Block {
|
|
||||||
var_id: Some(_),
|
|
||||||
..
|
|
||||||
} | CatchType::Closure { .. }
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
builder.push(
|
builder.push(
|
||||||
Instruction::OnErrorInto {
|
Instruction::OnErrorInto {
|
||||||
index: usize::MAX,
|
index: usize::MAX,
|
||||||
|
@ -444,9 +439,6 @@ pub(crate) fn compile_try(
|
||||||
io_reg,
|
io_reg,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Successful case: pop the error handler
|
|
||||||
builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?;
|
|
||||||
|
|
||||||
// Check for external command exit code failure, and also redirect that to the catch handler
|
// Check for external command exit code failure, and also redirect that to the catch handler
|
||||||
let failed_reg = builder.next_register()?;
|
let failed_reg = builder.next_register()?;
|
||||||
builder.push(
|
builder.push(
|
||||||
|
@ -458,14 +450,18 @@ pub(crate) fn compile_try(
|
||||||
)?;
|
)?;
|
||||||
let external_failed_branch = builder.branch_if_placeholder(failed_reg, catch_span)?;
|
let external_failed_branch = builder.branch_if_placeholder(failed_reg, catch_span)?;
|
||||||
|
|
||||||
|
// Successful case: pop the error handler
|
||||||
|
builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?;
|
||||||
|
|
||||||
// Jump over the failure case
|
// Jump over the failure case
|
||||||
let jump_index = builder.jump_placeholder(catch_span)?;
|
let jump_index = builder.jump_placeholder(catch_span)?;
|
||||||
|
|
||||||
// Set up an error handler preamble for failed external.
|
// Set up an error handler preamble for failed external.
|
||||||
// Draining the %io_reg results in Empty in that register, and also sets $env.LAST_EXIT_CODE
|
// Draining the %io_reg results in the error handler being called with Empty, and sets
|
||||||
// for us.
|
// $env.LAST_EXIT_CODE
|
||||||
builder.set_branch_target(external_failed_branch, builder.next_instruction_index())?;
|
builder.set_branch_target(external_failed_branch, builder.next_instruction_index())?;
|
||||||
builder.drain(io_reg, catch_span)?;
|
builder.drain(io_reg, catch_span)?;
|
||||||
|
builder.unreachable(catch_span)?;
|
||||||
|
|
||||||
// This is the real error handler - go back and set the right branch destination
|
// This is the real error handler - go back and set the right branch destination
|
||||||
builder.set_branch_target(error_handler_index, builder.next_instruction_index())?;
|
builder.set_branch_target(error_handler_index, builder.next_instruction_index())?;
|
||||||
|
@ -476,19 +472,35 @@ pub(crate) fn compile_try(
|
||||||
// Now compile whatever is necessary for the error handler
|
// Now compile whatever is necessary for the error handler
|
||||||
match catch_type {
|
match catch_type {
|
||||||
Some(CatchType::Block { block, var_id }) => {
|
Some(CatchType::Block { block, var_id }) => {
|
||||||
|
// Error will be in io_reg
|
||||||
|
builder.mark_register(io_reg)?;
|
||||||
if let Some(var_id) = var_id {
|
if let Some(var_id) = var_id {
|
||||||
// Error will be in io_reg
|
// Take a copy of the error as $err, since it will also be input
|
||||||
builder.mark_register(io_reg)?;
|
let err_reg = builder.next_register()?;
|
||||||
|
builder.push(
|
||||||
|
Instruction::Clone {
|
||||||
|
dst: err_reg,
|
||||||
|
src: io_reg,
|
||||||
|
}
|
||||||
|
.into_spanned(catch_span),
|
||||||
|
)?;
|
||||||
builder.push(
|
builder.push(
|
||||||
Instruction::StoreVariable {
|
Instruction::StoreVariable {
|
||||||
var_id,
|
var_id,
|
||||||
src: io_reg,
|
src: err_reg,
|
||||||
}
|
}
|
||||||
.into_spanned(catch_span),
|
.into_spanned(catch_span),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
// Compile the block, now that the variable is set
|
// Compile the block, now that the variable is set
|
||||||
compile_block(working_set, builder, block, redirect_modes, None, io_reg)?;
|
compile_block(
|
||||||
|
working_set,
|
||||||
|
builder,
|
||||||
|
block,
|
||||||
|
redirect_modes,
|
||||||
|
Some(io_reg),
|
||||||
|
io_reg,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
Some(CatchType::Closure { closure_reg }) => {
|
Some(CatchType::Closure { closure_reg }) => {
|
||||||
// We should call `do`. Error will be in io_reg
|
// We should call `do`. Error will be in io_reg
|
||||||
|
@ -498,17 +510,24 @@ pub(crate) fn compile_try(
|
||||||
span: call.head,
|
span: call.head,
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Take a copy of io_reg, because we pass it both as an argument and input
|
||||||
builder.mark_register(io_reg)?;
|
builder.mark_register(io_reg)?;
|
||||||
|
let err_reg = builder.next_register()?;
|
||||||
|
builder.push(
|
||||||
|
Instruction::Clone {
|
||||||
|
dst: err_reg,
|
||||||
|
src: io_reg,
|
||||||
|
}
|
||||||
|
.into_spanned(catch_span),
|
||||||
|
)?;
|
||||||
|
|
||||||
// Push the closure and the error
|
// Push the closure and the error
|
||||||
builder
|
builder
|
||||||
.push(Instruction::PushPositional { src: closure_reg }.into_spanned(catch_span))?;
|
.push(Instruction::PushPositional { src: closure_reg }.into_spanned(catch_span))?;
|
||||||
builder.push(Instruction::PushPositional { src: io_reg }.into_spanned(catch_span))?;
|
builder.push(Instruction::PushPositional { src: err_reg }.into_spanned(catch_span))?;
|
||||||
|
|
||||||
// Empty input to the block
|
// Call `$err | do $closure $err`
|
||||||
builder.load_empty(io_reg)?;
|
|
||||||
|
|
||||||
// Call `do $closure $err`
|
|
||||||
builder.push(
|
builder.push(
|
||||||
Instruction::Call {
|
Instruction::Call {
|
||||||
decl_id: do_decl_id,
|
decl_id: do_decl_id,
|
||||||
|
@ -540,10 +559,8 @@ pub(crate) fn compile_loop(
|
||||||
// Pseudocode:
|
// Pseudocode:
|
||||||
//
|
//
|
||||||
// drop %io_reg
|
// drop %io_reg
|
||||||
// LOOP: ...<block>...
|
// LOOP: %io_reg <- ...<block>...
|
||||||
// check-external-failed %failed_reg, %io_reg
|
|
||||||
// drain %io_reg
|
// drain %io_reg
|
||||||
// branch-if %failed_reg, END
|
|
||||||
// jump %LOOP
|
// jump %LOOP
|
||||||
// END: drop %io_reg
|
// END: drop %io_reg
|
||||||
let invalid = || CompileError::InvalidKeywordCall {
|
let invalid = || CompileError::InvalidKeywordCall {
|
||||||
|
@ -569,22 +586,12 @@ pub(crate) fn compile_loop(
|
||||||
io_reg,
|
io_reg,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Check for failed externals, and drain the output
|
// Drain the output, just like for a semicolon
|
||||||
let failed_reg = builder.next_register()?;
|
|
||||||
builder.push(
|
|
||||||
Instruction::CheckExternalFailed {
|
|
||||||
dst: failed_reg,
|
|
||||||
src: io_reg,
|
|
||||||
}
|
|
||||||
.into_spanned(call.head),
|
|
||||||
)?;
|
|
||||||
builder.drain(io_reg, call.head)?;
|
builder.drain(io_reg, call.head)?;
|
||||||
let failed_index = builder.branch_if_placeholder(failed_reg, call.head)?;
|
|
||||||
|
|
||||||
builder.jump(loop_index, call.head)?;
|
builder.jump(loop_index, call.head)?;
|
||||||
|
|
||||||
let end_index = builder.next_instruction_index();
|
let end_index = builder.next_instruction_index();
|
||||||
builder.set_branch_target(failed_index, end_index)?;
|
|
||||||
builder.end_loop(end_index, loop_index)?;
|
builder.end_loop(end_index, loop_index)?;
|
||||||
|
|
||||||
// State of %io_reg is not necessarily well defined here due to control flow, so make sure it's
|
// State of %io_reg is not necessarily well defined here due to control flow, so make sure it's
|
||||||
|
@ -605,14 +612,11 @@ pub(crate) fn compile_while(
|
||||||
) -> Result<(), CompileError> {
|
) -> Result<(), CompileError> {
|
||||||
// Pseudocode:
|
// Pseudocode:
|
||||||
//
|
//
|
||||||
// drop %io_reg
|
|
||||||
// LOOP: %io_reg <- <condition>
|
// LOOP: %io_reg <- <condition>
|
||||||
// branch-if %io_reg, TRUE
|
// branch-if %io_reg, TRUE
|
||||||
// jump FALSE
|
// jump FALSE
|
||||||
// TRUE: ...<block>...
|
// TRUE: %io_reg <- ...<block>...
|
||||||
// check-external-failed %failed_reg, %io_reg
|
|
||||||
// drain %io_reg
|
// drain %io_reg
|
||||||
// branch-if %failed_reg, FALSE
|
|
||||||
// jump LOOP
|
// jump LOOP
|
||||||
// FALSE: drop %io_reg
|
// FALSE: drop %io_reg
|
||||||
let invalid = || CompileError::InvalidKeywordCall {
|
let invalid = || CompileError::InvalidKeywordCall {
|
||||||
|
@ -626,7 +630,6 @@ pub(crate) fn compile_while(
|
||||||
let block = working_set.get_block(block_id);
|
let block = working_set.get_block(block_id);
|
||||||
|
|
||||||
builder.begin_loop();
|
builder.begin_loop();
|
||||||
builder.load_empty(io_reg)?;
|
|
||||||
|
|
||||||
let loop_index = builder.next_instruction_index();
|
let loop_index = builder.next_instruction_index();
|
||||||
|
|
||||||
|
@ -653,23 +656,13 @@ pub(crate) fn compile_while(
|
||||||
io_reg,
|
io_reg,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// We have to check the result for a failed external, and drain the output on each iteration
|
// Drain the result, just like for a semicolon
|
||||||
let failed_reg = builder.next_register()?;
|
|
||||||
builder.push(
|
|
||||||
Instruction::CheckExternalFailed {
|
|
||||||
dst: failed_reg,
|
|
||||||
src: io_reg,
|
|
||||||
}
|
|
||||||
.into_spanned(call.head),
|
|
||||||
)?;
|
|
||||||
builder.drain(io_reg, call.head)?;
|
builder.drain(io_reg, call.head)?;
|
||||||
let failed_index = builder.branch_if_placeholder(failed_reg, call.head)?;
|
|
||||||
|
|
||||||
builder.jump(loop_index, call.head)?;
|
builder.jump(loop_index, call.head)?;
|
||||||
|
|
||||||
let end_index = builder.next_instruction_index();
|
let end_index = builder.next_instruction_index();
|
||||||
builder.set_branch_target(jump_false_index, end_index)?;
|
builder.set_branch_target(jump_false_index, end_index)?;
|
||||||
builder.set_branch_target(failed_index, end_index)?;
|
|
||||||
|
|
||||||
builder.end_loop(end_index, loop_index)?;
|
builder.end_loop(end_index, loop_index)?;
|
||||||
|
|
||||||
|
@ -695,9 +688,7 @@ pub(crate) fn compile_for(
|
||||||
// LOOP: iterate %io_reg, %stream_reg, END
|
// LOOP: iterate %io_reg, %stream_reg, END
|
||||||
// store-variable $var, %io_reg
|
// store-variable $var, %io_reg
|
||||||
// %io_reg <- <...block...>
|
// %io_reg <- <...block...>
|
||||||
// check-external-failed %failed_reg, %io_reg
|
|
||||||
// drain %io_reg
|
// drain %io_reg
|
||||||
// branch-if %failed_reg, END
|
|
||||||
// jump LOOP
|
// jump LOOP
|
||||||
// END: drop %io_reg
|
// END: drop %io_reg
|
||||||
let invalid = || CompileError::InvalidKeywordCall {
|
let invalid = || CompileError::InvalidKeywordCall {
|
||||||
|
@ -767,28 +758,14 @@ pub(crate) fn compile_for(
|
||||||
io_reg,
|
io_reg,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Check for failed external
|
// Drain the output, just like for a semicolon
|
||||||
let failed_reg = builder.next_register()?;
|
|
||||||
builder.push(
|
|
||||||
Instruction::CheckExternalFailed {
|
|
||||||
dst: failed_reg,
|
|
||||||
src: io_reg,
|
|
||||||
}
|
|
||||||
.into_spanned(call.head),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Drain the output
|
|
||||||
builder.drain(io_reg, call.head)?;
|
builder.drain(io_reg, call.head)?;
|
||||||
|
|
||||||
// End the loop if there was a failure
|
|
||||||
let failed_index = builder.branch_if_placeholder(failed_reg, call.head)?;
|
|
||||||
|
|
||||||
// Loop back to iterate to get the next value
|
// Loop back to iterate to get the next value
|
||||||
builder.jump(iterate_index, call.head)?;
|
builder.jump(iterate_index, call.head)?;
|
||||||
|
|
||||||
// Update the iterate target to the end of the loop
|
// Update the iterate target to the end of the loop
|
||||||
let end_index = builder.next_instruction_index();
|
let end_index = builder.next_instruction_index();
|
||||||
builder.set_branch_target(failed_index, end_index)?;
|
|
||||||
builder.set_branch_target(iterate_index, end_index)?;
|
builder.set_branch_target(iterate_index, end_index)?;
|
||||||
|
|
||||||
// Handle break/continue
|
// Handle break/continue
|
||||||
|
|
|
@ -192,10 +192,22 @@ fn eval_ir_block_impl<D: DebugContext>(
|
||||||
Ok(InstructionResult::Return(reg_id)) => {
|
Ok(InstructionResult::Return(reg_id)) => {
|
||||||
return Ok(ctx.take_reg(reg_id));
|
return Ok(ctx.take_reg(reg_id));
|
||||||
}
|
}
|
||||||
|
Ok(InstructionResult::ExitCode(exit_code)) => {
|
||||||
|
if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
|
||||||
|
// If an error handler is set, branch there
|
||||||
|
prepare_error_handler(ctx, error_handler, None);
|
||||||
|
pc = error_handler.handler_index;
|
||||||
|
} else {
|
||||||
|
// If not, exit the block with the exit code
|
||||||
|
return Ok(PipelineData::new_external_stream_with_only_exit_code(
|
||||||
|
exit_code,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
|
if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
|
||||||
// If an error handler is set, branch there
|
// If an error handler is set, branch there
|
||||||
prepare_error_handler(ctx, error_handler, err.into_spanned(*span));
|
prepare_error_handler(ctx, error_handler, Some(err.into_spanned(*span)));
|
||||||
pc = error_handler.handler_index;
|
pc = error_handler.handler_index;
|
||||||
} else {
|
} else {
|
||||||
// If not, exit the block with the error
|
// If not, exit the block with the error
|
||||||
|
@ -219,19 +231,24 @@ fn eval_ir_block_impl<D: DebugContext>(
|
||||||
fn prepare_error_handler(
|
fn prepare_error_handler(
|
||||||
ctx: &mut EvalContext<'_>,
|
ctx: &mut EvalContext<'_>,
|
||||||
error_handler: ErrorHandler,
|
error_handler: ErrorHandler,
|
||||||
error: Spanned<ShellError>,
|
error: Option<Spanned<ShellError>>,
|
||||||
) {
|
) {
|
||||||
if let Some(reg_id) = error_handler.error_register {
|
if let Some(reg_id) = error_handler.error_register {
|
||||||
// Create the error value and put it in the register
|
if let Some(error) = error {
|
||||||
let value = Value::record(
|
// Create the error value and put it in the register
|
||||||
record! {
|
let value = Value::record(
|
||||||
"msg" => Value::string(format!("{}", error.item), error.span),
|
record! {
|
||||||
"debug" => Value::string(format!("{:?}", error.item), error.span),
|
"msg" => Value::string(format!("{}", error.item), error.span),
|
||||||
"raw" => Value::error(error.item, error.span),
|
"debug" => Value::string(format!("{:?}", error.item), error.span),
|
||||||
},
|
"raw" => Value::error(error.item, error.span),
|
||||||
error.span,
|
},
|
||||||
);
|
error.span,
|
||||||
ctx.put_reg(reg_id, PipelineData::Value(value, None));
|
);
|
||||||
|
ctx.put_reg(reg_id, PipelineData::Value(value, None));
|
||||||
|
} else {
|
||||||
|
// Set the register to empty
|
||||||
|
ctx.put_reg(reg_id, PipelineData::Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +258,7 @@ enum InstructionResult {
|
||||||
Continue,
|
Continue,
|
||||||
Branch(usize),
|
Branch(usize),
|
||||||
Return(RegId),
|
Return(RegId),
|
||||||
|
ExitCode(i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform an instruction
|
/// Perform an instruction
|
||||||
|
@ -253,6 +271,10 @@ fn eval_instruction<D: DebugContext>(
|
||||||
use self::InstructionResult::*;
|
use self::InstructionResult::*;
|
||||||
|
|
||||||
match instruction {
|
match instruction {
|
||||||
|
Instruction::Unreachable => Err(ShellError::IrEvalError {
|
||||||
|
msg: "Reached unreachable code".into(),
|
||||||
|
span: Some(*span),
|
||||||
|
}),
|
||||||
Instruction::LoadLiteral { dst, lit } => load_literal(ctx, *dst, lit, *span),
|
Instruction::LoadLiteral { dst, lit } => load_literal(ctx, *dst, lit, *span),
|
||||||
Instruction::LoadValue { dst, val } => {
|
Instruction::LoadValue { dst, val } => {
|
||||||
ctx.put_reg(*dst, Value::clone(val).into_pipeline_data());
|
ctx.put_reg(*dst, Value::clone(val).into_pipeline_data());
|
||||||
|
@ -285,8 +307,14 @@ fn eval_instruction<D: DebugContext>(
|
||||||
"LAST_EXIT_CODE".into(),
|
"LAST_EXIT_CODE".into(),
|
||||||
Value::int(exit_status.code() as i64, *span),
|
Value::int(exit_status.code() as i64, *span),
|
||||||
);
|
);
|
||||||
|
if exit_status.code() == 0 {
|
||||||
|
Ok(Continue)
|
||||||
|
} else {
|
||||||
|
Ok(ExitCode(exit_status.code()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Continue)
|
||||||
}
|
}
|
||||||
Ok(Continue)
|
|
||||||
}
|
}
|
||||||
Instruction::LoadVariable { dst, var_id } => {
|
Instruction::LoadVariable { dst, var_id } => {
|
||||||
let value = get_var(ctx, *var_id, *span)?;
|
let value = get_var(ctx, *var_id, *span)?;
|
||||||
|
|
|
@ -57,6 +57,9 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
|
||||||
const WIDTH: usize = 22;
|
const WIDTH: usize = 22;
|
||||||
|
|
||||||
match self.instruction {
|
match self.instruction {
|
||||||
|
Instruction::Unreachable => {
|
||||||
|
write!(f, "{:WIDTH$}", "unreachable")
|
||||||
|
}
|
||||||
Instruction::LoadLiteral { dst, lit } => {
|
Instruction::LoadLiteral { dst, lit } => {
|
||||||
let lit = FmtLiteral {
|
let lit = FmtLiteral {
|
||||||
literal: lit,
|
literal: lit,
|
||||||
|
|
|
@ -98,6 +98,8 @@ impl<'de> Deserialize<'de> for IrAstRef {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum Instruction {
|
pub enum Instruction {
|
||||||
|
/// Unreachable code path (error)
|
||||||
|
Unreachable,
|
||||||
/// Load a literal value into the `dst` register
|
/// Load a literal value into the `dst` register
|
||||||
LoadLiteral { dst: RegId, lit: Literal },
|
LoadLiteral { dst: RegId, lit: Literal },
|
||||||
/// Load a clone of a boxed value into the `dst` register (e.g. from const evaluation)
|
/// Load a clone of a boxed value into the `dst` register (e.g. from const evaluation)
|
||||||
|
@ -110,7 +112,11 @@ pub enum Instruction {
|
||||||
Collect { src_dst: RegId },
|
Collect { src_dst: RegId },
|
||||||
/// Drop the value/straem in a register, without draining
|
/// Drop the value/straem in a register, without draining
|
||||||
Drop { src: RegId },
|
Drop { src: RegId },
|
||||||
/// Drain the value/stream in a register and discard (e.g. semicolon)
|
/// Drain the value/stream in a register and discard (e.g. semicolon).
|
||||||
|
///
|
||||||
|
/// If passed a stream from an external command, sets $env.LAST_EXIT_CODE to the resulting exit
|
||||||
|
/// code, and invokes any available error handler with Empty, or if not available, returns an
|
||||||
|
/// exit-code-only stream, leaving the block.
|
||||||
Drain { src: RegId },
|
Drain { src: RegId },
|
||||||
/// Load the value of a variable into the `dst` register
|
/// Load the value of a variable into the `dst` register
|
||||||
LoadVariable { dst: RegId, var_id: VarId },
|
LoadVariable { dst: RegId, var_id: VarId },
|
||||||
|
|
Loading…
Reference in New Issue
Block a user