mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Merge pull request #7332 from RenjiSann/expr-other-approach
expr: Evaluate parenthesis content before checking for closing parenthesis
This commit is contained in:
commit
fae7bb0a57
5 changed files with 44 additions and 49 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2781,6 +2781,7 @@ dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"onig",
|
"onig",
|
||||||
|
"thiserror 2.0.11",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ num-bigint = { workspace = true }
|
||||||
num-traits = { workspace = true }
|
num-traits = { workspace = true }
|
||||||
onig = { workspace = true }
|
onig = { workspace = true }
|
||||||
uucore = { workspace = true }
|
uucore = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "expr"
|
name = "expr"
|
||||||
|
|
|
@ -3,18 +3,15 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use syntax_tree::AstNode;
|
use syntax_tree::{is_truthy, AstNode};
|
||||||
|
use thiserror::Error;
|
||||||
use uucore::{
|
use uucore::{
|
||||||
display::Quotable,
|
display::Quotable,
|
||||||
error::{UError, UResult},
|
error::{UError, UResult},
|
||||||
format_usage, help_about, help_section, help_usage,
|
format_usage, help_about, help_section, help_usage,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::syntax_tree::is_truthy;
|
|
||||||
|
|
||||||
mod syntax_tree;
|
mod syntax_tree;
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
|
@ -25,63 +22,36 @@ mod options {
|
||||||
|
|
||||||
pub type ExprResult<T> = Result<T, ExprError>;
|
pub type ExprResult<T> = Result<T, ExprError>;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Error, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ExprError {
|
pub enum ExprError {
|
||||||
|
#[error("syntax error: unexpected argument {}", .0.quote())]
|
||||||
UnexpectedArgument(String),
|
UnexpectedArgument(String),
|
||||||
|
#[error("syntax error: missing argument after {}", .0.quote())]
|
||||||
MissingArgument(String),
|
MissingArgument(String),
|
||||||
|
#[error("non-integer argument")]
|
||||||
NonIntegerArgument,
|
NonIntegerArgument,
|
||||||
|
#[error("missing operand")]
|
||||||
MissingOperand,
|
MissingOperand,
|
||||||
|
#[error("division by zero")]
|
||||||
DivisionByZero,
|
DivisionByZero,
|
||||||
|
#[error("Invalid regex expression")]
|
||||||
InvalidRegexExpression,
|
InvalidRegexExpression,
|
||||||
|
#[error("syntax error: expecting ')' after {}", .0.quote())]
|
||||||
ExpectedClosingBraceAfter(String),
|
ExpectedClosingBraceAfter(String),
|
||||||
|
#[error("syntax error: expecting ')' instead of {}", .0.quote())]
|
||||||
ExpectedClosingBraceInsteadOf(String),
|
ExpectedClosingBraceInsteadOf(String),
|
||||||
|
#[error("Unmatched ( or \\(")]
|
||||||
UnmatchedOpeningParenthesis,
|
UnmatchedOpeningParenthesis,
|
||||||
|
#[error("Unmatched ) or \\)")]
|
||||||
UnmatchedClosingParenthesis,
|
UnmatchedClosingParenthesis,
|
||||||
|
#[error("Unmatched \\{{")]
|
||||||
UnmatchedOpeningBrace,
|
UnmatchedOpeningBrace,
|
||||||
|
#[error("Unmatched ) or \\}}")]
|
||||||
UnmatchedClosingBrace,
|
UnmatchedClosingBrace,
|
||||||
|
#[error("Invalid content of {0}")]
|
||||||
InvalidContent(String),
|
InvalidContent(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ExprError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::UnexpectedArgument(s) => {
|
|
||||||
write!(f, "syntax error: unexpected argument {}", s.quote())
|
|
||||||
}
|
|
||||||
Self::MissingArgument(s) => {
|
|
||||||
write!(f, "syntax error: missing argument after {}", s.quote())
|
|
||||||
}
|
|
||||||
Self::NonIntegerArgument => write!(f, "non-integer argument"),
|
|
||||||
Self::MissingOperand => write!(f, "missing operand"),
|
|
||||||
Self::DivisionByZero => write!(f, "division by zero"),
|
|
||||||
Self::InvalidRegexExpression => write!(f, "Invalid regex expression"),
|
|
||||||
Self::ExpectedClosingBraceAfter(s) => {
|
|
||||||
write!(f, "syntax error: expecting ')' after {}", s.quote())
|
|
||||||
}
|
|
||||||
Self::ExpectedClosingBraceInsteadOf(s) => {
|
|
||||||
write!(f, "syntax error: expecting ')' instead of {}", s.quote())
|
|
||||||
}
|
|
||||||
Self::UnmatchedOpeningParenthesis => {
|
|
||||||
write!(f, "Unmatched ( or \\(")
|
|
||||||
}
|
|
||||||
Self::UnmatchedClosingParenthesis => {
|
|
||||||
write!(f, "Unmatched ) or \\)")
|
|
||||||
}
|
|
||||||
Self::UnmatchedOpeningBrace => {
|
|
||||||
write!(f, "Unmatched \\{{")
|
|
||||||
}
|
|
||||||
Self::UnmatchedClosingBrace => {
|
|
||||||
write!(f, "Unmatched ) or \\}}")
|
|
||||||
}
|
|
||||||
Self::InvalidContent(s) => {
|
|
||||||
write!(f, "Invalid content of {}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for ExprError {}
|
|
||||||
|
|
||||||
impl UError for ExprError {
|
impl UError for ExprError {
|
||||||
fn code(&self) -> i32 {
|
fn code(&self) -> i32 {
|
||||||
2
|
2
|
||||||
|
|
|
@ -292,7 +292,7 @@ const PRECEDENCE: &[&[(&str, BinOp)]] = &[
|
||||||
&[(":", BinOp::String(StringOp::Match))],
|
&[(":", BinOp::String(StringOp::Match))],
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum NumOrStr {
|
pub enum NumOrStr {
|
||||||
Num(BigInt),
|
Num(BigInt),
|
||||||
Str(String),
|
Str(String),
|
||||||
|
@ -343,6 +343,9 @@ impl NumOrStr {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum AstNode {
|
pub enum AstNode {
|
||||||
|
Evaluated {
|
||||||
|
value: NumOrStr,
|
||||||
|
},
|
||||||
Leaf {
|
Leaf {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
@ -366,8 +369,15 @@ impl AstNode {
|
||||||
Parser::new(input).parse()
|
Parser::new(input).parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn evaluated(self) -> ExprResult<Self> {
|
||||||
|
Ok(Self::Evaluated {
|
||||||
|
value: self.eval()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eval(&self) -> ExprResult<NumOrStr> {
|
pub fn eval(&self) -> ExprResult<NumOrStr> {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Evaluated { value } => Ok(value.clone()),
|
||||||
Self::Leaf { value } => Ok(value.to_string().into()),
|
Self::Leaf { value } => Ok(value.to_string().into()),
|
||||||
Self::BinOp {
|
Self::BinOp {
|
||||||
op_type,
|
op_type,
|
||||||
|
@ -536,7 +546,10 @@ impl<'a> Parser<'a> {
|
||||||
value: self.next()?.into(),
|
value: self.next()?.into(),
|
||||||
},
|
},
|
||||||
"(" => {
|
"(" => {
|
||||||
let s = self.parse_expression()?;
|
// Evaluate the node just after parsing to we detect arithmetic
|
||||||
|
// errors before checking for the closing parenthesis.
|
||||||
|
let s = self.parse_expression()?.evaluated()?;
|
||||||
|
|
||||||
match self.next() {
|
match self.next() {
|
||||||
Ok(")") => {}
|
Ok(")") => {}
|
||||||
// Since we have parsed at least a '(', there will be a token
|
// Since we have parsed at least a '(', there will be a token
|
||||||
|
@ -680,7 +693,9 @@ mod test {
|
||||||
AstNode::parse(&["(", "1", "+", "2", ")", "*", "3"]),
|
AstNode::parse(&["(", "1", "+", "2", ")", "*", "3"]),
|
||||||
Ok(op(
|
Ok(op(
|
||||||
BinOp::Numeric(NumericOp::Mul),
|
BinOp::Numeric(NumericOp::Mul),
|
||||||
op(BinOp::Numeric(NumericOp::Add), "1", "2"),
|
op(BinOp::Numeric(NumericOp::Add), "1", "2")
|
||||||
|
.evaluated()
|
||||||
|
.unwrap(),
|
||||||
"3"
|
"3"
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
|
@ -370,3 +370,11 @@ fn test_num_str_comparison() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is("1\n");
|
.stdout_is("1\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_eager_evaluation() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["(", "1", "/", "0"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("division by zero");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue