mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Reduce code duplication in eval.rs and eval_const.rs (#11192)
This commit is contained in:
parent
fc06afd051
commit
c1a30ac60f
6 changed files with 783 additions and 696 deletions
|
@ -2,12 +2,13 @@ use crate::{current_dir_str, get_full_help};
|
||||||
use nu_path::expand_path_with;
|
use nu_path::expand_path_with;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{
|
ast::{
|
||||||
eval_operator, Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr,
|
Argument, Assignment, Block, Call, Expr, Expression, PathMember, PipelineElement,
|
||||||
Expression, Math, Operator, PathMember, PipelineElement, RecordItem, Redirection,
|
Redirection,
|
||||||
},
|
},
|
||||||
engine::{Closure, EngineState, Stack},
|
engine::{Closure, EngineState, Stack},
|
||||||
DeclId, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, Record,
|
eval_base::Eval,
|
||||||
ShellError, Span, Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID,
|
DeclId, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Span,
|
||||||
|
Spanned, Type, Value, VarId, ENV_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::thread::{self, JoinHandle};
|
use std::thread::{self, JoinHandle};
|
||||||
|
@ -253,421 +254,7 @@ pub fn eval_expression(
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
match &expr.expr {
|
<EvalRuntime as Eval>::eval(engine_state, stack, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks the expression to see if it's a internal or external call. If so, passes the input
|
/// 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(
|
fn gen_save_call(
|
||||||
save_decl_id: DeclId,
|
save_decl_id: DeclId,
|
||||||
out_info: (Span, Expression, bool),
|
out_info: (Span, Expression, bool),
|
||||||
|
@ -1319,3 +902,262 @@ impl DataSaveJob {
|
||||||
self.inner.join()
|
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,
|
ImportPatternMember, Pipeline, PipelineElement,
|
||||||
},
|
},
|
||||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
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,
|
span, Alias, BlockId, DeclId, Exportable, Module, ModuleId, ParseError, PositionalArg,
|
||||||
ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, VarId,
|
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) {
|
let (overlay_name, _) = if let Some(expr) = call.positional_nth(0) {
|
||||||
match eval_constant(working_set, expr) {
|
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),
|
Ok(s) => (s, expr.span),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err.wrap(working_set, call_span));
|
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) {
|
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
||||||
match eval_constant(working_set, expr) {
|
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),
|
Ok(s) => (s, expr.span),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err.wrap(working_set, call_span));
|
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) {
|
let new_name = if let Some(kw_expression) = call.positional_nth(1) {
|
||||||
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
||||||
match eval_constant(working_set, new_name_expression) {
|
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 {
|
Ok(s) => Some(Spanned {
|
||||||
item: s,
|
item: s,
|
||||||
span: new_name_expression.span,
|
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) {
|
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
||||||
match eval_constant(working_set, expr) {
|
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),
|
Ok(s) => (s, expr.span),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err.wrap(working_set, call_span));
|
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,
|
Ok(s) => s,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
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| {
|
.map(|expr| {
|
||||||
let val =
|
let val =
|
||||||
eval_constant(working_set, expr).map_err(|err| err.wrap(working_set, call.head))?;
|
eval_constant(working_set, expr).map_err(|err| err.wrap(working_set, call.head))?;
|
||||||
let filename =
|
let filename = val
|
||||||
value_as_string(val, expr.span).map_err(|err| err.wrap(working_set, call.head))?;
|
.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 {
|
let Some(path) = find_in_dirs(&filename, working_set, &cwd, PLUGIN_DIRS_VAR) else {
|
||||||
return Err(ParseError::RegisteredFileNotFound(filename, expr.span));
|
return Err(ParseError::RegisteredFileNotFound(filename, expr.span));
|
||||||
|
|
|
@ -17,7 +17,7 @@ use nu_protocol::{
|
||||||
RecordItem,
|
RecordItem,
|
||||||
},
|
},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
eval_const::{eval_constant, value_as_string},
|
eval_const::eval_constant,
|
||||||
span, BlockId, DidYouMean, Flag, ParseError, PositionalArg, Signature, Span, Spanned,
|
span, BlockId, DidYouMean, Flag, ParseError, PositionalArg, Signature, Span, Spanned,
|
||||||
SyntaxShape, Type, Unit, VarId, ENV_VARIABLE_ID, IN_VARIABLE_ID,
|
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 head_expr = parse_value(working_set, *head_span, &SyntaxShape::Any);
|
||||||
|
|
||||||
let (maybe_module_id, head_name) = match eval_constant(working_set, &head_expr) {
|
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()),
|
Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
working_set.error(err.wrap(working_set, span(spans)));
|
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::{
|
use crate::{
|
||||||
ast::{
|
ast::{Assignment, Block, Call, Expr, Expression, PipelineElement},
|
||||||
eval_operator, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator,
|
|
||||||
PipelineElement, RecordItem,
|
|
||||||
},
|
|
||||||
engine::{EngineState, StateWorkingSet},
|
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 nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
collections::HashMap,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Value, ShellError> {
|
pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Value, ShellError> {
|
||||||
fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
|
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
|
/// Evaluate a constant value at parse time
|
||||||
///
|
|
||||||
/// Based off eval_expression() in the engine
|
|
||||||
pub fn eval_constant(
|
pub fn eval_constant(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
match &expr.expr {
|
<EvalConst as Eval>::eval(working_set, &mut (), 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)),
|
struct EvalConst;
|
||||||
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)),
|
|
||||||
Expr::Filepath(path) => Ok(Value::string(path.clone(), expr.span)),
|
impl Eval for EvalConst {
|
||||||
Expr::Var(var_id) => match working_set.get_variable(*var_id).const_val.as_ref() {
|
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()),
|
Some(val) => Ok(val.clone()),
|
||||||
None => Err(ShellError::NotAConstant(expr.span)),
|
None => Err(ShellError::NotAConstant(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) {
|
fn eval_call(
|
||||||
Ok(val) => Ok(val),
|
working_set: &StateWorkingSet,
|
||||||
// TODO: Better error conversion
|
_: &mut (),
|
||||||
Err(shell_error) => Err(ShellError::GenericError(
|
call: &Call,
|
||||||
"Error when following cell path".to_string(),
|
span: Span,
|
||||||
format!("{shell_error:?}"),
|
) -> Result<Value, ShellError> {
|
||||||
Some(expr.span),
|
// TODO: eval.rs uses call.head for the span rather than expr.span
|
||||||
None,
|
Ok(eval_const_call(working_set, call, PipelineData::empty())?.into_value(span))
|
||||||
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 }),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Value::record(record, expr.span))
|
fn eval_external_call(
|
||||||
}
|
_: &StateWorkingSet,
|
||||||
Expr::Table(headers, vals) => {
|
_: &mut (),
|
||||||
let mut output_headers = vec![];
|
_: &Expression,
|
||||||
for expr in headers {
|
_: &[Expression],
|
||||||
let header = value_as_string(eval_constant(working_set, expr)?, expr.span)?;
|
_: bool,
|
||||||
if let Some(idx) = output_headers
|
span: Span,
|
||||||
.iter()
|
) -> Result<Value, ShellError> {
|
||||||
.position(|existing| existing == &header)
|
// TODO: It may be more helpful to give not_a_const_command error
|
||||||
{
|
Err(ShellError::NotAConstant(span))
|
||||||
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![];
|
fn eval_subexpression(
|
||||||
for val in vals {
|
working_set: &StateWorkingSet,
|
||||||
let mut row = vec![];
|
_: &mut (),
|
||||||
for expr in val {
|
block_id: usize,
|
||||||
row.push(eval_constant(working_set, expr)?);
|
span: Span,
|
||||||
}
|
) -> Result<Value, ShellError> {
|
||||||
// length equality already ensured in parser
|
let block = working_set.get_block(block_id);
|
||||||
output_rows.push(Value::record(
|
Ok(
|
||||||
Record::from_raw_cols_vals(output_headers.clone(), row),
|
eval_const_subexpression(working_set, block, PipelineData::empty(), span)?
|
||||||
expr.span,
|
.into_value(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);
|
|
||||||
Ok(
|
|
||||||
eval_const_subexpression(working_set, block, PipelineData::empty(), expr.span)?
|
|
||||||
.into_value(expr.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 {
|
fn regex_match(
|
||||||
eval_constant(working_set, s)?
|
_: &StateWorkingSet,
|
||||||
} else {
|
_op_span: Span,
|
||||||
Value::Nothing {
|
_: &Value,
|
||||||
internal_span: expr.span,
|
_: &Value,
|
||||||
}
|
_: bool,
|
||||||
};
|
expr_span: Span,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
Err(ShellError::NotAConstant(expr_span))
|
||||||
|
}
|
||||||
|
|
||||||
let to = if let Some(t) = to {
|
fn eval_assignment(
|
||||||
eval_constant(working_set, t)?
|
_: &StateWorkingSet,
|
||||||
} else {
|
_: &mut (),
|
||||||
Value::Nothing {
|
_: &Expression,
|
||||||
internal_span: expr.span,
|
_: &Expression,
|
||||||
}
|
_: Assignment,
|
||||||
};
|
_op_span: Span,
|
||||||
Ok(Value::Range {
|
expr_span: Span,
|
||||||
val: Box::new(Range::new(expr.span, from, next, to, operator)?),
|
) -> Result<Value, ShellError> {
|
||||||
internal_span: expr.span,
|
Err(ShellError::NotAConstant(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 {
|
fn eval_row_condition_or_closure(
|
||||||
Operator::Boolean(boolean) => {
|
_: &StateWorkingSet,
|
||||||
let lhs = eval_constant(working_set, lhs)?;
|
_: &mut (),
|
||||||
match boolean {
|
_: usize,
|
||||||
Boolean::And => {
|
span: Span,
|
||||||
if lhs.is_false() {
|
) -> Result<Value, ShellError> {
|
||||||
Ok(Value::bool(false, expr.span))
|
Err(ShellError::NotAConstant(span))
|
||||||
} else {
|
}
|
||||||
let rhs = eval_constant(working_set, rhs)?;
|
|
||||||
lhs.and(op_span, &rhs, expr.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 {
|
fn eval_string_interpolation(
|
||||||
Math::Plus => lhs.add(op_span, &rhs, expr.span),
|
_: &StateWorkingSet,
|
||||||
Math::Minus => lhs.sub(op_span, &rhs, expr.span),
|
_: &mut (),
|
||||||
Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
|
_: &[Expression],
|
||||||
Math::Divide => lhs.div(op_span, &rhs, expr.span),
|
span: Span,
|
||||||
Math::Append => lhs.append(op_span, &rhs, expr.span),
|
) -> Result<Value, ShellError> {
|
||||||
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
|
Err(ShellError::NotAConstant(span))
|
||||||
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
|
}
|
||||||
Math::Pow => lhs.pow(op_span, &rhs, expr.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)?;
|
fn eval_glob_pattern(
|
||||||
match comparison {
|
_: &StateWorkingSet,
|
||||||
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
|
_: &mut (),
|
||||||
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
|
_: String,
|
||||||
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
|
span: Span,
|
||||||
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
|
) -> Result<Value, ShellError> {
|
||||||
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
|
Err(ShellError::NotAConstant(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),
|
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
||||||
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
|
Err(ShellError::NotAConstant(expr.span))
|
||||||
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
|
|
||||||
// RegEx comparison is not a constant
|
|
||||||
_ => Err(ShellError::NotAConstant(expr.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)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub mod cli_error;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod did_you_mean;
|
mod did_you_mean;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
|
pub mod eval_base;
|
||||||
pub mod eval_const;
|
pub mod eval_const;
|
||||||
mod example;
|
mod example;
|
||||||
mod exportable;
|
mod exportable;
|
||||||
|
|
Loading…
Reference in a new issue