@@ -8,7 +8,7 @@ import { build, buildString } from './compiler.js'
88import chainingSupported from './utilities/chainingSupported.js'
99import InvalidControlInput from './errors/InvalidControlInput.js'
1010import legacyMethods from './legacy.js'
11- import { downgrade } from './utilities/downgrade.js'
11+ import { precoerceNumber } from './utilities/downgrade.js'
1212
1313function isDeterministic ( method , engine , buildState ) {
1414 if ( Array . isArray ( method ) ) {
@@ -56,15 +56,16 @@ const oldAll = createArrayIterativeMethod('every', true)
5656const defaultMethods = {
5757 '+' : ( data ) => {
5858 if ( ! data ) return 0
59- if ( typeof data === 'string' ) return + data
60- if ( typeof data === 'number' ) return + data
61- if ( typeof data === 'boolean' ) return + data
59+ if ( typeof data === 'string' ) return precoerceNumber ( + data )
60+ if ( typeof data === 'number' ) return precoerceNumber ( + data )
61+ if ( typeof data === 'boolean' ) return precoerceNumber ( + data )
6262 if ( typeof data === 'object' && ! Array . isArray ( data ) ) throw new Error ( 'NaN' )
6363 let res = 0
6464 for ( let i = 0 ; i < data . length ; i ++ ) {
6565 if ( data [ i ] && typeof data [ i ] === 'object' ) throw new Error ( 'NaN' )
6666 res += + data [ i ]
6767 }
68+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
6869 return res
6970 } ,
7071 '*' : ( data ) => {
@@ -73,6 +74,7 @@ const defaultMethods = {
7374 if ( data [ i ] && typeof data [ i ] === 'object' ) throw new Error ( 'NaN' )
7475 res *= + data [ i ]
7576 }
77+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
7678 return res
7779 } ,
7880 '/' : ( data ) => {
@@ -82,13 +84,14 @@ const defaultMethods = {
8284 if ( ( data [ i ] && typeof data [ i ] === 'object' ) || ! data [ i ] ) throw new Error ( 'NaN' )
8385 res /= + data [ i ]
8486 }
87+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
8588 return res
8689 } ,
8790 '-' : ( data ) => {
8891 if ( ! data ) return 0
89- if ( typeof data === 'string' ) return - data
90- if ( typeof data === 'number' ) return - data
91- if ( typeof data === 'boolean' ) return - data
92+ if ( typeof data === 'string' ) return precoerceNumber ( - data )
93+ if ( typeof data === 'number' ) return precoerceNumber ( - data )
94+ if ( typeof data === 'boolean' ) return precoerceNumber ( - data )
9295 if ( typeof data === 'object' && ! Array . isArray ( data ) ) throw new Error ( 'NaN' )
9396 if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) throw new Error ( 'NaN' )
9497 if ( data . length === 1 ) return - data [ 0 ]
@@ -97,6 +100,7 @@ const defaultMethods = {
97100 if ( data [ i ] && typeof data [ i ] === 'object' ) throw new Error ( 'NaN' )
98101 res -= + data [ i ]
99102 }
103+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
100104 return res
101105 } ,
102106 '%' : ( data ) => {
@@ -106,6 +110,7 @@ const defaultMethods = {
106110 if ( data [ i ] && typeof data [ i ] === 'object' ) throw new Error ( 'NaN' )
107111 res %= + data [ i ]
108112 }
113+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
109114 return res
110115 } ,
111116 error : ( type ) => {
@@ -281,7 +286,51 @@ const defaultMethods = {
281286 } ,
282287 lazy : true
283288 } ,
284- '??' : defineCoalesce ( ) ,
289+ '??' : {
290+ [ Sync ] : ( data , buildState ) => isSyncDeep ( data , buildState . engine , buildState ) ,
291+ method : ( arr , _1 , _2 , engine ) => {
292+ // See "executeInLoop" above
293+ const executeInLoop = Array . isArray ( arr )
294+ if ( ! executeInLoop ) arr = engine . run ( arr , _1 , { above : _2 } )
295+
296+ let item
297+ for ( let i = 0 ; i < arr . length ; i ++ ) {
298+ item = executeInLoop ? engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
299+ if ( item !== null && item !== undefined ) return item
300+ }
301+
302+ if ( item === undefined ) return null
303+ return item
304+ } ,
305+ asyncMethod : async ( arr , _1 , _2 , engine ) => {
306+ // See "executeInLoop" above
307+ const executeInLoop = Array . isArray ( arr )
308+ if ( ! executeInLoop ) arr = await engine . run ( arr , _1 , { above : _2 } )
309+
310+ let item
311+ for ( let i = 0 ; i < arr . length ; i ++ ) {
312+ item = executeInLoop ? await engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
313+ if ( item !== null && item !== undefined ) return item
314+ }
315+
316+ if ( item === undefined ) return null
317+ return item
318+ } ,
319+ deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
320+ compile : ( data , buildState ) => {
321+ if ( ! chainingSupported ) return false
322+
323+ if ( Array . isArray ( data ) && data . length ) {
324+ return `(${ data . map ( ( i , x ) => {
325+ const built = buildString ( i , buildState )
326+ if ( Array . isArray ( i ) || ! i || typeof i !== 'object' || x === data . length - 1 ) return built
327+ return '(' + built + ')'
328+ } ) . join ( ' ?? ' ) } )`
329+ }
330+ return `(${ buildString ( data , buildState ) } ).reduce((a,b) => (a) ?? b, null)`
331+ } ,
332+ lazy : true
333+ } ,
285334 try : {
286335 [ Sync ] : ( data , buildState ) => isSyncDeep ( data , buildState . engine , buildState ) ,
287336 method : ( arr , _1 , _2 , engine ) => {
@@ -327,9 +376,6 @@ const defaultMethods = {
327376 throw lastError
328377 } ,
329378 deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
330- compile : ( data , buildState ) => {
331- return false
332- } ,
333379 lazy : true
334380 } ,
335381 and : {
@@ -753,64 +799,6 @@ const defaultMethods = {
753799 }
754800}
755801
756- /**
757- * Defines separate coalesce methods
758- */
759- function defineCoalesce ( func , panic ) {
760- let downgrade
761- if ( func ) downgrade = func
762- else downgrade = ( a ) => a
763-
764- return {
765- [ Sync ] : ( data , buildState ) => isSyncDeep ( data , buildState . engine , buildState ) ,
766- method : ( arr , _1 , _2 , engine ) => {
767- // See "executeInLoop" above
768- const executeInLoop = Array . isArray ( arr )
769- if ( ! executeInLoop ) arr = engine . run ( arr , _1 , { above : _2 } )
770-
771- let item
772- for ( let i = 0 ; i < arr . length ; i ++ ) {
773- item = executeInLoop ? engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
774- if ( downgrade ( item ) !== null && item !== undefined ) return item
775- }
776-
777- if ( item === undefined ) return null
778- if ( panic ) throw item
779- return item
780- } ,
781- asyncMethod : async ( arr , _1 , _2 , engine ) => {
782- // See "executeInLoop" above
783- const executeInLoop = Array . isArray ( arr )
784- if ( ! executeInLoop ) arr = await engine . run ( arr , _1 , { above : _2 } )
785-
786- let item
787- for ( let i = 0 ; i < arr . length ; i ++ ) {
788- item = executeInLoop ? await engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
789- if ( downgrade ( item ) !== null && item !== undefined ) return item
790- }
791-
792- if ( item === undefined ) return null
793- if ( panic ) throw item
794- return item
795- } ,
796- deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
797- compile : ( data , buildState ) => {
798- if ( ! chainingSupported ) return false
799- const funcCall = func ? 'downgrade' : ''
800- if ( Array . isArray ( data ) && data . length ) {
801- return `(${ data . map ( ( i , x ) => {
802- const built = buildString ( i , buildState )
803- if ( panic && x === data . length - 1 ) return `(typeof ((prev = ${ built } ) || 0).error !== 'undefined' || Number.isNaN(prev) ? (() => { throw prev.error })() : prev)`
804- if ( Array . isArray ( i ) || ! i || typeof i !== 'object' || x === data . length - 1 ) return built
805- return `${ funcCall } (` + built + ')'
806- } ) . join ( ' ?? ' ) } )`
807- }
808- return `(${ buildString ( data , buildState ) } ).reduce((a,b) => ${ funcCall } (a) ?? b, null)`
809- } ,
810- lazy : true
811- }
812- }
813-
814802function createArrayIterativeMethod ( name , useTruthy = false ) {
815803 return {
816804 deterministic : ( data , buildState ) => {
@@ -939,15 +927,15 @@ defaultMethods.if.compile = function (data, buildState) {
939927 * Transforms the operands of the arithmetic operation to numbers.
940928 */
941929function numberCoercion ( i , buildState ) {
942- if ( Array . isArray ( i ) ) return 'NaN'
943- if ( typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean' ) return `(+${ buildString ( i , buildState ) } )`
930+ if ( Array . isArray ( i ) ) return 'precoerceNumber( NaN) '
931+ if ( typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean' ) return `precoerceNumber (+${ buildString ( i , buildState ) } )`
944932 return `(+precoerceNumber(${ buildString ( i , buildState ) } ))`
945933}
946934
947935// @ts -ignore Allow custom attribute
948936defaultMethods [ '+' ] . compile = function ( data , buildState ) {
949937 if ( Array . isArray ( data ) ) return `(${ data . map ( i => numberCoercion ( i , buildState ) ) . join ( ' + ' ) } )`
950- if ( typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' ) return `(+${ buildString ( data , buildState ) } )`
938+ if ( typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' ) return `precoerceNumber (+${ buildString ( data , buildState ) } )`
951939 return buildState . compile `(Array.isArray(prev = ${ data } ) ? prev.reduce((a,b) => (+a)+(+precoerceNumber(b)), 0) : +precoerceNumber(prev))`
952940}
953941
0 commit comments