Allow operator in constants (#10212)

This pr fixes https://github.com/nushell/nushell/issues/10200

# Description

Allow unary and binary operators in constants, e.g.

```bash
const a = 1 + 2
const b = [0, 1, 2, 3] ++ [4]
```

# User-Facing Changes

Now constants can contain operators.

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

None

---------

Co-authored-by: Horasal <horsal@horsal.dev>
This commit is contained in:
Horasal
2023-09-05 23:35:58 +09:00
committed by GitHub
parent 7a728340de
commit 54394fe9af
5 changed files with 199 additions and 19 deletions

View File

@ -2,8 +2,8 @@ use crate::{current_dir_str, get_full_help};
use nu_path::expand_path_with;
use nu_protocol::{
ast::{
Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math,
Operator, PathMember, PipelineElement, Redirection,
eval_operator, Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr,
Expression, Math, Operator, PathMember, PipelineElement, Redirection,
},
engine::{EngineState, ProfilingConfig, Stack},
record, DataSource, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
@ -13,19 +13,6 @@ use nu_protocol::{
use std::collections::HashMap;
use std::time::Instant;
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
match op {
Expression {
expr: Expr::Operator(operator),
..
} => Ok(operator.clone()),
Expression { span, expr, .. } => Err(ShellError::UnknownOperator {
op_token: format!("{expr:?}"),
span: *span,
}),
}
}
pub fn eval_call(
engine_state: &EngineState,
caller_stack: &mut Stack,

View File

@ -12,6 +12,6 @@ pub use documentation::get_full_help;
pub use env::*;
pub use eval::{
eval_block, eval_block_with_early_return, eval_call, eval_expression,
eval_expression_with_input, eval_operator, eval_subexpression, eval_variable, redirect_env,
eval_expression_with_input, eval_subexpression, eval_variable, redirect_env,
};
pub use glob_from::glob_from;

View File

@ -1,8 +1,10 @@
use crate::Span;
use crate::{ShellError, Span};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use super::{Expr, Expression};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Comparison {
Equal,
@ -128,3 +130,16 @@ impl Display for RangeOperator {
}
}
}
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
match op {
Expression {
expr: Expr::Operator(operator),
..
} => Ok(operator.clone()),
Expression { span, expr, .. } => Err(ShellError::UnknownOperator {
op_token: format!("{expr:?}"),
span: *span,
}),
}
}

View File

@ -1,7 +1,10 @@
use crate::{
ast::{Block, Call, Expr, Expression, PipelineElement},
ast::{
eval_operator, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator,
PipelineElement,
},
engine::{EngineState, StateWorkingSet},
record, HistoryFileFormat, PipelineData, Record, ShellError, Span, Value,
record, HistoryFileFormat, PipelineData, Range, Record, ShellError, Span, Value,
};
use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
use std::path::PathBuf;
@ -342,6 +345,132 @@ pub fn eval_constant(
.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) => {
match (&math, &lhs.expr, &rhs.expr) {
// Multiple may generate super long string
// and stop the whole shell while highlighting
// e.g. '2 ** 60 * a'
(Math::Multiply, Expr::String(..), _)
| (Math::Multiply, _, Expr::String(..)) => {
return Err(ShellError::NotAConstant(expr.span))
}
_ => {}
}
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)),
}
}
_ => Err(ShellError::NotAConstant(expr.span)),
}
}