1
Fork 0
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:
Sylvestre Ledru 2023-12-14 18:50:50 +01:00 committed by GitHub
commit f248cc641c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 119 additions and 55 deletions

View file

@ -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(())

View file

@ -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,21 +458,26 @@ 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 {
// Edge case: `-` followed by nothing is truthy match s {
if s == "-" { NumOrStr::Num(num) => num != &BigInt::from(0),
return true; NumOrStr::Str(str) => {
// Edge case: `-` followed by nothing is truthy
if str == "-" {
return true;
}
let mut bytes = str.bytes();
// Empty string is falsy
let Some(first) = bytes.next() else {
return false;
};
let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0');
!is_zero
}
} }
let mut bytes = s.bytes();
// Empty string is falsy
let Some(first) = bytes.next() else {
return false;
};
let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0');
!is_zero
} }
#[cfg(test)] #[cfg(test)]