@@ -177,15 +177,14 @@ fn jaq_error(e: impl Into<Element>) -> ValR<JaqElement> {
177
177
Err ( jaq_err ( e) )
178
178
}
179
179
180
- fn jaq_unary_error ( v1 : Value , reason : & str ) -> ValR < JaqElement > {
181
- let type1 = v1 . ion_type ( ) ;
182
- jaq_error ( format ! ( "{type1 } ({v1 }) {reason}" ) )
180
+ fn jaq_unary_error ( a : Value , reason : & str ) -> ValR < JaqElement > {
181
+ let alpha = a . ion_type ( ) ;
182
+ jaq_error ( format ! ( "{alpha } ({a }) {reason}" ) )
183
183
}
184
184
185
- fn jaq_binary_error ( v1 : Value , v2 : Value , reason : & str ) -> ValR < JaqElement > {
186
- let type1 = v1. ion_type ( ) ;
187
- let type2 = v2. ion_type ( ) ;
188
- jaq_error ( format ! ( "{type1} ({v1}) and {type2} ({v2}) {reason}" ) )
185
+ fn jaq_binary_error ( a : Value , b : Value , reason : & str ) -> ValR < JaqElement > {
186
+ let ( alpha, beta) = ( a. ion_type ( ) , b. ion_type ( ) ) ;
187
+ jaq_error ( format ! ( "{alpha} ({a}) and {beta} ({b}) {reason}" ) )
189
188
}
190
189
191
190
// Convenience method to return a bare `JaqError`, not wrapped in a Result::Err.
@@ -241,6 +240,11 @@ impl Add for JaqElement {
241
240
fn add ( self , _rhs : Self ) -> Self :: Output {
242
241
let ( lhv, rhv) = ( self . into_value ( ) , _rhs. into_value ( ) ) ;
243
242
243
+ fn unknown_symbol_err ( a : Value , b : Value ) -> ValR < JaqElement > {
244
+ let ( alpha, beta) = ( a. ion_type ( ) , b. ion_type ( ) ) ;
245
+ jaq_error ( format ! ( "{alpha} ({a}) and {beta} ({b}) cannot be added" ) )
246
+ }
247
+
244
248
use ion_math:: { DecimalMath , ToFloat } ;
245
249
use Value :: * ;
246
250
@@ -255,14 +259,25 @@ impl Add for JaqElement {
255
259
// Sequences and strings concatenate
256
260
( List ( a) , List ( b) ) => ion_rs:: List :: from_iter ( a. into_iter ( ) . chain ( b) ) . into ( ) ,
257
261
( SExp ( a) , SExp ( b) ) => ion_rs:: SExp :: from_iter ( a. into_iter ( ) . chain ( b) ) . into ( ) ,
258
- //TODO: Does it make sense to concatenate a String and a Symbol? What type results?
262
+
263
+ // We don't work with unknown symbols
264
+ ( Symbol ( a) , b) if a. text ( ) . is_none ( ) => return unknown_symbol_err ( Symbol ( a) , b) ,
265
+ ( a, Symbol ( b) ) if b. text ( ) . is_none ( ) => return unknown_symbol_err ( a, Symbol ( b) ) ,
266
+
267
+ // Like text types add to the same type
259
268
( String ( a) , String ( b) ) => format ! ( "{}{}" , a. text( ) , b. text( ) ) . into ( ) ,
260
- ( Symbol ( a) , Symbol ( b) ) => match ( a. text ( ) , b. text ( ) ) {
261
- ( Some ( ta) , Some ( tb) ) => format ! ( "{}{}" , ta, tb) ,
262
- //TODO: Handle symbols with unknown text?
263
- _ => return jaq_binary_error ( Symbol ( a) , Symbol ( b) , "cannot be added" ) ,
269
+ ( Symbol ( a) , Symbol ( b) ) => {
270
+ Symbol ( format ! ( "{}{}" , a. text( ) . unwrap( ) , b. text( ) . unwrap( ) ) . into ( ) ) . into ( )
271
+ }
272
+
273
+ // Any combination of String/Symbol gets to be a String
274
+ // We have to account for these cases to allow string interpolation
275
+ ( String ( a) , Symbol ( b) ) => {
276
+ String ( format ! ( "{}{}" , a. text( ) , b. text( ) . unwrap( ) ) . into ( ) ) . into ( )
277
+ }
278
+ ( Symbol ( a) , String ( b) ) => {
279
+ String ( format ! ( "{}{}" , a. text( ) . unwrap( ) , b. text( ) ) . into ( ) ) . into ( )
264
280
}
265
- . into ( ) ,
266
281
267
282
// Structs merge
268
283
//TODO: Recursively remove duplicate fields, see doc comment for rules
@@ -278,7 +293,7 @@ impl Add for JaqElement {
278
293
( a @ Int ( _) | a @ Decimal ( _) , Float ( b) ) => ( a. to_f64 ( ) . unwrap ( ) + b) . into ( ) ,
279
294
( Float ( a) , b @ Int ( _) | b @ Decimal ( _) ) => ( a + b. to_f64 ( ) . unwrap ( ) ) . into ( ) ,
280
295
281
- ( a, b) => return jaq_binary_error ( a, b, "cannot be added" ) ,
296
+ ( a, b) => return unknown_symbol_err ( a, b) ,
282
297
} ;
283
298
284
299
Ok ( JaqElement :: from ( elt) )
@@ -519,29 +534,43 @@ impl jaq_core::ValT for JaqElement {
519
534
fn index ( self , index : & Self ) -> ValR < Self > {
520
535
use ion_rs:: Value :: * ;
521
536
522
- match ( self . value ( ) , index. value ( ) ) {
523
- ( List ( seq) | SExp ( seq) , Int ( i) ) => {
524
- let index = i
525
- . expect_usize ( )
526
- . map_err ( |_| jaq_err ( "index must be usize" ) ) ?;
527
- let element = seq
528
- . get ( index)
529
- . ok_or_else ( || jaq_err ( "index out of bounds" ) ) ?;
530
- Ok ( JaqElement :: from ( element. to_owned ( ) ) )
537
+ trait OrOwnedNull {
538
+ fn or_owned_null ( self ) -> Element ;
539
+ }
540
+
541
+ impl OrOwnedNull for Option < & Element > {
542
+ fn or_owned_null ( self ) -> Element {
543
+ self . map_or_else ( || Null ( IonType :: Null ) . into ( ) , Element :: to_owned)
531
544
}
532
- ( Struct ( strukt) , String ( name) ) => strukt
533
- . get ( name)
534
- . ok_or_else ( || jaq_err ( format ! ( "field name '{name}' not found" ) ) )
535
- . map ( Element :: to_owned)
536
- . map ( JaqElement :: from) ,
537
- ( Struct ( strukt) , Symbol ( name) ) => strukt
538
- . get ( name)
539
- . ok_or_else ( || jaq_err ( format ! ( "field name '{name}' not found" ) ) )
540
- . map ( Element :: to_owned)
541
- . map ( JaqElement :: from) ,
542
- ( Struct ( _) , Int ( i) ) => jaq_error ( format ! ( "cannot index struct with number ({i})" ) ) ,
543
- _ => jaq_error ( format ! ( "cannot index into {self:?}" ) ) ,
544
545
}
546
+
547
+ /// Handles the case where we want to index into a Sequence with a potentially-negative
548
+ /// value. Negative numbers index from the back of the sequence.
549
+ /// Returns an owned Null Element if the index is out of bounds.
550
+ fn index_i128 ( seq : & Sequence , index : Option < i128 > ) -> Element {
551
+ let opt = match index {
552
+ Some ( i @ ..0 ) => ( seq. len ( ) as i128 + i) . to_usize ( ) ,
553
+ Some ( i) => i. to_usize ( ) ,
554
+ None => None ,
555
+ } ;
556
+
557
+ opt. and_then ( |u| seq. get ( u) ) . or_owned_null ( )
558
+ }
559
+
560
+ let elt: Element = match ( self . value ( ) , index. value ( ) ) {
561
+ ( List ( seq) | SExp ( seq) , Int ( i) ) => index_i128 ( seq, i. as_i128 ( ) ) ,
562
+ ( List ( seq) | SExp ( seq) , Float ( f) ) => index_i128 ( seq, Some ( * f as i128 ) ) ,
563
+ ( List ( seq) | SExp ( seq) , Decimal ( d) ) => index_i128 ( seq, d. into_big_decimal ( ) . to_i128 ( ) ) ,
564
+ ( Struct ( strukt) , String ( name) ) => strukt. get ( name) . or_owned_null ( ) ,
565
+ ( Struct ( strukt) , Symbol ( name) ) => strukt. get ( name) . or_owned_null ( ) ,
566
+
567
+ ( a, b) => {
568
+ let ( alpha, beta) = ( a. ion_type ( ) , b. ion_type ( ) ) ;
569
+ return jaq_error ( format ! ( "cannot index {} with {}" , alpha, beta) ) ;
570
+ }
571
+ } ;
572
+
573
+ Ok ( JaqElement :: from ( elt) )
545
574
}
546
575
547
576
// Behavior for slicing containers.
@@ -578,9 +607,15 @@ impl jaq_core::ValT for JaqElement {
578
607
todo ! ( )
579
608
}
580
609
581
- // If we want "truthiness" for containers (e.g. empty list -> false), define that here
610
+ /// From https://jqlang.org/manual/#if-then-else-end
611
+ ///
612
+ /// > `if A then B else C end` will act the same as `B` if `A` produces a value other than
613
+ /// > `false` or `null`, but act the same as `C` otherwise.
582
614
fn as_bool ( & self ) -> bool {
583
- self . 0 . as_bool ( ) . unwrap_or ( false )
615
+ match self . 0 . value ( ) {
616
+ Value :: Null ( _) | Value :: Bool ( false ) => false ,
617
+ _ => true ,
618
+ }
584
619
}
585
620
586
621
// If the element is a text value, return its text.
0 commit comments