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), Named(&'a str, Option, Span, Option), Spread(RegId, Span, Option), } 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) }