Skip to content

[Draft] Decimal Precision #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ bench/
perf*.js
*.test.js
experiments/
suites/
suites/
precision/
2 changes: 2 additions & 0 deletions compatibility.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import defaultMethods from './defaultMethods.js'
import { Sync } from './constants.js'
const oldAll = defaultMethods.all

const all = {
[Sync]: defaultMethods.all[Sync],
method: (args, context, above, engine) => {
if (Array.isArray(args)) {
const first = engine.run(args[0], context, above)
Expand Down
186 changes: 55 additions & 131 deletions compatible.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import fs from 'fs'
import { LogicEngine, AsyncLogicEngine } from './index.js'
import { configurePrecision } from './precision/index.js'
import Decimal from 'decimal.js'

const tests = []

// get all json files from "suites" directory
Expand All @@ -13,141 +16,62 @@ for (const file of files) {
}
}

// eslint-disable-next-line no-labels
inline: {
const logic = new LogicEngine(undefined, { compatible: true })
const asyncLogic = new AsyncLogicEngine(undefined, { compatible: true })
const logicWithoutOptimization = new LogicEngine(undefined, { compatible: true })
const asyncLogicWithoutOptimization = new AsyncLogicEngine(undefined, { compatible: true })

describe('All of the compatible tests', () => {
tests.forEach((testCase) => {
test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)}`, () => {
expect(logic.run(testCase[0], testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (async)`, async () => {
expect(await asyncLogic.run(testCase[0], testCase[1])).toStrictEqual(
testCase[2]
)
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (built)`, () => {
const f = logic.build(testCase[0])
expect(f(testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (asyncBuilt)`, async () => {
const f = await asyncLogic.build(testCase[0])
expect(await f(testCase[1])).toStrictEqual(testCase[2])
})
const engines = []

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (noOptimization)`, () => {
expect(logicWithoutOptimization.run(testCase[0], testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (asyncNoOptimization)`, async () => {
expect(await asyncLogicWithoutOptimization.run(testCase[0], testCase[1])).toStrictEqual(
testCase[2]
)
})
for (let i = 0; i < 16; i++) {
let res = 'sync'
let engine = new LogicEngine(undefined, { compatible: true })
// sync / async
if (i & 1) {
engine = new AsyncLogicEngine(undefined, { compatible: true })
res = 'async'
}
// inline / disabled
if (i & 2) {
engine.disableInline = true
res += ' no-inline'
}
// optimized / not optimized
if (i & 4) {
engine.disableInterpretedOptimization = true
res += ' no-optimized'
}
// ieee754 / decimal
if (i & 8) {
configurePrecision(engine, Decimal)
res += ' decimal'

// Copy in another decimal engine
const preciseEngine = (i & 1) ? new AsyncLogicEngine(undefined, { compatible: true }) : new LogicEngine(undefined, { compatible: true })
preciseEngine.disableInline = engine.disableInline
preciseEngine.disableInterpretedOptimization = engine.disableInterpretedOptimization
configurePrecision(preciseEngine, 'precise')
engines.push([preciseEngine, res + ' improved'])
}
engines.push([engine, res])
}

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
describe('All of the compatible tests', () => {
for (const testCase of tests) {
for (const engine of engines) {
test(`${engine[1]} ${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (builtNoOptimization)`, () => {
const f = logicWithoutOptimization.build(testCase[0])
expect(f(testCase[1])).toStrictEqual(testCase[2])
)}`, async () => {
let result = await engine[0].run(testCase[0], testCase[1])
if ((result || 0).toNumber) result = Number(result)
if (Array.isArray(result)) result = result.map(i => (i || 0).toNumber ? Number(i) : i)
expect(result).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
test(`${engine[1]} ${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (asyncBuiltNoOptimization)`, async () => {
const f = await asyncLogicWithoutOptimization.build(testCase[0])
expect(await f(testCase[1])).toStrictEqual(testCase[2])
)} (built)`, async () => {
const f = await engine[0].build(testCase[0])
let result = await f(testCase[1])
if ((result || 0).toNumber) result = Number(result)
if (Array.isArray(result)) result = result.map(i => i.toNumber ? Number(i) : i)
expect(result).toStrictEqual(testCase[2])
})
})
})
}
// eslint-disable-next-line no-labels
notInline: {
const logic = new LogicEngine(undefined, { compatible: true })
const asyncLogic = new AsyncLogicEngine(undefined, { compatible: true })
const logicWithoutOptimization = new LogicEngine(undefined, { compatible: true })
const asyncLogicWithoutOptimization = new AsyncLogicEngine(undefined, { compatible: true })

logicWithoutOptimization.disableInline = true
logic.disableInline = true
asyncLogic.disableInline = true
asyncLogicWithoutOptimization.disableInline = true

// using a loop to disable the inline compilation mechanism.
tests.forEach((testCase) => {
test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)}`, () => {
expect(logic.run(testCase[0], testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (async)`, async () => {
expect(await asyncLogic.run(testCase[0], testCase[1])).toStrictEqual(
testCase[2]
)
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (built)`, () => {
const f = logic.build(testCase[0])
expect(f(testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (asyncBuilt)`, async () => {
const f = await asyncLogic.build(testCase[0])
expect(await f(testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (noOptimization)`, () => {
expect(logicWithoutOptimization.run(testCase[0], testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (asyncNoOptimization)`, async () => {
expect(await asyncLogicWithoutOptimization.run(testCase[0], testCase[1])).toStrictEqual(
testCase[2]
)
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (builtNoOptimization)`, () => {
const f = logicWithoutOptimization.build(testCase[0])
expect(f(testCase[1])).toStrictEqual(testCase[2])
})

test(`${JSON.stringify(testCase[0])} ${JSON.stringify(
testCase[1]
)} (asyncBuiltNoOptimization)`, async () => {
const f = await asyncLogicWithoutOptimization.build(testCase[0])
expect(await f(testCase[1])).toStrictEqual(testCase[2])
})
})
}
}
}
})
80 changes: 46 additions & 34 deletions defaultMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,20 @@ const defaultMethods = {
for (let i = 1; i < data.length; i++) res %= +data[i]
return res
},
max: (data) => Math.max(...data),
min: (data) => Math.min(...data),
max: (data) => {
let maximum = data[0]
for (let i = 1; i < data.length; i++) {
if (data[i] > maximum) maximum = data[i]
}
return maximum
},
min: (data) => {
let minimum = data[0]
for (let i = 1; i < data.length; i++) {
if (data[i] < minimum) minimum = data[i]
}
return minimum
},
in: ([item, array]) => (array || []).includes(item),
preserve: {
traverse: false,
Expand Down Expand Up @@ -212,6 +224,7 @@ const defaultMethods = {
// Why "executeInLoop"? Because if it needs to execute to get an array, I do not want to execute the arguments,
// Both for performance and safety reasons.
or: {
[Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
method: (arr, _1, _2, engine) => {
// See "executeInLoop" above
const executeInLoop = Array.isArray(arr)
Expand Down Expand Up @@ -250,6 +263,7 @@ const defaultMethods = {
traverse: false
},
and: {
[Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
method: (arr, _1, _2, engine) => {
// See "executeInLoop" above
const executeInLoop = Array.isArray(arr)
Expand Down Expand Up @@ -286,6 +300,8 @@ const defaultMethods = {
}
},
substr: ([string, from, end]) => {
if (from) from = +from
if (end) end = +end
if (end < 0) {
const result = string.substr(from)
return result.substr(0, result.length + end)
Expand All @@ -298,6 +314,7 @@ const defaultMethods = {
return 0
},
get: {
[Sync]: true,
method: ([data, key, defaultValue], context, above, engine) => {
const notFound = defaultValue === undefined ? null : defaultValue

Expand Down Expand Up @@ -379,6 +396,7 @@ const defaultMethods = {
some: createArrayIterativeMethod('some', true),
all: createArrayIterativeMethod('every', true),
none: {
[Sync]: (data, buildState) => isSyncDeep(data, buildState.engine, buildState),
traverse: false,
// todo: add async build & build
method: (val, context, above, engine) => {
Expand All @@ -399,7 +417,7 @@ const defaultMethods = {
},
merge: (arrays) => (Array.isArray(arrays) ? [].concat(...arrays) : [arrays]),
every: createArrayIterativeMethod('every'),
filter: createArrayIterativeMethod('filter'),
filter: createArrayIterativeMethod('filter', true),
reduce: {
deterministic: (data, buildState) => {
return (
Expand Down Expand Up @@ -508,11 +526,26 @@ const defaultMethods = {
},
'!': (value, _1, _2, engine) => Array.isArray(value) ? !engine.truthy(value[0]) : !engine.truthy(value),
'!!': (value, _1, _2, engine) => Boolean(Array.isArray(value) ? engine.truthy(value[0]) : engine.truthy(value)),
cat: (arr) => {
if (typeof arr === 'string') return arr
let res = ''
for (let i = 0; i < arr.length; i++) res += arr[i]
return res
cat: {
[Sync]: true,
method: (arr) => {
if (typeof arr === 'string') return arr
if (!Array.isArray(arr)) return arr.toString()
let res = ''
for (let i = 0; i < arr.length; i++) res += arr[i].toString()
return res
},
deterministic: true,
traverse: true,
optimizeUnary: true,
compile: (data, buildState) => {
if (typeof data === 'string') return JSON.stringify(data)
if (typeof data === 'number') return '"' + JSON.stringify(data) + '"'
if (!Array.isArray(data)) return false
let res = buildState.compile`''`
for (let i = 0; i < data.length; i++) res = buildState.compile`${res} + ${data[i]}`
return buildState.compile`(${res})`
}
},
keys: ([obj]) => typeof obj === 'object' ? Object.keys(obj) : [],
pipe: {
Expand Down Expand Up @@ -633,8 +666,8 @@ function createArrayIterativeMethod (name, useTruthy = false) {
(await engine.run(selector, context, {
above
})) || []
return asyncIterators[name](selector, (i, index) => {
const result = engine.run(mapper, i, {
return asyncIterators[name](selector, async (i, index) => {
const result = await engine.run(mapper, i, {
above: [{ iterator: selector, index }, context, above]
})
return useTruthy ? engine.truthy(result) : result
Expand All @@ -654,15 +687,16 @@ function createArrayIterativeMethod (name, useTruthy = false) {

const method = build(mapper, mapState)
const aboveArray = method.aboveDetected ? buildState.compile`[{ iterator: z, index: x }, context, above]` : buildState.compile`null`
const useTruthyMethod = useTruthy ? buildState.compile`engine.truthy` : buildState.compile``

if (async) {
if (!isSyncDeep(mapper, buildState.engine, buildState)) {
buildState.detectAsync = true
return buildState.compile`await asyncIterators[${name}](${selector} || [], async (i, x, z) => ${method}(i, x, ${aboveArray}))`
return buildState.compile`await asyncIterators[${name}](${selector} || [], async (i, x, z) => ${useTruthyMethod}(${method}(i, x, ${aboveArray})))`
}
}

return buildState.compile`(${selector} || [])[${name}]((i, x, z) => ${method}(i, x, ${aboveArray}))`
return buildState.compile`(${selector} || [])[${name}]((i, x, z) => ${useTruthyMethod}(${method}(i, x, ${aboveArray})))`
},
traverse: false
}
Expand Down Expand Up @@ -705,20 +739,6 @@ defaultMethods['<='].compile = function (data, buildState) {
return res
}
// @ts-ignore Allow custom attribute
defaultMethods.min.compile = function (data, buildState) {
if (!Array.isArray(data)) return false
return `Math.min(${data
.map((i) => buildString(i, buildState))
.join(', ')})`
}
// @ts-ignore Allow custom attribute
defaultMethods.max.compile = function (data, buildState) {
if (!Array.isArray(data)) return false
return `Math.max(${data
.map((i) => buildString(i, buildState))
.join(', ')})`
}
// @ts-ignore Allow custom attribute
defaultMethods['>'].compile = function (data, buildState) {
if (!Array.isArray(data)) return false
if (data.length < 2) return false
Expand Down Expand Up @@ -845,14 +865,6 @@ defaultMethods['*'].compile = function (data, buildState) {
return `(${buildString(data, buildState)}).reduce((a,b) => (+a)*(+b))`
}
}
// @ts-ignore Allow custom attribute
defaultMethods.cat.compile = function (data, buildState) {
if (typeof data === 'string') return JSON.stringify(data)
if (!Array.isArray(data)) return false
let res = buildState.compile`''`
for (let i = 0; i < data.length; i++) res = buildState.compile`${res} + ${data[i]}`
return buildState.compile`(${res})`
}

// @ts-ignore Allow custom attribute
defaultMethods['!'].compile = function (
Expand Down
Loading
Loading