Skip to content

Commit 20b77fd

Browse files
committed
Implement a tagged template system for compilation; reduce complexity all over the place, get rid of the need for 'useContext'
1 parent 0056bbd commit 20b77fd

7 files changed

+143
-303
lines changed

asyncLogic.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,13 @@ class AsyncLogicEngine {
9595
/**
9696
*
9797
* @param {String} name The name of the method being added.
98-
* @param {Function|{ traverse?: Boolean, method?: Function, asyncMethod?: Function, deterministic?: Function | Boolean }} method
99-
* @param {{ deterministic?: Boolean, useContext?: Boolean, async?: Boolean, sync?: Boolean }} annotations This is used by the compiler to help determine if it can optimize the function being generated.
98+
* @param {((args: any, context: any, above: any[], engine: AsyncLogicEngine) => any) | { traverse?: Boolean, method?: (args: any, context: any, above: any[], engine: AsyncLogicEngine) => any, asyncMethod?: (args: any, context: any, above: any[], engine: AsyncLogicEngine) => Promise<any>, deterministic?: Function | Boolean }} method
99+
* @param {{ deterministic?: Boolean, async?: Boolean, sync?: Boolean }} annotations This is used by the compiler to help determine if it can optimize the function being generated.
100100
*/
101101
addMethod (
102102
name,
103103
method,
104-
{ deterministic, async, sync, useContext } = {}
104+
{ deterministic, async, sync } = {}
105105
) {
106106
if (typeof async === 'undefined' && typeof sync === 'undefined') sync = false
107107
if (typeof sync !== 'undefined') async = !sync
@@ -112,17 +112,17 @@ class AsyncLogicEngine {
112112
else method = { method, traverse: true }
113113
} else method = { ...method }
114114

115-
Object.assign(method, omitUndefined({ deterministic, useContext }))
115+
Object.assign(method, omitUndefined({ deterministic }))
116116
// @ts-ignore
117-
this.fallback.addMethod(name, method, { deterministic, useContext })
117+
this.fallback.addMethod(name, method, { deterministic })
118118
this.methods[name] = declareSync(method, sync)
119119
}
120120

121121
/**
122122
* Adds a batch of functions to the engine
123123
* @param {String} name
124124
* @param {Object} obj
125-
* @param {{ deterministic?: Boolean, useContext?: Boolean, async?: Boolean, sync?: Boolean }} annotations Not recommended unless you're sure every function from the module will match these annotations.
125+
* @param {{ deterministic?: Boolean, async?: Boolean, sync?: Boolean }} annotations Not recommended unless you're sure every function from the module will match these annotations.
126126
*/
127127
addModule (name, obj, annotations = {}) {
128128
Object.getOwnPropertyNames(obj).forEach((key) => {

compatibility.js

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import defaultMethods from './defaultMethods.js'
22
const oldAll = defaultMethods.all
33

44
const all = {
5-
useContext: true,
65
method: (args, context, above, engine) => {
76
if (Array.isArray(args)) {
87
const first = engine.run(args[0], context, above)

compiler.js

+40-68
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,50 @@
33

44
import {
55
isSync,
6-
// Override is required for the compiler to operate as intended.
7-
Override,
8-
Sync
6+
Sync,
7+
Compiled
98
} from './constants.js'
109
import declareSync from './utilities/declareSync.js'
1110

1211
// asyncIterators is required for the compiler to operate as intended.
1312
import asyncIterators from './async_iterators.js'
1413

14+
/**
15+
* Provides a simple way to compile logic into a function that can be run.
16+
* @param {string[]} strings
17+
* @param {...any} items
18+
* @returns {{ [Compiled]: string }}
19+
*/
20+
function compileTemplate (strings, ...items) {
21+
let res = ''
22+
for (let i = 0; i < strings.length; i++) {
23+
res += strings[i]
24+
if (i < items.length) {
25+
if (typeof items[i] === 'function') {
26+
this.methods.push(items[i])
27+
res += 'methods[' + (this.methods.length - 1) + ']'
28+
} else if (items[i] && typeof items[i][Compiled] !== 'undefined') res += items[i][Compiled]
29+
else res += buildString(items[i], this)
30+
}
31+
}
32+
return { [Compiled]: res }
33+
}
34+
1535
/**
1636
* @typedef BuildState
1737
* Used to keep track of the compilation.
1838
* @property {*} [engine]
1939
* @property {Object} [notTraversed]
20-
* @property {Object} [functions]
2140
* @property {Object} [methods]
2241
* @property {Object} [state]
2342
* @property {Array} [processing]
2443
* @property {*} [async]
2544
* @property {Array} [above]
2645
* @property {Boolean} [asyncDetected]
2746
* @property {*} [values]
28-
* @property {Boolean} [useContext]
2947
* @property {Boolean} [avoidInlineAsync]
3048
* @property {string} [extraArguments]
31-
*
49+
* @property {(strings: string[], ...items: any[]) => { compiled: string }} [compile] A function that can be used to compile a template.
3250
*/
3351

3452
/**
@@ -117,11 +135,7 @@ function isDeepSync (method, engine) {
117135
function buildString (method, buildState = {}) {
118136
const {
119137
notTraversed = [],
120-
functions = {},
121-
// methods = [],
122-
// state,
123138
async,
124-
// above = [],
125139
processing = [],
126140
values = [],
127141
engine
@@ -150,7 +164,6 @@ function buildString (method, buildState = {}) {
150164
}
151165

152166
const func = method && Object.keys(method)[0]
153-
buildState.useContext = buildState.useContext || (engine.methods[func] || {}).useContext
154167

155168
if (method && typeof method === 'object') {
156169
if (!func) return pushValue(method)
@@ -159,7 +172,6 @@ function buildString (method, buildState = {}) {
159172
if (engine.isData(method, func)) return pushValue(method, true)
160173
throw new Error(`Method '${func}' was not found in the Logic Engine.`)
161174
}
162-
functions[func] = functions[func] || 2
163175

164176
if (
165177
!buildState.engine.disableInline &&
@@ -175,31 +187,25 @@ function buildString (method, buildState = {}) {
175187
}
176188

177189
if (engine.methods[func] && engine.methods[func].compile) {
178-
const str = engine.methods[func].compile(method[func], buildState)
190+
let str = engine.methods[func].compile(method[func], buildState)
191+
if (str[Compiled]) str = str[Compiled]
179192

180193
if ((str || '').startsWith('await')) buildState.asyncDetected = true
181194

182195
if (str !== false) return str
183196
}
184197

185198
if (typeof engine.methods[func] === 'function') {
186-
functions[func] = 1
187199
asyncDetected = !isSync(engine.methods[func])
188-
189-
return makeAsync(`gen["${func}"](` + buildString(method[func], buildState) + ')')
200+
return makeAsync(`engine.methods["${func}"](` + buildString(method[func], buildState) + ', context, above, engine)')
190201
} else {
191202
if (engine.methods[func] && (typeof engine.methods[func].traverse === 'undefined' ? true : engine.methods[func].traverse)) {
192-
functions[func] = 1
193203
asyncDetected = Boolean(async && engine.methods[func] && engine.methods[func].asyncMethod)
194-
195-
return makeAsync(`gen["${func}"](` + buildString(method[func], buildState) + ')')
204+
return makeAsync(`engine.methods["${func}"]${asyncDetected ? '.asyncMethod' : '.method'}(` + buildString(method[func], buildState) + ', context, above, engine)')
196205
} else {
197206
asyncDetected = Boolean(async && engine.methods[func] && engine.methods[func].asyncMethod)
198-
199-
functions[func] = 1
200207
notTraversed.push(method[func])
201-
202-
return makeAsync(`gen["${func}"](` + `notTraversed[${notTraversed.length - 1}]` + ')')
208+
return makeAsync(`engine.methods["${func}"]${asyncDetected ? '.asyncMethod' : '.method'}(` + `notTraversed[${notTraversed.length - 1}]` + ', context, above, engine)')
203209
}
204210
}
205211
}
@@ -218,14 +224,13 @@ function build (method, buildState = {}) {
218224
Object.assign(
219225
{
220226
notTraversed: [],
221-
functions: {},
222227
methods: [],
223228
state: {},
224229
processing: [],
225230
async: buildState.engine.async,
226-
above: [],
227231
asyncDetected: false,
228-
values: []
232+
values: [],
233+
compile: compileTemplate
229234
},
230235
buildState
231236
)
@@ -246,20 +251,19 @@ async function buildAsync (method, buildState = {}) {
246251
Object.assign(
247252
{
248253
notTraversed: [],
249-
functions: {},
250254
methods: [],
251255
state: {},
252256
processing: [],
253257
async: buildState.engine.async,
254-
above: [],
255258
asyncDetected: false,
256-
values: []
259+
values: [],
260+
compile: compileTemplate
257261
},
258262
buildState
259263
)
260264
)
261265
const str = buildString(method, buildState)
262-
buildState.processing = await Promise.all(buildState.processing)
266+
buildState.processing = await Promise.all(buildState.processing || [])
263267
return processBuiltString(method, str, buildState)
264268
}
265269

@@ -271,58 +275,26 @@ async function buildAsync (method, buildState = {}) {
271275
* @returns
272276
*/
273277
function processBuiltString (method, str, buildState) {
274-
const gen = {}
275278
const {
276-
functions,
277-
state,
278-
async,
279279
engine,
280-
above,
281280
methods,
282281
notTraversed,
283-
processing,
282+
processing = [],
284283
values
285284
} = buildState
286-
processing.forEach((item, x) => {
287-
str = str.replace(`__%%%${x}%%%__`, item)
288-
})
289-
Object.keys(functions).forEach((key) => {
290-
if (functions[key] === 2) return
291285

292-
if (!engine.methods[key]) throw new Error(`Method '${key}' was not found in the Logic Engine.`)
286+
const above = []
293287

294-
if (typeof engine.methods[key] === 'function') {
295-
const method = engine.methods[key]
296-
gen[key] = (input) => method(input, state, above, engine)
297-
} else {
298-
if (async && engine.methods[key].asyncMethod) {
299-
buildState.asyncDetected = true
300-
const method = engine.methods[key].asyncMethod
301-
gen[key] = (input) => method(input, state, above, engine)
302-
} else {
303-
const method = engine.methods[key].method
304-
gen[key] = (input) => method(input, state, above, engine)
305-
}
306-
}
288+
processing.forEach((item, x) => {
289+
str = str.replace(`__%%%${x}%%%__`, item)
307290
})
308291

309-
if (!Object.keys(functions).length) return method
310-
311-
let copyStateCall = 'state[Override] = context;'
312-
// console.log(buildState.useContext)
313-
314-
if (!buildState.useContext) {
315-
copyStateCall = ''
316-
str = str.replace(/state\[Override\]/g, 'context')
317-
}
318-
319-
methods.truthy = engine.truthy
320-
const final = `(state, values, methods, gen, notTraversed, Override, asyncIterators) => ${buildState.asyncDetected ? 'async' : ''} (context ${buildState.extraArguments ? ',' + buildState.extraArguments : ''}) => { ${copyStateCall} const result = ${str}; return result }`
292+
const final = `(values, methods, notTraversed, asyncIterators, engine, above) => ${buildState.asyncDetected ? 'async' : ''} (context ${buildState.extraArguments ? ',' + buildState.extraArguments : ''}) => { const result = ${str}; return result }`
321293

322294
// console.log(str)
323295
// console.log(final)
324296
// eslint-disable-next-line no-eval
325-
return declareSync((typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(state, values, methods, gen, notTraversed, Override, asyncIterators), !buildState.asyncDetected)
297+
return declareSync((typeof globalThis !== 'undefined' ? globalThis : global).eval(final)(values, methods, notTraversed, asyncIterators, engine, above), !buildState.asyncDetected)
326298
}
327299

328300
export { build }

constants.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
'use strict'
33

44
export const Sync = Symbol.for('json_logic_sync')
5-
export const Override = Symbol.for('json_logic_override')
5+
export const Compiled = Symbol.for('json_logic_compiled')
66
export const EfficientTop = Symbol.for('json_logic_efficientTop')
77

88
/**
@@ -22,7 +22,6 @@ export function isSync (item) {
2222

2323
export default {
2424
Sync,
25-
Override,
2625
EfficientTop,
2726
isSync
2827
}

0 commit comments

Comments
 (0)