1
+ use crate :: commands:: jq:: ion_math:: DecimalMath ;
1
2
use crate :: commands:: { CommandIo , IonCliCommand , WithIonCliArgument } ;
2
3
use crate :: input:: CommandInput ;
3
4
use crate :: output:: { CommandOutput , CommandOutputWriter } ;
4
5
use anyhow:: bail;
6
+ use bigdecimal:: ToPrimitive ;
5
7
use clap:: { arg, ArgMatches , Command } ;
6
8
use ion_rs:: {
7
9
AnyEncoding , Element , ElementReader , IonData , IonType , List , Reader , Sequence , Value ,
@@ -217,10 +219,29 @@ impl PartialOrd for JaqElement {
217
219
impl Add for JaqElement {
218
220
type Output = ValR < Self > ;
219
221
222
+ /// From: https://jqlang.org/manual/#addition
223
+ ///
224
+ /// > The operator `+` takes two filters, applies them both to the same input, and adds the
225
+ /// results together. What "adding" means depends on the types involved:
226
+ /// >
227
+ /// > - Numbers are added by normal arithmetic.
228
+ /// >
229
+ /// > - Arrays are added by being concatenated into a larger array.
230
+ /// >
231
+ /// > - Strings are added by being joined into a larger string.
232
+ /// >
233
+ /// > - Objects are added by merging, that is, inserting all the key-value pairs from both
234
+ /// > objects into a single combined object. If both objects contain a value for the same key,
235
+ /// > the object on the right of the `+` wins. (For recursive merge use the `*` operator.)
236
+ /// >
237
+ /// > `null` can be added to any value, and returns the other value unchanged.
238
+ ///
239
+ /// For Ion values we have slightly different semantics–we haven't yet implemented the
240
+ /// overriding and deduplicating of keys for structs, so structs are simply merged
220
241
fn add ( self , _rhs : Self ) -> Self :: Output {
221
242
let ( lhv, rhv) = ( self . into_value ( ) , _rhs. into_value ( ) ) ;
222
243
223
- use ion_math:: { DecimalMath , FloatMath } ;
244
+ use ion_math:: { DecimalMath , ToFloat } ;
224
245
use Value :: * ;
225
246
226
247
let elt: Element = match ( lhv, rhv) {
@@ -235,8 +256,15 @@ impl Add for JaqElement {
235
256
( List ( a) , List ( b) ) => ion_rs:: List :: from_iter ( a. into_iter ( ) . chain ( b) ) . into ( ) ,
236
257
( SExp ( a) , SExp ( b) ) => ion_rs:: SExp :: from_iter ( a. into_iter ( ) . chain ( b) ) . into ( ) ,
237
258
( String ( a) , String ( b) ) => format ! ( "{}{}" , a. text( ) , b. text( ) ) . into ( ) ,
259
+ ( Symbol ( a) , Symbol ( b) ) => match ( a. text ( ) , b. text ( ) ) {
260
+ ( Some ( ta) , Some ( tb) ) => format ! ( "{}{}" , ta, tb) ,
261
+ //TODO: Handle symbols with unknown text?
262
+ _ => return jaq_binary_error ( Symbol ( a) , Symbol ( b) , "cannot be added" ) ,
263
+ }
264
+ . into ( ) ,
265
+
238
266
// Structs merge
239
- //TODO: Recursively remove duplicate fields, first field position but b and last field wins
267
+ //TODO: Recursively remove duplicate fields, see doc comment for method
240
268
( Struct ( a) , Struct ( b) ) => a. clone_builder ( ) . with_fields ( b. fields ( ) ) . build ( ) . into ( ) ,
241
269
242
270
// Number types, only lossless operations
@@ -246,16 +274,13 @@ impl Add for JaqElement {
246
274
( Decimal ( a) , Int ( b) ) | ( Int ( b) , Decimal ( a) ) => a. add ( b) . into ( ) ,
247
275
248
276
// Only try potentially lossy Float conversions when we've run out of the other options
249
- ( b, Float ( a) ) | ( Float ( a) , b)
250
- if matches ! ( b. ion_type( ) , IonType :: Int | IonType :: Decimal ) =>
251
- {
252
- let Some ( f) = b. clone ( ) . to_f64 ( ) else {
253
- return jaq_unary_error ( b, "cannot be an f64" ) ;
277
+ ( a @ Int ( _) | a @ Decimal ( _) , Float ( b) ) | ( Float ( b) , a @ Int ( _) | a @ Decimal ( _) ) => {
278
+ let Some ( f) = a. clone ( ) . to_f64 ( ) else {
279
+ return jaq_unary_error ( a, "cannot be an f64" ) ;
254
280
} ;
255
- ( f + a ) . into ( )
281
+ ( f + b ) . into ( )
256
282
}
257
283
258
- // Note this includes timestamps
259
284
( a, b) => return jaq_binary_error ( a, b, "cannot be added" ) ,
260
285
} ;
261
286
@@ -266,10 +291,14 @@ impl Add for JaqElement {
266
291
impl Sub for JaqElement {
267
292
type Output = ValR < Self > ;
268
293
294
+ /// From: https://jqlang.org/manual/#subtraction
295
+ ///
296
+ /// > As well as normal arithmetic subtraction on numbers, the `-` operator can be used on
297
+ /// > arrays to remove all occurrences of the second array's elements from the first array.
269
298
fn sub ( self , _rhs : Self ) -> Self :: Output {
270
299
let ( lhv, rhv) = ( self . into_value ( ) , _rhs. into_value ( ) ) ;
271
300
272
- use ion_math:: { DecimalMath , FloatMath } ;
301
+ use ion_math:: { DecimalMath , ToFloat } ;
273
302
use Value :: * ;
274
303
275
304
let elt: Element = match ( lhv, rhv) {
@@ -287,7 +316,7 @@ impl Sub for JaqElement {
287
316
}
288
317
289
318
// Number types, only lossless operations
290
- ( Int ( a) , Int ( b) ) => ( a + -b) . into ( ) , //TODO: use bare - when Int implements Sub
319
+ ( Int ( a) , Int ( b) ) => ( a + -b) . into ( ) , //TODO: use bare - with ion-rs > rc.11
291
320
( Float ( a) , Float ( b) ) => ( a - b) . into ( ) ,
292
321
( Decimal ( a) , Decimal ( b) ) => a. sub ( b) . into ( ) ,
293
322
( Decimal ( a) , Int ( b) ) => a. sub ( b) . into ( ) ,
@@ -307,7 +336,6 @@ impl Sub for JaqElement {
307
336
( a - f) . into ( )
308
337
}
309
338
310
- // Note this includes timestamps, strings, structs, and all nulls
311
339
( a, b) => return jaq_binary_error ( a, b, "cannot be subtracted" ) ,
312
340
} ;
313
341
@@ -318,32 +346,172 @@ impl Sub for JaqElement {
318
346
impl Mul for JaqElement {
319
347
type Output = ValR < Self > ;
320
348
349
+ /// From: https://jqlang.org/manual/#multiplication-division-modulo
350
+ ///
351
+ /// > - Multiplying a string by a number produces the concatenation of that string that many times.
352
+ /// > `"x" * 0` produces `""`.
353
+ /// >
354
+ /// > - Multiplying two objects will merge them recursively: this works like addition but if both
355
+ /// > objects contain a value for the same key, and the values are objects, the two are merged
356
+ /// > with the same strategy.
321
357
fn mul ( self , _rhs : Self ) -> Self :: Output {
322
- todo ! ( )
358
+ let ( lhv, rhv) = ( self . into_value ( ) , _rhs. into_value ( ) ) ;
359
+
360
+ use ion_math:: { DecimalMath , ToFloat } ;
361
+ use Value :: * ;
362
+
363
+ let elt: Element = match ( lhv, rhv) {
364
+ ( String ( a) , Int ( b) ) | ( Int ( b) , String ( a) ) => match b. as_usize ( ) {
365
+ Some ( n) => a. text ( ) . repeat ( n) . into ( ) ,
366
+ None => Null ( IonType :: Null ) . into ( ) ,
367
+ } ,
368
+
369
+ ( Symbol ( a) , Int ( b) ) | ( Int ( b) , Symbol ( a) ) => match ( b. as_usize ( ) , a. text ( ) ) {
370
+ ( Some ( n) , Some ( t) ) => t. repeat ( n) . into ( ) ,
371
+ //TODO: Handle symbols with unknown text?
372
+ _ => Null ( IonType :: Null ) . into ( ) ,
373
+ } ,
374
+
375
+ // Structs merge recursively
376
+ //TODO: Recursively remove duplicate fields, recursively merge if struct fields collide
377
+ ( Struct ( a) , Struct ( b) ) => a. clone_builder ( ) . with_fields ( b. fields ( ) ) . build ( ) . into ( ) ,
378
+
379
+ // Number types, only lossless operations
380
+ //TODO: use (a*b) when using ion-rs > rc.11
381
+ ( Int ( a) , Int ( b) ) => ( a. expect_i128 ( ) . unwrap ( ) * b. expect_i128 ( ) . unwrap ( ) ) . into ( ) ,
382
+ ( Float ( a) , Float ( b) ) => ( a * b) . into ( ) ,
383
+ ( Decimal ( a) , Decimal ( b) ) => a. mul ( b) . into ( ) ,
384
+ ( Decimal ( a) , Int ( b) ) | ( Int ( b) , Decimal ( a) ) => a. mul ( b) . into ( ) ,
385
+
386
+ // Only try potentially lossy Float conversions when we've run out of the other options
387
+ ( a @ Int ( _) | a @ Decimal ( _) , Float ( b) ) | ( Float ( b) , a @ Int ( _) | a @ Decimal ( _) ) => {
388
+ let Some ( f) = a. clone ( ) . to_f64 ( ) else {
389
+ return jaq_unary_error ( a, "cannot be an f64" ) ;
390
+ } ;
391
+ ( f * b) . into ( )
392
+ }
393
+
394
+ ( a, b) => return jaq_binary_error ( a, b, "cannot be multiplied" ) ,
395
+ } ;
396
+
397
+ Ok ( JaqElement :: from ( elt) )
323
398
}
324
399
}
325
400
326
401
impl Div for JaqElement {
327
402
type Output = ValR < Self > ;
328
403
404
+ /// From: https://jqlang.org/manual/#multiplication-division-modulo
405
+ ///
406
+ /// > Dividing a string by another splits the first using the second as separators.
329
407
fn div ( self , _rhs : Self ) -> Self :: Output {
330
- todo ! ( )
408
+ let ( lhv, rhv) = ( self . into_value ( ) , _rhs. into_value ( ) ) ;
409
+
410
+ use ion_math:: { DecimalMath , ToFloat } ;
411
+ use Value :: * ;
412
+
413
+ let elt: Element = match ( lhv, rhv) {
414
+ // Dividing a string by another splits the first using the second as separators.
415
+ ( String ( a) , String ( b) ) => {
416
+ let split = a. text ( ) . split ( b. text ( ) ) ;
417
+ let iter = split. map ( |s| String ( s. into ( ) ) ) . map ( Element :: from) ;
418
+ ion_rs:: List :: from_iter ( iter) . into ( )
419
+ }
420
+ ( Symbol ( a) , Symbol ( b) ) => match ( a. text ( ) , b. text ( ) ) {
421
+ ( Some ( ta) , Some ( tb) ) => {
422
+ let iter = ta. split ( tb) . map ( |s| Symbol ( s. into ( ) ) ) . map ( Element :: from) ;
423
+ ion_rs:: List :: from_iter ( iter)
424
+ }
425
+ //TODO: Handle symbols with unknown text?
426
+ _ => return jaq_binary_error ( Symbol ( a) , Symbol ( b) , "cannot be divided" ) ,
427
+ }
428
+ . into ( ) ,
429
+
430
+ // Number types, only lossless operations
431
+ ( Int ( a) , Int ( b) ) => ( a. expect_i128 ( ) . unwrap ( ) / b. expect_i128 ( ) . unwrap ( ) ) . into ( ) ,
432
+ ( Float ( a) , Float ( b) ) => ( a / b) . into ( ) ,
433
+ ( Decimal ( a) , Decimal ( b) ) => a. div ( b) . into ( ) ,
434
+ ( Decimal ( a) , Int ( b) ) => a. div ( b) . into ( ) ,
435
+ ( Int ( a) , Decimal ( b) ) => a. div ( b) . into ( ) ,
436
+
437
+ // Only try potentially lossy Float conversions when we've run out of the other options
438
+ ( a @ Int ( _) | a @ Decimal ( _) , Float ( b) ) => {
439
+ let Some ( f) = a. clone ( ) . to_f64 ( ) else {
440
+ return jaq_unary_error ( a, "cannot be an f64" ) ;
441
+ } ;
442
+ ( f / b) . into ( )
443
+ }
444
+ ( Float ( a) , b @ Int ( _) | b @ Decimal ( _) ) => {
445
+ let Some ( f) = b. clone ( ) . to_f64 ( ) else {
446
+ return jaq_unary_error ( b, "cannot be an f64" ) ;
447
+ } ;
448
+ ( a / f) . into ( )
449
+ }
450
+
451
+ ( a, b) => return jaq_binary_error ( a, b, "cannot be divided" ) ,
452
+ } ;
453
+
454
+ Ok ( JaqElement :: from ( elt) )
331
455
}
332
456
}
333
457
334
458
impl Rem for JaqElement {
335
459
type Output = ValR < Self > ;
336
460
337
461
fn rem ( self , _rhs : Self ) -> Self :: Output {
338
- todo ! ( )
462
+ let ( lhv, rhv) = ( self . into_value ( ) , _rhs. into_value ( ) ) ;
463
+
464
+ use ion_math:: { DecimalMath , ToFloat } ;
465
+ use Value :: * ;
466
+
467
+ let elt: Element = match ( lhv, rhv) {
468
+ // Number types, only lossless operations
469
+ ( Int ( a) , Int ( b) ) => ( a. expect_i128 ( ) . unwrap ( ) % b. expect_i128 ( ) . unwrap ( ) ) . into ( ) ,
470
+ ( Float ( a) , Float ( b) ) => ( a % b) . into ( ) ,
471
+ ( Decimal ( a) , Decimal ( b) ) => a. rem ( b) . into ( ) ,
472
+ ( Decimal ( a) , Int ( b) ) => a. rem ( b) . into ( ) ,
473
+ ( Int ( a) , Decimal ( b) ) => a. rem ( b) . into ( ) ,
474
+
475
+ // Only try potentially lossy Float conversions when we've run out of the other options
476
+ ( a @ Int ( _) | a @ Decimal ( _) , Float ( b) ) => {
477
+ let Some ( f) = a. clone ( ) . to_f64 ( ) else {
478
+ return jaq_unary_error ( a, "cannot be an f64" ) ;
479
+ } ;
480
+ ( f % b) . into ( )
481
+ }
482
+ ( Float ( a) , b @ Int ( _) | b @ Decimal ( _) ) => {
483
+ let Some ( f) = b. clone ( ) . to_f64 ( ) else {
484
+ return jaq_unary_error ( b, "cannot be an f64" ) ;
485
+ } ;
486
+ ( a % f) . into ( )
487
+ }
488
+
489
+ ( a, b) => return jaq_binary_error ( a, b, "cannot be divided (remainder)" ) ,
490
+ } ;
491
+
492
+ Ok ( JaqElement :: from ( elt) )
339
493
}
340
494
}
341
495
342
496
impl Neg for JaqElement {
343
497
type Output = ValR < Self > ;
344
498
345
499
fn neg ( self ) -> Self :: Output {
346
- todo ! ( )
500
+ let val = self . into_value ( ) ;
501
+
502
+ use ion_math:: DecimalMath ;
503
+ use Value :: * ;
504
+
505
+ let elt: Element = match val {
506
+ // Only number types can be negated
507
+ Int ( a) => ( -a) . into ( ) ,
508
+ Float ( a) => ( -a) . into ( ) ,
509
+ Decimal ( a) => ( -a. to_big_decimal ( ) ) . to_decimal ( ) . into ( ) ,
510
+
511
+ other => return jaq_unary_error ( other, "cannot be negated" ) ,
512
+ } ;
513
+
514
+ Ok ( JaqElement :: from ( elt) )
347
515
}
348
516
}
349
517
@@ -473,11 +641,15 @@ impl jaq_std::ValT for JaqElement {
473
641
}
474
642
475
643
fn as_isize ( & self ) -> Option < isize > {
476
- todo ! ( )
644
+ match self . 0 . value ( ) {
645
+ Value :: Int ( i) => i. expect_i64 ( ) . unwrap ( ) . to_isize ( ) ,
646
+ Value :: Decimal ( d) => d. to_big_decimal ( ) . to_isize ( ) ,
647
+ _ => None ,
648
+ }
477
649
}
478
650
479
651
fn as_f64 ( & self ) -> Result < f64 , jaq_core:: Error < Self > > {
480
- use ion_math:: FloatMath ;
652
+ use ion_math:: ToFloat ;
481
653
self . 0
482
654
. value ( )
483
655
. clone ( )
@@ -498,21 +670,30 @@ pub(crate) mod ion_math {
498
670
use ion_rs:: decimal:: coefficient:: Sign ;
499
671
use ion_rs:: { Decimal , Int , Value } ;
500
672
501
- pub ( crate ) trait DecimalMath {
673
+ /// We can't provide math traits for Decimal directly, so we have a helper trait
674
+ pub ( crate ) trait DecimalMath : Sized {
502
675
fn to_big_decimal ( self ) -> BigDecimal ;
503
676
fn to_decimal ( self ) -> Decimal ;
504
- fn add ( self , v2 : impl DecimalMath ) -> Decimal
505
- where
506
- Self : Sized ,
507
- {
677
+
678
+ fn add ( self , v2 : impl DecimalMath ) -> Decimal {
508
679
( self . to_big_decimal ( ) + v2. to_big_decimal ( ) ) . to_decimal ( )
509
680
}
510
- fn sub ( self , v2 : impl DecimalMath ) -> Decimal
511
- where
512
- Self : Sized ,
513
- {
681
+
682
+ fn sub ( self , v2 : impl DecimalMath ) -> Decimal {
514
683
( self . to_big_decimal ( ) - v2. to_big_decimal ( ) ) . to_decimal ( )
515
684
}
685
+
686
+ fn mul ( self , v2 : impl DecimalMath ) -> Decimal {
687
+ ( self . to_big_decimal ( ) * v2. to_big_decimal ( ) ) . to_decimal ( )
688
+ }
689
+
690
+ fn div ( self , v2 : impl DecimalMath ) -> Decimal {
691
+ ( self . to_big_decimal ( ) / v2. to_big_decimal ( ) ) . to_decimal ( )
692
+ }
693
+
694
+ fn rem ( self , v2 : impl DecimalMath ) -> Decimal {
695
+ ( self . to_big_decimal ( ) % v2. to_big_decimal ( ) ) . to_decimal ( )
696
+ }
516
697
}
517
698
518
699
impl DecimalMath for Decimal {
@@ -554,17 +735,17 @@ pub(crate) mod ion_math {
554
735
}
555
736
}
556
737
557
- pub ( crate ) trait FloatMath {
738
+ pub ( crate ) trait ToFloat {
558
739
fn to_f64 ( self ) -> Option < f64 > ;
559
740
}
560
741
561
- impl FloatMath for f64 {
742
+ impl ToFloat for f64 {
562
743
fn to_f64 ( self ) -> Option < f64 > {
563
744
Some ( self )
564
745
}
565
746
}
566
747
567
- impl FloatMath for Int {
748
+ impl ToFloat for Int {
568
749
fn to_f64 ( self ) -> Option < f64 > {
569
750
self . as_i128 ( ) . and_then ( |data| {
570
751
let float = data as f64 ;
@@ -573,17 +754,18 @@ pub(crate) mod ion_math {
573
754
}
574
755
}
575
756
576
- impl FloatMath for Decimal {
757
+ impl ToFloat for Decimal {
577
758
fn to_f64 ( self ) -> Option < f64 > {
578
759
self . to_big_decimal ( ) . to_f64 ( )
579
760
}
580
761
}
581
762
582
- impl FloatMath for Value {
763
+ impl ToFloat for Value {
583
764
fn to_f64 ( self ) -> Option < f64 > {
584
765
match self {
585
766
Value :: Int ( i) => i. to_f64 ( ) ,
586
767
Value :: Decimal ( d) => d. to_f64 ( ) ,
768
+ Value :: Float ( f) => f. to_f64 ( ) ,
587
769
_ => None ,
588
770
}
589
771
}
0 commit comments