forked from extern/nushell
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:
parent
7a728340de
commit
54394fe9af
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use nu_test_support::nu;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
const MODULE_SETUP: &str = r#"
|
||||
module spam {
|
||||
@ -107,6 +108,54 @@ fn const_nothing() {
|
||||
assert_eq!(actual.out, "nothing");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(&["const x = not false", "$x"], "true")]
|
||||
#[case(&["const x = false", "const y = not $x", "$y"], "true")]
|
||||
#[case(&["const x = not false", "const y = not $x", "$y"], "false")]
|
||||
fn const_unary_operator(#[case] inp: &[&str], #[case] expect: &str) {
|
||||
let actual = nu!(&inp.join("; "));
|
||||
assert_eq!(actual.out, expect);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(&["const x = 1 + 2", "$x"], "3")]
|
||||
#[case(&["const x = 1 * 2", "$x"], "2")]
|
||||
#[case(&["const x = 4 / 2", "$x"], "2")]
|
||||
#[case(&["const x = 4 mod 3", "$x"], "1")]
|
||||
#[case(&["const x = 5.0 / 2.0", "$x"], "2.5")]
|
||||
#[case(&[r#"const x = "a" + "b" "#, "$x"], "ab")]
|
||||
#[case(&[r#"const x = "a" ++ "b" "#, "$x"], "ab")]
|
||||
#[case(&[r#"const x = [1,2] ++ [3]"#, "$x | describe"], "list<int>")]
|
||||
#[case(&[r#"const x = 0x[1,2] ++ 0x[3]"#, "$x | describe"], "binary")]
|
||||
#[case(&[r#"const x = 0x[1,2] ++ [3]"#, "$x | describe"], "list<any>")]
|
||||
#[case(&["const x = 1 < 2", "$x"], "true")]
|
||||
#[case(&["const x = (3 * 200) > (2 * 100)", "$x"], "true")]
|
||||
#[case(&["const x = (3 * 200) < (2 * 100)", "$x"], "false")]
|
||||
#[case(&["const x = (3 * 200) == (2 * 300)", "$x"], "true")]
|
||||
fn const_binary_operator(#[case] inp: &[&str], #[case] expect: &str) {
|
||||
let actual = nu!(&inp.join("; "));
|
||||
assert_eq!(actual.out, expect);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(&["const x = 1 / 0", "$x"], "division by zero")]
|
||||
#[case(&["const x = 10 ** 10000000", "$x"], "pow operation overflowed")]
|
||||
#[case(&["const x = 2 ** 62 * 2", "$x"], "multiply operation overflowed")]
|
||||
#[case(&["const x = 1 ++ 0", "$x"], "doesn't support this value")]
|
||||
#[case(&["const x = 20 * a", "$x"], "Value is not a parse-time constant")]
|
||||
fn const_operator_error(#[case] inp: &[&str], #[case] expect: &str) {
|
||||
let actual = nu!(&inp.join("; "));
|
||||
assert!(actual.err.contains(expect));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(&["const x = (1..3)", "$x | math sum"], "6")]
|
||||
#[case(&["const x = (1..3)", "$x | describe"], "range")]
|
||||
fn const_range(#[case] inp: &[&str], #[case] expect: &str) {
|
||||
let actual = nu!(&inp.join("; "));
|
||||
assert_eq!(actual.out, expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_subexpression_supported() {
|
||||
let inp = &["const x = ('spam')", "$x"];
|
||||
|
Loading…
Reference in New Issue
Block a user