5
5
6
6
// spell-checker:ignore (ToDO) ints paren prec multibytes
7
7
8
- use num_bigint:: BigInt ;
8
+ use num_bigint:: { BigInt , ParseBigIntError } ;
9
+ use num_traits:: ToPrimitive ;
9
10
use onig:: { Regex , RegexOptions , Syntax } ;
10
11
11
12
use crate :: { ExprError , ExprResult } ;
@@ -45,7 +46,7 @@ pub enum StringOp {
45
46
}
46
47
47
48
impl BinOp {
48
- fn eval ( & self , left : & AstNode , right : & AstNode ) -> ExprResult < String > {
49
+ fn eval ( & self , left : & AstNode , right : & AstNode ) -> ExprResult < NumOrStr > {
49
50
match self {
50
51
Self :: Relation ( op) => op. eval ( left, right) ,
51
52
Self :: Numeric ( op) => op. eval ( left, right) ,
@@ -55,10 +56,10 @@ impl BinOp {
55
56
}
56
57
57
58
impl RelationOp {
58
- fn eval ( & self , a : & AstNode , b : & AstNode ) -> ExprResult < String > {
59
+ fn eval ( & self , a : & AstNode , b : & AstNode ) -> ExprResult < NumOrStr > {
59
60
let a = a. eval ( ) ?;
60
61
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 ( ) ) {
62
63
match self {
63
64
Self :: Lt => a < b,
64
65
Self :: Leq => a <= b,
@@ -79,24 +80,18 @@ impl RelationOp {
79
80
}
80
81
} ;
81
82
if b {
82
- Ok ( "1" . into ( ) )
83
+ Ok ( 1 . into ( ) )
83
84
} else {
84
- Ok ( "0" . into ( ) )
85
+ Ok ( 0 . into ( ) )
85
86
}
86
87
}
87
88
}
88
89
89
90
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 {
100
95
Self :: Add => a + b,
101
96
Self :: Sub => a - b,
102
97
Self :: Mul => a * b,
@@ -110,13 +105,12 @@ impl NumericOp {
110
105
} ;
111
106
a % b
112
107
}
113
- }
114
- . to_string ( ) )
108
+ } ) )
115
109
}
116
110
}
117
111
118
112
impl StringOp {
119
- fn eval ( & self , left : & AstNode , right : & AstNode ) -> ExprResult < String > {
113
+ fn eval ( & self , left : & AstNode , right : & AstNode ) -> ExprResult < NumOrStr > {
120
114
match self {
121
115
Self :: Or => {
122
116
let left = left. eval ( ) ?;
@@ -127,23 +121,23 @@ impl StringOp {
127
121
if is_truthy ( & right) {
128
122
return Ok ( right) ;
129
123
}
130
- Ok ( "0" . into ( ) )
124
+ Ok ( 0 . into ( ) )
131
125
}
132
126
Self :: And => {
133
127
let left = left. eval ( ) ?;
134
128
if !is_truthy ( & left) {
135
- return Ok ( "0" . into ( ) ) ;
129
+ return Ok ( 0 . into ( ) ) ;
136
130
}
137
131
let right = right. eval ( ) ?;
138
132
if !is_truthy ( & right) {
139
- return Ok ( "0" . into ( ) ) ;
133
+ return Ok ( 0 . into ( ) ) ;
140
134
}
141
135
Ok ( left)
142
136
}
143
137
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) ;
147
141
let re = Regex :: with_options (
148
142
& re_string,
149
143
RegexOptions :: REGEX_OPTION_NONE ,
@@ -158,19 +152,20 @@ impl StringOp {
158
152
} else {
159
153
re. find ( & left)
160
154
. map_or ( "0" . to_string ( ) , |( start, end) | ( end - start) . to_string ( ) )
161
- } )
155
+ }
156
+ . into ( ) )
162
157
}
163
158
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 ( ) ;
166
161
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 ( ) {
168
163
if ch_n == ch_h {
169
- return Ok ( ( current_idx + 1 ) . to_string ( ) ) ;
164
+ return Ok ( ( current_idx + 1 ) . into ( ) ) ;
170
165
}
171
166
}
172
167
}
173
- Ok ( "0" . to_string ( ) )
168
+ Ok ( 0 . into ( ) )
174
169
}
175
170
}
176
171
}
@@ -200,6 +195,55 @@ const PRECEDENCE: &[&[(&str, BinOp)]] = &[
200
195
& [ ( ":" , BinOp :: String ( StringOp :: Match ) ) ] ,
201
196
] ;
202
197
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
+
203
247
#[ derive( Debug , PartialEq , Eq ) ]
204
248
pub enum AstNode {
205
249
Leaf {
@@ -225,9 +269,9 @@ impl AstNode {
225
269
Parser :: new ( input) . parse ( )
226
270
}
227
271
228
- pub fn eval ( & self ) -> ExprResult < String > {
272
+ pub fn eval ( & self ) -> ExprResult < NumOrStr > {
229
273
match self {
230
- Self :: Leaf { value } => Ok ( value. into ( ) ) ,
274
+ Self :: Leaf { value } => Ok ( value. to_string ( ) . into ( ) ) ,
231
275
Self :: BinOp {
232
276
op_type,
233
277
left,
@@ -238,7 +282,7 @@ impl AstNode {
238
282
pos,
239
283
length,
240
284
} => {
241
- let string = string. eval ( ) ?;
285
+ let string: String = string. eval ( ) ?. eval_as_string ( ) ;
242
286
243
287
// The GNU docs say:
244
288
//
@@ -247,16 +291,31 @@ impl AstNode {
247
291
//
248
292
// So we coerce errors into 0 to make that the only case we
249
293
// 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 ) ;
252
306
253
307
let ( Some ( pos) , Some ( _) ) = ( pos. checked_sub ( 1 ) , length. checked_sub ( 1 ) ) else {
254
- return Ok ( String :: new ( ) ) ;
308
+ return Ok ( String :: new ( ) . into ( ) ) ;
255
309
} ;
256
310
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 ( ) )
258
317
}
259
- Self :: Length { string } => Ok ( string. eval ( ) ?. chars ( ) . count ( ) . to_string ( ) ) ,
318
+ Self :: Length { string } => Ok ( string. eval ( ) ?. eval_as_string ( ) . chars ( ) . count ( ) . into ( ) ) ,
260
319
}
261
320
}
262
321
}
@@ -399,21 +458,26 @@ impl<'a> Parser<'a> {
399
458
/// Determine whether `expr` should evaluate the string as "truthy"
400
459
///
401
460
/// 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
+ }
407
469
408
- let mut bytes = s . bytes ( ) ;
470
+ let mut bytes = str . bytes ( ) ;
409
471
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
+ } ;
414
476
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
+ }
417
481
}
418
482
419
483
#[ cfg( test) ]
0 commit comments