Skip to content

Commit bcae129

Browse files
committed
Fix up the implementation
1 parent 9d4f8bf commit bcae129

File tree

5 files changed

+72
-86
lines changed

5 files changed

+72
-86
lines changed

compatible.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ describe('All of the compatible tests', () => {
5656
if (Array.isArray(result)) result = result.map(i => (i || 0).toNumber ? Number(i) : i)
5757
expect(correction(result)).toStrictEqual(testCase.result)
5858
} catch (err) {
59+
if (err.message.includes('expect')) throw err
5960
expect(testCase.error).toStrictEqual(true)
6061
}
6162
})
@@ -70,6 +71,7 @@ describe('All of the compatible tests', () => {
7071
if (Array.isArray(result)) result = result.map(i => i.toNumber ? Number(i) : i)
7172
expect(correction(result)).toStrictEqual(testCase.result)
7273
} catch (err) {
74+
if (err.message.includes('expect')) throw err
7375
expect(testCase.error).toStrictEqual(true)
7476
}
7577
})

compiler.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import asyncIterators from './async_iterators.js'
1212
import { coerceArray } from './utilities/coerceArray.js'
1313
import { countArguments } from './utilities/countArguments.js'
14-
import { downgrade, precoerceNumber } from './utilities/downgrade.js'
14+
import { precoerceNumber } from './utilities/downgrade.js'
1515

1616
/**
1717
* Provides a simple way to compile logic into a function that can be run.
@@ -310,12 +310,12 @@ function processBuiltString (method, str, buildState) {
310310
str = str.replace(`__%%%${x}%%%__`, item)
311311
})
312312

313-
const final = `(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, downgrade, precoerceNumber) => ${buildState.asyncDetected ? 'async' : ''} (context ${buildState.extraArguments ? ',' + buildState.extraArguments : ''}) => { ${str.includes('prev') ? 'let prev;' : ''} const result = ${str}; return result }`
313+
const final = `(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, precoerceNumber) => ${buildState.asyncDetected ? 'async' : ''} (context ${buildState.extraArguments ? ',' + buildState.extraArguments : ''}) => { ${str.includes('prev') ? 'let prev;' : ''} const result = ${str}; return result }`
314314
// console.log(str)
315315
// console.log(final)
316316
// eslint-disable-next-line no-eval
317317
return Object.assign(
318-
(typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, downgrade, precoerceNumber), {
318+
(typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, precoerceNumber), {
319319
[Sync]: !buildState.asyncDetected,
320320
aboveDetected: typeof str === 'string' && str.includes(', above')
321321
})

defaultMethods.js

Lines changed: 60 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { build, buildString } from './compiler.js'
88
import chainingSupported from './utilities/chainingSupported.js'
99
import InvalidControlInput from './errors/InvalidControlInput.js'
1010
import legacyMethods from './legacy.js'
11-
import { downgrade } from './utilities/downgrade.js'
11+
import { precoerceNumber } from './utilities/downgrade.js'
1212

1313
function isDeterministic (method, engine, buildState) {
1414
if (Array.isArray(method)) {
@@ -56,15 +56,16 @@ const oldAll = createArrayIterativeMethod('every', true)
5656
const 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-
814802
function 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
*/
941929
function 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
948936
defaultMethods['+'].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

suites/multiply.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@
157157
"error": true,
158158
"data": null
159159
},
160+
{
161+
"description": "Multiply with a single string produces NaN",
162+
"rule": { "*": ["Hey"] },
163+
"error": true,
164+
"data": null
165+
},
160166
{
161167
"description": "Multiply with Array produces NaN",
162168
"rule": { "*": [[1], 1] },

utilities/downgrade.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
2-
/**
3-
* Used to make an "error" piece of data null, for the purposes of coalescing.
4-
* @param {any} item
5-
*/
6-
export function downgrade (item) {
7-
if (item && typeof item === 'object' && 'error' in item) return null
8-
if (Number.isNaN(item)) return null
9-
return item
10-
}
11-
121
/**
132
* Used to precoerce a data value to a number, for the purposes of coalescing.
143
* @param {any} item
154
*/
165
export function precoerceNumber (item) {
6+
if (Number.isNaN(item)) throw new Error('NaN')
177
if (!item) return item
188
if (typeof item === 'object') throw new Error('NaN')
199
return item

0 commit comments

Comments
 (0)