@@ -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,68 +56,65 @@ 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
62- if ( typeof data === 'object' && ! Array . isArray ( data ) ) return Number . NaN
59+ if ( typeof data === 'string' ) return precoerceNumber ( + data )
60+ if ( typeof data === 'number' ) return precoerceNumber ( + data )
61+ if ( typeof data === 'boolean' ) return precoerceNumber ( + data )
62+ if ( typeof data === 'object' && ! Array . isArray ( data ) ) throw new Error ( ' NaN' )
6363 let res = 0
6464 for ( let i = 0 ; i < data . length ; i ++ ) {
65- if ( data [ i ] && typeof data [ i ] === 'object' ) return Number . NaN
65+ 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 ) => {
7172 let res = 1
7273 for ( let i = 0 ; i < data . length ; i ++ ) {
73- if ( data [ i ] && typeof data [ i ] === 'object' ) return Number . NaN
74+ 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 ) => {
79- if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) return Number . NaN
81+ if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) throw new Error ( ' NaN' )
8082 let res = + data [ 0 ]
8183 for ( let i = 1 ; i < data . length ; i ++ ) {
82- if ( ( data [ i ] && typeof data [ i ] === 'object' ) || ! data [ i ] ) return Number . NaN
84+ 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 === 'object' && ! Array . isArray ( data ) ) return Number . NaN
93- if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) return Number . NaN
92+ if ( typeof data === 'string' ) return precoerceNumber ( - data )
93+ if ( typeof data === 'number' ) return precoerceNumber ( - data )
94+ if ( typeof data === 'boolean' ) return precoerceNumber ( - data )
95+ if ( typeof data === 'object' && ! Array . isArray ( data ) ) throw new Error ( ' NaN' )
96+ if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) throw new Error ( ' NaN' )
9497 if ( data . length === 1 ) return - data [ 0 ]
9598 let res = data [ 0 ]
9699 for ( let i = 1 ; i < data . length ; i ++ ) {
97- if ( data [ i ] && typeof data [ i ] === 'object' ) return Number . NaN
100+ 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 ) => {
103- if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) return Number . NaN
107+ if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) throw new Error ( ' NaN' )
104108 let res = + data [ 0 ]
105109 for ( let i = 1 ; i < data . length ; i ++ ) {
106- if ( data [ i ] && typeof data [ i ] === 'object' ) return Number . NaN
110+ 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 ) => {
112- if ( Array . isArray ( type ) ) type = type [ 0 ]
113- if ( type === 'NaN' ) return Number . NaN
114- return { error : type }
115- } ,
116- panic : ( item ) => {
117- if ( Array . isArray ( item ) ) item = item [ 0 ]
118- if ( Number . isNaN ( item ) ) throw new Error ( 'NaN was returned from expression' )
119- if ( item && item . error ) throw item . error
120- return item
117+ throw new Error ( type )
121118 } ,
122119 max : ( data ) => Math . max ( ...data ) ,
123120 min : ( data ) => Math . min ( ...data ) ,
@@ -289,8 +286,98 @@ const defaultMethods = {
289286 } ,
290287 lazy : true
291288 } ,
292- '??' : defineCoalesce ( ) ,
293- try : defineCoalesce ( downgrade , true ) ,
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+ } ,
334+ try : {
335+ [ Sync ] : ( data , buildState ) => isSyncDeep ( data , buildState . engine , buildState ) ,
336+ method : ( arr , _1 , _2 , engine ) => {
337+ // See "executeInLoop" above
338+ const executeInLoop = Array . isArray ( arr )
339+ if ( ! executeInLoop ) arr = engine . run ( arr , _1 , { above : _2 } )
340+
341+ let item
342+ let lastError
343+ for ( let i = 0 ; i < arr . length ; i ++ ) {
344+ try {
345+ // Todo: make this message thing more robust.
346+ if ( lastError ) item = engine . run ( arr [ i ] , { error : lastError . message || lastError . constructor . name } , { above : [ null , _1 , _2 ] } )
347+ else item = executeInLoop ? engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
348+ return item
349+ } catch ( e ) {
350+ if ( Number . isNaN ( e ) ) lastError = { message : 'NaN' }
351+ else lastError = e
352+ }
353+ }
354+
355+ throw lastError
356+ } ,
357+ asyncMethod : async ( arr , _1 , _2 , engine ) => {
358+ // See "executeInLoop" above
359+ const executeInLoop = Array . isArray ( arr )
360+ if ( ! executeInLoop ) arr = await engine . run ( arr , _1 , { above : _2 } )
361+
362+ let item
363+ let lastError
364+ for ( let i = 0 ; i < arr . length ; i ++ ) {
365+ try {
366+ // Todo: make this message thing more robust.
367+ if ( lastError ) item = await engine . run ( arr [ i ] , { error : lastError . message || lastError . constructor . name } , { above : [ null , _1 , _2 ] } )
368+ else item = executeInLoop ? await engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
369+ return item
370+ } catch ( e ) {
371+ if ( Number . isNaN ( e ) ) lastError = { message : 'NaN' }
372+ else lastError = e
373+ }
374+ }
375+
376+ throw lastError
377+ } ,
378+ deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
379+ lazy : true
380+ } ,
294381 and : {
295382 [ Sync ] : ( data , buildState ) => isSyncDeep ( data , buildState . engine , buildState ) ,
296383 method : ( arr , _1 , _2 , engine ) => {
@@ -712,64 +799,6 @@ const defaultMethods = {
712799 }
713800}
714801
715- /**
716- * Defines separate coalesce methods
717- */
718- function defineCoalesce ( func , panic ) {
719- let downgrade
720- if ( func ) downgrade = func
721- else downgrade = ( a ) => a
722-
723- return {
724- [ Sync ] : ( data , buildState ) => isSyncDeep ( data , buildState . engine , buildState ) ,
725- method : ( arr , _1 , _2 , engine ) => {
726- // See "executeInLoop" above
727- const executeInLoop = Array . isArray ( arr )
728- if ( ! executeInLoop ) arr = engine . run ( arr , _1 , { above : _2 } )
729-
730- let item
731- for ( let i = 0 ; i < arr . length ; i ++ ) {
732- item = executeInLoop ? engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
733- if ( downgrade ( item ) !== null && item !== undefined ) return item
734- }
735-
736- if ( item === undefined ) return null
737- if ( panic ) throw item
738- return item
739- } ,
740- asyncMethod : async ( arr , _1 , _2 , engine ) => {
741- // See "executeInLoop" above
742- const executeInLoop = Array . isArray ( arr )
743- if ( ! executeInLoop ) arr = await engine . run ( arr , _1 , { above : _2 } )
744-
745- let item
746- for ( let i = 0 ; i < arr . length ; i ++ ) {
747- item = executeInLoop ? await engine . run ( arr [ i ] , _1 , { above : _2 } ) : arr [ i ]
748- if ( downgrade ( item ) !== null && item !== undefined ) return item
749- }
750-
751- if ( item === undefined ) return null
752- if ( panic ) throw item
753- return item
754- } ,
755- deterministic : ( data , buildState ) => isDeterministic ( data , buildState . engine , buildState ) ,
756- compile : ( data , buildState ) => {
757- if ( ! chainingSupported ) return false
758- const funcCall = func ? 'downgrade' : ''
759- if ( Array . isArray ( data ) && data . length ) {
760- return `(${ data . map ( ( i , x ) => {
761- const built = buildString ( i , buildState )
762- if ( panic && x === data . length - 1 ) return `(typeof ((prev = ${ built } ) || 0).error !== 'undefined' || Number.isNaN(prev) ? (() => { throw prev.error })() : prev)`
763- if ( Array . isArray ( i ) || ! i || typeof i !== 'object' || x === data . length - 1 ) return built
764- return `${ funcCall } (` + built + ')'
765- } ) . join ( ' ?? ' ) } )`
766- }
767- return `(${ buildString ( data , buildState ) } ).reduce((a,b) => ${ funcCall } (a) ?? b, null)`
768- } ,
769- lazy : true
770- }
771- }
772-
773802function createArrayIterativeMethod ( name , useTruthy = false ) {
774803 return {
775804 deterministic : ( data , buildState ) => {
@@ -898,15 +927,24 @@ defaultMethods.if.compile = function (data, buildState) {
898927 * Transforms the operands of the arithmetic operation to numbers.
899928 */
900929function numberCoercion ( i , buildState ) {
901- if ( Array . isArray ( i ) ) return 'NaN'
902- if ( typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean' ) return `(+${ buildString ( i , buildState ) } )`
903- return `(+precoerceNumber(${ buildString ( i , buildState ) } ))`
930+ if ( Array . isArray ( i ) ) return 'precoerceNumber(NaN)'
931+
932+ if ( typeof i === 'number' || typeof i === 'boolean' ) return '+' + buildString ( i , buildState )
933+ if ( typeof i === 'string' ) return '+' + precoerceNumber ( + i )
934+
935+ // check if it's already a number once built
936+ const f = buildString ( i , buildState )
937+
938+ // regex match
939+ if ( / ^ - ? \d + ( \. \d * ) ? $ / . test ( f ) ) return '+' + f
940+
941+ return `(+precoerceNumber(${ f } ))`
904942}
905943
906944// @ts -ignore Allow custom attribute
907945defaultMethods [ '+' ] . compile = function ( data , buildState ) {
908946 if ( Array . isArray ( data ) ) return `(${ data . map ( i => numberCoercion ( i , buildState ) ) . join ( ' + ' ) } )`
909- if ( typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' ) return `(+${ buildString ( data , buildState ) } )`
947+ if ( typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' ) return `precoerceNumber (+${ buildString ( data , buildState ) } )`
910948 return buildState . compile `(Array.isArray(prev = ${ data } ) ? prev.reduce((a,b) => (+a)+(+precoerceNumber(b)), 0) : +precoerceNumber(prev))`
911949}
912950
@@ -933,11 +971,11 @@ defaultMethods['/'].compile = function (data, buildState) {
933971 if ( Array . isArray ( data ) ) {
934972 return `(${ data . map ( ( i , x ) => {
935973 let res = numberCoercion ( i , buildState )
936- if ( x ) res = `(${ res } || NaN)`
974+ if ( x ) res = `precoerceNumber (${ res } || NaN)`
937975 return res
938976 } ) . join ( ' / ' ) } )`
939977 }
940- return `(${ buildString ( data , buildState ) } ).reduce((a,b) => (+precoerceNumber(a))/(+precoerceNumber(b) || NaN))`
978+ return `(${ buildString ( data , buildState ) } ).reduce((a,b) => (+precoerceNumber(a))/(+precoerceNumber(b || NaN) ))`
941979}
942980// @ts -ignore Allow custom attribute
943981defaultMethods [ '*' ] . compile = function ( data , buildState ) {
@@ -964,7 +1002,7 @@ defaultMethods['!!'].compile = function (data, buildState) {
9641002defaultMethods . none . deterministic = defaultMethods . some . deterministic
9651003
9661004// @ts -ignore Allowing a optimizeUnary attribute that can be used for performance optimizations
967- defaultMethods [ '+' ] . optimizeUnary = defaultMethods [ '-' ] . optimizeUnary = defaultMethods [ '!' ] . optimizeUnary = defaultMethods [ '!!' ] . optimizeUnary = defaultMethods . cat . optimizeUnary = defaultMethods . error . optimizeUnary = defaultMethods . panic . optimizeUnary = true
1005+ defaultMethods [ '+' ] . optimizeUnary = defaultMethods [ '-' ] . optimizeUnary = defaultMethods [ '!' ] . optimizeUnary = defaultMethods [ '!!' ] . optimizeUnary = defaultMethods . cat . optimizeUnary = defaultMethods . error . optimizeUnary = true
9681006
9691007export default {
9701008 ...defaultMethods ,
0 commit comments