#[allow(deprecated)]
use crate::get_full_help;
use crate::{EvalBlockWithEarlyReturnFn, eval_ir::eval_ir_block};
use nu_protocol::{
BlockId, Config, ENV_VARIABLE_ID, IntoPipelineData, PipelineData, PipelineExecutionData,
ShellError, Signature, Span, Value, VarId,
ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember},
debugger::{DebugContext, WithDebug, WithoutDebug},
engine::{Closure, EngineState, EnvName, EnvVars, Stack},
eval_base::Eval,
};
use nu_utils::IgnoreCaseExt;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Clone)]
pub struct CallEval {
callee_stack: Stack,
head_span: Span,
callee_span: Span,
arg_index: usize,
named_args: Vec<String>,
rest_args: Vec<Value>,
eval: EvalBlockWithEarlyReturnFn,
}
impl CallEval {
pub fn new(
callee_stack: Stack,
call_head: Span,
callee_span: Span,
eval: EvalBlockWithEarlyReturnFn,
) -> Self {
Self {
callee_stack,
head_span: call_head,
callee_span,
arg_index: 0,
named_args: Vec::new(),
rest_args: Vec::new(),
eval,
}
}
pub fn add_positional(
&mut self,
signature: &Signature,
value: Cow<Value>,
) -> Result<&mut Self, ShellError> {
let maybe_param = match self
.arg_index
.checked_sub(signature.required_positional.len())
{
None => signature.required_positional.get(self.arg_index),
Some(opt_idx) if opt_idx < signature.optional_positional.len() => {
signature.optional_positional.get(opt_idx)
}
_ => None,
};
if let Some(param) = maybe_param {
let param_type = param.shape.to_type();
if !value.is_subtype_of(¶m_type) {
return Err(ShellError::CantConvert {
to_type: param_type.to_string(),
from_type: value.get_type().to_string(),
span: value.span(),
help: None,
});
}
let var_id = param
.var_id
.expect("internal error: all custom parameters must have var_ids");
self.callee_stack.add_var(var_id, value.into_owned());
self.arg_index += 1;
Ok(self)
} else {
let Some(rest_positional) = &signature.rest_positional else {
return Ok(self);
};
let param_type = rest_positional.shape.to_type();
if !value.is_subtype_of(¶m_type) {
return Err(ShellError::CantConvert {
to_type: param_type.to_string(),
from_type: value.get_type().to_string(),
span: value.span(),
help: None,
});
}
self.rest_args.push(value.into_owned());
Ok(self)
}
}
pub fn add_named(
&mut self,
signature: &Signature,
long: &str,
short: Option<String>,
value: Option<Cow<Value>>,
) -> Result<&mut Self, ShellError> {
let named = signature.named.iter().find(|named| {
long == named.long
|| short
.as_deref()
.zip(named.short)
.is_some_and(|(arg, param)| {
let mut buf = [0; 4];
param.encode_utf8(&mut buf) == arg
})
});
if let Some(named) = named {
let var_id = named
.var_id
.expect("internal error: all custom parameters must have var_ids");
let value = value
.or_else(|| named.default_value.as_ref().map(Cow::Borrowed))
.unwrap_or_else(|| Cow::Owned(Value::bool(true, self.head_span)));
self.callee_stack.add_var(var_id, value.into_owned());
self.named_args.push(long.to_string());
}
Ok(self)
}
pub fn with_env(
&mut self,
env_vars: &[Arc<EnvVars>],
env_hidden: &Arc<HashMap<String, HashSet<EnvName>>>,
) -> &mut Self {
self.callee_stack.with_env(env_vars, env_hidden);
self
}
pub fn debug(&mut self, debug: bool) -> &mut Self {
if debug {
self.eval = eval_block_with_early_return::<WithDebug>
} else {
self.eval = eval_block_with_early_return::<WithoutDebug>
};
self
}
pub fn run(
&mut self,
engine_state: &EngineState,
block: &Block,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
self.finalize_arguments(&block.signature)?;
self.arg_index = 0;
self.rest_args.clear();
(self.eval)(engine_state, &mut self.callee_stack, block, input).map(|p| p.body)
}
pub fn redirect_env(&self, engine_state: &EngineState, stack: &mut Stack) {
redirect_env(engine_state, stack, &self.callee_stack);
}
fn finalize_arguments(&mut self, signature: &Signature) -> Result<(), ShellError> {
let remaining_positionals = signature
.required_positional
.iter()
.map(|p| (p, true))
.chain(signature.optional_positional.iter().map(|p| (p, false)))
.skip(self.arg_index);
for (param, required) in remaining_positionals {
let var_id = param
.var_id
.expect("internal error: all custom parameters must have var_ids");
let maybe_value = param
.default_value
.clone()
.or((!required).then_some(Value::nothing(self.callee_span)));
if let Some(value) = maybe_value {
self.callee_stack.add_var(var_id, value);
} else {
return Err(ShellError::MissingParameter {
param_name: param.name.to_string(),
span: self.callee_span,
});
}
}
if let Some(rest_positional) = &signature.rest_positional {
let span = self
.rest_args
.first()
.map(|x| x.span())
.unwrap_or(self.callee_span);
self.callee_stack.add_var(
rest_positional
.var_id
.expect("Internal error: rest positional parameter lackes var_id"),
Value::list(self.rest_args.to_owned(), span),
);
}
let remaining_flags = signature
.named
.iter()
.filter(|flag| !self.named_args.contains(&flag.long))
.filter_map(|flag| Some((flag.var_id?, flag)));
for (var_id, flag) in remaining_flags {
if flag.arg.is_none() {
self.callee_stack
.add_var(var_id, Value::bool(false, self.head_span));
} else {
let value = flag
.default_value
.clone()
.unwrap_or(Value::nothing(self.head_span));
self.callee_stack.add_var(var_id, value);
}
}
Ok(())
}
}
pub fn eval_call<D: DebugContext>(
engine_state: &EngineState,
caller_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
engine_state.signals().check(&call.head)?;
let decl = engine_state.get_decl(call.decl_id);
if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
let help = get_full_help(decl, engine_state, caller_stack, call.head);
Ok(Value::string(help, call.head).into_pipeline_data())
} else if let Some(block_id) = decl.block_id() {
let block = engine_state.get_block(block_id);
let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
let maximum_call_stack_depth: u64 = engine_state.config.recursion_limit as u64;
callee_stack.recursion_count += 1;
if callee_stack.recursion_count > maximum_call_stack_depth {
callee_stack.recursion_count = 0;
return Err(ShellError::RecursionLimitReached {
recursion_limit: maximum_call_stack_depth,
span: block.span,
});
}
let mut call_eval = CallEval::new(
callee_stack,
call.head,
block.span.unwrap_or(Span::unknown()),
eval_block_with_early_return::<D>,
);
for arg in call.positional_iter() {
let result = eval_expression::<D>(engine_state, caller_stack, arg)?;
call_eval.add_positional(&decl.signature(), Cow::Owned(result))?;
}
for call_named in call.named_iter() {
let result: Option<Cow<Value>> = if let Some(arg) = &call_named.2 {
Some(Cow::Owned(eval_expression::<D>(
engine_state,
caller_stack,
arg,
)?))
} else {
None
};
call_eval.add_named(
&decl.signature(),
&call_named.0.item,
call_named.1.clone().map(|x| x.item),
result,
)?;
}
let result = call_eval.run(engine_state, block, input);
if block.redirect_env {
call_eval.redirect_env(engine_state, caller_stack);
}
result
} else {
decl.run(engine_state, caller_stack, &call.into(), input)
}
}
pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
let caller_env_vars = caller_stack.get_env_var_names(engine_state);
for var in caller_env_vars.iter() {
if !callee_stack.has_env_var(engine_state, var) {
caller_stack.hide_env_var(engine_state, var);
}
}
for (var, value) in callee_stack.get_stack_env_vars() {
caller_stack.add_env_var(var, value);
}
caller_stack.config.clone_from(&callee_stack.config);
}
fn eval_external(
engine_state: &EngineState,
stack: &mut Stack,
head: &Expression,
args: &[ExternalArgument],
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let decl_id = engine_state
.find_decl("run-external".as_bytes(), &[])
.ok_or(ShellError::ExternalNotSupported {
span: head.span(&engine_state),
})?;
let command = engine_state.get_decl(decl_id);
let mut call = Call::new(head.span(&engine_state));
call.add_positional(head.clone());
for arg in args {
match arg {
ExternalArgument::Regular(expr) => call.add_positional(expr.clone()),
ExternalArgument::Spread(expr) => call.add_spread(expr.clone()),
}
}
command.run(engine_state, stack, &(&call).into(), input)
}
pub fn eval_expression<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
expr: &Expression,
) -> Result<Value, ShellError> {
let stack = &mut stack.start_collect_value();
<EvalRuntime as Eval>::eval::<D>(engine_state, stack, expr)
}
pub fn eval_expression_with_input<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
expr: &Expression,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
match &expr.expr {
Expr::Call(call) => {
input = eval_call::<D>(engine_state, stack, call, input)?;
}
Expr::ExternalCall(head, args) => {
input = eval_external(engine_state, stack, head, args, input)?;
}
Expr::Collect(var_id, expr) => {
input = eval_collect::<D>(engine_state, stack, *var_id, expr, input)?;
}
Expr::Subexpression(block_id) => {
let block = engine_state.get_block(*block_id);
input = eval_subexpression::<D>(engine_state, stack, block, input)?;
}
Expr::FullCellPath(full_cell_path) => match &full_cell_path.head {
Expression {
expr: Expr::Subexpression(block_id),
span,
..
} => {
let block = engine_state.get_block(*block_id);
if !full_cell_path.tail.is_empty() {
let stack = &mut stack.start_collect_value();
input = eval_subexpression::<D>(engine_state, stack, block, input)?
.into_value(*span)?
.follow_cell_path(&full_cell_path.tail)?
.into_owned()
.into_pipeline_data()
} else {
input = eval_subexpression::<D>(engine_state, stack, block, input)?;
}
}
_ => {
let input_value = input.into_value(expr.span)?;
stack.add_var(nu_protocol::IN_VARIABLE_ID, input_value);
input = eval_expression::<D>(engine_state, stack, expr)?.into_pipeline_data();
}
},
Expr::StringInterpolation(_) | Expr::GlobInterpolation(_, _) => {
let input_value = input.into_value(expr.span)?;
stack.add_var(nu_protocol::IN_VARIABLE_ID, input_value);
let value = eval_expression::<D>(engine_state, stack, expr)?;
input = PipelineData::Value(value, None);
}
_ => {
let input_value = input.into_value(expr.span)?;
stack.add_var(nu_protocol::IN_VARIABLE_ID, input_value);
let value = eval_expression::<D>(engine_state, stack, expr)?;
input = PipelineData::Value(value, None);
}
};
Ok(input)
}
pub fn eval_block<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
input: PipelineData,
) -> Result<PipelineExecutionData, ShellError> {
let result = eval_ir_block::<D>(engine_state, stack, block, input);
if let Err(ShellError::Exit { code }) = &result {
std::process::exit(*code)
}
if let Err(err) = &result {
stack.set_last_error(err);
}
result
}
pub fn eval_block_with_early_return<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
input: PipelineData,
) -> Result<PipelineExecutionData, ShellError> {
match eval_block::<D>(engine_state, stack, block, input) {
Err(ShellError::Return { span: _, value }) => Ok(PipelineExecutionData::from(
PipelineData::value(*value, None),
)),
Err(ShellError::Exit { code }) => std::process::exit(code),
x => x,
}
}
pub fn eval_collect<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
var_id: VarId,
expr: &Expression,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = input.span().unwrap_or(expr.span);
let metadata = input.take_metadata().and_then(|m| m.for_collect());
let input = input.into_value(span)?;
stack.add_var(var_id, input.clone());
let result = eval_expression_with_input::<D>(
engine_state,
stack,
expr,
input.into_pipeline_data_with_metadata(metadata),
);
stack.remove_var(var_id);
result
}
pub fn eval_subexpression<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
eval_block::<D>(engine_state, stack, block, input).map(|p| p.body)
}
pub fn eval_variable(
engine_state: &EngineState,
stack: &Stack,
var_id: VarId,
span: Span,
) -> Result<Value, ShellError> {
match var_id {
nu_protocol::NU_VARIABLE_ID => {
if let Some(val) = engine_state.get_constant(var_id) {
Ok(val.clone())
} else {
Err(ShellError::VariableNotFoundAtRuntime { span })
}
}
ENV_VARIABLE_ID => {
let env_vars = stack.get_env_vars(engine_state);
let env_columns = env_vars.keys();
let env_values = env_vars.values();
let mut pairs = env_columns
.map(|x| x.to_string())
.zip(env_values.cloned())
.collect::<Vec<(String, Value)>>();
pairs.sort_by(|a, b| a.0.cmp(&b.0));
Ok(Value::record(pairs.into_iter().collect(), span))
}
var_id => stack.get_var(var_id, span),
}
}
struct EvalRuntime;
impl Eval for EvalRuntime {
type State<'a> = &'a EngineState;
type MutState = Stack;
fn get_config(engine_state: Self::State<'_>, stack: &mut Stack) -> Arc<Config> {
stack.get_config(engine_state)
}
fn eval_var(
engine_state: &EngineState,
stack: &mut Stack,
var_id: VarId,
span: Span,
) -> Result<Value, ShellError> {
eval_variable(engine_state, stack, var_id, span)
}
fn eval_call<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_: Span,
) -> Result<Value, ShellError> {
eval_call::<D>(engine_state, stack, call, PipelineData::empty())?.into_value(call.head)
}
fn eval_external_call(
engine_state: &EngineState,
stack: &mut Stack,
head: &Expression,
args: &[ExternalArgument],
_: Span,
) -> Result<Value, ShellError> {
let span = head.span(&engine_state);
eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span)
}
fn eval_collect<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
var_id: VarId,
expr: &Expression,
) -> Result<Value, ShellError> {
eval_collect::<D>(engine_state, stack, var_id, expr, PipelineData::empty())?
.into_value(expr.span)
}
fn eval_subexpression<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
span: Span,
) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id);
eval_subexpression::<D>(engine_state, stack, block, PipelineData::empty())?.into_value(span)
}
fn regex_match(
engine_state: &EngineState,
op_span: Span,
lhs: &Value,
rhs: &Value,
invert: bool,
expr_span: Span,
) -> Result<Value, ShellError> {
lhs.regex_match(engine_state, op_span, rhs, invert, expr_span)
}
fn eval_assignment<D: DebugContext>(
engine_state: &EngineState,
stack: &mut Stack,
lhs: &Expression,
rhs: &Expression,
assignment: Assignment,
op_span: Span,
_expr_span: Span,
) -> Result<Value, ShellError> {
let rhs = eval_expression::<D>(engine_state, stack, rhs)?;
let rhs = match assignment {
Assignment::Assign => rhs,
Assignment::AddAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.add(op_span, &rhs, op_span)?
}
Assignment::SubtractAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.sub(op_span, &rhs, op_span)?
}
Assignment::MultiplyAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.mul(op_span, &rhs, op_span)?
}
Assignment::DivideAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.div(op_span, &rhs, op_span)?
}
Assignment::ConcatenateAssign => {
let lhs = eval_expression::<D>(engine_state, stack, lhs)?;
lhs.concat(op_span, &rhs, op_span)?
}
};
match &lhs.expr {
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
let var_info = engine_state.get_var(*var_id);
if var_info.mutable {
stack.add_var(*var_id, rhs);
Ok(Value::nothing(lhs.span(&engine_state)))
} else {
Err(ShellError::AssignmentRequiresMutableVar {
lhs_span: lhs.span(&engine_state),
})
}
}
Expr::FullCellPath(cell_path) => {
match &cell_path.head.expr {
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
let is_env = var_id == &ENV_VARIABLE_ID;
if is_env || engine_state.get_var(*var_id).mutable {
let mut lhs =
eval_expression::<D>(engine_state, stack, &cell_path.head)?;
if is_env {
if cell_path.tail.is_empty() {
return Err(ShellError::CannotReplaceEnv {
span: cell_path.head.span(&engine_state),
});
}
let (key, span) = match &cell_path.tail[0] {
PathMember::String { val, span, .. } => (val.to_string(), span),
PathMember::Int { val, span, .. } => (val.to_string(), span),
};
let original_key = if let Value::Record { val: record, .. } = &lhs {
record
.iter()
.rev()
.map(|(k, _)| k)
.find(|x| x.eq_ignore_case(&key))
.cloned()
.unwrap_or(key)
} else {
key
};
lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
let value = lhs.follow_cell_path(&[{
let mut pm = cell_path.tail[0].clone();
pm.make_insensitive();
pm
}])?;
if is_automatic_env_var(&original_key) {
return Err(ShellError::AutomaticEnvVarSetManually {
envvar_name: original_key,
span: *span,
});
}
let is_config = original_key == "config";
stack.add_env_var(original_key, value.into_owned());
if is_config {
stack.update_config(engine_state)?;
}
} else {
lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
stack.add_var(*var_id, lhs);
}
Ok(Value::nothing(cell_path.head.span(&engine_state)))
} else {
Err(ShellError::AssignmentRequiresMutableVar {
lhs_span: lhs.span(&engine_state),
})
}
}
_ => Err(ShellError::AssignmentRequiresVar {
lhs_span: lhs.span(&engine_state),
}),
}
}
_ => Err(ShellError::AssignmentRequiresVar {
lhs_span: lhs.span(&engine_state),
}),
}
}
fn eval_row_condition_or_closure(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
span: Span,
) -> Result<Value, ShellError> {
let captures = engine_state
.get_block(block_id)
.captures
.iter()
.map(|(id, span)| {
stack
.get_var(*id, *span)
.or_else(|_| {
engine_state
.get_var(*id)
.const_val
.clone()
.ok_or(ShellError::VariableNotFoundAtRuntime { span: *span })
})
.map(|var| (*id, var))
})
.collect::<Result<_, _>>()?;
Ok(Value::closure(Closure { block_id, captures }, span))
}
fn eval_overlay(engine_state: &EngineState, span: Span) -> Result<Value, ShellError> {
let name = String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string();
Ok(Value::string(name, span))
}
fn unreachable(engine_state: &EngineState, expr: &Expression) -> Result<Value, ShellError> {
Ok(Value::nothing(expr.span(&engine_state)))
}
}
pub(crate) fn is_automatic_env_var(var: &str) -> bool {
let names = ["PWD", "FILE_PWD", "CURRENT_FILE"];
names.iter().any(|&name| {
if cfg!(windows) {
name.eq_ignore_case(var)
} else {
name.eq(var)
}
})
}