22'use strict'
33
44import asyncIterators from './async_iterators.js'
5- import { Sync , isSync } from './constants.js'
5+ import { Sync , isSync , Unfound } from './constants.js'
66import declareSync from './utilities/declareSync.js'
77import { build , buildString } from './compiler.js'
88import chainingSupported from './utilities/chainingSupported.js'
@@ -161,6 +161,43 @@ const defaultMethods = {
161161 xor : ( [ a , b ] ) => a ^ b ,
162162 // Why "executeInLoop"? Because if it needs to execute to get an array, I do not want to execute the arguments,
163163 // Both for performance and safety reasons.
164+ '??' : {
165+ method : ( arr , _1 , _2 , engine ) => {
166+ // See "executeInLoop" above
167+ const executeInLoop = Array . isArray ( arr )
168+ if ( ! executeInLoop ) arr = engine . run ( arr , _1 , { above : _2 } )
169+
170+ let item
171+ for ( let i = 0 ; i < arr . length ; i ++ ) {
172+ item = executeInLoop ? engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
173+ if ( item !== null && item !== undefined ) return item
174+ }
175+
176+ if ( item === undefined ) return null
177+ return item
178+ } ,
179+ asyncMethod : async ( arr , _1 , _2 , engine ) => {
180+ // See "executeInLoop" above
181+ const executeInLoop = Array . isArray ( arr )
182+ if ( ! executeInLoop ) arr = await engine . run ( arr , _1 , { above : _2 } )
183+
184+ let item
185+ for ( let i = 0 ; i < arr . length ; i ++ ) {
186+ item = executeInLoop ? await engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
187+ if ( item !== null && item !== undefined ) return item
188+ }
189+
190+ if ( item === undefined ) return null
191+ return item
192+ } ,
193+ deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
194+ compile : ( data , buildState ) => {
195+ if ( ! chainingSupported ) return false
196+ if ( Array . isArray ( data ) && data . length ) return `(${ data . map ( ( i ) => buildString ( i , buildState ) ) . join ( ' ?? ' ) } )`
197+ return `(${ buildString ( data , buildState ) } ).reduce((a,b) => a ?? b, null)`
198+ } ,
199+ traverse : false
200+ } ,
164201 or : {
165202 method : ( arr , _1 , _2 , engine ) => {
166203 // See "executeInLoop" above
@@ -191,11 +228,8 @@ const defaultMethods = {
191228 deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
192229 compile : ( data , buildState ) => {
193230 if ( ! buildState . engine . truthy . IDENTITY ) return false
194- if ( Array . isArray ( data ) ) {
195- return `(${ data . map ( ( i ) => buildString ( i , buildState ) ) . join ( ' || ' ) } )`
196- } else {
197- return `(${ buildString ( data , buildState ) } ).reduce((a,b) => a||b, false)`
198- }
231+ if ( Array . isArray ( data ) && data . length ) return `(${ data . map ( ( i ) => buildString ( i , buildState ) ) . join ( ' || ' ) } )`
232+ return `(${ buildString ( data , buildState ) } ).reduce((a,b) => a||b, false)`
199233 } ,
200234 traverse : false
201235 } ,
@@ -228,11 +262,8 @@ const defaultMethods = {
228262 deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
229263 compile : ( data , buildState ) => {
230264 if ( ! buildState . engine . truthy . IDENTITY ) return false
231- if ( Array . isArray ( data ) ) {
232- return `(${ data . map ( ( i ) => buildString ( i , buildState ) ) . join ( ' && ' ) } )`
233- } else {
234- return `(${ buildString ( data , buildState ) } ).reduce((a,b) => a&&b, true)`
235- }
265+ if ( Array . isArray ( data ) && data . length ) return `(${ data . map ( ( i ) => buildString ( i , buildState ) ) . join ( ' && ' ) } )`
266+ return `(${ buildString ( data , buildState ) } ).reduce((a,b) => a&&b, true)`
236267 }
237268 } ,
238269 substr : ( [ string , from , end ] ) => {
@@ -267,12 +298,20 @@ const defaultMethods = {
267298 }
268299 }
269300 } ,
270- // Adding this to spec something out, not to merge it quite yet
301+ exists : {
302+ method : ( key , context , above , engine ) => {
303+ const result = defaultMethods . val . method ( key , context , above , engine , Unfound )
304+ return result !== Unfound
305+ } ,
306+ traverse : true ,
307+ deterministic : false
308+ } ,
271309 val : {
272- method : ( args , context , above , engine ) => {
310+ method : ( args , context , above , engine , /** @type { null | Symbol } */ unFound = null ) => {
273311 if ( Array . isArray ( args ) && args . length === 1 ) args = args [ 0 ]
274312 // A unary optimization
275313 if ( ! Array . isArray ( args ) ) {
314+ if ( unFound && ! ( context && args in context ) ) return unFound
276315 const result = context [ args ]
277316 if ( typeof result === 'undefined' ) return null
278317 return result
@@ -295,11 +334,12 @@ const defaultMethods = {
295334 }
296335 // This block handles traversing the path
297336 for ( let i = start ; i < args . length ; i ++ ) {
337+ if ( unFound && ! ( result && args [ i ] in result ) ) return unFound
298338 if ( result === null || result === undefined ) return null
299339 result = result [ args [ i ] ]
300340 }
301- if ( typeof result === 'undefined' ) return null
302- if ( typeof result === 'function' && ! engine . allowFunctions ) return null
341+ if ( typeof result === 'undefined' ) return unFound
342+ if ( typeof result === 'function' && ! engine . allowFunctions ) return unFound
303343 return result
304344 } ,
305345 optimizeUnary : true ,
0 commit comments