help operators refactor (#13307)

# Description
Refactors `help operators` so that its output is always up to date with
the parser.

# User-Facing Changes
- The order of output rows for `help operators` was changed.
- `not` is now listed as a boolean operator instead of a comparison
operator.
- Edited some of the descriptions for the operators.
This commit is contained in:
Ian Manske 2024-07-06 18:09:12 +00:00 committed by GitHub
parent d2a1f96dbd
commit fa183b6669
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 174 additions and 306 deletions

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::ast::{Assignment, Bits, Boolean, Comparison, Math, Operator};
#[derive(Clone)] #[derive(Clone)]
pub struct HelpOperators; pub struct HelpOperators;
@ -27,282 +28,148 @@ impl Command for HelpOperators {
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let op_info = generate_operator_info(); let mut operators = [
let mut recs = vec![]; Operator::Assignment(Assignment::Assign),
Operator::Assignment(Assignment::PlusAssign),
for op in op_info { Operator::Assignment(Assignment::AppendAssign),
recs.push(Value::record( Operator::Assignment(Assignment::MinusAssign),
Operator::Assignment(Assignment::MultiplyAssign),
Operator::Assignment(Assignment::DivideAssign),
Operator::Comparison(Comparison::Equal),
Operator::Comparison(Comparison::NotEqual),
Operator::Comparison(Comparison::LessThan),
Operator::Comparison(Comparison::GreaterThan),
Operator::Comparison(Comparison::LessThanOrEqual),
Operator::Comparison(Comparison::GreaterThanOrEqual),
Operator::Comparison(Comparison::RegexMatch),
Operator::Comparison(Comparison::NotRegexMatch),
Operator::Comparison(Comparison::In),
Operator::Comparison(Comparison::NotIn),
Operator::Comparison(Comparison::StartsWith),
Operator::Comparison(Comparison::EndsWith),
Operator::Math(Math::Plus),
Operator::Math(Math::Append),
Operator::Math(Math::Minus),
Operator::Math(Math::Multiply),
Operator::Math(Math::Divide),
Operator::Math(Math::Modulo),
Operator::Math(Math::FloorDivision),
Operator::Math(Math::Pow),
Operator::Bits(Bits::BitOr),
Operator::Bits(Bits::BitXor),
Operator::Bits(Bits::BitAnd),
Operator::Bits(Bits::ShiftLeft),
Operator::Bits(Bits::ShiftRight),
Operator::Boolean(Boolean::And),
Operator::Boolean(Boolean::Or),
Operator::Boolean(Boolean::Xor),
]
.into_iter()
.map(|op| {
Value::record(
record! { record! {
"type" => Value::string(op.op_type, head), "type" => Value::string(op_type(&op), head),
"operator" => Value::string(op.operator, head), "operator" => Value::string(op.to_string(), head),
"name" => Value::string(op.name, head), "name" => Value::string(name(&op), head),
"description" => Value::string(op.description, head), "description" => Value::string(description(&op), head),
"precedence" => Value::int(op.precedence, head), "precedence" => Value::int(op.precedence().into(), head),
},
head,
)
})
.collect::<Vec<_>>();
operators.push(Value::record(
record! {
"type" => Value::string("Boolean", head),
"operator" => Value::string("not", head),
"name" => Value::string("Not", head),
"description" => Value::string("Negates a value or expression.", head),
"precedence" => Value::int(0, head),
}, },
head, head,
)); ));
}
Ok(Value::list(recs, head).into_pipeline_data()) Ok(Value::list(operators, head).into_pipeline_data())
} }
} }
struct OperatorInfo { fn op_type(operator: &Operator) -> &'static str {
op_type: String, match operator {
operator: String, Operator::Comparison(_) => "Comparison",
name: String, Operator::Math(_) => "Math",
description: String, Operator::Boolean(_) => "Boolean",
precedence: i64, Operator::Bits(_) => "Bitwise",
Operator::Assignment(_) => "Assignment",
}
} }
fn generate_operator_info() -> Vec<OperatorInfo> { fn name(operator: &Operator) -> String {
vec![ match operator {
OperatorInfo { Operator::Comparison(op) => format!("{op:?}"),
op_type: "Assignment".into(), Operator::Math(op) => format!("{op:?}"),
operator: "=".into(), Operator::Boolean(op) => format!("{op:?}"),
name: "Assign".into(), Operator::Bits(op) => format!("{op:?}"),
description: "Assigns a value to a variable.".into(), Operator::Assignment(op) => format!("{op:?}"),
precedence: 10, }
}, }
OperatorInfo {
op_type: "Assignment".into(), fn description(operator: &Operator) -> &'static str {
operator: "+=".into(), // NOTE: if you have to add an operator here, also add it to the operator list above.
name: "PlusAssign".into(), match operator {
description: "Adds a value to a variable.".into(), Operator::Comparison(Comparison::Equal) => "Checks if two values are equal.",
precedence: 10, Operator::Comparison(Comparison::NotEqual) => "Checks if two values are not equal.",
}, Operator::Comparison(Comparison::LessThan) => "Checks if a value is less than another.",
OperatorInfo { Operator::Comparison(Comparison::GreaterThan) => {
op_type: "Assignment".into(), "Checks if a value is greater than another."
operator: "++=".into(), }
name: "AppendAssign".into(), Operator::Comparison(Comparison::LessThanOrEqual) => {
description: "Appends a list or a value to a variable.".into(), "Checks if a value is less than or equal to another."
precedence: 10, }
}, Operator::Comparison(Comparison::GreaterThanOrEqual) => {
OperatorInfo { "Checks if a value is greater than or equal to another."
op_type: "Assignment".into(), }
operator: "-=".into(), Operator::Comparison(Comparison::RegexMatch) => {
name: "MinusAssign".into(), "Checks if a value matches a regular expression."
description: "Subtracts a value from a variable.".into(), }
precedence: 10, Operator::Comparison(Comparison::NotRegexMatch) => {
}, "Checks if a value does not match a regular expression."
OperatorInfo { }
op_type: "Assignment".into(), Operator::Comparison(Comparison::In) => {
operator: "*=".into(), "Checks if a value is in a list, is part of a string, or is a key in a record."
name: "MultiplyAssign".into(), }
description: "Multiplies a variable by a value.".into(), Operator::Comparison(Comparison::NotIn) => {
precedence: 10, "Checks if a value is not in a list, is not part of a string, or is not a key in a record."
}, }
OperatorInfo { Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.",
op_type: "Assignment".into(), Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.",
operator: "/=".into(), Operator::Math(Math::Plus) => "Adds two values.",
name: "DivideAssign".into(), Operator::Math(Math::Append) => {
description: "Divides a variable by a value.".into(), "Appends two lists, a list and a value, two strings, or two binary values."
precedence: 10, }
}, Operator::Math(Math::Minus) => "Subtracts two values.",
OperatorInfo { Operator::Math(Math::Multiply) => "Multiplies two values.",
op_type: "Comparison".into(), Operator::Math(Math::Divide) => "Divides two values.",
operator: "==".into(), Operator::Math(Math::Modulo) => "Divides two values and returns the remainder.",
name: "Equal".into(), Operator::Math(Math::FloorDivision) => "Divides two values and floors the result.",
description: "Checks if two values are equal.".into(), Operator::Math(Math::Pow) => "Raises one value to the power of another.",
precedence: 80, Operator::Boolean(Boolean::And) => "Checks if both values are true.",
}, Operator::Boolean(Boolean::Or) => "Checks if either value is true.",
OperatorInfo { Operator::Boolean(Boolean::Xor) => "Checks if one value is true and the other is false.",
op_type: "Comparison".into(), Operator::Bits(Bits::BitOr) => "Performs a bitwise OR on two values.",
operator: "!=".into(), Operator::Bits(Bits::BitXor) => "Performs a bitwise XOR on two values.",
name: "NotEqual".into(), Operator::Bits(Bits::BitAnd) => "Performs a bitwise AND on two values.",
description: "Checks if two values are not equal.".into(), Operator::Bits(Bits::ShiftLeft) => "Bitwise shifts a value left by another.",
precedence: 80, Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.",
}, Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.",
OperatorInfo { Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.",
op_type: "Comparison".into(), Operator::Assignment(Assignment::AppendAssign) => {
operator: "<".into(), "Appends a list, a value, a string, or a binary value to a variable."
name: "LessThan".into(), }
description: "Checks if a value is less than another.".into(), Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.",
precedence: 80, Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.",
}, Operator::Assignment(Assignment::DivideAssign) => "Divides a variable by a value.",
OperatorInfo { }
op_type: "Comparison".into(),
operator: "<=".into(),
name: "LessThanOrEqual".into(),
description: "Checks if a value is less than or equal to another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: ">".into(),
name: "GreaterThan".into(),
description: "Checks if a value is greater than another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: ">=".into(),
name: "GreaterThanOrEqual".into(),
description: "Checks if a value is greater than or equal to another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "=~".into(),
name: "RegexMatch".into(),
description: "Checks if a value matches a regular expression.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "!~".into(),
name: "NotRegexMatch".into(),
description: "Checks if a value does not match a regular expression.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "in".into(),
name: "In".into(),
description: "Checks if a value is in a list or string.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "not-in".into(),
name: "NotIn".into(),
description: "Checks if a value is not in a list or string.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "starts-with".into(),
name: "StartsWith".into(),
description: "Checks if a string starts with another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "ends-with".into(),
name: "EndsWith".into(),
description: "Checks if a string ends with another.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Comparison".into(),
operator: "not".into(),
name: "UnaryNot".into(),
description: "Negates a value or expression.".into(),
precedence: 0,
},
OperatorInfo {
op_type: "Math".into(),
operator: "+".into(),
name: "Plus".into(),
description: "Adds two values.".into(),
precedence: 90,
},
OperatorInfo {
op_type: "Math".into(),
operator: "++".into(),
name: "Append".into(),
description: "Appends two lists or a list and a value.".into(),
precedence: 80,
},
OperatorInfo {
op_type: "Math".into(),
operator: "-".into(),
name: "Minus".into(),
description: "Subtracts two values.".into(),
precedence: 90,
},
OperatorInfo {
op_type: "Math".into(),
operator: "*".into(),
name: "Multiply".into(),
description: "Multiplies two values.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "/".into(),
name: "Divide".into(),
description: "Divides two values.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "//".into(),
name: "FloorDivision".into(),
description: "Divides two values and floors the result.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "mod".into(),
name: "Modulo".into(),
description: "Divides two values and returns the remainder.".into(),
precedence: 95,
},
OperatorInfo {
op_type: "Math".into(),
operator: "**".into(),
name: "Pow ".into(),
description: "Raises one value to the power of another.".into(),
precedence: 100,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-or".into(),
name: "BitOr".into(),
description: "Performs a bitwise OR on two values.".into(),
precedence: 60,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-xor".into(),
name: "BitXor".into(),
description: "Performs a bitwise XOR on two values.".into(),
precedence: 70,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-and".into(),
name: "BitAnd".into(),
description: "Performs a bitwise AND on two values.".into(),
precedence: 75,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-shl".into(),
name: "ShiftLeft".into(),
description: "Shifts a value left by another.".into(),
precedence: 85,
},
OperatorInfo {
op_type: "Bitwise".into(),
operator: "bit-shr".into(),
name: "ShiftRight".into(),
description: "Shifts a value right by another.".into(),
precedence: 85,
},
OperatorInfo {
op_type: "Boolean".into(),
operator: "and".into(),
name: "And".into(),
description: "Checks if two values are true.".into(),
precedence: 50,
},
OperatorInfo {
op_type: "Boolean".into(),
operator: "or".into(),
name: "Or".into(),
description: "Checks if either value is true.".into(),
precedence: 40,
},
OperatorInfo {
op_type: "Boolean".into(),
operator: "xor".into(),
name: "Xor".into(),
description: "Checks if one value is true and the other is false.".into(),
precedence: 45,
},
]
} }
#[cfg(test)] #[cfg(test)]

