mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #5614 from Arp-1/feat-refactor-expr
expr: Optimizing for integer values
This commit is contained in:
commit
f248cc641c
2 changed files with 119 additions and 55 deletions
|
@ -108,9 +108,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
.map(|v| v.into_iter().map(|s| s.as_ref()).collect::<Vec<_>>())
|
.map(|v| v.into_iter().map(|s| s.as_ref()).collect::<Vec<_>>())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let res = AstNode::parse(&token_strings)?.eval()?;
|
let res: String = AstNode::parse(&token_strings)?.eval()?.eval_as_string();
|
||||||
println!("{res}");
|
println!("{res}");
|
||||||
if !is_truthy(&res) {
|
if !is_truthy(&res.into()) {
|
||||||
return Err(1.into());
|
return Err(1.into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) ints paren prec multibytes
|
// spell-checker:ignore (ToDO) ints paren prec multibytes
|
||||||
|
|
||||||
use num_bigint::BigInt;
|
use num_bigint::{BigInt, ParseBigIntError};
|
||||||
|
use num_traits::ToPrimitive;
|
||||||
use onig::{Regex, RegexOptions, Syntax};
|
use onig::{Regex, RegexOptions, Syntax};
|
||||||
|
|
||||||
use crate::{ExprError, ExprResult};
|
use crate::{ExprError, ExprResult};
|
||||||
|
@ -45,7 +46,7 @@ pub enum StringOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BinOp {
|
impl BinOp {
|
||||||
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<String> {
|
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<NumOrStr> {
|
||||||
match self {
|
match self {
|
||||||
Self::Relation(op) => op.eval(left, right),
|
Self::Relation(op) => op.eval(left, right),
|
||||||
Self::Numeric(op) => op.eval(left, right),
|
Self::Numeric(op) => op.eval(left, right),
|
||||||
|
@ -55,10 +56,10 @@ impl BinOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelationOp {
|
impl RelationOp {
|
||||||
fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult<String> {
|
fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult<NumOrStr> {
|
||||||
let a = a.eval()?;
|
let a = a.eval()?;
|
||||||
let b = b.eval()?;
|
let b = b.eval()?;
|
||||||
let b = if let (Ok(a), Ok(b)) = (a.parse::<BigInt>(), b.parse::<BigInt>()) {
|
let b = if let (Ok(a), Ok(b)) = (&a.to_bigint(), &b.to_bigint()) {
|
||||||
match self {
|
match self {
|
||||||
Self::Lt => a < b,
|
Self::Lt => a < b,
|
||||||
Self::Leq => a <= b,
|
Self::Leq => a <= b,
|
||||||
|
@ -79,24 +80,18 @@ impl RelationOp {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if b {
|
if b {
|
||||||
Ok("1".into())
|
Ok(1.into())
|
||||||
} else {
|
} else {
|
||||||
Ok("0".into())
|
Ok(0.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NumericOp {
|
impl NumericOp {
|
||||||
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<String> {
|
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<NumOrStr> {
|
||||||
let a: BigInt = left
|
let a = left.eval()?.eval_as_bigint()?;
|
||||||
.eval()?
|
let b = right.eval()?.eval_as_bigint()?;
|
||||||
.parse()
|
Ok(NumOrStr::Num(match self {
|
||||||
.map_err(|_| ExprError::NonIntegerArgument)?;
|
|
||||||
let b: BigInt = right
|
|
||||||
.eval()?
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| ExprError::NonIntegerArgument)?;
|
|
||||||
Ok(match self {
|
|
||||||
Self::Add => a + b,
|
Self::Add => a + b,
|
||||||
Self::Sub => a - b,
|
Self::Sub => a - b,
|
||||||
Self::Mul => a * b,
|
Self::Mul => a * b,
|
||||||
|
@ -110,13 +105,12 @@ impl NumericOp {
|
||||||
};
|
};
|
||||||
a % b
|
a % b
|
||||||
}
|
}
|
||||||
}
|
}))
|
||||||
.to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StringOp {
|
impl StringOp {
|
||||||
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<String> {
|
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<NumOrStr> {
|
||||||
match self {
|
match self {
|
||||||
Self::Or => {
|
Self::Or => {
|
||||||
let left = left.eval()?;
|
let left = left.eval()?;
|
||||||
|
@ -127,23 +121,23 @@ impl StringOp {
|
||||||
if is_truthy(&right) {
|
if is_truthy(&right) {
|
||||||
return Ok(right);
|
return Ok(right);
|
||||||
}
|
}
|
||||||
Ok("0".into())
|
Ok(0.into())
|
||||||
}
|
}
|
||||||
Self::And => {
|
Self::And => {
|
||||||
let left = left.eval()?;
|
let left = left.eval()?;
|
||||||
if !is_truthy(&left) {
|
if !is_truthy(&left) {
|
||||||
return Ok("0".into());
|
return Ok(0.into());
|
||||||
}
|
}
|
||||||
let right = right.eval()?;
|
let right = right.eval()?;
|
||||||
if !is_truthy(&right) {
|
if !is_truthy(&right) {
|
||||||
return Ok("0".into());
|
return Ok(0.into());
|
||||||
}
|
}
|
||||||
Ok(left)
|
Ok(left)
|
||||||
}
|
}
|
||||||
Self::Match => {
|
Self::Match => {
|
||||||
let left = left.eval()?;
|
let left = left.eval()?.eval_as_string();
|
||||||
let right = right.eval()?;
|
let right = right.eval()?.eval_as_string();
|
||||||
let re_string = format!("^{}", &right);
|
let re_string = format!("^{}", right);
|
||||||
let re = Regex::with_options(
|
let re = Regex::with_options(
|
||||||
&re_string,
|
&re_string,
|
||||||
RegexOptions::REGEX_OPTION_NONE,
|
RegexOptions::REGEX_OPTION_NONE,
|
||||||
|
@ -158,19 +152,20 @@ impl StringOp {
|
||||||
} else {
|
} else {
|
||||||
re.find(&left)
|
re.find(&left)
|
||||||
.map_or("0".to_string(), |(start, end)| (end - start).to_string())
|
.map_or("0".to_string(), |(start, end)| (end - start).to_string())
|
||||||
})
|
}
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
Self::Index => {
|
Self::Index => {
|
||||||
let left = left.eval()?;
|
let left = left.eval()?.eval_as_string();
|
||||||
let right = right.eval()?;
|
let right = right.eval()?.eval_as_string();
|
||||||
for (current_idx, ch_h) in left.chars().enumerate() {
|
for (current_idx, ch_h) in left.chars().enumerate() {
|
||||||
for ch_n in right.chars() {
|
for ch_n in right.to_string().chars() {
|
||||||
if ch_n == ch_h {
|
if ch_n == ch_h {
|
||||||
return Ok((current_idx + 1).to_string());
|
return Ok((current_idx + 1).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok("0".to_string())
|
Ok(0.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,6 +195,55 @@ const PRECEDENCE: &[&[(&str, BinOp)]] = &[
|
||||||
&[(":", BinOp::String(StringOp::Match))],
|
&[(":", BinOp::String(StringOp::Match))],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
|
pub enum NumOrStr {
|
||||||
|
Num(BigInt),
|
||||||
|
Str(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for NumOrStr {
|
||||||
|
fn from(num: usize) -> Self {
|
||||||
|
Self::Num(BigInt::from(num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BigInt> for NumOrStr {
|
||||||
|
fn from(num: BigInt) -> Self {
|
||||||
|
Self::Num(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for NumOrStr {
|
||||||
|
fn from(str: String) -> Self {
|
||||||
|
Self::Str(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NumOrStr {
|
||||||
|
pub fn to_bigint(&self) -> Result<BigInt, ParseBigIntError> {
|
||||||
|
match self {
|
||||||
|
Self::Num(num) => Ok(num.clone()),
|
||||||
|
Self::Str(str) => str.parse::<BigInt>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_as_bigint(self) -> ExprResult<BigInt> {
|
||||||
|
match self {
|
||||||
|
Self::Num(num) => Ok(num),
|
||||||
|
Self::Str(str) => str
|
||||||
|
.parse::<BigInt>()
|
||||||
|
.map_err(|_| ExprError::NonIntegerArgument),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_as_string(self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Num(num) => num.to_string(),
|
||||||
|
Self::Str(str) => str,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum AstNode {
|
pub enum AstNode {
|
||||||
Leaf {
|
Leaf {
|
||||||
|
@ -225,9 +269,9 @@ impl AstNode {
|
||||||
Parser::new(input).parse()
|
Parser::new(input).parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval(&self) -> ExprResult<String> {
|
pub fn eval(&self) -> ExprResult<NumOrStr> {
|
||||||
match self {
|
match self {
|
||||||
Self::Leaf { value } => Ok(value.into()),
|
Self::Leaf { value } => Ok(value.to_string().into()),
|
||||||
Self::BinOp {
|
Self::BinOp {
|
||||||
op_type,
|
op_type,
|
||||||
left,
|
left,
|
||||||
|
@ -238,7 +282,7 @@ impl AstNode {
|
||||||
pos,
|
pos,
|
||||||
length,
|
length,
|
||||||
} => {
|
} => {
|
||||||
let string = string.eval()?;
|
let string: String = string.eval()?.eval_as_string();
|
||||||
|
|
||||||
// The GNU docs say:
|
// The GNU docs say:
|
||||||
//
|
//
|
||||||
|
@ -247,16 +291,31 @@ impl AstNode {
|
||||||
//
|
//
|
||||||
// So we coerce errors into 0 to make that the only case we
|
// So we coerce errors into 0 to make that the only case we
|
||||||
// have to care about.
|
// have to care about.
|
||||||
let pos: usize = pos.eval()?.parse().unwrap_or(0);
|
let pos = pos
|
||||||
let length: usize = length.eval()?.parse().unwrap_or(0);
|
.eval()?
|
||||||
|
.eval_as_bigint()
|
||||||
|
.ok()
|
||||||
|
.and_then(|n| n.to_usize())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let length = length
|
||||||
|
.eval()?
|
||||||
|
.eval_as_bigint()
|
||||||
|
.ok()
|
||||||
|
.and_then(|n| n.to_usize())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else {
|
let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else {
|
||||||
return Ok(String::new());
|
return Ok(String::new().into());
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(string.chars().skip(pos).take(length).collect())
|
Ok(string
|
||||||
|
.chars()
|
||||||
|
.skip(pos)
|
||||||
|
.take(length)
|
||||||
|
.collect::<String>()
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
Self::Length { string } => Ok(string.eval()?.chars().count().to_string()),
|
Self::Length { string } => Ok(string.eval()?.eval_as_string().chars().count().into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,13 +458,16 @@ impl<'a> Parser<'a> {
|
||||||
/// Determine whether `expr` should evaluate the string as "truthy"
|
/// Determine whether `expr` should evaluate the string as "truthy"
|
||||||
///
|
///
|
||||||
/// Truthy strings are either empty or match the regex "-?0+".
|
/// Truthy strings are either empty or match the regex "-?0+".
|
||||||
pub fn is_truthy(s: &str) -> bool {
|
pub fn is_truthy(s: &NumOrStr) -> bool {
|
||||||
|
match s {
|
||||||
|
NumOrStr::Num(num) => num != &BigInt::from(0),
|
||||||
|
NumOrStr::Str(str) => {
|
||||||
// Edge case: `-` followed by nothing is truthy
|
// Edge case: `-` followed by nothing is truthy
|
||||||
if s == "-" {
|
if str == "-" {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bytes = s.bytes();
|
let mut bytes = str.bytes();
|
||||||
|
|
||||||
// Empty string is falsy
|
// Empty string is falsy
|
||||||
let Some(first) = bytes.next() else {
|
let Some(first) = bytes.next() else {
|
||||||
|
@ -415,6 +477,8 @@ pub fn is_truthy(s: &str) -> bool {
|
||||||
let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0');
|
let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0');
|
||||||
!is_zero
|
!is_zero
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue