mirror of
https://github.com/nushell/nushell
synced 2025-01-13 05:38:57 +00:00
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
5 changed files with 199 additions and 19 deletions
|
@ -2,8 +2,8 @@ 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::{
|
||||||
Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math,
|
eval_operator, Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr,
|
||||||
Operator, PathMember, PipelineElement, Redirection,
|
Expression, Math, Operator, PathMember, PipelineElement, Redirection,
|
||||||
},
|
},
|
||||||
engine::{EngineState, ProfilingConfig, Stack},
|
engine::{EngineState, ProfilingConfig, Stack},
|
||||||
record, DataSource, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
record, DataSource, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||||
|
@ -13,19 +13,6 @@ use nu_protocol::{
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Instant;
|
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(
|
pub fn eval_call(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
caller_stack: &mut Stack,
|
caller_stack: &mut Stack,
|
||||||
|
|
|
@ -12,6 +12,6 @@ pub use documentation::get_full_help;
|
||||||
pub use env::*;
|
pub use env::*;
|
||||||
pub use eval::{
|
pub use eval::{
|
||||||
eval_block, eval_block_with_early_return, eval_call, eval_expression,
|
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;
|
pub use glob_from::glob_from;
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::Span;
|
use crate::{ShellError, Span};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use super::{Expr, Expression};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Comparison {
|
pub enum Comparison {
|
||||||
Equal,
|
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::{
|
use crate::{
|
||||||
ast::{Block, Call, Expr, Expression, PipelineElement},
|
ast::{
|
||||||
|
eval_operator, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator,
|
||||||
|
PipelineElement,
|
||||||
|
},
|
||||||
engine::{EngineState, StateWorkingSet},
|
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 nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -342,6 +345,132 @@ pub fn eval_constant(
|
||||||
.into_value(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) => {
|
||||||
|
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)),
|
_ => Err(ShellError::NotAConstant(expr.span)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use nu_test_support::nu;
|
use nu_test_support::nu;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
const MODULE_SETUP: &str = r#"
|
const MODULE_SETUP: &str = r#"
|
||||||
module spam {
|
module spam {
|
||||||
|
@ -107,6 +108,54 @@ fn const_nothing() {
|
||||||
assert_eq!(actual.out, "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]
|
#[test]
|
||||||
fn const_subexpression_supported() {
|
fn const_subexpression_supported() {
|
||||||
let inp = &["const x = ('spam')", "$x"];
|
let inp = &["const x = ('spam')", "$x"];
|
||||||
|
|
Loading…
Reference in a new issue