View File

@ -4950,7 +4950,7 @@ pub fn parse_math_expression(
let mut expr_stack: Vec<Expression> = vec![]; let mut expr_stack: Vec<Expression> = vec![];
let mut idx = 0; let mut idx = 0;
let mut last_prec = 1000000; let mut last_prec = u8::MAX;
let first_span = working_set.get_span_contents(spans[0]); let first_span = working_set.get_span_contents(spans[0]);

View File

@ -27,42 +27,9 @@ impl Expression {
} }
} }
pub fn precedence(&self) -> usize { pub fn precedence(&self) -> u8 {
match &self.expr { match &self.expr {
Expr::Operator(operator) => { Expr::Operator(operator) => operator.precedence(),
use super::operator::*;
// Higher precedence binds tighter
match operator {
Operator::Math(Math::Pow) => 100,
Operator::Math(Math::Multiply)
| Operator::Math(Math::Divide)
| Operator::Math(Math::Modulo)
| Operator::Math(Math::FloorDivision) => 95,
Operator::Math(Math::Plus) | Operator::Math(Math::Minus) => 90,
Operator::Bits(Bits::ShiftLeft) | Operator::Bits(Bits::ShiftRight) => 85,
Operator::Comparison(Comparison::NotRegexMatch)
| Operator::Comparison(Comparison::RegexMatch)
| Operator::Comparison(Comparison::StartsWith)
| Operator::Comparison(Comparison::EndsWith)
| Operator::Comparison(Comparison::LessThan)
| Operator::Comparison(Comparison::LessThanOrEqual)
| Operator::Comparison(Comparison::GreaterThan)
| Operator::Comparison(Comparison::GreaterThanOrEqual)
| Operator::Comparison(Comparison::Equal)
| Operator::Comparison(Comparison::NotEqual)
| Operator::Comparison(Comparison::In)
| Operator::Comparison(Comparison::NotIn)
| Operator::Math(Math::Append) => 80,
Operator::Bits(Bits::BitAnd) => 75,
Operator::Bits(Bits::BitXor) => 70,
Operator::Bits(Bits::BitOr) => 60,
Operator::Boolean(Boolean::And) => 50,
Operator::Boolean(Boolean::Xor) => 45,
Operator::Boolean(Boolean::Or) => 40,
Operator::Assignment(_) => 10,
}
}
_ => 0, _ => 0,
} }
} }

