mirror of
https://github.com/nushell/nushell.git
synced 2024-11-25 09:53:43 +01:00
Reduce code duplication in eval.rs and eval_const.rs (#11192)
This commit is contained in:
parent
fc06afd051
commit
c1a30ac60f
@ -2,12 +2,13 @@ use crate::{current_dir_str, get_full_help};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
ast::{
|
||||
eval_operator, Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr,
|
||||
Expression, Math, Operator, PathMember, PipelineElement, RecordItem, Redirection,
|
||||
Argument, Assignment, Block, Call, Expr, Expression, PathMember, PipelineElement,
|
||||
Redirection,
|
||||
},
|
||||
engine::{Closure, EngineState, Stack},
|
||||
DeclId, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, Record,
|
||||
ShellError, Span, Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID,
|
||||
eval_base::Eval,
|
||||
DeclId, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Span,
|
||||
Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::thread::{self, JoinHandle};
|
||||
@ -253,421 +254,7 @@ pub fn eval_expression(
|
||||
stack: &mut Stack,
|
||||
expr: &Expression,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &expr.expr {
|
||||
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
||||
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
||||
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
||||
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)),
|
||||
Expr::ValueWithUnit(e, unit) => match eval_expression(engine_state, stack, e)? {
|
||||
Value::Int { val, .. } => compute(val, unit.item, unit.span),
|
||||
x => Err(ShellError::CantConvert {
|
||||
to_type: "unit value".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: e.span,
|
||||
help: None,
|
||||
}),
|
||||
},
|
||||
Expr::Range(from, next, to, operator) => {
|
||||
let from = if let Some(f) = from {
|
||||
eval_expression(engine_state, stack, f)?
|
||||
} else {
|
||||
Value::nothing(expr.span)
|
||||
};
|
||||
|
||||
let next = if let Some(s) = next {
|
||||
eval_expression(engine_state, stack, s)?
|
||||
} else {
|
||||
Value::nothing(expr.span)
|
||||
};
|
||||
|
||||
let to = if let Some(t) = to {
|
||||
eval_expression(engine_state, stack, t)?
|
||||
} else {
|
||||
Value::nothing(expr.span)
|
||||
};
|
||||
|
||||
Ok(Value::range(
|
||||
Range::new(expr.span, from, next, to, operator)?,
|
||||
expr.span,
|
||||
))
|
||||
}
|
||||
Expr::Var(var_id) => eval_variable(engine_state, stack, *var_id, expr.span),
|
||||
Expr::VarDecl(_) => Ok(Value::nothing(expr.span)),
|
||||
Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr.span)),
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
let value = eval_expression(engine_state, stack, &cell_path.head)?;
|
||||
|
||||
value.follow_cell_path(&cell_path.tail, false)
|
||||
}
|
||||
Expr::ImportPattern(_) => Ok(Value::nothing(expr.span)),
|
||||
Expr::Overlay(_) => {
|
||||
let name =
|
||||
String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).to_string();
|
||||
|
||||
Ok(Value::string(name, expr.span))
|
||||
}
|
||||
Expr::Call(call) => {
|
||||
// FIXME: protect this collect with ctrl-c
|
||||
Ok(eval_call(engine_state, stack, call, PipelineData::empty())?.into_value(call.head))
|
||||
}
|
||||
Expr::ExternalCall(head, args, is_subexpression) => {
|
||||
let span = head.span;
|
||||
// FIXME: protect this collect with ctrl-c
|
||||
Ok(eval_external(
|
||||
engine_state,
|
||||
stack,
|
||||
head,
|
||||
args,
|
||||
PipelineData::empty(),
|
||||
RedirectTarget::Piped(false, false),
|
||||
*is_subexpression,
|
||||
)?
|
||||
.into_value(span))
|
||||
}
|
||||
Expr::DateTime(dt) => Ok(Value::date(*dt, expr.span)),
|
||||
Expr::Operator(_) => Ok(Value::nothing(expr.span)),
|
||||
Expr::MatchPattern(pattern) => Ok(Value::match_pattern(*pattern.clone(), expr.span)),
|
||||
Expr::MatchBlock(_) => Ok(Value::nothing(expr.span)), // match blocks are handled by `match`
|
||||
Expr::UnaryNot(expr) => {
|
||||
let lhs = eval_expression(engine_state, stack, expr)?;
|
||||
match lhs {
|
||||
Value::Bool { val, .. } => Ok(Value::bool(!val, expr.span)),
|
||||
other => Err(ShellError::TypeMismatch {
|
||||
err_message: format!("expected bool, found {}", other.get_type()),
|
||||
span: expr.span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Expr::BinaryOp(lhs, op, rhs) => {
|
||||
let op_span = op.span;
|
||||
let op = eval_operator(op)?;
|
||||
|
||||
match op {
|
||||
Operator::Boolean(boolean) => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
match boolean {
|
||||
Boolean::And => {
|
||||
if lhs.is_false() {
|
||||
Ok(Value::bool(false, expr.span))
|
||||
} else {
|
||||
let rhs = eval_expression(engine_state, stack, rhs)?;
|
||||
lhs.and(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
Boolean::Or => {
|
||||
if lhs.is_true() {
|
||||
Ok(Value::bool(true, expr.span))
|
||||
} else {
|
||||
let rhs = eval_expression(engine_state, stack, rhs)?;
|
||||
lhs.or(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
Boolean::Xor => {
|
||||
let rhs = eval_expression(engine_state, stack, rhs)?;
|
||||
lhs.xor(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
Operator::Math(math) => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
let rhs = eval_expression(engine_state, stack, rhs)?;
|
||||
|
||||
match math {
|
||||
Math::Plus => lhs.add(op_span, &rhs, expr.span),
|
||||
Math::Minus => lhs.sub(op_span, &rhs, expr.span),
|
||||
Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
|
||||
Math::Divide => lhs.div(op_span, &rhs, expr.span),
|
||||
Math::Append => lhs.append(op_span, &rhs, expr.span),
|
||||
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
|
||||
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
|
||||
Math::Pow => lhs.pow(op_span, &rhs, expr.span),
|
||||
}
|
||||
}
|
||||
Operator::Comparison(comparison) => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
let rhs = eval_expression(engine_state, stack, rhs)?;
|
||||
match comparison {
|
||||
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
|
||||
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
|
||||
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
|
||||
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
|
||||
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
|
||||
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr.span),
|
||||
Comparison::In => lhs.r#in(op_span, &rhs, expr.span),
|
||||
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr.span),
|
||||
Comparison::RegexMatch => {
|
||||
lhs.regex_match(engine_state, op_span, &rhs, false, expr.span)
|
||||
}
|
||||
Comparison::NotRegexMatch => {
|
||||
lhs.regex_match(engine_state, op_span, &rhs, true, expr.span)
|
||||
}
|
||||
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
|
||||
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
|
||||
}
|
||||
}
|
||||
Operator::Bits(bits) => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
let rhs = eval_expression(engine_state, stack, rhs)?;
|
||||
match bits {
|
||||
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr.span),
|
||||
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr.span),
|
||||
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr.span),
|
||||
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr.span),
|
||||
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr.span),
|
||||
}
|
||||
}
|
||||
Operator::Assignment(assignment) => {
|
||||
let rhs = eval_expression(engine_state, stack, rhs)?;
|
||||
|
||||
let rhs = match assignment {
|
||||
Assignment::Assign => rhs,
|
||||
Assignment::PlusAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.add(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::MinusAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.sub(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::MultiplyAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.mul(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::DivideAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.div(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::AppendAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.append(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))
|
||||
} else {
|
||||
Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span })
|
||||
}
|
||||
}
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
match &cell_path.head.expr {
|
||||
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
|
||||
// The $env variable is considered "mutable" in Nushell.
|
||||
// As such, give it special treatment here.
|
||||
let is_env = var_id == &ENV_VARIABLE_ID;
|
||||
if is_env || engine_state.get_var(*var_id).mutable {
|
||||
let mut lhs =
|
||||
eval_expression(engine_state, stack, &cell_path.head)?;
|
||||
|
||||
lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
|
||||
if is_env {
|
||||
if cell_path.tail.is_empty() {
|
||||
return Err(ShellError::CannotReplaceEnv {
|
||||
span: cell_path.head.span,
|
||||
});
|
||||
}
|
||||
|
||||
// The special $env treatment: for something like $env.config.history.max_size = 2000,
|
||||
// get $env.config (or whichever one it is) AFTER the above mutation, and set it
|
||||
// as the "config" environment variable.
|
||||
let vardata = lhs.follow_cell_path(
|
||||
&[cell_path.tail[0].clone()],
|
||||
false,
|
||||
)?;
|
||||
match &cell_path.tail[0] {
|
||||
PathMember::String { val, span, .. } => {
|
||||
if val == "FILE_PWD"
|
||||
|| val == "CURRENT_FILE"
|
||||
|| val == "PWD"
|
||||
{
|
||||
return Err(ShellError::AutomaticEnvVarSetManually {
|
||||
envvar_name: val.to_string(),
|
||||
span: *span,
|
||||
});
|
||||
} else {
|
||||
stack.add_env_var(val.to_string(), vardata);
|
||||
}
|
||||
}
|
||||
// In case someone really wants an integer env-var
|
||||
PathMember::Int { val, .. } => {
|
||||
stack.add_env_var(val.to_string(), vardata);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stack.add_var(*var_id, lhs);
|
||||
}
|
||||
Ok(Value::nothing(cell_path.head.span))
|
||||
} else {
|
||||
Err(ShellError::AssignmentRequiresMutableVar {
|
||||
lhs_span: lhs.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Subexpression(block_id) => {
|
||||
let block = engine_state.get_block(*block_id);
|
||||
|
||||
// FIXME: protect this collect with ctrl-c
|
||||
Ok(
|
||||
eval_subexpression(engine_state, stack, block, PipelineData::empty())?
|
||||
.into_value(expr.span),
|
||||
)
|
||||
}
|
||||
Expr::RowCondition(block_id) | Expr::Closure(block_id) => {
|
||||
let block_id = *block_id;
|
||||
let captures = engine_state
|
||||
.get_block(block_id)
|
||||
.captures
|
||||
.iter()
|
||||
.map(|&id| stack.get_var(id, expr.span).map(|var| (id, var)))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(Value::closure(Closure { block_id, captures }, expr.span))
|
||||
}
|
||||
Expr::Block(block_id) => Ok(Value::block(*block_id, expr.span)),
|
||||
Expr::List(x) => {
|
||||
let mut output = vec![];
|
||||
for expr in x {
|
||||
match &expr.expr {
|
||||
Expr::Spread(expr) => match eval_expression(engine_state, stack, expr)? {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
},
|
||||
_ => output.push(eval_expression(engine_state, stack, expr)?),
|
||||
}
|
||||
}
|
||||
Ok(Value::list(output, expr.span))
|
||||
}
|
||||
Expr::Record(items) => {
|
||||
let mut record = Record::new();
|
||||
|
||||
let mut col_names = HashMap::new();
|
||||
|
||||
for item in items {
|
||||
match item {
|
||||
RecordItem::Pair(col, val) => {
|
||||
// avoid duplicate cols
|
||||
let col_name = eval_expression(engine_state, stack, col)?.as_string()?;
|
||||
if let Some(orig_span) = col_names.get(&col_name) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name,
|
||||
second_use: col.span,
|
||||
first_use: *orig_span,
|
||||
});
|
||||
} else {
|
||||
col_names.insert(col_name.clone(), col.span);
|
||||
record.push(col_name, eval_expression(engine_state, stack, val)?);
|
||||
}
|
||||
}
|
||||
RecordItem::Spread(_, inner) => {
|
||||
match eval_expression(engine_state, stack, inner)? {
|
||||
Value::Record { val: inner_val, .. } => {
|
||||
for (col_name, val) in inner_val {
|
||||
if let Some(orig_span) = col_names.get(&col_name) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name,
|
||||
second_use: inner.span,
|
||||
first_use: *orig_span,
|
||||
});
|
||||
} else {
|
||||
col_names.insert(col_name.clone(), inner.span);
|
||||
record.push(col_name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(ShellError::CannotSpreadAsRecord { span: inner.span }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::record(record, expr.span))
|
||||
}
|
||||
Expr::Table(headers, vals) => {
|
||||
let mut output_headers = vec![];
|
||||
for expr in headers {
|
||||
let header = eval_expression(engine_state, stack, expr)?.as_string()?;
|
||||
if let Some(idx) = output_headers
|
||||
.iter()
|
||||
.position(|existing| existing == &header)
|
||||
{
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name: header,
|
||||
second_use: expr.span,
|
||||
first_use: headers[idx].span,
|
||||
});
|
||||
} else {
|
||||
output_headers.push(header);
|
||||
}
|
||||
}
|
||||
|
||||
let mut output_rows = vec![];
|
||||
for val in vals {
|
||||
let mut row = vec![];
|
||||
for expr in val {
|
||||
row.push(eval_expression(engine_state, stack, expr)?);
|
||||
}
|
||||
output_rows.push(Value::record(
|
||||
Record::from_raw_cols_vals(output_headers.clone(), row),
|
||||
expr.span,
|
||||
));
|
||||
}
|
||||
Ok(Value::list(output_rows, expr.span))
|
||||
}
|
||||
Expr::Keyword(_, _, expr) => eval_expression(engine_state, stack, expr),
|
||||
Expr::StringInterpolation(exprs) => {
|
||||
let mut parts = vec![];
|
||||
for expr in exprs {
|
||||
parts.push(eval_expression(engine_state, stack, expr)?);
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
|
||||
parts
|
||||
.into_iter()
|
||||
.into_pipeline_data(None)
|
||||
.collect_string("", config)
|
||||
.map(|x| Value::string(x, expr.span))
|
||||
}
|
||||
Expr::String(s) => Ok(Value::string(s.clone(), expr.span)),
|
||||
Expr::Filepath(s) => {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let path = expand_path_with(s, cwd);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), expr.span))
|
||||
}
|
||||
Expr::Directory(s) => {
|
||||
if s == "-" {
|
||||
Ok(Value::string("-", expr.span))
|
||||
} else {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let path = expand_path_with(s, cwd);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), expr.span))
|
||||
}
|
||||
}
|
||||
Expr::GlobPattern(s) => {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let path = expand_path_with(s, cwd);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), expr.span))
|
||||
}
|
||||
Expr::Signature(_) => Ok(Value::nothing(expr.span)),
|
||||
Expr::Garbage => Ok(Value::nothing(expr.span)),
|
||||
Expr::Nothing => Ok(Value::nothing(expr.span)),
|
||||
Expr::Spread(_) => Ok(Value::nothing(expr.span)), // Spread operator only occurs in lists
|
||||
}
|
||||
<EvalRuntime as Eval>::eval(engine_state, stack, expr)
|
||||
}
|
||||
|
||||
/// Checks the expression to see if it's a internal or external call. If so, passes the input
|
||||
@ -1215,10 +802,6 @@ pub fn eval_variable(
|
||||
}
|
||||
}
|
||||
|
||||
fn compute(size: i64, unit: Unit, span: Span) -> Result<Value, ShellError> {
|
||||
unit.to_value(size, span)
|
||||
}
|
||||
|
||||
fn gen_save_call(
|
||||
save_decl_id: DeclId,
|
||||
out_info: (Span, Expression, bool),
|
||||
@ -1319,3 +902,262 @@ impl DataSaveJob {
|
||||
self.inner.join()
|
||||
}
|
||||
}
|
||||
|
||||
struct EvalRuntime;
|
||||
|
||||
impl Eval for EvalRuntime {
|
||||
type State<'a> = &'a EngineState;
|
||||
|
||||
type MutState = Stack;
|
||||
|
||||
fn eval_filepath(
|
||||
engine_state: Self::State<'_>,
|
||||
stack: &mut Self::MutState,
|
||||
path: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let path = expand_path_with(path, cwd);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), span))
|
||||
}
|
||||
|
||||
fn eval_directory(
|
||||
engine_state: Self::State<'_>,
|
||||
stack: &mut Self::MutState,
|
||||
path: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
if path == "-" {
|
||||
Ok(Value::string("-", span))
|
||||
} else {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let path = expand_path_with(path, cwd);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), span))
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
// FIXME: protect this collect with ctrl-c
|
||||
Ok(eval_call(engine_state, stack, call, PipelineData::empty())?.into_value(call.head))
|
||||
}
|
||||
|
||||
fn eval_external_call(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
head: &Expression,
|
||||
args: &[Expression],
|
||||
is_subexpression: bool,
|
||||
_: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let span = head.span;
|
||||
// FIXME: protect this collect with ctrl-c
|
||||
Ok(eval_external(
|
||||
engine_state,
|
||||
stack,
|
||||
head,
|
||||
args,
|
||||
PipelineData::empty(),
|
||||
RedirectTarget::Piped(false, false),
|
||||
is_subexpression,
|
||||
)?
|
||||
.into_value(span))
|
||||
}
|
||||
|
||||
fn eval_subexpression(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
// FIXME: protect this collect with ctrl-c
|
||||
Ok(eval_subexpression(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(
|
||||
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(engine_state, stack, rhs)?;
|
||||
|
||||
let rhs = match assignment {
|
||||
Assignment::Assign => rhs,
|
||||
Assignment::PlusAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.add(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::MinusAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.sub(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::MultiplyAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.mul(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::DivideAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.div(op_span, &rhs, op_span)?
|
||||
}
|
||||
Assignment::AppendAssign => {
|
||||
let lhs = eval_expression(engine_state, stack, lhs)?;
|
||||
lhs.append(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))
|
||||
} else {
|
||||
Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span })
|
||||
}
|
||||
}
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
match &cell_path.head.expr {
|
||||
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
|
||||
// The $env variable is considered "mutable" in Nushell.
|
||||
// As such, give it special treatment here.
|
||||
let is_env = var_id == &ENV_VARIABLE_ID;
|
||||
if is_env || engine_state.get_var(*var_id).mutable {
|
||||
let mut lhs = eval_expression(engine_state, stack, &cell_path.head)?;
|
||||
|
||||
lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
|
||||
if is_env {
|
||||
if cell_path.tail.is_empty() {
|
||||
return Err(ShellError::CannotReplaceEnv {
|
||||
span: cell_path.head.span,
|
||||
});
|
||||
}
|
||||
|
||||
// The special $env treatment: for something like $env.config.history.max_size = 2000,
|
||||
// get $env.config (or whichever one it is) AFTER the above mutation, and set it
|
||||
// as the "config" environment variable.
|
||||
let vardata =
|
||||
lhs.follow_cell_path(&[cell_path.tail[0].clone()], false)?;
|
||||
match &cell_path.tail[0] {
|
||||
PathMember::String { val, span, .. } => {
|
||||
if val == "FILE_PWD"
|
||||
|| val == "CURRENT_FILE"
|
||||
|| val == "PWD"
|
||||
{
|
||||
return Err(ShellError::AutomaticEnvVarSetManually {
|
||||
envvar_name: val.to_string(),
|
||||
span: *span,
|
||||
});
|
||||
} else {
|
||||
stack.add_env_var(val.to_string(), vardata);
|
||||
}
|
||||
}
|
||||
// In case someone really wants an integer env-var
|
||||
PathMember::Int { val, .. } => {
|
||||
stack.add_env_var(val.to_string(), vardata);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stack.add_var(*var_id, lhs);
|
||||
}
|
||||
Ok(Value::nothing(cell_path.head.span))
|
||||
} else {
|
||||
Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span })
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_row_condition_or_closure(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let captures = engine_state
|
||||
.get_block(block_id)
|
||||
.captures
|
||||
.iter()
|
||||
.map(|&id| stack.get_var(id, span).map(|var| (id, var)))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(Value::closure(Closure { block_id, captures }, span))
|
||||
}
|
||||
|
||||
fn eval_string_interpolation(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
exprs: &[Expression],
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let mut parts = vec![];
|
||||
for expr in exprs {
|
||||
parts.push(eval_expression(engine_state, stack, expr)?);
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
|
||||
parts
|
||||
.into_iter()
|
||||
.into_pipeline_data(None)
|
||||
.collect_string("", config)
|
||||
.map(|x| Value::string(x, 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 eval_glob_pattern(
|
||||
engine_state: Self::State<'_>,
|
||||
stack: &mut Self::MutState,
|
||||
pattern: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let path = expand_path_with(pattern, cwd);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), span))
|
||||
}
|
||||
|
||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
||||
Ok(Value::nothing(expr.span))
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use nu_protocol::{
|
||||
ImportPatternMember, Pipeline, PipelineElement,
|
||||
},
|
||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||
eval_const::{eval_constant, value_as_string},
|
||||
eval_const::eval_constant,
|
||||
span, Alias, BlockId, DeclId, Exportable, Module, ModuleId, ParseError, PositionalArg,
|
||||
ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, VarId,
|
||||
};
|
||||
@ -2666,7 +2666,7 @@ pub fn parse_overlay_new(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
|
||||
let (overlay_name, _) = if let Some(expr) = call.positional_nth(0) {
|
||||
match eval_constant(working_set, expr) {
|
||||
Ok(val) => match value_as_string(val, expr.span) {
|
||||
Ok(val) => match val.as_string() {
|
||||
Ok(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
@ -2715,7 +2715,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
|
||||
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
||||
match eval_constant(working_set, expr) {
|
||||
Ok(val) => match value_as_string(val, expr.span) {
|
||||
Ok(val) => match val.as_string() {
|
||||
Ok(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
@ -2738,7 +2738,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
let new_name = if let Some(kw_expression) = call.positional_nth(1) {
|
||||
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
||||
match eval_constant(working_set, new_name_expression) {
|
||||
Ok(val) => match value_as_string(val, new_name_expression.span) {
|
||||
Ok(val) => match val.as_string() {
|
||||
Ok(s) => Some(Spanned {
|
||||
item: s,
|
||||
span: new_name_expression.span,
|
||||
@ -2932,7 +2932,7 @@ pub fn parse_overlay_hide(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
|
||||
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
||||
match eval_constant(working_set, expr) {
|
||||
Ok(val) => match value_as_string(val, expr.span) {
|
||||
Ok(val) => match val.as_string() {
|
||||
Ok(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
@ -3393,7 +3393,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
|
||||
}
|
||||
};
|
||||
|
||||
let filename = match value_as_string(val, spans[1]) {
|
||||
let filename = match val.as_string() {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
||||
@ -3589,8 +3589,9 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe
|
||||
.map(|expr| {
|
||||
let val =
|
||||
eval_constant(working_set, expr).map_err(|err| err.wrap(working_set, call.head))?;
|
||||
let filename =
|
||||
value_as_string(val, expr.span).map_err(|err| err.wrap(working_set, call.head))?;
|
||||
let filename = val
|
||||
.as_string()
|
||||
.map_err(|err| err.wrap(working_set, call.head))?;
|
||||
|
||||
let Some(path) = find_in_dirs(&filename, working_set, &cwd, PLUGIN_DIRS_VAR) else {
|
||||
return Err(ParseError::RegisteredFileNotFound(filename, expr.span));
|
||||
|
@ -17,7 +17,7 @@ use nu_protocol::{
|
||||
RecordItem,
|
||||
},
|
||||
engine::StateWorkingSet,
|
||||
eval_const::{eval_constant, value_as_string},
|
||||
eval_const::eval_constant,
|
||||
span, BlockId, DidYouMean, Flag, ParseError, PositionalArg, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Unit, VarId, ENV_VARIABLE_ID, IN_VARIABLE_ID,
|
||||
};
|
||||
@ -2703,7 +2703,7 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -
|
||||
let head_expr = parse_value(working_set, *head_span, &SyntaxShape::Any);
|
||||
|
||||
let (maybe_module_id, head_name) = match eval_constant(working_set, &head_expr) {
|
||||
Ok(val) => match value_as_string(val, head_expr.span) {
|
||||
Ok(val) => match val.as_string() {
|
||||
Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
||||
Err(err) => {
|
||||
working_set.error(err.wrap(working_set, span(spans)));
|
||||
|
379
crates/nu-protocol/src/eval_base.rs
Normal file
379
crates/nu-protocol/src/eval_base.rs
Normal file
@ -0,0 +1,379 @@
|
||||
use crate::{
|
||||
ast::{
|
||||
eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression, Math,
|
||||
Operator, RecordItem,
|
||||
},
|
||||
Range, Record, ShellError, Span, Value, VarId,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// To share implementations for regular eval and const eval
|
||||
pub trait Eval {
|
||||
/// State that doesn't need to be mutated.
|
||||
/// EngineState for regular eval and StateWorkingSet for const eval
|
||||
type State<'a>: Copy;
|
||||
|
||||
/// State that needs to be mutated.
|
||||
/// This is the stack for regular eval, and unused by const eval
|
||||
type MutState;
|
||||
|
||||
fn eval(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
expr: &Expression,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &expr.expr {
|
||||
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
||||
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
||||
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
||||
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)),
|
||||
Expr::Filepath(path) => Self::eval_filepath(state, mut_state, path.clone(), expr.span),
|
||||
Expr::Directory(path) => {
|
||||
Self::eval_directory(state, mut_state, path.clone(), expr.span)
|
||||
}
|
||||
Expr::Var(var_id) => Self::eval_var(state, mut_state, *var_id, expr.span),
|
||||
Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr.span)),
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
let value = Self::eval(state, mut_state, &cell_path.head)?;
|
||||
|
||||
value.follow_cell_path(&cell_path.tail, false)
|
||||
}
|
||||
Expr::DateTime(dt) => Ok(Value::date(*dt, expr.span)),
|
||||
Expr::List(x) => {
|
||||
let mut output = vec![];
|
||||
for expr in x {
|
||||
match &expr.expr {
|
||||
Expr::Spread(expr) => match Self::eval(state, mut_state, expr)? {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
},
|
||||
_ => output.push(Self::eval(state, mut_state, expr)?),
|
||||
}
|
||||
}
|
||||
Ok(Value::list(output, expr.span))
|
||||
}
|
||||
Expr::Record(items) => {
|
||||
let mut record = Record::new();
|
||||
let mut col_names = HashMap::new();
|
||||
for item in items {
|
||||
match item {
|
||||
RecordItem::Pair(col, val) => {
|
||||
// avoid duplicate cols
|
||||
let col_name = Self::eval(state, mut_state, col)?.as_string()?;
|
||||
if let Some(orig_span) = col_names.get(&col_name) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name,
|
||||
second_use: col.span,
|
||||
first_use: *orig_span,
|
||||
});
|
||||
} else {
|
||||
col_names.insert(col_name.clone(), col.span);
|
||||
record.push(col_name, Self::eval(state, mut_state, val)?);
|
||||
}
|
||||
}
|
||||
RecordItem::Spread(_, inner) => {
|
||||
match Self::eval(state, mut_state, inner)? {
|
||||
Value::Record { val: inner_val, .. } => {
|
||||
for (col_name, val) in inner_val {
|
||||
if let Some(orig_span) = col_names.get(&col_name) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name,
|
||||
second_use: inner.span,
|
||||
first_use: *orig_span,
|
||||
});
|
||||
} else {
|
||||
col_names.insert(col_name.clone(), inner.span);
|
||||
record.push(col_name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::CannotSpreadAsRecord {
|
||||
span: inner.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::record(record, expr.span))
|
||||
}
|
||||
Expr::Table(headers, vals) => {
|
||||
let mut output_headers = vec![];
|
||||
for expr in headers {
|
||||
let header = Self::eval(state, mut_state, expr)?.as_string()?;
|
||||
if let Some(idx) = output_headers
|
||||
.iter()
|
||||
.position(|existing| existing == &header)
|
||||
{
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name: header,
|
||||
second_use: expr.span,
|
||||
first_use: headers[idx].span,
|
||||
});
|
||||
} else {
|
||||
output_headers.push(header);
|
||||
}
|
||||
}
|
||||
|
||||
let mut output_rows = vec![];
|
||||
for val in vals {
|
||||
let mut row = vec![];
|
||||
for expr in val {
|
||||
row.push(Self::eval(state, mut_state, expr)?);
|
||||
}
|
||||
// length equality already ensured in parser
|
||||
output_rows.push(Value::record(
|
||||
Record::from_raw_cols_vals(output_headers.clone(), row),
|
||||
expr.span,
|
||||
));
|
||||
}
|
||||
Ok(Value::list(output_rows, expr.span))
|
||||
}
|
||||
Expr::Keyword(_, _, expr) => Self::eval(state, mut_state, expr),
|
||||
Expr::String(s) => Ok(Value::string(s.clone(), expr.span)),
|
||||
Expr::Nothing => Ok(Value::nothing(expr.span)),
|
||||
Expr::ValueWithUnit(e, unit) => match Self::eval(state, mut_state, e)? {
|
||||
Value::Int { val, .. } => unit.item.to_value(val, unit.span),
|
||||
x => Err(ShellError::CantConvert {
|
||||
to_type: "unit value".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: e.span,
|
||||
help: None,
|
||||
}),
|
||||
},
|
||||
Expr::Call(call) => Self::eval_call(state, mut_state, call, expr.span),
|
||||
Expr::ExternalCall(head, args, is_subexpression) => {
|
||||
Self::eval_external_call(state, mut_state, head, args, *is_subexpression, expr.span)
|
||||
}
|
||||
Expr::Subexpression(block_id) => {
|
||||
Self::eval_subexpression(state, mut_state, *block_id, expr.span)
|
||||
}
|
||||
Expr::Range(from, next, to, operator) => {
|
||||
let from = if let Some(f) = from {
|
||||
Self::eval(state, mut_state, f)?
|
||||
} else {
|
||||
Value::nothing(expr.span)
|
||||
};
|
||||
|
||||
let next = if let Some(s) = next {
|
||||
Self::eval(state, mut_state, s)?
|
||||
} else {
|
||||
Value::nothing(expr.span)
|
||||
};
|
||||
|
||||
let to = if let Some(t) = to {
|
||||
Self::eval(state, mut_state, t)?
|
||||
} else {
|
||||
Value::nothing(expr.span)
|
||||
};
|
||||
Ok(Value::range(
|
||||
Range::new(expr.span, from, next, to, operator)?,
|
||||
expr.span,
|
||||
))
|
||||
}
|
||||
Expr::UnaryNot(expr) => {
|
||||
let lhs = Self::eval(state, mut_state, expr)?;
|
||||
match lhs {
|
||||
Value::Bool { val, .. } => Ok(Value::bool(!val, expr.span)),
|
||||
other => Err(ShellError::TypeMismatch {
|
||||
err_message: format!("expected bool, found {}", other.get_type()),
|
||||
span: expr.span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Expr::BinaryOp(lhs, op, rhs) => {
|
||||
let op_span = op.span;
|
||||
let op = eval_operator(op)?;
|
||||
|
||||
match op {
|
||||
Operator::Boolean(boolean) => {
|
||||
let lhs = Self::eval(state, mut_state, lhs)?;
|
||||
match boolean {
|
||||
Boolean::And => {
|
||||
if lhs.is_false() {
|
||||
Ok(Value::bool(false, expr.span))
|
||||
} else {
|
||||
let rhs = Self::eval(state, mut_state, rhs)?;
|
||||
lhs.and(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
Boolean::Or => {
|
||||
if lhs.is_true() {
|
||||
Ok(Value::bool(true, expr.span))
|
||||
} else {
|
||||
let rhs = Self::eval(state, mut_state, rhs)?;
|
||||
lhs.or(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
Boolean::Xor => {
|
||||
let rhs = Self::eval(state, mut_state, rhs)?;
|
||||
lhs.xor(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
Operator::Math(math) => {
|
||||
let lhs = Self::eval(state, mut_state, lhs)?;
|
||||
let rhs = Self::eval(state, mut_state, rhs)?;
|
||||
|
||||
match math {
|
||||
Math::Plus => lhs.add(op_span, &rhs, expr.span),
|
||||
Math::Minus => lhs.sub(op_span, &rhs, expr.span),
|
||||
Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
|
||||
Math::Divide => lhs.div(op_span, &rhs, expr.span),
|
||||
Math::Append => lhs.append(op_span, &rhs, expr.span),
|
||||
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
|
||||
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
|
||||
Math::Pow => lhs.pow(op_span, &rhs, expr.span),
|
||||
}
|
||||
}
|
||||
Operator::Comparison(comparison) => {
|
||||
let lhs = Self::eval(state, mut_state, lhs)?;
|
||||
let rhs = Self::eval(state, mut_state, rhs)?;
|
||||
match comparison {
|
||||
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
|
||||
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
|
||||
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
|
||||
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
|
||||
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
|
||||
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr.span),
|
||||
Comparison::In => lhs.r#in(op_span, &rhs, expr.span),
|
||||
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr.span),
|
||||
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
|
||||
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
|
||||
Comparison::RegexMatch => {
|
||||
Self::regex_match(state, op_span, &lhs, &rhs, false, expr.span)
|
||||
}
|
||||
Comparison::NotRegexMatch => {
|
||||
Self::regex_match(state, op_span, &lhs, &rhs, true, expr.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
Operator::Bits(bits) => {
|
||||
let lhs = Self::eval(state, mut_state, lhs)?;
|
||||
let rhs = Self::eval(state, mut_state, rhs)?;
|
||||
match bits {
|
||||
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr.span),
|
||||
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr.span),
|
||||
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr.span),
|
||||
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr.span),
|
||||
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr.span),
|
||||
}
|
||||
}
|
||||
Operator::Assignment(assignment) => Self::eval_assignment(
|
||||
state, mut_state, lhs, rhs, assignment, op_span, expr.span,
|
||||
),
|
||||
}
|
||||
}
|
||||
Expr::Block(block_id) => Ok(Value::block(*block_id, expr.span)),
|
||||
Expr::RowCondition(block_id) | Expr::Closure(block_id) => {
|
||||
Self::eval_row_condition_or_closure(state, mut_state, *block_id, expr.span)
|
||||
}
|
||||
Expr::StringInterpolation(exprs) => {
|
||||
Self::eval_string_interpolation(state, mut_state, exprs, expr.span)
|
||||
}
|
||||
Expr::Overlay(_) => Self::eval_overlay(state, expr.span),
|
||||
Expr::GlobPattern(pattern) => {
|
||||
Self::eval_glob_pattern(state, mut_state, pattern.clone(), expr.span)
|
||||
}
|
||||
Expr::MatchPattern(pattern) => Ok(Value::match_pattern(*pattern.clone(), expr.span)),
|
||||
Expr::MatchBlock(_) // match blocks are handled by `match`
|
||||
| Expr::VarDecl(_)
|
||||
| Expr::ImportPattern(_)
|
||||
| Expr::Signature(_)
|
||||
| Expr::Spread(_)
|
||||
| Expr::Operator(_)
|
||||
| Expr::Garbage => Self::unreachable(expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_filepath(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
path: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_directory(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
path: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_var(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
var_id: VarId,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_call(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
call: &Call,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_external_call(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
head: &Expression,
|
||||
args: &[Expression],
|
||||
is_subexpression: bool,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_subexpression(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn regex_match(
|
||||
state: Self::State<'_>,
|
||||
op_span: Span,
|
||||
lhs: &Value,
|
||||
rhs: &Value,
|
||||
invert: bool,
|
||||
expr_span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_assignment(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
lhs: &Expression,
|
||||
rhs: &Expression,
|
||||
assignment: Assignment,
|
||||
op_span: Span,
|
||||
expr_span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_row_condition_or_closure(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_string_interpolation(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
exprs: &[Expression],
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_overlay(state: Self::State<'_>, span: Span) -> Result<Value, ShellError>;
|
||||
|
||||
fn eval_glob_pattern(
|
||||
state: Self::State<'_>,
|
||||
mut_state: &mut Self::MutState,
|
||||
pattern: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError>;
|
||||
|
||||
/// For expressions that should never actually be evaluated
|
||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError>;
|
||||
}
|
@ -1,16 +1,11 @@
|
||||
use crate::{
|
||||
ast::{
|
||||
eval_operator, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator,
|
||||
PipelineElement, RecordItem,
|
||||
},
|
||||
ast::{Assignment, Block, Call, Expr, Expression, PipelineElement},
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
record, HistoryFileFormat, PipelineData, Range, Record, ShellError, Span, Value,
|
||||
eval_base::Eval,
|
||||
record, HistoryFileFormat, PipelineData, Record, ShellError, Span, Value, VarId,
|
||||
};
|
||||
use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Value, ShellError> {
|
||||
fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
|
||||
@ -264,271 +259,140 @@ pub fn eval_constant_with_input(
|
||||
}
|
||||
|
||||
/// Evaluate a constant value at parse time
|
||||
///
|
||||
/// Based off eval_expression() in the engine
|
||||
pub fn eval_constant(
|
||||
working_set: &StateWorkingSet,
|
||||
expr: &Expression,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &expr.expr {
|
||||
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
||||
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
||||
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
||||
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)),
|
||||
Expr::Filepath(path) => Ok(Value::string(path.clone(), expr.span)),
|
||||
Expr::Var(var_id) => match working_set.get_variable(*var_id).const_val.as_ref() {
|
||||
<EvalConst as Eval>::eval(working_set, &mut (), expr)
|
||||
}
|
||||
|
||||
struct EvalConst;
|
||||
|
||||
impl Eval for EvalConst {
|
||||
type State<'a> = &'a StateWorkingSet<'a>;
|
||||
|
||||
type MutState = ();
|
||||
|
||||
fn eval_filepath(
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
path: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
Ok(Value::string(path, span))
|
||||
}
|
||||
|
||||
fn eval_directory(
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
_: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(span))
|
||||
}
|
||||
|
||||
fn eval_var(
|
||||
working_set: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
var_id: VarId,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
match working_set.get_variable(var_id).const_val.as_ref() {
|
||||
Some(val) => Ok(val.clone()),
|
||||
None => Err(ShellError::NotAConstant(expr.span)),
|
||||
},
|
||||
Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr.span)),
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
let value = eval_constant(working_set, &cell_path.head)?;
|
||||
|
||||
match value.follow_cell_path(&cell_path.tail, false) {
|
||||
Ok(val) => Ok(val),
|
||||
// TODO: Better error conversion
|
||||
Err(shell_error) => Err(ShellError::GenericError(
|
||||
"Error when following cell path".to_string(),
|
||||
format!("{shell_error:?}"),
|
||||
Some(expr.span),
|
||||
None,
|
||||
vec![],
|
||||
)),
|
||||
}
|
||||
}
|
||||
Expr::DateTime(dt) => Ok(Value::date(*dt, expr.span)),
|
||||
Expr::List(x) => {
|
||||
let mut output = vec![];
|
||||
for expr in x {
|
||||
match &expr.expr {
|
||||
Expr::Spread(expr) => match eval_constant(working_set, expr)? {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
},
|
||||
_ => output.push(eval_constant(working_set, expr)?),
|
||||
}
|
||||
}
|
||||
Ok(Value::list(output, expr.span))
|
||||
}
|
||||
Expr::Record(items) => {
|
||||
let mut record = Record::new();
|
||||
let mut col_names = HashMap::new();
|
||||
for item in items {
|
||||
match item {
|
||||
RecordItem::Pair(col, val) => {
|
||||
// avoid duplicate cols
|
||||
let col_name =
|
||||
value_as_string(eval_constant(working_set, col)?, expr.span)?;
|
||||
if let Some(orig_span) = col_names.get(&col_name) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name,
|
||||
second_use: col.span,
|
||||
first_use: *orig_span,
|
||||
});
|
||||
} else {
|
||||
col_names.insert(col_name.clone(), col.span);
|
||||
record.push(col_name, eval_constant(working_set, val)?);
|
||||
}
|
||||
}
|
||||
RecordItem::Spread(_, inner) => match eval_constant(working_set, inner)? {
|
||||
Value::Record { val: inner_val, .. } => {
|
||||
for (col_name, val) in inner_val {
|
||||
if let Some(orig_span) = col_names.get(&col_name) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name,
|
||||
second_use: inner.span,
|
||||
first_use: *orig_span,
|
||||
});
|
||||
} else {
|
||||
col_names.insert(col_name.clone(), inner.span);
|
||||
record.push(col_name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(ShellError::CannotSpreadAsRecord { span: inner.span }),
|
||||
},
|
||||
None => Err(ShellError::NotAConstant(span)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::record(record, expr.span))
|
||||
}
|
||||
Expr::Table(headers, vals) => {
|
||||
let mut output_headers = vec![];
|
||||
for expr in headers {
|
||||
let header = value_as_string(eval_constant(working_set, expr)?, expr.span)?;
|
||||
if let Some(idx) = output_headers
|
||||
.iter()
|
||||
.position(|existing| existing == &header)
|
||||
{
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name: header,
|
||||
second_use: expr.span,
|
||||
first_use: headers[idx].span,
|
||||
});
|
||||
} else {
|
||||
output_headers.push(header);
|
||||
}
|
||||
fn eval_call(
|
||||
working_set: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
call: &Call,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
// TODO: eval.rs uses call.head for the span rather than expr.span
|
||||
Ok(eval_const_call(working_set, call, PipelineData::empty())?.into_value(span))
|
||||
}
|
||||
|
||||
let mut output_rows = vec![];
|
||||
for val in vals {
|
||||
let mut row = vec![];
|
||||
for expr in val {
|
||||
row.push(eval_constant(working_set, expr)?);
|
||||
fn eval_external_call(
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
_: &Expression,
|
||||
_: &[Expression],
|
||||
_: bool,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
// TODO: It may be more helpful to give not_a_const_command error
|
||||
Err(ShellError::NotAConstant(span))
|
||||
}
|
||||
// length equality already ensured in parser
|
||||
output_rows.push(Value::record(
|
||||
Record::from_raw_cols_vals(output_headers.clone(), row),
|
||||
expr.span,
|
||||
));
|
||||
}
|
||||
Ok(Value::list(output_rows, expr.span))
|
||||
}
|
||||
Expr::Keyword(_, _, expr) => eval_constant(working_set, expr),
|
||||
Expr::String(s) => Ok(Value::string(s.clone(), expr.span)),
|
||||
Expr::Nothing => Ok(Value::nothing(expr.span)),
|
||||
Expr::ValueWithUnit(expr, unit) => {
|
||||
if let Ok(Value::Int { val, .. }) = eval_constant(working_set, expr) {
|
||||
unit.item.to_value(val, unit.span)
|
||||
} else {
|
||||
Err(ShellError::NotAConstant(expr.span))
|
||||
}
|
||||
}
|
||||
Expr::Call(call) => {
|
||||
Ok(eval_const_call(working_set, call, PipelineData::empty())?.into_value(expr.span))
|
||||
}
|
||||
Expr::Subexpression(block_id) => {
|
||||
let block = working_set.get_block(*block_id);
|
||||
|
||||
fn eval_subexpression(
|
||||
working_set: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
block_id: usize,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let block = working_set.get_block(block_id);
|
||||
Ok(
|
||||
eval_const_subexpression(working_set, block, PipelineData::empty(), expr.span)?
|
||||
.into_value(expr.span),
|
||||
eval_const_subexpression(working_set, block, PipelineData::empty(), span)?
|
||||
.into_value(span),
|
||||
)
|
||||
}
|
||||
Expr::Range(from, next, to, operator) => {
|
||||
let from = if let Some(f) = from {
|
||||
eval_constant(working_set, f)?
|
||||
} else {
|
||||
Value::Nothing {
|
||||
internal_span: expr.span,
|
||||
}
|
||||
};
|
||||
|
||||
let next = if let Some(s) = next {
|
||||
eval_constant(working_set, s)?
|
||||
} else {
|
||||
Value::Nothing {
|
||||
internal_span: expr.span,
|
||||
fn regex_match(
|
||||
_: &StateWorkingSet,
|
||||
_op_span: Span,
|
||||
_: &Value,
|
||||
_: &Value,
|
||||
_: bool,
|
||||
expr_span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(expr_span))
|
||||
}
|
||||
};
|
||||
|
||||
let to = if let Some(t) = to {
|
||||
eval_constant(working_set, t)?
|
||||
} else {
|
||||
Value::Nothing {
|
||||
internal_span: expr.span,
|
||||
fn eval_assignment(
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
_: &Expression,
|
||||
_: &Expression,
|
||||
_: Assignment,
|
||||
_op_span: Span,
|
||||
expr_span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(expr_span))
|
||||
}
|
||||
};
|
||||
Ok(Value::Range {
|
||||
val: Box::new(Range::new(expr.span, from, next, to, operator)?),
|
||||
internal_span: expr.span,
|
||||
})
|
||||
}
|
||||
Expr::UnaryNot(expr) => {
|
||||
let lhs = eval_constant(working_set, expr)?;
|
||||
match lhs {
|
||||
Value::Bool { val, .. } => Ok(Value::bool(!val, expr.span)),
|
||||
_ => Err(ShellError::TypeMismatch {
|
||||
err_message: "bool".to_string(),
|
||||
span: expr.span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Expr::BinaryOp(lhs, op, rhs) => {
|
||||
let op_span = op.span;
|
||||
let op = eval_operator(op)?;
|
||||
|
||||
match op {
|
||||
Operator::Boolean(boolean) => {
|
||||
let lhs = eval_constant(working_set, lhs)?;
|
||||
match boolean {
|
||||
Boolean::And => {
|
||||
if lhs.is_false() {
|
||||
Ok(Value::bool(false, expr.span))
|
||||
} else {
|
||||
let rhs = eval_constant(working_set, rhs)?;
|
||||
lhs.and(op_span, &rhs, expr.span)
|
||||
fn eval_row_condition_or_closure(
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
_: usize,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(span))
|
||||
}
|
||||
}
|
||||
Boolean::Or => {
|
||||
if lhs.is_true() {
|
||||
Ok(Value::bool(true, expr.span))
|
||||
} else {
|
||||
let rhs = eval_constant(working_set, rhs)?;
|
||||
lhs.or(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
Boolean::Xor => {
|
||||
let rhs = eval_constant(working_set, rhs)?;
|
||||
lhs.xor(op_span, &rhs, expr.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
Operator::Math(math) => {
|
||||
let lhs = eval_constant(working_set, lhs)?;
|
||||
let rhs = eval_constant(working_set, rhs)?;
|
||||
|
||||
match math {
|
||||
Math::Plus => lhs.add(op_span, &rhs, expr.span),
|
||||
Math::Minus => lhs.sub(op_span, &rhs, expr.span),
|
||||
Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
|
||||
Math::Divide => lhs.div(op_span, &rhs, expr.span),
|
||||
Math::Append => lhs.append(op_span, &rhs, expr.span),
|
||||
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
|
||||
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
|
||||
Math::Pow => lhs.pow(op_span, &rhs, expr.span),
|
||||
fn eval_string_interpolation(
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
_: &[Expression],
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(span))
|
||||
}
|
||||
|
||||
fn eval_overlay(_: &StateWorkingSet, span: Span) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(span))
|
||||
}
|
||||
Operator::Comparison(comparison) => {
|
||||
let lhs = eval_constant(working_set, lhs)?;
|
||||
let rhs = eval_constant(working_set, rhs)?;
|
||||
match comparison {
|
||||
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
|
||||
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
|
||||
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
|
||||
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
|
||||
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
|
||||
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr.span),
|
||||
Comparison::In => lhs.r#in(op_span, &rhs, expr.span),
|
||||
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr.span),
|
||||
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
|
||||
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
|
||||
// RegEx comparison is not a constant
|
||||
_ => Err(ShellError::NotAConstant(expr.span)),
|
||||
|
||||
fn eval_glob_pattern(
|
||||
_: &StateWorkingSet,
|
||||
_: &mut (),
|
||||
_: String,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(span))
|
||||
}
|
||||
}
|
||||
Operator::Bits(bits) => {
|
||||
let lhs = eval_constant(working_set, lhs)?;
|
||||
let rhs = eval_constant(working_set, rhs)?;
|
||||
match bits {
|
||||
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr.span),
|
||||
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr.span),
|
||||
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr.span),
|
||||
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr.span),
|
||||
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr.span),
|
||||
}
|
||||
}
|
||||
Operator::Assignment(_) => Err(ShellError::NotAConstant(expr.span)),
|
||||
}
|
||||
}
|
||||
Expr::Block(block_id) => Ok(Value::block(*block_id, expr.span)),
|
||||
_ => Err(ShellError::NotAConstant(expr.span)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value as a string
|
||||
pub fn value_as_string(value: Value, span: Span) -> Result<String, ShellError> {
|
||||
match value {
|
||||
Value::String { val, .. } => Ok(val),
|
||||
_ => Err(ShellError::NotAConstant(span)),
|
||||
|
||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
||||
Err(ShellError::NotAConstant(expr.span))
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ pub mod cli_error;
|
||||
pub mod config;
|
||||
mod did_you_mean;
|
||||
pub mod engine;
|
||||
pub mod eval_base;
|
||||
pub mod eval_const;
|
||||
mod example;
|
||||
mod exportable;
|
||||
|
Loading…
Reference in New Issue
Block a user