diff --git a/compatible.test.js b/compatible.test.js index df23adb..8573934 100644 --- a/compatible.test.js +++ b/compatible.test.js @@ -56,6 +56,7 @@ describe('All of the compatible tests', () => { if (Array.isArray(result)) result = result.map(i => (i || 0).toNumber ? Number(i) : i) expect(correction(result)).toStrictEqual(testCase.result) } catch (err) { + if (err.message && err.message.includes('expect')) throw err expect(testCase.error).toStrictEqual(true) } }) @@ -70,6 +71,7 @@ describe('All of the compatible tests', () => { if (Array.isArray(result)) result = result.map(i => i.toNumber ? Number(i) : i) expect(correction(result)).toStrictEqual(testCase.result) } catch (err) { + if (err.message && err.message.includes('expect')) throw err expect(testCase.error).toStrictEqual(true) } }) diff --git a/compiler.js b/compiler.js index 94a41f2..b8bc485 100644 --- a/compiler.js +++ b/compiler.js @@ -11,7 +11,7 @@ import { import asyncIterators from './async_iterators.js' import { coerceArray } from './utilities/coerceArray.js' import { countArguments } from './utilities/countArguments.js' -import { downgrade, precoerceNumber } from './utilities/downgrade.js' +import { precoerceNumber } from './utilities/downgrade.js' /** * Provides a simple way to compile logic into a function that can be run. @@ -310,12 +310,12 @@ function processBuiltString (method, str, buildState) { str = str.replace(`__%%%${x}%%%__`, item) }) - 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 }` + 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 }` // console.log(str) // console.log(final) // eslint-disable-next-line no-eval return Object.assign( - (typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, downgrade, precoerceNumber), { + (typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(values, methods, notTraversed, asyncIterators, engine, above, coerceArray, precoerceNumber), { [Sync]: !buildState.asyncDetected, aboveDetected: typeof str === 'string' && str.includes(', above') }) diff --git a/defaultMethods.js b/defaultMethods.js index 2cf48b0..e842901 100644 --- a/defaultMethods.js +++ b/defaultMethods.js @@ -8,7 +8,7 @@ import { build, buildString } from './compiler.js' import chainingSupported from './utilities/chainingSupported.js' import InvalidControlInput from './errors/InvalidControlInput.js' import legacyMethods from './legacy.js' -import { downgrade } from './utilities/downgrade.js' +import { precoerceNumber } from './utilities/downgrade.js' function isDeterministic (method, engine, buildState) { if (Array.isArray(method)) { @@ -56,68 +56,65 @@ const oldAll = createArrayIterativeMethod('every', true) const defaultMethods = { '+': (data) => { if (!data) return 0 - if (typeof data === 'string') return +data - if (typeof data === 'number') return +data - if (typeof data === 'boolean') return +data - if (typeof data === 'object' && !Array.isArray(data)) return Number.NaN + if (typeof data === 'string') return precoerceNumber(+data) + if (typeof data === 'number') return precoerceNumber(+data) + if (typeof data === 'boolean') return precoerceNumber(+data) + if (typeof data === 'object' && !Array.isArray(data)) throw new Error('NaN') let res = 0 for (let i = 0; i < data.length; i++) { - if (data[i] && typeof data[i] === 'object') return Number.NaN + if (data[i] && typeof data[i] === 'object') throw new Error('NaN') res += +data[i] } + if (Number.isNaN(res)) throw new Error('NaN') return res }, '*': (data) => { let res = 1 for (let i = 0; i < data.length; i++) { - if (data[i] && typeof data[i] === 'object') return Number.NaN + if (data[i] && typeof data[i] === 'object') throw new Error('NaN') res *= +data[i] } + if (Number.isNaN(res)) throw new Error('NaN') return res }, '/': (data) => { - if (data[0] && typeof data[0] === 'object') return Number.NaN + if (data[0] && typeof data[0] === 'object') throw new Error('NaN') let res = +data[0] for (let i = 1; i < data.length; i++) { - if ((data[i] && typeof data[i] === 'object') || !data[i]) return Number.NaN + if ((data[i] && typeof data[i] === 'object') || !data[i]) throw new Error('NaN') res /= +data[i] } + if (Number.isNaN(res)) throw new Error('NaN') return res }, '-': (data) => { if (!data) return 0 - if (typeof data === 'string') return -data - if (typeof data === 'number') return -data - if (typeof data === 'boolean') return -data - if (typeof data === 'object' && !Array.isArray(data)) return Number.NaN - if (data[0] && typeof data[0] === 'object') return Number.NaN + if (typeof data === 'string') return precoerceNumber(-data) + if (typeof data === 'number') return precoerceNumber(-data) + if (typeof data === 'boolean') return precoerceNumber(-data) + if (typeof data === 'object' && !Array.isArray(data)) throw new Error('NaN') + if (data[0] && typeof data[0] === 'object') throw new Error('NaN') if (data.length === 1) return -data[0] let res = data[0] for (let i = 1; i < data.length; i++) { - if (data[i] && typeof data[i] === 'object') return Number.NaN + if (data[i] && typeof data[i] === 'object') throw new Error('NaN') res -= +data[i] } + if (Number.isNaN(res)) throw new Error('NaN') return res }, '%': (data) => { - if (data[0] && typeof data[0] === 'object') return Number.NaN + if (data[0] && typeof data[0] === 'object') throw new Error('NaN') let res = +data[0] for (let i = 1; i < data.length; i++) { - if (data[i] && typeof data[i] === 'object') return Number.NaN + if (data[i] && typeof data[i] === 'object') throw new Error('NaN') res %= +data[i] } + if (Number.isNaN(res)) throw new Error('NaN') return res }, error: (type) => { - if (Array.isArray(type)) type = type[0] - if (type === 'NaN') return Number.NaN - return { error: type } - }, - panic: (item) => { - if (Array.isArray(item)) item = item[0] - if (Number.isNaN(item)) throw new Error('NaN was returned from expression') - if (item && item.error) throw item.error - return item + throw new Error(type) }, max: (data) => Math.max(...data), min: (data) => Math.min(...data), @@ -289,8 +286,98 @@ const defaultMethods = { }, lazy: true }, - '??': defineCoalesce(), - try: defineCoalesce(downgrade, true), + '??': { + [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState), + method: (arr, _1, _2, engine) => { + // See "executeInLoop" above + const executeInLoop = Array.isArray(arr) + if (!executeInLoop) arr = engine.run(arr, _1, { above: _2 }) + + let item + for (let i = 0; i < arr.length; i++) { + item = executeInLoop ? engine.run(arr[i], _1, { above: _2 }) : arr[i] + if (item !== null && item !== undefined) return item + } + + if (item === undefined) return null + return item + }, + asyncMethod: async (arr, _1, _2, engine) => { + // See "executeInLoop" above + const executeInLoop = Array.isArray(arr) + if (!executeInLoop) arr = await engine.run(arr, _1, { above: _2 }) + + let item + for (let i = 0; i < arr.length; i++) { + item = executeInLoop ? await engine.run(arr[i], _1, { above: _2 }) : arr[i] + if (item !== null && item !== undefined) return item + } + + if (item === undefined) return null + return item + }, + deterministic: (data, buildState) => isDeterministic(data, buildState.engine, buildState), + compile: (data, buildState) => { + if (!chainingSupported) return false + + if (Array.isArray(data) && data.length) { + return `(${data.map((i, x) => { + const built = buildString(i, buildState) + if (Array.isArray(i) || !i || typeof i !== 'object' || x === data.length - 1) return built + return '(' + built + ')' + }).join(' ?? ')})` + } + return `(${buildString(data, buildState)}).reduce((a,b) => (a) ?? b, null)` + }, + lazy: true + }, + try: { + [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState), + method: (arr, _1, _2, engine) => { + // See "executeInLoop" above + const executeInLoop = Array.isArray(arr) + if (!executeInLoop) arr = engine.run(arr, _1, { above: _2 }) + + let item + let lastError + for (let i = 0; i < arr.length; i++) { + try { + // Todo: make this message thing more robust. + if (lastError) item = engine.run(arr[i], { error: lastError.message || lastError.constructor.name }, { above: [null, _1, _2] }) + else item = executeInLoop ? engine.run(arr[i], _1, { above: _2 }) : arr[i] + return item + } catch (e) { + if (Number.isNaN(e)) lastError = { message: 'NaN' } + else lastError = e + } + } + + throw lastError + }, + asyncMethod: async (arr, _1, _2, engine) => { + // See "executeInLoop" above + const executeInLoop = Array.isArray(arr) + if (!executeInLoop) arr = await engine.run(arr, _1, { above: _2 }) + + let item + let lastError + for (let i = 0; i < arr.length; i++) { + try { + // Todo: make this message thing more robust. + if (lastError) item = await engine.run(arr[i], { error: lastError.message || lastError.constructor.name }, { above: [null, _1, _2] }) + else item = executeInLoop ? await engine.run(arr[i], _1, { above: _2 }) : arr[i] + return item + } catch (e) { + if (Number.isNaN(e)) lastError = { message: 'NaN' } + else lastError = e + } + } + + throw lastError + }, + deterministic: (data, buildState) => isDeterministic(data, buildState.engine, buildState), + lazy: true + }, and: { [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState), method: (arr, _1, _2, engine) => { @@ -712,64 +799,6 @@ const defaultMethods = { } } -/** - * Defines separate coalesce methods - */ -function defineCoalesce (func, panic) { - let downgrade - if (func) downgrade = func - else downgrade = (a) => a - - return { - [Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState), - method: (arr, _1, _2, engine) => { - // See "executeInLoop" above - const executeInLoop = Array.isArray(arr) - if (!executeInLoop) arr = engine.run(arr, _1, { above: _2 }) - - let item - for (let i = 0; i < arr.length; i++) { - item = executeInLoop ? engine.run(arr[i], _1, { above: _2 }) : arr[i] - if (downgrade(item) !== null && item !== undefined) return item - } - - if (item === undefined) return null - if (panic) throw item - return item - }, - asyncMethod: async (arr, _1, _2, engine) => { - // See "executeInLoop" above - const executeInLoop = Array.isArray(arr) - if (!executeInLoop) arr = await engine.run(arr, _1, { above: _2 }) - - let item - for (let i = 0; i < arr.length; i++) { - item = executeInLoop ? await engine.run(arr[i], _1, { above: _2 }) : arr[i] - if (downgrade(item) !== null && item !== undefined) return item - } - - if (item === undefined) return null - if (panic) throw item - return item - }, - deterministic: (data, buildState) => isDeterministic(data, buildState.engine, buildState), - compile: (data, buildState) => { - if (!chainingSupported) return false - const funcCall = func ? 'downgrade' : '' - if (Array.isArray(data) && data.length) { - return `(${data.map((i, x) => { - const built = buildString(i, buildState) - if (panic && x === data.length - 1) return `(typeof ((prev = ${built}) || 0).error !== 'undefined' || Number.isNaN(prev) ? (() => { throw prev.error })() : prev)` - if (Array.isArray(i) || !i || typeof i !== 'object' || x === data.length - 1) return built - return `${funcCall}(` + built + ')' - }).join(' ?? ')})` - } - return `(${buildString(data, buildState)}).reduce((a,b) => ${funcCall}(a) ?? b, null)` - }, - lazy: true - } -} - function createArrayIterativeMethod (name, useTruthy = false) { return { deterministic: (data, buildState) => { @@ -898,15 +927,24 @@ defaultMethods.if.compile = function (data, buildState) { * Transforms the operands of the arithmetic operation to numbers. */ function numberCoercion (i, buildState) { - if (Array.isArray(i)) return 'NaN' - if (typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean') return `(+${buildString(i, buildState)})` - return `(+precoerceNumber(${buildString(i, buildState)}))` + if (Array.isArray(i)) return 'precoerceNumber(NaN)' + + if (typeof i === 'number' || typeof i === 'boolean') return '+' + buildString(i, buildState) + if (typeof i === 'string') return '+' + precoerceNumber(+i) + + // check if it's already a number once built + const f = buildString(i, buildState) + + // regex match + if (/^-?\d+(\.\d*)?$/.test(f)) return '+' + f + + return `(+precoerceNumber(${f}))` } // @ts-ignore Allow custom attribute defaultMethods['+'].compile = function (data, buildState) { if (Array.isArray(data)) return `(${data.map(i => numberCoercion(i, buildState)).join(' + ')})` - if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') return `(+${buildString(data, buildState)})` + if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') return `precoerceNumber(+${buildString(data, buildState)})` return buildState.compile`(Array.isArray(prev = ${data}) ? prev.reduce((a,b) => (+a)+(+precoerceNumber(b)), 0) : +precoerceNumber(prev))` } @@ -933,11 +971,11 @@ defaultMethods['/'].compile = function (data, buildState) { if (Array.isArray(data)) { return `(${data.map((i, x) => { let res = numberCoercion(i, buildState) - if (x) res = `(${res}||NaN)` + if (x) res = `precoerceNumber(${res} || NaN)` return res }).join(' / ')})` } - return `(${buildString(data, buildState)}).reduce((a,b) => (+precoerceNumber(a))/(+precoerceNumber(b) || NaN))` + return `(${buildString(data, buildState)}).reduce((a,b) => (+precoerceNumber(a))/(+precoerceNumber(b || NaN)))` } // @ts-ignore Allow custom attribute defaultMethods['*'].compile = function (data, buildState) { @@ -964,7 +1002,7 @@ defaultMethods['!!'].compile = function (data, buildState) { defaultMethods.none.deterministic = defaultMethods.some.deterministic // @ts-ignore Allowing a optimizeUnary attribute that can be used for performance optimizations -defaultMethods['+'].optimizeUnary = defaultMethods['-'].optimizeUnary = defaultMethods['!'].optimizeUnary = defaultMethods['!!'].optimizeUnary = defaultMethods.cat.optimizeUnary = defaultMethods.error.optimizeUnary = defaultMethods.panic.optimizeUnary = true +defaultMethods['+'].optimizeUnary = defaultMethods['-'].optimizeUnary = defaultMethods['!'].optimizeUnary = defaultMethods['!!'].optimizeUnary = defaultMethods.cat.optimizeUnary = defaultMethods.error.optimizeUnary = true export default { ...defaultMethods, diff --git a/general.test.js b/general.test.js index cd48987..e038c1c 100644 --- a/general.test.js +++ b/general.test.js @@ -279,14 +279,14 @@ describe('Various Test Cases', () => { it('should throw on a soft error when panic is used', async () => { const rule = { - panic: { '+': 'hi' } + try: { '+': 'hi' } } for (const engine of normalEngines) await testEngine(engine, rule, {}, Error) for (const engine of permissiveEngines) await testEngine(engine, rule, {}, Error) const rule2 = { - panic: { error: 'Yeet' } + try: { error: 'Yeet' } } for (const engine of normalEngines) await testEngine(engine, rule2, {}, Error) diff --git a/suites/divide.json b/suites/divide.json index 2778d36..61d95f5 100644 --- a/suites/divide.json +++ b/suites/divide.json @@ -150,37 +150,37 @@ { "description": "Divide by Zero", "rule": { "/": [0, 0] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Divide by NaN", "rule": { "/": [1, { "error": "NaN" }] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Divide with String produces NaN", "rule": { "/": [1, "a"] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Divide with Array produces NaN", "rule": { "/": [1, [1]] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Any division by zero should return NaN", "rule": { "/": [1, 0] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Any division by zero should return NaN (2)", "rule": { "/": [8, 2, 0] }, - "result": { "error": "NaN" }, + "error": true, "data": null } ] \ No newline at end of file diff --git a/suites/error.json b/suites/error.json new file mode 100644 index 0000000..ae10f6b --- /dev/null +++ b/suites/error.json @@ -0,0 +1,14 @@ +[ + { + "description": "Creates an error object", + "rule": { "error": "hello" }, + "data": null, + "error": true + }, + { + "description": "NaN creates an error object or measured equivalent (equivalence class test)", + "rule": { "error": "NaN" }, + "data": null, + "error": true + } +] \ No newline at end of file diff --git a/suites/minus.json b/suites/minus.json index 3c0cdd7..75ced97 100644 --- a/suites/minus.json +++ b/suites/minus.json @@ -118,19 +118,19 @@ { "description": "Subtraction with NaN", "rule": { "-": [{ "error": "NaN" }, 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Subtraction with string produces NaN", "rule": { "-": ["Hey", 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Subtraction with Array produces NaN", "rule": { "-": [[1], 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null } ] \ No newline at end of file diff --git a/suites/modulo.json b/suites/modulo.json index 4b82954..4eb30a6 100644 --- a/suites/modulo.json +++ b/suites/modulo.json @@ -161,19 +161,19 @@ { "description": "Modulo with NaN", "rule": { "%": [{ "error": "NaN" }, 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Modulo with string produces NaN", "rule": { "%": ["Hey", 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Modulo with array produces NaN", "rule": { "%": [[1], 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null } ] \ No newline at end of file diff --git a/suites/multiply.json b/suites/multiply.json index b43ca5d..7e66f5b 100644 --- a/suites/multiply.json +++ b/suites/multiply.json @@ -148,19 +148,25 @@ { "description": "Multiply with NaN", "rule": { "*": [{ "error": "NaN" }, 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Multiply with string produces NaN", "rule": { "*": ["Hey", 1] }, - "result": { "error": "NaN" }, + "error": true, + "data": null + }, + { + "description": "Multiply with a single string produces NaN", + "rule": { "*": ["Hey"] }, + "error": true, "data": null }, { "description": "Multiply with Array produces NaN", "rule": { "*": [[1], 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null } ] \ No newline at end of file diff --git a/suites/panic.json b/suites/panic.json deleted file mode 100644 index 65d8184..0000000 --- a/suites/panic.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "description": "Can promote a soft error to a hard error with panic", - "rule": { "panic": { "error": "Some error" } }, - "error": true, - "data": null - }, - { - "description": "Panic with an error emitted from an operator", - "rule": { "panic": [{ "/": [1, 0] }] }, - "error": true, - "data": null - }, - { - "description": "Panic with an error pulled from context", - "rule": { "panic": [{ "val": "x" }] }, - "error": true, - "data": { "x": { "error": "Some error" } } - }, - { - "description": "Panic within an iterator", - "rule": { "map": [[1, 2, 3], { "panic": [{ "/": [0,0] }] }] }, - "error": true, - "data": null - }, - { - "description": "Panic if the user is not an admin", - "rule": { "panic": [{ "if": [{"val": ["user", "admin"]}, true, { "error": "Not an admin" }] }] }, - "data": { "user": { "admin": false } }, - "error": true - } -] \ No newline at end of file diff --git a/suites/plus.json b/suites/plus.json index 100bdf3..1f5a548 100644 --- a/suites/plus.json +++ b/suites/plus.json @@ -144,55 +144,55 @@ { "description": "Addition with NaN", "rule": { "+": [{ "error": "NaN" }, 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Addition with string produces NaN", "rule": { "+": ["Hey", 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Addition with Array produces NaN", "rule": { "+": [[1], 1] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Addition with Array from context produces NaN", "rule": { "+": [{ "val": "x" }, 1] }, - "result": { "error": "NaN" }, + "error": true, "data": { "x": [1] } }, { "description": "Addition with Object produces NaN", "rule": { "+": [{ "val": "x" }, 1] }, - "result": { "error": "NaN" }, + "error": true, "data": { "x": {} } }, { "description": "Plus Operator with Single Operand, Invalid String Produces NaN", "rule": { "+": "Hello" }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Plus Operator with Single Operand, Array Input Produces NaN", "rule": { "+": [[1]] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Plus Operator with Single Operand, Object Input Produces NaN", "rule": { "+": [{}] }, - "result": { "error": "NaN" }, + "error": true, "data": null }, { "description": "Plus Operator with Single Operand, Direct Object Input Produces NaN", "rule": { "+": {} }, - "result": { "error": "NaN" }, + "error": true, "data": null } ] \ No newline at end of file diff --git a/suites/truthiness.json b/suites/truthiness.json index 0e073e5..4026d1a 100644 --- a/suites/truthiness.json +++ b/suites/truthiness.json @@ -55,19 +55,6 @@ "result": false, "data": null }, - "# Some error tests", - { - "description": "Truthy: NaN", - "rule": { "!!": { "error": "NaN" } }, - "result": true, - "data": null - }, - { - "description": "Truthy: Arbitrary error", - "rule": { "!!": { "error": "Some error" } }, - "result": true, - "data": null - }, "# Context Object Tests", { "description": "Truthy: Zero Key Object", diff --git a/suites/try.json b/suites/try.json index a498c3e..8450bc6 100644 --- a/suites/try.json +++ b/suites/try.json @@ -12,12 +12,6 @@ "result": 1, "data": { "hello": "world" } }, - { - "description": "Coalesce an error pulled from context; errors are just data.", - "rule": { "try": [{ "val": "x" }, 1]}, - "data": { "x": { "error": "Some error" }}, - "result": 1 - }, { "description": "Panics if none of the values are valid", "rule": { "try": [{ "error": "Some error" }, { "error": "Some other error" }] }, @@ -29,5 +23,83 @@ "rule": { "try": [{ "error": "Some error" }, { "/": [0, 0] }] }, "error": true, "data": null + }, + { + "description": "Can promote a soft error to a hard error", + "rule": { "try": { "error": "Some error" } }, + "error": true, + "data": null + }, + { + "description": "Panic with an error emitted from an operator", + "rule": { "try": [{ "/": [1, 0] }] }, + "error": true, + "data": null + }, + { + "description": "Panic within an iterator", + "rule": { "map": [[1, 2, 3], { "try": [{ "/": [0,0] }] }] }, + "error": true, + "data": null + }, + { + "description": "Panic if the user is not an admin", + "rule": { "try": [{ "if": [{"val": ["user", "admin"]}, true, { "error": "Not an admin" }] }] }, + "data": { "user": { "admin": false } }, + "error": true + }, + { + "description": "Try can work further up the AST with Exceptions", + "rule": { + "try": [{ + "if": [ + true, + { "map": [[1,2,3], {"/": [0, 0] }]}, + null + ] + }, 10] + }, + "result": 10, + "data": null + }, + { + "description": "The context switches for the try coalescing to the previous error", + "rule": { + "try": [ + { "if": [true, { "error": "Some error" }, null] }, + { "val": [] } + ] + }, + "result": { "error": "Some error"}, + "data": null + }, + { + "description": "Try can work further up the AST with Exceptions, and return the error", + "rule": { + "try": [{ + "if": [ + true, + { "map": [[1,2,3], {"/": [0, 0] }]}, + null + ] + }, { "val": [] }] + }, + "result": { "error": "NaN" }, + "data": null + }, + "# Not Proposed", + { + "description": "Try can work further up the AST with Exceptions; Grabbing other Context value", + "rule": { + "try": [{ + "if": [ + true, + { "map": [[1,2,3], {"/": [0, 0] }]}, + null + ] + }, { "val": [[2], "fallback"] }] + }, + "result": "Hello", + "data": { "fallback": "Hello" } } ] \ No newline at end of file diff --git a/utilities/downgrade.js b/utilities/downgrade.js index 9fb056e..ef3d02d 100644 --- a/utilities/downgrade.js +++ b/utilities/downgrade.js @@ -1,20 +1,10 @@ - -/** - * Used to make an "error" piece of data null, for the purposes of coalescing. - * @param {any} item - */ -export function downgrade (item) { - if (item && typeof item === 'object' && 'error' in item) return null - if (Number.isNaN(item)) return null - return item -} - /** * Used to precoerce a data value to a number, for the purposes of coalescing. * @param {any} item */ export function precoerceNumber (item) { + if (Number.isNaN(item)) throw new Error('NaN') if (!item) return item - if (typeof item === 'object') return Number.isNaN + if (typeof item === 'object') throw new Error('NaN') return item }