View File

@ -68,6 +68,40 @@ pub enum Operator {
Assignment(Assignment), Assignment(Assignment),
} }
impl Operator {
pub fn precedence(&self) -> u8 {
match self {
Self::Math(Math::Pow) => 100,
Self::Math(Math::Multiply)
| Self::Math(Math::Divide)
| Self::Math(Math::Modulo)
| Self::Math(Math::FloorDivision) => 95,
Self::Math(Math::Plus) | Self::Math(Math::Minus) => 90,
Self::Bits(Bits::ShiftLeft) | Self::Bits(Bits::ShiftRight) => 85,
Self::Comparison(Comparison::NotRegexMatch)
| Self::Comparison(Comparison::RegexMatch)
| Self::Comparison(Comparison::StartsWith)
| Self::Comparison(Comparison::EndsWith)
| Self::Comparison(Comparison::LessThan)
| Self::Comparison(Comparison::LessThanOrEqual)
| Self::Comparison(Comparison::GreaterThan)
| Self::Comparison(Comparison::GreaterThanOrEqual)
| Self::Comparison(Comparison::Equal)
| Self::Comparison(Comparison::NotEqual)
| Self::Comparison(Comparison::In)
| Self::Comparison(Comparison::NotIn)
| Self::Math(Math::Append) => 80,
Self::Bits(Bits::BitAnd) => 75,
Self::Bits(Bits::BitXor) => 70,
Self::Bits(Bits::BitOr) => 60,
Self::Boolean(Boolean::And) => 50,
Self::Boolean(Boolean::Xor) => 45,
Self::Boolean(Boolean::Or) => 40,
Self::Assignment(_) => 10,
}
}
}
impl Display for Operator { impl Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@ -95,10 +129,10 @@ impl Display for Operator {
Operator::Math(Math::Multiply) => write!(f, "*"), Operator::Math(Math::Multiply) => write!(f, "*"),
Operator::Math(Math::Divide) => write!(f, "/"), Operator::Math(Math::Divide) => write!(f, "/"),
Operator::Math(Math::Modulo) => write!(f, "mod"), Operator::Math(Math::Modulo) => write!(f, "mod"),
Operator::Math(Math::FloorDivision) => write!(f, "fdiv"), Operator::Math(Math::FloorDivision) => write!(f, "//"),
Operator::Math(Math::Pow) => write!(f, "**"), Operator::Math(Math::Pow) => write!(f, "**"),
Operator::Boolean(Boolean::And) => write!(f, "&&"), Operator::Boolean(Boolean::And) => write!(f, "and"),
Operator::Boolean(Boolean::Or) => write!(f, "||"), Operator::Boolean(Boolean::Or) => write!(f, "or"),
Operator::Boolean(Boolean::Xor) => write!(f, "xor"), Operator::Boolean(Boolean::Xor) => write!(f, "xor"),
Operator::Bits(Bits::BitOr) => write!(f, "bit-or"), Operator::Bits(Bits::BitOr) => write!(f, "bit-or"),
Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"), Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"),