-
Notifications
You must be signed in to change notification settings - Fork 511
Expand file tree
/
Copy pathoperator-map.js
More file actions
188 lines (169 loc) · 6.27 KB
/
operator-map.js
File metadata and controls
188 lines (169 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
'use strict'
import Operator from './operator'
import OperatorDecorator from './operator-decorator'
import defaultOperators from './engine-default-operators'
import defaultDecorators from './engine-default-operator-decorators'
import debug from './debug'
let sharedInstance = null
export default class OperatorMap {
constructor (options = {}) {
this.operators = new Map()
this.decorators = new Map()
this.parent = options.parent || null
}
/**
* Returns a shared OperatorMap pre-populated with default operators and decorators.
* This singleton is reused across Engine instances to avoid redundant allocations.
* @returns {OperatorMap}
*/
static shared () {
if (!sharedInstance) {
sharedInstance = new OperatorMap()
defaultOperators.forEach(o => sharedInstance.addOperator(o))
defaultDecorators.forEach(d => sharedInstance.addOperatorDecorator(d))
}
return sharedInstance
}
/**
* Add a custom operator definition
* @param {string} operatorOrName - operator identifier within the condition; i.e. instead of 'equals', 'greaterThan', etc
* @param {function(factValue, jsonValue)} callback - the method to execute when the operator is encountered.
*/
addOperator (operatorOrName, cb) {
let operator
if (operatorOrName instanceof Operator) {
operator = operatorOrName
} else {
operator = new Operator(operatorOrName, cb)
}
debug('operatorMap::addOperator', { name: operator.name })
this.operators.set(operator.name, operator)
}
/**
* Remove a custom operator definition
* @param {string} operatorOrName - operator identifier within the condition; i.e. instead of 'equals', 'greaterThan', etc
* @param {function(factValue, jsonValue)} callback - the method to execute when the operator is encountered.
*/
removeOperator (operatorOrName) {
let operatorName
if (operatorOrName instanceof Operator) {
operatorName = operatorOrName.name
} else {
operatorName = operatorOrName
}
// Delete all the operators that end in :operatorName these
// were decorated on-the-fly leveraging this operator
const suffix = ':' + operatorName
const operatorNames = Array.from(this.operators.keys())
for (let i = 0; i < operatorNames.length; i++) {
if (operatorNames[i].endsWith(suffix)) {
this.operators.delete(operatorNames[i])
}
}
return this.operators.delete(operatorName)
}
/**
* Add a custom operator decorator
* @param {string} decoratorOrName - decorator identifier within the condition; i.e. instead of 'everyFact', 'someValue', etc
* @param {function(factValue, jsonValue, next)} callback - the method to execute when the decorator is encountered.
*/
addOperatorDecorator (decoratorOrName, cb) {
let decorator
if (decoratorOrName instanceof OperatorDecorator) {
decorator = decoratorOrName
} else {
decorator = new OperatorDecorator(decoratorOrName, cb)
}
debug('operatorMap::addOperatorDecorator', { name: decorator.name })
this.decorators.set(decorator.name, decorator)
}
/**
* Remove a custom operator decorator
* @param {string} decoratorOrName - decorator identifier within the condition; i.e. instead of 'everyFact', 'someValue', etc
*/
removeOperatorDecorator (decoratorOrName) {
let decoratorName
if (decoratorOrName instanceof OperatorDecorator) {
decoratorName = decoratorOrName.name
} else {
decoratorName = decoratorOrName
}
// Delete all the operators that include decoratorName: these
// were decorated on-the-fly leveraging this decorator
const prefix = decoratorName + ':'
const operatorNames = Array.from(this.operators.keys())
for (let i = 0; i < operatorNames.length; i++) {
if (operatorNames[i].includes(prefix)) {
this.operators.delete(operatorNames[i])
}
}
return this.decorators.delete(decoratorName)
}
/**
* Get the Operator, or null applies decorators as needed
* Checks local operators first, then falls back to parent if set.
* @param {string} name - the name of the operator including any decorators
* @returns an operator or null
*/
get (name) {
// Fast path: check local cache first
if (this.operators.has(name)) {
return this.operators.get(name)
}
// Check parent for cached decorated operators
if (this.parent && this.parent.operators.has(name)) {
return this.parent.operators.get(name)
}
const decorators = []
let opName = name
// while we don't already have this operator
while (!this._hasOperator(opName)) {
// try splitting on the decorator symbol (:)
const firstDecoratorIndex = opName.indexOf(':')
if (firstDecoratorIndex > 0) {
// if there is a decorator, and it's a valid decorator
const decoratorName = opName.slice(0, firstDecoratorIndex)
const decorator = this._getDecorator(decoratorName)
if (!decorator) {
debug('operatorMap::get invalid decorator', { name: decoratorName })
return null
}
// we're going to apply this later, use unshift since we'll apply in reverse order
decorators.unshift(decorator)
// continue looking for a known operator with the rest of the name
opName = opName.slice(firstDecoratorIndex + 1)
} else {
debug('operatorMap::get no operator', { name: opName })
return null
}
}
let op = this._getOperator(opName)
// apply all the decorators
for (let i = 0; i < decorators.length; i++) {
op = decorators[i].decorate(op)
// create an entry for the decorated operation so we don't need
// to do this again
this.operators.set(op.name, op)
}
// return the operation
return op
}
/**
* Check if operator exists locally or in parent
*/
_hasOperator (name) {
return this.operators.has(name) || (this.parent && this.parent.operators.has(name))
}
/**
* Get operator from local map or parent
*/
_getOperator (name) {
return this.operators.get(name) || (this.parent && this.parent.operators.get(name))
}
/**
* Get decorator from local map or parent
*/
_getDecorator (name) {
return this.decorators.get(name) || (this.parent && this.parent.decorators.get(name))
}
}