@@ -231,53 +231,34 @@ export function evaluate(
231231 }
232232
233233 case "UpdateOp" : {
234- const pathResults = evaluate ( value , ast . path , ctx ) ;
235- const valueResults = evaluate ( value , ast . value , ctx ) ;
236-
237- return pathResults . flatMap ( ( current ) =>
238- valueResults . map ( ( newVal ) => {
239- switch ( ast . op ) {
240- case "=" :
241- return newVal ;
242- case "+=" :
243- if ( typeof current === "number" && typeof newVal === "number" )
244- return current + newVal ;
245- if ( typeof current === "string" && typeof newVal === "string" )
246- return current + newVal ;
247- if ( Array . isArray ( current ) && Array . isArray ( newVal ) )
248- return [ ...current , ...newVal ] ;
249- if (
250- current &&
251- newVal &&
252- typeof current === "object" &&
253- typeof newVal === "object"
254- ) {
255- return { ...current , ...newVal } ;
256- }
257- return newVal ;
258- case "-=" :
259- if ( typeof current === "number" && typeof newVal === "number" )
260- return current - newVal ;
261- return current ;
262- case "*=" :
263- if ( typeof current === "number" && typeof newVal === "number" )
264- return current * newVal ;
265- return current ;
266- case "/=" :
267- if ( typeof current === "number" && typeof newVal === "number" )
268- return current / newVal ;
269- return current ;
270- case "%=" :
271- if ( typeof current === "number" && typeof newVal === "number" )
272- return current % newVal ;
273- return current ;
274- case "//=" :
275- return current === null || current === false ? newVal : current ;
276- default :
277- return newVal ;
278- }
279- } ) ,
280- ) ;
234+ return [ applyUpdate ( value , ast . path , ast . op , ast . value , ctx ) ] ;
235+ }
236+
237+ case "Reduce" : {
238+ const items = evaluate ( value , ast . expr , ctx ) ;
239+ let accumulator = evaluate ( value , ast . init , ctx ) [ 0 ] ;
240+ for ( const item of items ) {
241+ const newCtx = withVar ( ctx , ast . varName , item ) ;
242+ accumulator = evaluate ( accumulator , ast . update , newCtx ) [ 0 ] ;
243+ }
244+ return [ accumulator ] ;
245+ }
246+
247+ case "Foreach" : {
248+ const items = evaluate ( value , ast . expr , ctx ) ;
249+ let state = evaluate ( value , ast . init , ctx ) [ 0 ] ;
250+ const results : JqValue [ ] = [ ] ;
251+ for ( const item of items ) {
252+ const newCtx = withVar ( ctx , ast . varName , item ) ;
253+ state = evaluate ( state , ast . update , newCtx ) [ 0 ] ;
254+ if ( ast . extract ) {
255+ const extracted = evaluate ( state , ast . extract , newCtx ) ;
256+ results . push ( ...extracted ) ;
257+ } else {
258+ results . push ( state ) ;
259+ }
260+ }
261+ return results ;
281262 }
282263
283264 default : {
@@ -294,6 +275,241 @@ function normalizeIndex(idx: number, len: number): number {
294275 return Math . min ( idx , len ) ;
295276}
296277
278+ function applyUpdate (
279+ root : JqValue ,
280+ pathExpr : AstNode ,
281+ op : string ,
282+ valueExpr : AstNode ,
283+ ctx : EvalContext ,
284+ ) : JqValue {
285+ function computeNewValue ( current : JqValue , newVal : JqValue ) : JqValue {
286+ switch ( op ) {
287+ case "=" :
288+ return newVal ;
289+ case "|=" : {
290+ const results = evaluate ( current , valueExpr , ctx ) ;
291+ return results [ 0 ] ?? null ;
292+ }
293+ case "+=" :
294+ if ( typeof current === "number" && typeof newVal === "number" )
295+ return current + newVal ;
296+ if ( typeof current === "string" && typeof newVal === "string" )
297+ return current + newVal ;
298+ if ( Array . isArray ( current ) && Array . isArray ( newVal ) )
299+ return [ ...current , ...newVal ] ;
300+ if (
301+ current &&
302+ newVal &&
303+ typeof current === "object" &&
304+ typeof newVal === "object"
305+ ) {
306+ return { ...current , ...newVal } ;
307+ }
308+ return newVal ;
309+ case "-=" :
310+ if ( typeof current === "number" && typeof newVal === "number" )
311+ return current - newVal ;
312+ return current ;
313+ case "*=" :
314+ if ( typeof current === "number" && typeof newVal === "number" )
315+ return current * newVal ;
316+ return current ;
317+ case "/=" :
318+ if ( typeof current === "number" && typeof newVal === "number" )
319+ return current / newVal ;
320+ return current ;
321+ case "%=" :
322+ if ( typeof current === "number" && typeof newVal === "number" )
323+ return current % newVal ;
324+ return current ;
325+ case "//=" :
326+ return current === null || current === false ? newVal : current ;
327+ default :
328+ return newVal ;
329+ }
330+ }
331+
332+ function updateRecursive (
333+ val : JqValue ,
334+ path : AstNode ,
335+ transform : ( current : JqValue ) => JqValue ,
336+ ) : JqValue {
337+ switch ( path . type ) {
338+ case "Identity" :
339+ return transform ( val ) ;
340+
341+ case "Field" : {
342+ if ( path . base ) {
343+ return updateRecursive ( val , path . base , ( baseVal ) => {
344+ if (
345+ baseVal &&
346+ typeof baseVal === "object" &&
347+ ! Array . isArray ( baseVal )
348+ ) {
349+ const obj = { ...baseVal } as Record < string , unknown > ;
350+ obj [ path . name ] = transform ( obj [ path . name ] ) ;
351+ return obj ;
352+ }
353+ return baseVal ;
354+ } ) ;
355+ }
356+ if ( val && typeof val === "object" && ! Array . isArray ( val ) ) {
357+ const obj = { ...val } as Record < string , unknown > ;
358+ obj [ path . name ] = transform ( obj [ path . name ] ) ;
359+ return obj ;
360+ }
361+ return val ;
362+ }
363+
364+ case "Index" : {
365+ const indices = evaluate ( root , path . index , ctx ) ;
366+ const idx = indices [ 0 ] ;
367+
368+ if ( path . base ) {
369+ return updateRecursive ( val , path . base , ( baseVal ) => {
370+ if ( typeof idx === "number" && Array . isArray ( baseVal ) ) {
371+ const arr = [ ...baseVal ] ;
372+ const i = idx < 0 ? arr . length + idx : idx ;
373+ if ( i >= 0 && i < arr . length ) {
374+ arr [ i ] = transform ( arr [ i ] ) ;
375+ }
376+ return arr ;
377+ }
378+ if (
379+ typeof idx === "string" &&
380+ baseVal &&
381+ typeof baseVal === "object" &&
382+ ! Array . isArray ( baseVal )
383+ ) {
384+ const obj = { ...baseVal } as Record < string , unknown > ;
385+ obj [ idx ] = transform ( obj [ idx ] ) ;
386+ return obj ;
387+ }
388+ return baseVal ;
389+ } ) ;
390+ }
391+
392+ if ( typeof idx === "number" && Array . isArray ( val ) ) {
393+ const arr = [ ...val ] ;
394+ const i = idx < 0 ? arr . length + idx : idx ;
395+ if ( i >= 0 && i < arr . length ) {
396+ arr [ i ] = transform ( arr [ i ] ) ;
397+ }
398+ return arr ;
399+ }
400+ if (
401+ typeof idx === "string" &&
402+ val &&
403+ typeof val === "object" &&
404+ ! Array . isArray ( val )
405+ ) {
406+ const obj = { ...val } as Record < string , unknown > ;
407+ obj [ idx ] = transform ( obj [ idx ] ) ;
408+ return obj ;
409+ }
410+ return val ;
411+ }
412+
413+ case "Iterate" : {
414+ const applyToContainer = ( container : JqValue ) : JqValue => {
415+ if ( Array . isArray ( container ) ) {
416+ return container . map ( ( item ) => transform ( item ) ) ;
417+ }
418+ if ( container && typeof container === "object" ) {
419+ const obj : Record < string , unknown > = { } ;
420+ for ( const [ k , v ] of Object . entries ( container ) ) {
421+ obj [ k ] = transform ( v ) ;
422+ }
423+ return obj ;
424+ }
425+ return container ;
426+ } ;
427+
428+ if ( path . base ) {
429+ return updateRecursive ( val , path . base , applyToContainer ) ;
430+ }
431+ return applyToContainer ( val ) ;
432+ }
433+
434+ case "Pipe" : {
435+ const leftResult = updateRecursive ( val , path . left , ( x ) => x ) ;
436+ return updateRecursive ( leftResult , path . right , transform ) ;
437+ }
438+
439+ default :
440+ return transform ( val ) ;
441+ }
442+ }
443+
444+ const transformer = ( current : JqValue ) : JqValue => {
445+ if ( op === "|=" ) {
446+ return computeNewValue ( current , current ) ;
447+ }
448+ const newVals = evaluate ( root , valueExpr , ctx ) ;
449+ return computeNewValue ( current , newVals [ 0 ] ?? null ) ;
450+ } ;
451+
452+ return updateRecursive ( root , pathExpr , transformer ) ;
453+ }
454+
455+ function applyDel ( root : JqValue , pathExpr : AstNode , ctx : EvalContext ) : JqValue {
456+ function deleteAt ( val : JqValue , path : AstNode ) : JqValue {
457+ switch ( path . type ) {
458+ case "Identity" :
459+ return null ;
460+
461+ case "Field" : {
462+ if ( val && typeof val === "object" && ! Array . isArray ( val ) ) {
463+ const obj = { ...val } as Record < string , unknown > ;
464+ delete obj [ path . name ] ;
465+ return obj ;
466+ }
467+ return val ;
468+ }
469+
470+ case "Index" : {
471+ const indices = evaluate ( root , path . index , ctx ) ;
472+ const idx = indices [ 0 ] ;
473+
474+ if ( typeof idx === "number" && Array . isArray ( val ) ) {
475+ const arr = [ ...val ] ;
476+ const i = idx < 0 ? arr . length + idx : idx ;
477+ if ( i >= 0 && i < arr . length ) {
478+ arr . splice ( i , 1 ) ;
479+ }
480+ return arr ;
481+ }
482+ if (
483+ typeof idx === "string" &&
484+ val &&
485+ typeof val === "object" &&
486+ ! Array . isArray ( val )
487+ ) {
488+ const obj = { ...val } as Record < string , unknown > ;
489+ delete obj [ idx ] ;
490+ return obj ;
491+ }
492+ return val ;
493+ }
494+
495+ case "Iterate" : {
496+ if ( Array . isArray ( val ) ) {
497+ return [ ] ;
498+ }
499+ if ( val && typeof val === "object" ) {
500+ return { } ;
501+ }
502+ return val ;
503+ }
504+
505+ default :
506+ return val ;
507+ }
508+ }
509+
510+ return deleteAt ( root , pathExpr ) ;
511+ }
512+
297513function isTruthy ( v : JqValue ) : boolean {
298514 return v !== null && v !== false ;
299515}
@@ -326,7 +542,9 @@ function evalBinaryOp(
326542
327543 if ( op === "//" ) {
328544 const leftVals = evaluate ( value , left , ctx ) ;
329- const nonNull = leftVals . filter ( ( v ) => v !== null && v !== false ) ;
545+ const nonNull = leftVals . filter (
546+ ( v ) => v !== null && v !== undefined && v !== false ,
547+ ) ;
330548 if ( nonNull . length > 0 ) return nonNull ;
331549 return evaluate ( value , right , ctx ) ;
332550 }
@@ -655,7 +873,9 @@ function evalBuiltin(
655873 case "flatten" : {
656874 if ( ! Array . isArray ( value ) ) return [ null ] ;
657875 const depth =
658- args . length > 0 ? ( evaluate ( value , args [ 0 ] , ctx ) [ 0 ] as number ) : 1 ;
876+ args . length > 0
877+ ? ( evaluate ( value , args [ 0 ] , ctx ) [ 0 ] as number )
878+ : Number . POSITIVE_INFINITY ;
659879 return [ value . flat ( depth ) ] ;
660880 }
661881
@@ -816,6 +1036,48 @@ function evalBuiltin(
8161036 return paths ;
8171037 }
8181038
1039+ case "del" : {
1040+ if ( args . length === 0 ) return [ value ] ;
1041+ return [ applyDel ( value , args [ 0 ] , ctx ) ] ;
1042+ }
1043+
1044+ case "paths" : {
1045+ const paths : ( string | number ) [ ] [ ] = [ ] ;
1046+ const walk = ( v : JqValue , path : ( string | number ) [ ] ) => {
1047+ if ( v && typeof v === "object" ) {
1048+ if ( Array . isArray ( v ) ) {
1049+ for ( let i = 0 ; i < v . length ; i ++ ) {
1050+ paths . push ( [ ...path , i ] ) ;
1051+ walk ( v [ i ] , [ ...path , i ] ) ;
1052+ }
1053+ } else {
1054+ for ( const key of Object . keys ( v ) ) {
1055+ paths . push ( [ ...path , key ] ) ;
1056+ walk ( ( v as Record < string , unknown > ) [ key ] , [ ...path , key ] ) ;
1057+ }
1058+ }
1059+ }
1060+ } ;
1061+ walk ( value , [ ] ) ;
1062+ if ( args . length > 0 ) {
1063+ return paths . filter ( ( p ) => {
1064+ let v : JqValue = value ;
1065+ for ( const k of p ) {
1066+ if ( Array . isArray ( v ) && typeof k === "number" ) {
1067+ v = v [ k ] ;
1068+ } else if ( v && typeof v === "object" && typeof k === "string" ) {
1069+ v = ( v as Record < string , unknown > ) [ k ] ;
1070+ } else {
1071+ return false ;
1072+ }
1073+ }
1074+ const results = evaluate ( v , args [ 0 ] , ctx ) ;
1075+ return results . some ( isTruthy ) ;
1076+ } ) ;
1077+ }
1078+ return paths ;
1079+ }
1080+
8191081 case "leaf_paths" : {
8201082 const paths : ( string | number ) [ ] [ ] = [ ] ;
8211083 const walk = ( v : JqValue , path : ( string | number ) [ ] ) => {
0 commit comments