Skip to content

Commit f248cc6

Browse files
authored
Merge pull request #5614 from Arp-1/feat-refactor-expr
expr: Optimizing for integer values
2 parents 66bd065 + 7f23faf commit f248cc6

File tree

2 files changed

+117
-53
lines changed

2 files changed

+117
-53
lines changed

src/uu/expr/src/expr.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
108108
.map(|v| v.into_iter().map(|s| s.as_ref()).collect::<Vec<_>>())
109109
.unwrap_or_default();
110110

111-
let res = AstNode::parse(&token_strings)?.eval()?;
111+
let res: String = AstNode::parse(&token_strings)?.eval()?.eval_as_string();
112112
println!("{res}");
113-
if !is_truthy(&res) {
113+
if !is_truthy(&res.into()) {
114114
return Err(1.into());
115115
}
116116
Ok(())

src/uu/expr/src/syntax_tree.rs

Lines changed: 115 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
// spell-checker:ignore (ToDO) ints paren prec multibytes
77

8-
use num_bigint::BigInt;
8+
use num_bigint::{BigInt, ParseBigIntError};
9+
use num_traits::ToPrimitive;
910
use onig::{Regex, RegexOptions, Syntax};
1011

1112
use crate::{ExprError, ExprResult};
@@ -45,7 +46,7 @@ pub enum StringOp {
4546
}
4647

4748
impl BinOp {
48-
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<String> {
49+
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<NumOrStr> {
4950
match self {
5051
Self::Relation(op) => op.eval(left, right),
5152
Self::Numeric(op) => op.eval(left, right),
@@ -55,10 +56,10 @@ impl BinOp {
5556
}
5657

5758
impl RelationOp {
58-
fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult<String> {
59+
fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult<NumOrStr> {
5960
let a = a.eval()?;
6061
let b = b.eval()?;
61-
let b = if let (Ok(a), Ok(b)) = (a.parse::<BigInt>(), b.parse::<BigInt>()) {
62+
let b = if let (Ok(a), Ok(b)) = (&a.to_bigint(), &b.to_bigint()) {
6263
match self {
6364
Self::Lt => a < b,
6465
Self::Leq => a <= b,
@@ -79,24 +80,18 @@ impl RelationOp {
7980
}
8081
};
8182
if b {
82-
Ok("1".into())
83+
Ok(1.into())
8384
} else {
84-
Ok("0".into())
85+
Ok(0.into())
8586
}
8687
}
8788
}
8889

8990
impl NumericOp {
90-
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<String> {
91-
let a: BigInt = left
92-
.eval()?
93-
.parse()
94-
.map_err(|_| ExprError::NonIntegerArgument)?;
95-
let b: BigInt = right
96-
.eval()?
97-
.parse()
98-
.map_err(|_| ExprError::NonIntegerArgument)?;
99-
Ok(match self {
91+
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<NumOrStr> {
92+
let a = left.eval()?.eval_as_bigint()?;
93+
let b = right.eval()?.eval_as_bigint()?;
94+
Ok(NumOrStr::Num(match self {
10095
Self::Add => a + b,
10196
Self::Sub => a - b,
10297
Self::Mul => a * b,
@@ -110,13 +105,12 @@ impl NumericOp {
110105
};
111106
a % b
112107
}
113-
}
114-
.to_string())
108+
}))
115109
}
116110
}
117111

118112
impl StringOp {
119-
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<String> {
113+
fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult<NumOrStr> {
120114
match self {
121115
Self::Or => {
122116
let left = left.eval()?;
@@ -127,23 +121,23 @@ impl StringOp {
127121
if is_truthy(&right) {
128122
return Ok(right);
129123
}
130-
Ok("0".into())
124+
Ok(0.into())
131125
}
132126
Self::And => {
133127
let left = left.eval()?;
134128
if !is_truthy(&left) {
135-
return Ok("0".into());
129+
return Ok(0.into());
136130
}
137131
let right = right.eval()?;
138132
if !is_truthy(&right) {
139-
return Ok("0".into());
133+
return Ok(0.into());
140134
}
141135
Ok(left)
142136
}
143137
Self::Match => {
144-
let left = left.eval()?;
145-
let right = right.eval()?;
146-
let re_string = format!("^{}", &right);
138+
let left = left.eval()?.eval_as_string();
139+
let right = right.eval()?.eval_as_string();
140+
let re_string = format!("^{}", right);
147141
let re = Regex::with_options(
148142
&re_string,
149143
RegexOptions::REGEX_OPTION_NONE,
@@ -158,19 +152,20 @@ impl StringOp {
158152
} else {
159153
re.find(&left)
160154
.map_or("0".to_string(), |(start, end)| (end - start).to_string())
161-
})
155+
}
156+
.into())
162157
}
163158
Self::Index => {
164-
let left = left.eval()?;
165-
let right = right.eval()?;
159+
let left = left.eval()?.eval_as_string();
160+
let right = right.eval()?.eval_as_string();
166161
for (current_idx, ch_h) in left.chars().enumerate() {
167-
for ch_n in right.chars() {
162+
for ch_n in right.to_string().chars() {
168163
if ch_n == ch_h {
169-
return Ok((current_idx + 1).to_string());
164+
return Ok((current_idx + 1).into());
170165
}
171166
}
172167
}
173-
Ok("0".to_string())
168+
Ok(0.into())
174169
}
175170
}
176171
}
@@ -200,6 +195,55 @@ const PRECEDENCE: &[&[(&str, BinOp)]] = &[
200195
&[(":", BinOp::String(StringOp::Match))],
201196
];
202197

198+
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd)]
199+
pub enum NumOrStr {
200+
Num(BigInt),
201+
Str(String),
202+
}
203+
204+
impl From<usize> for NumOrStr {
205+
fn from(num: usize) -> Self {
206+
Self::Num(BigInt::from(num))
207+
}
208+
}
209+
210+
impl From<BigInt> for NumOrStr {
211+
fn from(num: BigInt) -> Self {
212+
Self::Num(num)
213+
}
214+
}
215+
216+
impl From<String> for NumOrStr {
217+
fn from(str: String) -> Self {
218+
Self::Str(str)
219+
}
220+
}
221+
222+
impl NumOrStr {
223+
pub fn to_bigint(&self) -> Result<BigInt, ParseBigIntError> {
224+
match self {
225+
Self::Num(num) => Ok(num.clone()),
226+
Self::Str(str) => str.parse::<BigInt>(),
227+
}
228+
}
229+
230+
pub fn eval_as_bigint(self) -> ExprResult<BigInt> {
231+
match self {
232+
Self::Num(num) => Ok(num),
233+
Self::Str(str) => str
234+
.parse::<BigInt>()
235+
.map_err(|_| ExprError::NonIntegerArgument),
236+
}
237+
}
238+
239+
pub fn eval_as_string(self) -> String {
240+
match self {
241+
Self::Num(num) => num.to_string(),
242+
Self::Str(str) => str,
243+
}
244+
}
245+
}
246+
203247
#[derive(Debug, PartialEq, Eq)]
204248
pub enum AstNode {
205249
Leaf {
@@ -225,9 +269,9 @@ impl AstNode {
225269
Parser::new(input).parse()
226270
}
227271

228-
pub fn eval(&self) -> ExprResult<String> {
272+
pub fn eval(&self) -> ExprResult<NumOrStr> {
229273
match self {
230-
Self::Leaf { value } => Ok(value.into()),
274+
Self::Leaf { value } => Ok(value.to_string().into()),
231275
Self::BinOp {
232276
op_type,
233277
left,
@@ -238,7 +282,7 @@ impl AstNode {
238282
pos,
239283
length,
240284
} => {
241-
let string = string.eval()?;
285+
let string: String = string.eval()?.eval_as_string();
242286

243287
// The GNU docs say:
244288
//
@@ -247,16 +291,31 @@ impl AstNode {
247291
//
248292
// So we coerce errors into 0 to make that the only case we
249293
// have to care about.
250-
let pos: usize = pos.eval()?.parse().unwrap_or(0);
251-
let length: usize = length.eval()?.parse().unwrap_or(0);
294+
let pos = pos
295+
.eval()?
296+
.eval_as_bigint()
297+
.ok()
298+
.and_then(|n| n.to_usize())
299+
.unwrap_or(0);
300+
let length = length
301+
.eval()?
302+
.eval_as_bigint()
303+
.ok()
304+
.and_then(|n| n.to_usize())
305+
.unwrap_or(0);
252306

253307
let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else {
254-
return Ok(String::new());
308+
return Ok(String::new().into());
255309
};
256310

257-
Ok(string.chars().skip(pos).take(length).collect())
311+
Ok(string
312+
.chars()
313+
.skip(pos)
314+
.take(length)
315+
.collect::<String>()
316+
.into())
258317
}
259-
Self::Length { string } => Ok(string.eval()?.chars().count().to_string()),
318+
Self::Length { string } => Ok(string.eval()?.eval_as_string().chars().count().into()),
260319
}
261320
}
262321
}
@@ -399,21 +458,26 @@ impl<'a> Parser<'a> {
399458
/// Determine whether `expr` should evaluate the string as "truthy"
400459
///
401460
/// Truthy strings are either empty or match the regex "-?0+".
402-
pub fn is_truthy(s: &str) -> bool {
403-
// Edge case: `-` followed by nothing is truthy
404-
if s == "-" {
405-
return true;
406-
}
461+
pub fn is_truthy(s: &NumOrStr) -> bool {
462+
match s {
463+
NumOrStr::Num(num) => num != &BigInt::from(0),
464+
NumOrStr::Str(str) => {
465+
// Edge case: `-` followed by nothing is truthy
466+
if str == "-" {
467+
return true;
468+
}
407469

408-
let mut bytes = s.bytes();
470+
let mut bytes = str.bytes();
409471

410-
// Empty string is falsy
411-
let Some(first) = bytes.next() else {
412-
return false;
413-
};
472+
// Empty string is falsy
473+
let Some(first) = bytes.next() else {
474+
return false;
475+
};
414476

415-
let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0');
416-
!is_zero
477+
let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0');
478+
!is_zero
479+
}
480+
}
417481
}
418482

419483
#[cfg(test)]

0 commit comments

Comments
 (0)