Skip to content

Commit 647a977

Browse files
committed
Set up NaN coercion properly
1 parent 071906e commit 647a977

File tree

4 files changed

+40
-47
lines changed

4 files changed

+40
-47
lines changed

compiler.js

Lines changed: 4 additions & 4 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 } from './utilities/downgrade.js'
14+
import { downgrade, precoerceNumber } from './utilities/downgrade.js'
1515

1616
/**
1717
* Provides a simple way to compile logic into a function that can be run.
@@ -194,7 +194,7 @@ function buildString (method, buildState = {}) {
194194
}
195195

196196
let lower = method[func]
197-
if (!lower || typeof lower !== 'object') lower = [lower]
197+
if ((!lower || typeof lower !== 'object') && (typeof engine.methods[func].traverse === 'undefined' || engine.methods[func].traverse)) lower = [lower]
198198

199199
if (engine.methods[func] && engine.methods[func].compile) {
200200
let str = engine.methods[func].compile(lower, buildState)
@@ -308,12 +308,12 @@ function processBuiltString (method, str, buildState) {
308308
str = str.replace(`__%%%${x}%%%__`, item)
309309
})
310310

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

defaultMethods.js

Lines changed: 20 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -887,36 +887,27 @@ defaultMethods['==='].compile = function (data, buildState) {
887887
for (let i = 2; i < data.length; i++) res = buildState.compile`(${res} && ${data[i - 1]} === ${data[i]})`
888888
return res
889889
}
890+
891+
/**
892+
* Transforms the operands of the arithmetic operation to numbers.
893+
*/
894+
function numberCoercion (i, buildState) {
895+
if (Array.isArray(i)) return 'NaN'
896+
if (typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean') return `(+${buildString(i, buildState)})`
897+
return `(+precoerceNumber(${buildString(i, buildState)}))`
898+
}
899+
890900
// @ts-ignore Allow custom attribute
891901
defaultMethods['+'].compile = function (data, buildState) {
892-
if (Array.isArray(data)) {
893-
return `(${data
894-
.map((i) => {
895-
// Todo: Actually make this correct, this is a decent optimization but
896-
// does not coerce the built string.
897-
if (Array.isArray(i)) return 'NaN'
898-
return `(+${buildString(i, buildState)})`
899-
})
900-
.join(' + ')})`
901-
} else if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
902-
return `(+${buildString(data, buildState)})`
903-
} else {
904-
return `([].concat(${buildString(
905-
data,
906-
buildState
907-
)})).reduce((a,b) => (+a)+(+b), 0)`
908-
}
902+
if (Array.isArray(data)) return `(${data.map(i => numberCoercion(i, buildState)).join(' + ')})`
903+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') return `(+${buildString(data, buildState)})`
904+
return `([].concat(${buildString(data, buildState)})).reduce((a,b) => (+a)+(+precoerceNumber(b)), 0)`
909905
}
910906

911907
// @ts-ignore Allow custom attribute
912908
defaultMethods['%'].compile = function (data, buildState) {
913-
if (Array.isArray(data)) {
914-
return `(${data
915-
.map((i) => `(+${buildString(i, buildState)})`)
916-
.join(' % ')})`
917-
} else {
918-
return `(${buildString(data, buildState)}).reduce((a,b) => (+a)%(+b))`
919-
}
909+
if (Array.isArray(data)) return `(${data.map(i => numberCoercion(i, buildState)).join(' % ')})`
910+
return `(${buildString(data, buildState)}).reduce((a,b) => (+a)%(+precoerceNumber(b)))`
920911
}
921912

922913
// @ts-ignore Allow custom attribute
@@ -927,11 +918,7 @@ defaultMethods.in.compile = function (data, buildState) {
927918

928919
// @ts-ignore Allow custom attribute
929920
defaultMethods['-'].compile = function (data, buildState) {
930-
if (Array.isArray(data)) {
931-
return `${data.length === 1 ? '-' : ''}(${data
932-
.map((i) => `(+${buildString(i, buildState)})`)
933-
.join(' - ')})`
934-
}
921+
if (Array.isArray(data)) return `${data.length === 1 ? '-' : ''}(${data.map(i => numberCoercion(i, buildState)).join(' - ')})`
935922
if (typeof data === 'string' || typeof data === 'number') {
936923
return `(-${buildString(data, buildState)})`
937924
} else {
@@ -943,23 +930,13 @@ defaultMethods['-'].compile = function (data, buildState) {
943930
}
944931
// @ts-ignore Allow custom attribute
945932
defaultMethods['/'].compile = function (data, buildState) {
946-
if (Array.isArray(data)) {
947-
return `(${data
948-
.map((i) => `(+${buildString(i, buildState)})`)
949-
.join(' / ')})`
950-
} else {
951-
return `(${buildString(data, buildState)}).reduce((a,b) => (+a)/(+b))`
952-
}
933+
if (Array.isArray(data)) return `(${data.map(i => numberCoercion(i, buildState)).join(' / ')})`
934+
return `(${buildString(data, buildState)}).reduce((a,b) => (+a)/(+b))`
953935
}
954936
// @ts-ignore Allow custom attribute
955937
defaultMethods['*'].compile = function (data, buildState) {
956-
if (Array.isArray(data)) {
957-
return `(${data
958-
.map((i) => `(+${buildString(i, buildState)})`)
959-
.join(' * ')})`
960-
} else {
961-
return `(${buildString(data, buildState)}).reduce((a,b) => (+a)*(+b))`
962-
}
938+
if (Array.isArray(data)) return `(${data.map(i => numberCoercion(i, buildState)).join(' * ')})`
939+
return `(${buildString(data, buildState)}).reduce((a,b) => (+a)*(+b))`
963940
}
964941
// @ts-ignore Allow custom attribute
965942
defaultMethods.cat.compile = function (data, buildState) {

suites/plus.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@
159159
"result": { "error": "NaN" },
160160
"data": null
161161
},
162+
{
163+
"description": "Addition with Array from context produces NaN",
164+
"rule": { "+": [{ "val": "x" }, 1] },
165+
"result": { "error": "NaN" },
166+
"data": { "x": [1] }
167+
},
162168
{
163169
"description": "Addition with Object produces NaN",
164170
"rule": { "+": [{ "val": "x" }, 1] },

utilities/downgrade.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@ export function downgrade (item) {
88
if (Number.isNaN(item)) return null
99
return item
1010
}
11+
12+
/**
13+
* Used to precoerce a data value to a number, for the purposes of coalescing.
14+
* @param {any} item
15+
*/
16+
export function precoerceNumber (item) {
17+
if (!item) return item
18+
if (typeof item === 'object') return Number.isNaN
19+
return item
20+
}

0 commit comments

Comments
 (0)