try/catch (wip)
This commit is contained in:
parent
b3b59c8f9c
commit
7a055563a9
|
@ -197,6 +197,12 @@ impl BlockBuilder {
|
||||||
stream: _,
|
stream: _,
|
||||||
end_index: _,
|
end_index: _,
|
||||||
} => self.mark_register(*dst)?,
|
} => self.mark_register(*dst)?,
|
||||||
|
Instruction::PushErrorHandler { index: _ } => (),
|
||||||
|
Instruction::PushErrorHandlerVar {
|
||||||
|
index: _,
|
||||||
|
error_var: _,
|
||||||
|
} => (),
|
||||||
|
Instruction::PopErrorHandler => (),
|
||||||
Instruction::Return { src } => self.free_register(*src)?,
|
Instruction::Return { src } => self.free_register(*src)?,
|
||||||
}
|
}
|
||||||
let index = self.next_instruction_index();
|
let index = self.next_instruction_index();
|
||||||
|
@ -299,7 +305,7 @@ impl BlockBuilder {
|
||||||
self.jump(usize::MAX, span)
|
self.jump(usize::MAX, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modify a `branch-if`, `jump`, or `iterate` instruction's branch target `index`
|
/// Modify a branching instruction's branch target `index`
|
||||||
pub(crate) fn set_branch_target(
|
pub(crate) fn set_branch_target(
|
||||||
&mut self,
|
&mut self,
|
||||||
instruction_index: usize,
|
instruction_index: usize,
|
||||||
|
@ -311,7 +317,9 @@ impl BlockBuilder {
|
||||||
| Instruction::Jump { index }
|
| Instruction::Jump { index }
|
||||||
| Instruction::Iterate {
|
| Instruction::Iterate {
|
||||||
end_index: index, ..
|
end_index: index, ..
|
||||||
},
|
}
|
||||||
|
| Instruction::PushErrorHandler { index }
|
||||||
|
| Instruction::PushErrorHandlerVar { index, .. },
|
||||||
) => {
|
) => {
|
||||||
*index = target_index;
|
*index = target_index;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -31,6 +31,9 @@ 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);
|
||||||
}
|
}
|
||||||
|
"try" => {
|
||||||
|
return compile_try(working_set, builder, call, redirect_modes, io_reg);
|
||||||
|
}
|
||||||
"loop" => {
|
"loop" => {
|
||||||
return compile_loop(working_set, builder, call, redirect_modes, io_reg);
|
return compile_loop(working_set, builder, call, redirect_modes, io_reg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,100 @@ pub(crate) fn compile_let(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compile a call to `try`, setting an error handler over the evaluated block
|
||||||
|
pub(crate) fn compile_try(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
builder: &mut BlockBuilder,
|
||||||
|
call: &Call,
|
||||||
|
redirect_modes: RedirectModes,
|
||||||
|
io_reg: RegId,
|
||||||
|
) -> Result<(), CompileError> {
|
||||||
|
// Pseudocode:
|
||||||
|
//
|
||||||
|
// push-error-handler-var ERR, $err // or without var
|
||||||
|
// %io_reg <- <...block...> <- %io_reg
|
||||||
|
// pop-error-handler
|
||||||
|
// jump END
|
||||||
|
// ERR: %io_reg <- <...catch block...> // set to empty if none
|
||||||
|
// END:
|
||||||
|
let invalid = || CompileError::InvalidKeywordCall {
|
||||||
|
keyword: "try".into(),
|
||||||
|
span: call.head,
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_arg = call.positional_nth(0).ok_or_else(invalid)?;
|
||||||
|
let block_id = block_arg.as_block().ok_or_else(invalid)?;
|
||||||
|
let block = working_set.get_block(block_id);
|
||||||
|
|
||||||
|
let catch_block = match call.positional_nth(1) {
|
||||||
|
Some(kw_expr) => {
|
||||||
|
let catch_expr = kw_expr.as_keyword().ok_or_else(invalid)?;
|
||||||
|
let catch_block_id = catch_expr.as_block().ok_or_else(invalid)?;
|
||||||
|
Some(working_set.get_block(catch_block_id))
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let catch_var_id = catch_block
|
||||||
|
.and_then(|b| b.signature.get_positional(0))
|
||||||
|
.and_then(|v| v.var_id);
|
||||||
|
|
||||||
|
// Put the error handler placeholder
|
||||||
|
let error_handler_index = if let Some(catch_var_id) = catch_var_id {
|
||||||
|
builder.push(
|
||||||
|
Instruction::PushErrorHandlerVar {
|
||||||
|
index: usize::MAX,
|
||||||
|
error_var: catch_var_id,
|
||||||
|
}
|
||||||
|
.into_spanned(call.head),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
builder.push(Instruction::PushErrorHandler { index: usize::MAX }.into_spanned(call.head))?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compile the block
|
||||||
|
compile_block(
|
||||||
|
working_set,
|
||||||
|
builder,
|
||||||
|
block,
|
||||||
|
redirect_modes.clone(),
|
||||||
|
Some(io_reg),
|
||||||
|
io_reg,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Successful case: pop the error handler
|
||||||
|
builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?;
|
||||||
|
|
||||||
|
// Jump over the failure case
|
||||||
|
let jump_index =
|
||||||
|
builder.jump_placeholder(catch_block.and_then(|b| b.span).unwrap_or(call.head))?;
|
||||||
|
|
||||||
|
// This is the error handler - go back and set the right branch destination
|
||||||
|
builder.set_branch_target(error_handler_index, builder.next_instruction_index())?;
|
||||||
|
|
||||||
|
// Mark out register as likely not clean - state in error handler is not well defined
|
||||||
|
builder.mark_register(io_reg)?;
|
||||||
|
|
||||||
|
// If we have a catch block, compile that
|
||||||
|
if let Some(catch_block) = catch_block {
|
||||||
|
compile_block(
|
||||||
|
working_set,
|
||||||
|
builder,
|
||||||
|
catch_block,
|
||||||
|
redirect_modes,
|
||||||
|
None,
|
||||||
|
io_reg,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// Otherwise just set out to empty.
|
||||||
|
builder.load_empty(io_reg)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the end - if we succeeded, should jump here
|
||||||
|
builder.set_branch_target(jump_index, builder.next_instruction_index())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Compile a call to `loop` (via `jump`)
|
/// Compile a call to `loop` (via `jump`)
|
||||||
pub(crate) fn compile_loop(
|
pub(crate) fn compile_loop(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use nu_path::expand_path_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator},
|
ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator},
|
||||||
debugger::DebugContext,
|
debugger::DebugContext,
|
||||||
engine::{Argument, Closure, EngineState, Redirection, Stack},
|
engine::{Argument, Closure, EngineState, ErrorHandler, Redirection, Stack},
|
||||||
ir::{Call, DataSlice, Instruction, IrBlock, Literal, RedirectMode},
|
ir::{Call, DataSlice, Instruction, IrBlock, Literal, RedirectMode},
|
||||||
DeclId, IntoPipelineData, IntoSpanned, ListStream, OutDest, PipelineData, Range, Record, RegId,
|
DeclId, IntoPipelineData, IntoSpanned, ListStream, OutDest, PipelineData, Range, Record, RegId,
|
||||||
ShellError, Span, Value, VarId,
|
ShellError, Span, Value, VarId,
|
||||||
|
@ -24,7 +24,8 @@ pub fn eval_ir_block<D: DebugContext>(
|
||||||
|
|
||||||
let block_span = block.span;
|
let block_span = block.span;
|
||||||
|
|
||||||
let args_base = stack.argument_stack.get_base();
|
let args_base = stack.arguments.get_base();
|
||||||
|
let error_handler_base = stack.error_handlers.get_base();
|
||||||
let mut registers = stack.register_buf_cache.acquire(ir_block.register_count);
|
let mut registers = stack.register_buf_cache.acquire(ir_block.register_count);
|
||||||
|
|
||||||
let result = eval_ir_block_impl::<D>(
|
let result = eval_ir_block_impl::<D>(
|
||||||
|
@ -33,6 +34,7 @@ pub fn eval_ir_block<D: DebugContext>(
|
||||||
stack,
|
stack,
|
||||||
data: &ir_block.data,
|
data: &ir_block.data,
|
||||||
args_base,
|
args_base,
|
||||||
|
error_handler_base,
|
||||||
redirect_out: None,
|
redirect_out: None,
|
||||||
redirect_err: None,
|
redirect_err: None,
|
||||||
registers: &mut registers[..],
|
registers: &mut registers[..],
|
||||||
|
@ -43,6 +45,8 @@ pub fn eval_ir_block<D: DebugContext>(
|
||||||
);
|
);
|
||||||
|
|
||||||
stack.register_buf_cache.release(registers);
|
stack.register_buf_cache.release(registers);
|
||||||
|
stack.error_handlers.leave_frame(error_handler_base);
|
||||||
|
stack.arguments.leave_frame(args_base);
|
||||||
|
|
||||||
D::leave_block(engine_state, block);
|
D::leave_block(engine_state, block);
|
||||||
|
|
||||||
|
@ -66,6 +70,8 @@ struct EvalContext<'a> {
|
||||||
data: &'a Arc<[u8]>,
|
data: &'a Arc<[u8]>,
|
||||||
/// Base index on the argument stack to reset to after a call
|
/// Base index on the argument stack to reset to after a call
|
||||||
args_base: usize,
|
args_base: usize,
|
||||||
|
/// Base index on the error handler stack to reset to after a call
|
||||||
|
error_handler_base: usize,
|
||||||
/// State set by redirect-out
|
/// State set by redirect-out
|
||||||
redirect_out: Option<Redirection>,
|
redirect_out: Option<Redirection>,
|
||||||
/// State set by redirect-err
|
/// State set by redirect-err
|
||||||
|
@ -122,16 +128,26 @@ fn eval_ir_block_impl<D: DebugContext>(
|
||||||
"{pc:-4}: {}",
|
"{pc:-4}: {}",
|
||||||
instruction.display(ctx.engine_state, ctx.data)
|
instruction.display(ctx.engine_state, ctx.data)
|
||||||
);
|
);
|
||||||
match eval_instruction(ctx, instruction, span)? {
|
match eval_instruction(ctx, instruction, span) {
|
||||||
InstructionResult::Continue => {
|
Ok(InstructionResult::Continue) => {
|
||||||
pc += 1;
|
pc += 1;
|
||||||
}
|
}
|
||||||
InstructionResult::Branch(next_pc) => {
|
Ok(InstructionResult::Branch(next_pc)) => {
|
||||||
pc = next_pc;
|
pc = next_pc;
|
||||||
}
|
}
|
||||||
InstructionResult::Return(reg_id) => {
|
Ok(InstructionResult::Return(reg_id)) => {
|
||||||
return Ok(ctx.take_reg(reg_id));
|
return Ok(ctx.take_reg(reg_id));
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) {
|
||||||
|
// If an error handler is set, branch there
|
||||||
|
error_handler.prepare_stack(ctx.engine_state, &mut ctx.stack, err);
|
||||||
|
pc = error_handler.handler_index;
|
||||||
|
} else {
|
||||||
|
// If not, exit the block with the error
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,19 +270,19 @@ fn eval_instruction(
|
||||||
Instruction::PushPositional { src } => {
|
Instruction::PushPositional { src } => {
|
||||||
let val = ctx.collect_reg(*src, *span)?;
|
let val = ctx.collect_reg(*src, *span)?;
|
||||||
ctx.stack
|
ctx.stack
|
||||||
.argument_stack
|
.arguments
|
||||||
.push(Argument::Positional { span: *span, val });
|
.push(Argument::Positional { span: *span, val });
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
}
|
}
|
||||||
Instruction::AppendRest { src } => {
|
Instruction::AppendRest { src } => {
|
||||||
let vals = ctx.collect_reg(*src, *span)?;
|
let vals = ctx.collect_reg(*src, *span)?;
|
||||||
ctx.stack
|
ctx.stack
|
||||||
.argument_stack
|
.arguments
|
||||||
.push(Argument::Spread { span: *span, vals });
|
.push(Argument::Spread { span: *span, vals });
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
}
|
}
|
||||||
Instruction::PushFlag { name } => {
|
Instruction::PushFlag { name } => {
|
||||||
ctx.stack.argument_stack.push(Argument::Flag {
|
ctx.stack.arguments.push(Argument::Flag {
|
||||||
data: ctx.data.clone(),
|
data: ctx.data.clone(),
|
||||||
name: *name,
|
name: *name,
|
||||||
span: *span,
|
span: *span,
|
||||||
|
@ -275,7 +291,7 @@ fn eval_instruction(
|
||||||
}
|
}
|
||||||
Instruction::PushNamed { name, src } => {
|
Instruction::PushNamed { name, src } => {
|
||||||
let val = ctx.collect_reg(*src, *span)?;
|
let val = ctx.collect_reg(*src, *span)?;
|
||||||
ctx.stack.argument_stack.push(Argument::Named {
|
ctx.stack.arguments.push(Argument::Named {
|
||||||
data: ctx.data.clone(),
|
data: ctx.data.clone(),
|
||||||
name: *name,
|
name: *name,
|
||||||
span: *span,
|
span: *span,
|
||||||
|
@ -469,6 +485,24 @@ fn eval_instruction(
|
||||||
stream,
|
stream,
|
||||||
end_index,
|
end_index,
|
||||||
} => eval_iterate(ctx, *dst, *stream, *end_index),
|
} => eval_iterate(ctx, *dst, *stream, *end_index),
|
||||||
|
Instruction::PushErrorHandler { index } => {
|
||||||
|
ctx.stack.error_handlers.push(ErrorHandler {
|
||||||
|
handler_index: *index,
|
||||||
|
error_variable: None,
|
||||||
|
});
|
||||||
|
Ok(Continue)
|
||||||
|
}
|
||||||
|
Instruction::PushErrorHandlerVar { index, error_var } => {
|
||||||
|
ctx.stack.error_handlers.push(ErrorHandler {
|
||||||
|
handler_index: *index,
|
||||||
|
error_variable: Some(*error_var),
|
||||||
|
});
|
||||||
|
Ok(Continue)
|
||||||
|
}
|
||||||
|
Instruction::PopErrorHandler => {
|
||||||
|
ctx.stack.error_handlers.pop(ctx.error_handler_base);
|
||||||
|
Ok(Continue)
|
||||||
|
}
|
||||||
Instruction::Return { src } => Ok(Return(*src)),
|
Instruction::Return { src } => Ok(Return(*src)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -651,7 +685,7 @@ fn eval_call(
|
||||||
} = ctx;
|
} = ctx;
|
||||||
|
|
||||||
// TODO: handle block eval
|
// TODO: handle block eval
|
||||||
let args_len = stack.argument_stack.get_len(*args_base);
|
let args_len = stack.arguments.get_len(*args_base);
|
||||||
let decl = engine_state.get_decl(decl_id);
|
let decl = engine_state.get_decl(decl_id);
|
||||||
|
|
||||||
// Set up redirect modes
|
// Set up redirect modes
|
||||||
|
@ -659,7 +693,7 @@ fn eval_call(
|
||||||
|
|
||||||
// should this be precalculated? ideally we just use the call builder...
|
// should this be precalculated? ideally we just use the call builder...
|
||||||
let span = stack
|
let span = stack
|
||||||
.argument_stack
|
.arguments
|
||||||
.get_args(*args_base, args_len)
|
.get_args(*args_base, args_len)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.fold(head, |span, arg| {
|
.fold(head, |span, arg| {
|
||||||
|
@ -678,7 +712,7 @@ fn eval_call(
|
||||||
// Run the call
|
// Run the call
|
||||||
let result = decl.run(engine_state, &mut stack, &(&call).into(), input);
|
let result = decl.run(engine_state, &mut stack, &(&call).into(), input);
|
||||||
// Important that this runs:
|
// Important that this runs:
|
||||||
stack.argument_stack.leave_frame(ctx.args_base);
|
stack.arguments.leave_frame(ctx.args_base);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
75
crates/nu-protocol/src/engine/error_handler.rs
Normal file
75
crates/nu-protocol/src/engine/error_handler.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use crate::{record, ShellError, Value, VarId};
|
||||||
|
|
||||||
|
use super::{EngineState, Stack};
|
||||||
|
|
||||||
|
/// Describes an error handler stored during IR evaluation.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ErrorHandler {
|
||||||
|
/// Instruction index within the block that will handle the error
|
||||||
|
pub handler_index: usize,
|
||||||
|
/// Variable to put the error information into, when an error occurs
|
||||||
|
pub error_variable: Option<VarId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorHandler {
|
||||||
|
/// Add `error_variable` to the stack with the error value.
|
||||||
|
pub fn prepare_stack(&self, engine_state: &EngineState, stack: &mut Stack, error: ShellError) {
|
||||||
|
if let Some(var_id) = self.error_variable {
|
||||||
|
let span = engine_state.get_var(var_id).declaration_span;
|
||||||
|
let value = Value::record(
|
||||||
|
record! {
|
||||||
|
"msg" => Value::string(format!("{}", error), span),
|
||||||
|
"debug" => Value::string(format!("{:?}", error), span),
|
||||||
|
"raw" => Value::error(error, span),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
stack.add_var(var_id, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeps track of error handlers pushed during evaluation of an IR block.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ErrorHandlerStack {
|
||||||
|
handlers: Vec<ErrorHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorHandlerStack {
|
||||||
|
pub const fn new() -> ErrorHandlerStack {
|
||||||
|
ErrorHandlerStack { handlers: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current base of the stack, which establishes a frame.
|
||||||
|
pub fn get_base(&self) -> usize {
|
||||||
|
self.handlers.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new error handler onto the stack.
|
||||||
|
pub fn push(&mut self, handler: ErrorHandler) {
|
||||||
|
self.handlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to pop an error handler from the stack. Won't go below `base`, to avoid retrieving a
|
||||||
|
/// handler belonging to a parent frame.
|
||||||
|
pub fn pop(&mut self, base: usize) -> Option<ErrorHandler> {
|
||||||
|
if self.handlers.len() > base {
|
||||||
|
self.handlers.pop()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the stack to the state it was in at the beginning of the frame, in preparation to
|
||||||
|
/// return control to the parent frame.
|
||||||
|
pub fn leave_frame(&mut self, base: usize) {
|
||||||
|
if self.handlers.len() >= base {
|
||||||
|
self.handlers.truncate(base);
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"ErrorHandlerStack bug: tried to leave frame at {base}, but current base is {}",
|
||||||
|
self.get_base()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ mod call_info;
|
||||||
mod capture_block;
|
mod capture_block;
|
||||||
mod command;
|
mod command;
|
||||||
mod engine_state;
|
mod engine_state;
|
||||||
|
mod error_handler;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
mod pattern_match;
|
mod pattern_match;
|
||||||
mod register_buf_cache;
|
mod register_buf_cache;
|
||||||
|
@ -23,6 +24,7 @@ pub use call_info::*;
|
||||||
pub use capture_block::*;
|
pub use capture_block::*;
|
||||||
pub use command::*;
|
pub use command::*;
|
||||||
pub use engine_state::*;
|
pub use engine_state::*;
|
||||||
|
pub use error_handler::*;
|
||||||
pub use overlay::*;
|
pub use overlay::*;
|
||||||
pub use pattern_match::*;
|
pub use pattern_match::*;
|
||||||
pub use register_buf_cache::*;
|
pub use register_buf_cache::*;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::{
|
engine::{
|
||||||
EngineState, Redirection, StackCallArgGuard, StackCaptureGuard, StackIoGuard, StackOutDest,
|
ArgumentStack, EngineState, ErrorHandlerStack, Redirection, RegisterBufCache,
|
||||||
DEFAULT_OVERLAY_NAME,
|
StackCallArgGuard, StackCaptureGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME,
|
||||||
},
|
},
|
||||||
OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID,
|
OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
|
@ -11,8 +11,6 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ArgumentStack, RegisterBufCache};
|
|
||||||
|
|
||||||
/// Environment variables per overlay
|
/// Environment variables per overlay
|
||||||
pub type EnvVars = HashMap<String, HashMap<String, Value>>;
|
pub type EnvVars = HashMap<String, HashMap<String, Value>>;
|
||||||
|
|
||||||
|
@ -46,7 +44,9 @@ pub struct Stack {
|
||||||
/// Cached register buffers for IR evaluation
|
/// Cached register buffers for IR evaluation
|
||||||
pub register_buf_cache: RegisterBufCache,
|
pub register_buf_cache: RegisterBufCache,
|
||||||
/// Argument stack for IR evaluation
|
/// Argument stack for IR evaluation
|
||||||
pub argument_stack: ArgumentStack,
|
pub arguments: ArgumentStack,
|
||||||
|
/// Error handler stack for IR evaluation
|
||||||
|
pub error_handlers: ErrorHandlerStack,
|
||||||
/// Set true to always use IR mode
|
/// Set true to always use IR mode
|
||||||
pub use_ir: bool,
|
pub use_ir: bool,
|
||||||
pub recursion_count: u64,
|
pub recursion_count: u64,
|
||||||
|
@ -77,7 +77,8 @@ impl Stack {
|
||||||
env_hidden: HashMap::new(),
|
env_hidden: HashMap::new(),
|
||||||
active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
|
active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()],
|
||||||
register_buf_cache: RegisterBufCache::new(),
|
register_buf_cache: RegisterBufCache::new(),
|
||||||
argument_stack: ArgumentStack::new(),
|
arguments: ArgumentStack::new(),
|
||||||
|
error_handlers: ErrorHandlerStack::new(),
|
||||||
use_ir: false,
|
use_ir: false,
|
||||||
recursion_count: 0,
|
recursion_count: 0,
|
||||||
parent_stack: None,
|
parent_stack: None,
|
||||||
|
@ -97,7 +98,8 @@ impl Stack {
|
||||||
env_hidden: parent.env_hidden.clone(),
|
env_hidden: parent.env_hidden.clone(),
|
||||||
active_overlays: parent.active_overlays.clone(),
|
active_overlays: parent.active_overlays.clone(),
|
||||||
register_buf_cache: RegisterBufCache::new(),
|
register_buf_cache: RegisterBufCache::new(),
|
||||||
argument_stack: ArgumentStack::new(),
|
arguments: ArgumentStack::new(),
|
||||||
|
error_handlers: ErrorHandlerStack::new(),
|
||||||
use_ir: parent.use_ir,
|
use_ir: parent.use_ir,
|
||||||
recursion_count: parent.recursion_count,
|
recursion_count: parent.recursion_count,
|
||||||
vars: vec![],
|
vars: vec![],
|
||||||
|
@ -269,7 +271,8 @@ impl Stack {
|
||||||
env_hidden: self.env_hidden.clone(),
|
env_hidden: self.env_hidden.clone(),
|
||||||
active_overlays: self.active_overlays.clone(),
|
active_overlays: self.active_overlays.clone(),
|
||||||
register_buf_cache: RegisterBufCache::new(),
|
register_buf_cache: RegisterBufCache::new(),
|
||||||
argument_stack: ArgumentStack::new(),
|
arguments: ArgumentStack::new(),
|
||||||
|
error_handlers: ErrorHandlerStack::new(),
|
||||||
use_ir: self.use_ir,
|
use_ir: self.use_ir,
|
||||||
recursion_count: self.recursion_count,
|
recursion_count: self.recursion_count,
|
||||||
parent_stack: None,
|
parent_stack: None,
|
||||||
|
@ -302,7 +305,8 @@ impl Stack {
|
||||||
env_hidden: self.env_hidden.clone(),
|
env_hidden: self.env_hidden.clone(),
|
||||||
active_overlays: self.active_overlays.clone(),
|
active_overlays: self.active_overlays.clone(),
|
||||||
register_buf_cache: RegisterBufCache::new(),
|
register_buf_cache: RegisterBufCache::new(),
|
||||||
argument_stack: ArgumentStack::new(),
|
arguments: ArgumentStack::new(),
|
||||||
|
error_handlers: ErrorHandlerStack::new(),
|
||||||
use_ir: self.use_ir,
|
use_ir: self.use_ir,
|
||||||
recursion_count: self.recursion_count,
|
recursion_count: self.recursion_count,
|
||||||
parent_stack: None,
|
parent_stack: None,
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl Call {
|
||||||
|
|
||||||
/// Get the arguments for this call from the arguments stack.
|
/// Get the arguments for this call from the arguments stack.
|
||||||
pub fn arguments<'a>(&self, stack: &'a Stack) -> &'a [Argument] {
|
pub fn arguments<'a>(&self, stack: &'a Stack) -> &'a [Argument] {
|
||||||
stack.argument_stack.get_args(self.args_base, self.args_len)
|
stack.arguments.get_args(self.args_base, self.args_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The span encompassing the arguments
|
/// The span encompassing the arguments
|
||||||
|
@ -205,7 +205,7 @@ impl Call {
|
||||||
|
|
||||||
/// Resets the [`Stack`] to its state before the call was made.
|
/// Resets the [`Stack`] to its state before the call was made.
|
||||||
pub fn leave(&self, stack: &mut Stack) {
|
pub fn leave(&self, stack: &mut Stack) {
|
||||||
stack.argument_stack.leave_frame(self.args_base);
|
stack.arguments.leave_frame(self.args_base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,13 +218,13 @@ impl CallBuilder {
|
||||||
/// Add an argument to the [`Stack`] and reference it from the [`Call`].
|
/// Add an argument to the [`Stack`] and reference it from the [`Call`].
|
||||||
pub fn add_argument(&mut self, stack: &mut Stack, argument: Argument) -> &mut Self {
|
pub fn add_argument(&mut self, stack: &mut Stack, argument: Argument) -> &mut Self {
|
||||||
if self.inner.args_len == 0 {
|
if self.inner.args_len == 0 {
|
||||||
self.inner.args_base = stack.argument_stack.get_base();
|
self.inner.args_base = stack.arguments.get_base();
|
||||||
}
|
}
|
||||||
self.inner.args_len += 1;
|
self.inner.args_len += 1;
|
||||||
if let Some(span) = argument.span() {
|
if let Some(span) = argument.span() {
|
||||||
self.inner.span = self.inner.span.append(span);
|
self.inner.span = self.inner.span.append(span);
|
||||||
}
|
}
|
||||||
stack.argument_stack.push(argument);
|
stack.arguments.push(argument);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub struct FmtInstruction<'a> {
|
||||||
|
|
||||||
impl<'a> fmt::Display for FmtInstruction<'a> {
|
impl<'a> fmt::Display for FmtInstruction<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
const WIDTH: usize = 20;
|
const WIDTH: usize = 22;
|
||||||
|
|
||||||
match self.instruction {
|
match self.instruction {
|
||||||
Instruction::LoadLiteral { dst, lit } => {
|
Instruction::LoadLiteral { dst, lit } => {
|
||||||
|
@ -170,6 +170,20 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
|
||||||
} => {
|
} => {
|
||||||
write!(f, "{:WIDTH$} {dst}, {stream}, end {end_index}", "iterate")
|
write!(f, "{:WIDTH$} {dst}, {stream}, end {end_index}", "iterate")
|
||||||
}
|
}
|
||||||
|
Instruction::PushErrorHandler { index } => {
|
||||||
|
write!(f, "{:WIDTH$} {index}", "push-error-handler")
|
||||||
|
}
|
||||||
|
Instruction::PushErrorHandlerVar { index, error_var } => {
|
||||||
|
let error_var = FmtVar::new(self.engine_state, *error_var);
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:WIDTH$} {index}, {error_var}",
|
||||||
|
"push-error-handler-var"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::PopErrorHandler => {
|
||||||
|
write!(f, "{:WIDTH$}", "pop-error-handler")
|
||||||
|
}
|
||||||
Instruction::Return { src } => {
|
Instruction::Return { src } => {
|
||||||
write!(f, "{:WIDTH$} {src}", "return")
|
write!(f, "{:WIDTH$} {src}", "return")
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,13 @@ pub enum Instruction {
|
||||||
stream: RegId,
|
stream: RegId,
|
||||||
end_index: usize,
|
end_index: usize,
|
||||||
},
|
},
|
||||||
|
/// Push an error handler, without capturing the error value
|
||||||
|
PushErrorHandler { index: usize },
|
||||||
|
/// Push an error handler, capturing the error value into the `error_var`
|
||||||
|
PushErrorHandlerVar { index: usize, error_var: VarId },
|
||||||
|
/// Pop an error handler. This is not necessary when control flow is directed to the error
|
||||||
|
/// handler due to an error.
|
||||||
|
PopErrorHandler,
|
||||||
/// Return from the block with the value in the register
|
/// Return from the block with the value in the register
|
||||||
Return { src: RegId },
|
Return { src: RegId },
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,3 +338,24 @@ fn early_return_from_while() {
|
||||||
fn early_return_from_for() {
|
fn early_return_from_for() {
|
||||||
test_eval("do { for x in [pass fail] { return $x } }", Eq("pass"))
|
test_eval("do { for x in [pass fail] { return $x } }", Eq("pass"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn try_no_catch() {
|
||||||
|
test_eval("try { error make { msg: foo } }; 'pass'", Eq("pass"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn try_catch_no_var() {
|
||||||
|
test_eval(
|
||||||
|
"try { error make { msg: foo } } catch { 'pass' }",
|
||||||
|
Eq("pass"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn try_catch_var() {
|
||||||
|
test_eval(
|
||||||
|
"try { error make { msg: foo } } catch { |err| $err.msg }",
|
||||||
|
Eq("foo"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user