2
2
'use strict'
3
3
4
4
import asyncIterators from './async_iterators.js'
5
- import { Sync , isSync } from './constants.js'
5
+ import { Sync , isSync , Unfound } from './constants.js'
6
6
import declareSync from './utilities/declareSync.js'
7
7
import { build , buildString } from './compiler.js'
8
8
import chainingSupported from './utilities/chainingSupported.js'
@@ -161,6 +161,43 @@ const defaultMethods = {
161
161
xor : ( [ a , b ] ) => a ^ b ,
162
162
// Why "executeInLoop"? Because if it needs to execute to get an array, I do not want to execute the arguments,
163
163
// 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
+ } ,
164
201
or : {
165
202
method : ( arr , _1 , _2 , engine ) => {
166
203
// See "executeInLoop" above
@@ -191,11 +228,8 @@ const defaultMethods = {
191
228
deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
192
229
compile : ( data , buildState ) => {
193
230
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)`
199
233
} ,
200
234
traverse : false
201
235
} ,
@@ -228,11 +262,8 @@ const defaultMethods = {
228
262
deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
229
263
compile : ( data , buildState ) => {
230
264
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)`
236
267
}
237
268
} ,
238
269
substr : ( [ string , from , end ] ) => {
@@ -267,12 +298,20 @@ const defaultMethods = {
267
298
}
268
299
}
269
300
} ,
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
+ } ,
271
309
val : {
272
- method : ( args , context , above , engine ) => {
310
+ method : ( args , context , above , engine , /** @type { null | Symbol } */ unFound = null ) => {
273
311
if ( Array . isArray ( args ) && args . length === 1 ) args = args [ 0 ]
274
312
// A unary optimization
275
313
if ( ! Array . isArray ( args ) ) {
314
+ if ( unFound && ! ( context && args in context ) ) return unFound
276
315
const result = context [ args ]
277
316
if ( typeof result === 'undefined' ) return null
278
317
return result
@@ -295,11 +334,12 @@ const defaultMethods = {
295
334
}
296
335
// This block handles traversing the path
297
336
for ( let i = start ; i < args . length ; i ++ ) {
337
+ if ( unFound && ! ( result && args [ i ] in result ) ) return unFound
298
338
if ( result === null || result === undefined ) return null
299
339
result = result [ args [ i ] ]
300
340
}
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
303
343
return result
304
344
} ,
305
345
optimizeUnary : true ,
0 commit comments