Reduce code duplication in eval.rs and eval_const.rs (#11192)

This commit is contained in:
Yash Thakur 2023-12-04 14:13:47 -05:00 committed by GitHub
parent fc06afd051
commit c1a30ac60f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 783 additions and 696 deletions

View File

@ -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))
}
}

View File

@ -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));

View File

@ -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)));

View 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>;
}

View File

@ -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) {
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 }),
},
} }
} }
Ok(Value::record(record, expr.span)) fn eval_call(
} working_set: &StateWorkingSet,
Expr::Table(headers, vals) => { _: &mut (),
let mut output_headers = vec![]; call: &Call,
for expr in headers { span: Span,
let header = value_as_string(eval_constant(working_set, expr)?, expr.span)?; ) -> Result<Value, ShellError> {
if let Some(idx) = output_headers // TODO: eval.rs uses call.head for the span rather than expr.span
.iter() Ok(eval_const_call(working_set, call, PipelineData::empty())?.into_value(span))
.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![]; fn eval_external_call(
for val in vals { _: &StateWorkingSet,
let mut row = vec![]; _: &mut (),
for expr in val { _: &Expression,
row.push(eval_constant(working_set, expr)?); _: &[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( fn eval_subexpression(
Record::from_raw_cols_vals(output_headers.clone(), row), working_set: &StateWorkingSet,
expr.span, _: &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(), span)?
.into_value(span),
)
} }
Ok(Value::list(output_rows, expr.span))
fn regex_match(
_: &StateWorkingSet,
_op_span: Span,
_: &Value,
_: &Value,
_: bool,
expr_span: Span,
) -> Result<Value, ShellError> {
Err(ShellError::NotAConstant(expr_span))
} }
Expr::Keyword(_, _, expr) => eval_constant(working_set, expr),
Expr::String(s) => Ok(Value::string(s.clone(), expr.span)), fn eval_assignment(
Expr::Nothing => Ok(Value::nothing(expr.span)), _: &StateWorkingSet,
Expr::ValueWithUnit(expr, unit) => { _: &mut (),
if let Ok(Value::Int { val, .. }) = eval_constant(working_set, expr) { _: &Expression,
unit.item.to_value(val, unit.span) _: &Expression,
} else { _: Assignment,
_op_span: Span,
expr_span: Span,
) -> Result<Value, ShellError> {
Err(ShellError::NotAConstant(expr_span))
}
fn eval_row_condition_or_closure(
_: &StateWorkingSet,
_: &mut (),
_: usize,
span: Span,
) -> Result<Value, ShellError> {
Err(ShellError::NotAConstant(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))
}
fn eval_glob_pattern(
_: &StateWorkingSet,
_: &mut (),
_: String,
span: Span,
) -> Result<Value, ShellError> {
Err(ShellError::NotAConstant(span))
}
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
Err(ShellError::NotAConstant(expr.span)) 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 {
eval_constant(working_set, s)?
} else {
Value::Nothing {
internal_span: expr.span,
}
};
let to = if let Some(t) = to {
eval_constant(working_set, t)?
} else {
Value::Nothing {
internal_span: 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)
}
}
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),
}
}
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)),
}
}
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)),
}
}

View File

@ -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;