nushell/crates/nu-engine/src/compile/call.rs

253 lines
8.4 KiB
Rust

use std::sync::Arc;
use nu_protocol::{
ast::{Argument, Call, Expression, ExternalArgument},
engine::StateWorkingSet,
ir::{Instruction, IrAstRef, Literal},
IntoSpanned, RegId, Span, Spanned,
};
use super::{compile_expression, keyword::*, BlockBuilder, CompileError, RedirectModes};
pub(crate) fn compile_call(
working_set: &StateWorkingSet,
builder: &mut BlockBuilder,
call: &Call,
redirect_modes: RedirectModes,
io_reg: RegId,
) -> Result<(), CompileError> {
let decl = working_set.get_decl(call.decl_id);
// Check if this call has --help - if so, just redirect to `help`
if call.named_iter().any(|(name, _, _)| name.item == "help") {
return compile_help(
working_set,
builder,
decl.name().into_spanned(call.head),
io_reg,
);
}
// Try to figure out if this is a keyword call like `if`, and handle those specially
if decl.is_keyword() {
match decl.name() {
"if" => {
return compile_if(working_set, builder, call, redirect_modes, io_reg);
}
"match" => {
return compile_match(working_set, builder, call, redirect_modes, io_reg);
}
"const" => {
// This differs from the behavior of the const command, which adds the const value
// to the stack. Since `load-variable` also checks `engine_state` for the variable
// and will get a const value though, is it really necessary to do that?
return builder.load_empty(io_reg);
}
"alias" => {
// Alias does nothing
return builder.load_empty(io_reg);
}
"let" | "mut" => {
return compile_let(working_set, builder, call, redirect_modes, io_reg);
}
"try" => {
return compile_try(working_set, builder, call, redirect_modes, io_reg);
}
"loop" => {
return compile_loop(working_set, builder, call, redirect_modes, io_reg);
}
"while" => {
return compile_while(working_set, builder, call, redirect_modes, io_reg);
}
"for" => {
return compile_for(working_set, builder, call, redirect_modes, io_reg);
}
"break" => {
return compile_break(working_set, builder, call, redirect_modes, io_reg);
}
"continue" => {
return compile_continue(working_set, builder, call, redirect_modes, io_reg);
}
"return" => {
return compile_return(working_set, builder, call, redirect_modes, io_reg);
}
_ => (),
}
}
// Keep AST if the decl needs it.
let requires_ast = decl.requires_ast_for_arguments();
// It's important that we evaluate the args first before trying to set up the argument
// state for the call.
//
// We could technically compile anything that isn't another call safely without worrying about
// the argument state, but we'd have to check all of that first and it just isn't really worth
// it.
enum CompiledArg<'a> {
Positional(RegId, Span, Option<IrAstRef>),
Named(&'a str, Option<RegId>, Span, Option<IrAstRef>),
Spread(RegId, Span, Option<IrAstRef>),
}
let mut compiled_args = vec![];
for arg in &call.arguments {
let arg_reg = arg
.expr()
.map(|expr| {
let arg_reg = builder.next_register()?;
compile_expression(
working_set,
builder,
expr,
RedirectModes::capture_out(arg.span()),
None,
arg_reg,
)?;
Ok(arg_reg)
})
.transpose()?;
let ast_ref = arg
.expr()
.filter(|_| requires_ast)
.map(|expr| IrAstRef(Arc::new(expr.clone())));
match arg {
Argument::Positional(_) | Argument::Unknown(_) => {
compiled_args.push(CompiledArg::Positional(
arg_reg.expect("expr() None in non-Named"),
arg.span(),
ast_ref,
))
}
Argument::Named((name, _, _)) => compiled_args.push(CompiledArg::Named(
name.item.as_str(),
arg_reg,
arg.span(),
ast_ref,
)),
Argument::Spread(_) => compiled_args.push(CompiledArg::Spread(
arg_reg.expect("expr() None in non-Named"),
arg.span(),
ast_ref,
)),
}
}
// Now that the args are all compiled, set up the call state (argument stack and redirections)
for arg in compiled_args {
match arg {
CompiledArg::Positional(reg, span, ast_ref) => {
builder.push(Instruction::PushPositional { src: reg }.into_spanned(span))?;
builder.set_last_ast(ast_ref);
}
CompiledArg::Named(name, Some(reg), span, ast_ref) => {
let name = builder.data(name)?;
builder.push(Instruction::PushNamed { name, src: reg }.into_spanned(span))?;
builder.set_last_ast(ast_ref);
}
CompiledArg::Named(name, None, span, ast_ref) => {
let name = builder.data(name)?;
builder.push(Instruction::PushFlag { name }.into_spanned(span))?;
builder.set_last_ast(ast_ref);
}
CompiledArg::Spread(reg, span, ast_ref) => {
builder.push(Instruction::AppendRest { src: reg }.into_spanned(span))?;
builder.set_last_ast(ast_ref);
}
}
}
// Add any parser info from the call
for (name, info) in &call.parser_info {
let name = builder.data(name)?;
let info = Box::new(info.clone());
builder.push(Instruction::PushParserInfo { name, info }.into_spanned(call.head))?;
}
if let Some(mode) = redirect_modes.out {
builder.push(mode.map(|mode| Instruction::RedirectOut { mode }))?;
}
if let Some(mode) = redirect_modes.err {
builder.push(mode.map(|mode| Instruction::RedirectErr { mode }))?;
}
// The state is set up, so we can do the call into io_reg
builder.push(
Instruction::Call {
decl_id: call.decl_id,
src_dst: io_reg,
}
.into_spanned(call.head),
)?;
Ok(())
}
pub(crate) fn compile_help(
working_set: &StateWorkingSet<'_>,
builder: &mut BlockBuilder,
decl_name: Spanned<&str>,
io_reg: RegId,
) -> Result<(), CompileError> {
let help_command_id =
working_set
.find_decl(b"help")
.ok_or_else(|| CompileError::MissingRequiredDeclaration {
decl_name: "help".into(),
span: decl_name.span,
})?;
let name_data = builder.data(decl_name.item)?;
let name_literal = builder.literal(decl_name.map(|_| Literal::String(name_data)))?;
builder.push(Instruction::PushPositional { src: name_literal }.into_spanned(decl_name.span))?;
builder.push(
Instruction::Call {
decl_id: help_command_id,
src_dst: io_reg,
}
.into_spanned(decl_name.span),
)?;
Ok(())
}
pub(crate) fn compile_external_call(
working_set: &StateWorkingSet,
builder: &mut BlockBuilder,
head: &Expression,
args: &[ExternalArgument],
redirect_modes: RedirectModes,
io_reg: RegId,
) -> Result<(), CompileError> {
// Pass everything to run-external
let run_external_id = working_set
.find_decl(b"run-external")
.ok_or_else(|| CompileError::RunExternalNotFound { span: head.span })?;
let mut call = Call::new(head.span);
call.decl_id = run_external_id;
call.arguments.push(Argument::Positional(head.clone()));
for arg in args {
match arg {
ExternalArgument::Regular(expr) => {
call.arguments.push(Argument::Positional(expr.clone()));
}
ExternalArgument::Spread(expr) => {
call.arguments.push(Argument::Spread(expr.clone()));
}
}
}
compile_call(working_set, builder, &call, redirect_modes, io_reg)
}