@@ -8,7 +8,7 @@ import { build, buildString } from './compiler.js'
8
8
import chainingSupported from './utilities/chainingSupported.js'
9
9
import InvalidControlInput from './errors/InvalidControlInput.js'
10
10
import legacyMethods from './legacy.js'
11
- import { downgrade } from './utilities/downgrade.js'
11
+ import { precoerceNumber } from './utilities/downgrade.js'
12
12
13
13
function isDeterministic ( method , engine , buildState ) {
14
14
if ( Array . isArray ( method ) ) {
@@ -56,68 +56,65 @@ const oldAll = createArrayIterativeMethod('every', true)
56
56
const defaultMethods = {
57
57
'+' : ( data ) => {
58
58
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' )
63
63
let res = 0
64
64
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' )
66
66
res += + data [ i ]
67
67
}
68
+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
68
69
return res
69
70
} ,
70
71
'*' : ( data ) => {
71
72
let res = 1
72
73
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' )
74
75
res *= + data [ i ]
75
76
}
77
+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
76
78
return res
77
79
} ,
78
80
'/' : ( data ) => {
79
- if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) return Number . NaN
81
+ if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) throw new Error ( ' NaN' )
80
82
let res = + data [ 0 ]
81
83
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' )
83
85
res /= + data [ i ]
84
86
}
87
+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
85
88
return res
86
89
} ,
87
90
'-' : ( data ) => {
88
91
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' )
94
97
if ( data . length === 1 ) return - data [ 0 ]
95
98
let res = data [ 0 ]
96
99
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' )
98
101
res -= + data [ i ]
99
102
}
103
+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
100
104
return res
101
105
} ,
102
106
'%' : ( data ) => {
103
- if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) return Number . NaN
107
+ if ( data [ 0 ] && typeof data [ 0 ] === 'object' ) throw new Error ( ' NaN' )
104
108
let res = + data [ 0 ]
105
109
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' )
107
111
res %= + data [ i ]
108
112
}
113
+ if ( Number . isNaN ( res ) ) throw new Error ( 'NaN' )
109
114
return res
110
115
} ,
111
116
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 )
121
118
} ,
122
119
max : ( data ) => Math . max ( ...data ) ,
123
120
min : ( data ) => Math . min ( ...data ) ,
@@ -289,8 +286,98 @@ const defaultMethods = {
289
286
} ,
290
287
lazy : true
291
288
} ,
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
+ } ,
294
381
and : {
295
382
[ Sync ] : ( data , buildState ) => isSyncDeep ( data , buildState . engine , buildState ) ,
296
383
method : ( arr , _1 , _2 , engine ) => {
@@ -712,64 +799,6 @@ const defaultMethods = {
712
799
}
713
800
}
714
801
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
-
773
802
function createArrayIterativeMethod ( name , useTruthy = false ) {
774
803
return {
775
804
deterministic : ( data , buildState ) => {
@@ -898,15 +927,24 @@ defaultMethods.if.compile = function (data, buildState) {
898
927
* Transforms the operands of the arithmetic operation to numbers.
899
928
*/
900
929
function 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 } ))`
904
942
}
905
943
906
944
// @ts -ignore Allow custom attribute
907
945
defaultMethods [ '+' ] . compile = function ( data , buildState ) {
908
946
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 ) } )`
910
948
return buildState . compile `(Array.isArray(prev = ${ data } ) ? prev.reduce((a,b) => (+a)+(+precoerceNumber(b)), 0) : +precoerceNumber(prev))`
911
949
}
912
950
@@ -933,11 +971,11 @@ defaultMethods['/'].compile = function (data, buildState) {
933
971
if ( Array . isArray ( data ) ) {
934
972
return `(${ data . map ( ( i , x ) => {
935
973
let res = numberCoercion ( i , buildState )
936
- if ( x ) res = `(${ res } || NaN)`
974
+ if ( x ) res = `precoerceNumber (${ res } || NaN)`
937
975
return res
938
976
} ) . join ( ' / ' ) } )`
939
977
}
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) ))`
941
979
}
942
980
// @ts -ignore Allow custom attribute
943
981
defaultMethods [ '*' ] . compile = function ( data , buildState ) {
@@ -964,7 +1002,7 @@ defaultMethods['!!'].compile = function (data, buildState) {
964
1002
defaultMethods . none . deterministic = defaultMethods . some . deterministic
965
1003
966
1004
// @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
968
1006
969
1007
export default {
970
1008
...defaultMethods ,
0 commit comments