@jbrowse/jexl 4.0.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +163 -20
  2. package/dist/Expression.d.ts +16 -0
  3. package/dist/Expression.js +28 -4
  4. package/dist/Expression.js.map +1 -1
  5. package/dist/Jexl.d.ts +20 -8
  6. package/dist/Jexl.js +34 -15
  7. package/dist/Jexl.js.map +1 -1
  8. package/dist/Lexer.d.ts +4 -3
  9. package/dist/Lexer.js +29 -21
  10. package/dist/Lexer.js.map +1 -1
  11. package/dist/analyze.d.ts +84 -0
  12. package/dist/analyze.js +259 -0
  13. package/dist/analyze.js.map +1 -0
  14. package/dist/check.d.ts +109 -0
  15. package/dist/check.js +1267 -0
  16. package/dist/check.js.map +1 -0
  17. package/dist/collections.d.ts +3 -0
  18. package/dist/collections.js +140 -0
  19. package/dist/collections.js.map +1 -0
  20. package/dist/conditions.d.ts +62 -0
  21. package/dist/conditions.js +207 -0
  22. package/dist/conditions.js.map +1 -0
  23. package/dist/errors.d.ts +5 -0
  24. package/dist/errors.js +18 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/evaluator/compile.d.ts +10 -7
  27. package/dist/evaluator/compile.js +292 -99
  28. package/dist/evaluator/compile.js.map +1 -1
  29. package/dist/grammar.d.ts +22 -9
  30. package/dist/grammar.js +83 -55
  31. package/dist/grammar.js.map +1 -1
  32. package/dist/index.d.ts +9 -0
  33. package/dist/index.js +16 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/operators.d.ts +34 -0
  36. package/dist/operators.js +131 -0
  37. package/dist/operators.js.map +1 -0
  38. package/dist/parser/Parser.d.ts +48 -91
  39. package/dist/parser/Parser.js +347 -221
  40. package/dist/parser/Parser.js.map +1 -1
  41. package/dist/types.d.ts +11 -5
  42. package/esm/Expression.d.ts +16 -0
  43. package/esm/Expression.js +28 -4
  44. package/esm/Expression.js.map +1 -1
  45. package/esm/Jexl.d.ts +20 -8
  46. package/esm/Jexl.js +34 -15
  47. package/esm/Jexl.js.map +1 -1
  48. package/esm/Lexer.d.ts +4 -3
  49. package/esm/Lexer.js +29 -21
  50. package/esm/Lexer.js.map +1 -1
  51. package/esm/analyze.d.ts +84 -0
  52. package/esm/analyze.js +256 -0
  53. package/esm/analyze.js.map +1 -0
  54. package/esm/check.d.ts +109 -0
  55. package/esm/check.js +1260 -0
  56. package/esm/check.js.map +1 -0
  57. package/esm/collections.d.ts +3 -0
  58. package/esm/collections.js +137 -0
  59. package/esm/collections.js.map +1 -0
  60. package/esm/conditions.d.ts +62 -0
  61. package/esm/conditions.js +200 -0
  62. package/esm/conditions.js.map +1 -0
  63. package/esm/errors.d.ts +5 -0
  64. package/esm/errors.js +14 -0
  65. package/esm/errors.js.map +1 -0
  66. package/esm/evaluator/compile.d.ts +10 -7
  67. package/esm/evaluator/compile.js +292 -99
  68. package/esm/evaluator/compile.js.map +1 -1
  69. package/esm/grammar.d.ts +22 -9
  70. package/esm/grammar.js +79 -53
  71. package/esm/grammar.js.map +1 -1
  72. package/esm/index.d.ts +9 -0
  73. package/esm/index.js +4 -0
  74. package/esm/index.js.map +1 -1
  75. package/esm/operators.d.ts +34 -0
  76. package/esm/operators.js +123 -0
  77. package/esm/operators.js.map +1 -0
  78. package/esm/parser/Parser.d.ts +48 -91
  79. package/esm/parser/Parser.js +349 -192
  80. package/esm/parser/Parser.js.map +1 -1
  81. package/esm/types.d.ts +11 -5
  82. package/package.json +1 -1
  83. package/src/Expression.ts +34 -4
  84. package/src/Jexl.ts +52 -16
  85. package/src/Lexer.ts +32 -21
  86. package/src/analyze.ts +378 -0
  87. package/src/check.ts +1682 -0
  88. package/src/collections.ts +148 -0
  89. package/src/conditions.ts +262 -0
  90. package/src/errors.ts +15 -0
  91. package/src/evaluator/compile.ts +351 -102
  92. package/src/grammar.ts +137 -55
  93. package/src/index.ts +15 -0
  94. package/src/operators.ts +151 -0
  95. package/src/parser/Parser.ts +389 -199
  96. package/src/types.ts +13 -3
  97. package/dist/parser/handlers.d.ts +0 -115
  98. package/dist/parser/handlers.js +0 -352
  99. package/dist/parser/handlers.js.map +0 -1
  100. package/dist/parser/states.d.ts +0 -47
  101. package/dist/parser/states.js +0 -176
  102. package/dist/parser/states.js.map +0 -1
  103. package/esm/parser/handlers.d.ts +0 -115
  104. package/esm/parser/handlers.js +0 -328
  105. package/esm/parser/handlers.js.map +0 -1
  106. package/esm/parser/states.d.ts +0 -47
  107. package/esm/parser/states.js +0 -140
  108. package/esm/parser/states.js.map +0 -1
  109. package/src/parser/handlers.ts +0 -372
  110. package/src/parser/states.ts +0 -159
package/src/grammar.ts CHANGED
@@ -5,6 +5,16 @@
5
5
 
6
6
  /* eslint eqeqeq:0 */
7
7
 
8
+ import { collectionFunctions } from './collections.ts'
9
+ import {
10
+ addsPairwise,
11
+ anyPair,
12
+ isIn,
13
+ isList,
14
+ matches,
15
+ pairwise
16
+ } from './operators.ts'
17
+
8
18
  import type { JexlValue } from './types.ts'
9
19
 
10
20
  export type BinaryOpEval = (left: JexlValue, right: JexlValue) => JexlValue
@@ -40,6 +50,8 @@ export interface BinaryOp {
40
50
  * which is how `-x` is distinguished from `a - x`.
41
51
  */
42
52
  unaryEval?: (right: JexlValue) => JexlValue
53
+ /** Groups a chain from the right, so `a ^ b ^ c` is `a ^ (b ^ c)`. */
54
+ rightAssociative?: boolean
43
55
  }
44
56
 
45
57
  export type UnaryOpEval = (right: JexlValue) => JexlValue
@@ -52,9 +64,9 @@ export interface UnaryOp {
52
64
 
53
65
  /**
54
66
  * The punctuation elements of the grammar. Unlike operators these carry no
55
- * behavior of their own; the Parser's state machine gives them meaning. The
56
- * type is a literal union rather than `string` so that `GrammarElement` is a
57
- * discriminated union, letting operator properties be accessed without casts.
67
+ * behavior of their own; the Parser gives them meaning. The type is a literal
68
+ * union rather than `string` so that `GrammarElement` is a discriminated union,
69
+ * letting operator properties be accessed without casts.
58
70
  */
59
71
  export interface SimpleElement {
60
72
  type:
@@ -69,6 +81,7 @@ export interface SimpleElement {
69
81
  | 'closeParen'
70
82
  | 'question'
71
83
  | 'semicolon'
84
+ | 'arrow'
72
85
  }
73
86
 
74
87
  export type GrammarElement = BinaryOp | UnaryOp | SimpleElement
@@ -76,20 +89,71 @@ export type GrammarElement = BinaryOp | UnaryOp | SimpleElement
76
89
  /** A registered function, as jexl calls it once the operands are evaluated. */
77
90
  export type GrammarFn = (...args: JexlValue[]) => JexlValue
78
91
 
92
+ /**
93
+ * Reads `key` off `subject` for `subject.key` and `subject[key]`, in place of
94
+ * a plain property read. Like a registered function, it is handed host values
95
+ * jexl has no type for, so the subject is only promised not to be nullish.
96
+ */
97
+ export type GetMember = (
98
+ subject: NonNullable<unknown>,
99
+ key: string | number
100
+ ) => unknown
101
+
102
+ /**
103
+ * Called once per bare variable name as an expression compiles. A reader it
104
+ * returns replaces `context[name]` for that name; `undefined` keeps it.
105
+ */
106
+ export type VariableReader = (
107
+ name: string
108
+ ) => ((context: Record<string, unknown>) => unknown) | undefined
109
+
79
110
  export interface Grammar {
80
111
  elements: Record<string, GrammarElement>
81
112
  functions: Record<string, GrammarFn>
113
+ getMember?: GetMember
114
+ variableReader?: VariableReader
82
115
  }
83
116
 
84
- /**
85
- * Returns the binding power of a grammar element, or 0 for elements that
86
- * aren't operators and therefore don't participate in precedence.
87
- */
88
- export function precedenceOf(elem: GrammarElement | undefined) {
89
- return elem && (elem.type === 'binaryOp' || elem.type === 'unaryOp')
90
- ? elem.precedence
91
- : 0
117
+ const plus = (left: JexlValue, right: JexlValue) =>
118
+ (left as number) + (right as number)
119
+ const minus = (left: JexlValue, right: JexlValue) =>
120
+ (left as number) - (right as number)
121
+ const times = (left: JexlValue, right: JexlValue) =>
122
+ (left as number) * (right as number)
123
+ const divide = (left: JexlValue, right: JexlValue) =>
124
+ (left as number) / (right as number)
125
+ const floorDivide = (left: JexlValue, right: JexlValue) =>
126
+ Math.floor((left as number) / (right as number))
127
+ const modulo = (left: JexlValue, right: JexlValue) =>
128
+ (left as number) % (right as number)
129
+ const power = (left: JexlValue, right: JexlValue) =>
130
+ (left as number) ** (right as number)
131
+ const loose = (left: JexlValue, right: JexlValue) => left == right
132
+ // a missing value orders against nothing, as bcftools skips one; JavaScript
133
+ // alone would read null as 0, making [null, 0.2] < 0.05 true
134
+ const greater = (left: JexlValue, right: JexlValue) =>
135
+ left != null && right != null && (left as number) > (right as number)
136
+ const atLeast = (left: JexlValue, right: JexlValue) =>
137
+ left != null && right != null && (left as number) >= (right as number)
138
+ const less = (left: JexlValue, right: JexlValue) =>
139
+ left != null && right != null && (left as number) < (right as number)
140
+ const atMost = (left: JexlValue, right: JexlValue) =>
141
+ left != null && right != null && (left as number) <= (right as number)
142
+ // named so the compiler can recognize the built-in forms and short-circuit
143
+ // without the thunks a host's evalOnDemand operator is handed
144
+ export const and: BinaryOpEvalOnDemand = (left, right) => {
145
+ const leftVal = left.eval()
146
+ return leftVal ? right.eval() : leftVal
92
147
  }
148
+ export const or: BinaryOpEvalOnDemand = (left, right) => {
149
+ const leftVal = left.eval()
150
+ return leftVal || right.eval()
151
+ }
152
+ export const nullish: BinaryOpEvalOnDemand = (left, right) =>
153
+ left.eval() ?? right.eval()
154
+
155
+ const equals = (left: JexlValue, right: JexlValue) =>
156
+ isList(left, right) ? anyPair(loose, left, right) : left == right
93
157
 
94
158
  export const getGrammar = (): Grammar => ({
95
159
  /**
@@ -109,111 +173,129 @@ export const getGrammar = (): Grammar => ({
109
173
  ')': { type: 'closeParen' },
110
174
  '?': { type: 'question' },
111
175
  ';': { type: 'semicolon' },
176
+ '=>': { type: 'arrow' },
112
177
  '+': {
113
178
  type: 'binaryOp',
114
179
  precedence: 30,
115
- eval: (left, right) => (left as number) + (right as number)
180
+ eval: (left, right) =>
181
+ addsPairwise(left, right)
182
+ ? pairwise(plus, left, right)
183
+ : plus(left, right)
116
184
  },
117
185
  '-': {
118
186
  type: 'binaryOp',
119
187
  precedence: 30,
120
- eval: (left, right) => (left as number) - (right as number),
121
- unaryEval: (right) => -(right as number)
188
+ eval: (left, right) =>
189
+ isList(left, right) ? pairwise(minus, left, right) : minus(left, right),
190
+ unaryEval: (right) =>
191
+ Array.isArray(right)
192
+ ? right.map((value) => -(value as number))
193
+ : -(right as number)
122
194
  },
123
195
  '*': {
124
196
  type: 'binaryOp',
125
197
  precedence: 40,
126
- eval: (left, right) => (left as number) * (right as number)
198
+ eval: (left, right) =>
199
+ isList(left, right) ? pairwise(times, left, right) : times(left, right)
127
200
  },
128
201
  '/': {
129
202
  type: 'binaryOp',
130
203
  precedence: 40,
131
- eval: (left, right) => (left as number) / (right as number)
204
+ eval: (left, right) =>
205
+ isList(left, right)
206
+ ? pairwise(divide, left, right)
207
+ : divide(left, right)
132
208
  },
133
209
  '//': {
134
210
  type: 'binaryOp',
135
211
  precedence: 40,
136
- eval: (left, right) => Math.floor((left as number) / (right as number))
212
+ eval: (left, right) =>
213
+ isList(left, right)
214
+ ? pairwise(floorDivide, left, right)
215
+ : floorDivide(left, right)
137
216
  },
138
217
  '%': {
139
218
  type: 'binaryOp',
140
219
  precedence: 50,
141
- eval: (left, right) => (left as number) % (right as number)
220
+ eval: (left, right) =>
221
+ isList(left, right)
222
+ ? pairwise(modulo, left, right)
223
+ : modulo(left, right)
142
224
  },
143
225
  '^': {
144
226
  type: 'binaryOp',
145
227
  precedence: 50,
146
- eval: (left, right) => (left as number) ** (right as number)
228
+ rightAssociative: true,
229
+ eval: (left, right) =>
230
+ isList(left, right) ? pairwise(power, left, right) : power(left, right)
147
231
  },
148
232
  '==': {
149
233
  type: 'binaryOp',
150
234
  precedence: 20,
151
- eval: (left, right) => left == right
235
+ eval: equals
152
236
  },
153
237
  '!=': {
154
238
  type: 'binaryOp',
155
239
  precedence: 20,
156
- eval: (left, right) => left != right
240
+ eval: (left, right) => !equals(left, right)
241
+ },
242
+ '~': {
243
+ type: 'binaryOp',
244
+ precedence: 20,
245
+ eval: matches
246
+ },
247
+ '!~': {
248
+ type: 'binaryOp',
249
+ precedence: 20,
250
+ eval: (left, right) => !matches(left, right)
157
251
  },
158
252
  '>': {
159
253
  type: 'binaryOp',
160
254
  precedence: 20,
161
- eval: (left, right) => (left as number) > (right as number)
255
+ eval: (left, right) =>
256
+ isList(left, right)
257
+ ? anyPair(greater, left, right)
258
+ : greater(left, right)
162
259
  },
163
260
  '>=': {
164
261
  type: 'binaryOp',
165
262
  precedence: 20,
166
- eval: (left, right) => (left as number) >= (right as number)
263
+ eval: (left, right) =>
264
+ isList(left, right)
265
+ ? anyPair(atLeast, left, right)
266
+ : atLeast(left, right)
167
267
  },
168
268
  '<': {
169
269
  type: 'binaryOp',
170
270
  precedence: 20,
171
- eval: (left, right) => (left as number) < (right as number)
271
+ eval: (left, right) =>
272
+ isList(left, right) ? anyPair(less, left, right) : less(left, right)
172
273
  },
173
274
  '<=': {
174
275
  type: 'binaryOp',
175
276
  precedence: 20,
176
- eval: (left, right) => (left as number) <= (right as number)
277
+ eval: (left, right) =>
278
+ isList(left, right) ? anyPair(atMost, left, right) : atMost(left, right)
177
279
  },
178
280
  '&&': {
179
281
  type: 'binaryOp',
180
- precedence: 10,
181
- evalOnDemand: (left, right) => {
182
- const leftVal = left.eval()
183
- if (!leftVal) {
184
- return leftVal
185
- }
186
- return right.eval()
187
- }
282
+ precedence: 11,
283
+ evalOnDemand: and
188
284
  },
189
285
  '||': {
190
286
  type: 'binaryOp',
191
287
  precedence: 10,
192
- evalOnDemand: (left, right) => {
193
- const leftVal = left.eval()
194
- if (leftVal) {
195
- return leftVal
196
- }
197
- return right.eval()
198
- }
288
+ evalOnDemand: or
289
+ },
290
+ '??': {
291
+ type: 'binaryOp',
292
+ precedence: 10,
293
+ evalOnDemand: nullish
199
294
  },
200
295
  in: {
201
296
  type: 'binaryOp',
202
297
  precedence: 20,
203
- eval: (left, right) => {
204
- if (typeof right === 'string') {
205
- // only a primitive has a meaningful substring form. An absent or
206
- // structured left operand is not "in" a string, and must not be
207
- // coerced to '' — every string contains the empty string, so that
208
- // made `feature.missingAttr in "someString"` true for every feature.
209
- return typeof left === 'string' ||
210
- typeof left === 'number' ||
211
- typeof left === 'boolean'
212
- ? right.includes(String(left))
213
- : false
214
- }
215
- return Array.isArray(right) ? right.includes(left) : false
216
- }
298
+ eval: isIn
217
299
  },
218
300
  '!': {
219
301
  type: 'unaryOp',
@@ -243,5 +325,5 @@ export const getGrammar = (): Grammar => ({
243
325
  * than throw. An error is only appropriate when the function would normally
244
326
  * return a value, but cannot due to some other failure.
245
327
  */
246
- functions: {}
328
+ functions: { ...collectionFunctions }
247
329
  })
package/src/index.ts CHANGED
@@ -4,7 +4,22 @@
4
4
  */
5
5
 
6
6
  export { Jexl, default } from './Jexl.ts'
7
+ export type { JexlOptions } from './Jexl.ts'
8
+ export type { GetMember, VariableReader } from './grammar.ts'
7
9
  export { default as Expression } from './Expression.ts'
10
+ export { analyze } from './analyze.ts'
11
+ export { check, print, recordOf, union } from './check.ts'
12
+ export {
13
+ callSubject,
14
+ conditions,
15
+ fromConditions,
16
+ pathSubject,
17
+ printCondition
18
+ } from './conditions.ts'
8
19
  export { default as Lexer } from './Lexer.ts'
20
+ export { JexlSyntaxError } from './errors.ts'
9
21
  export { getGrammar } from './grammar.ts'
22
+ export type * from './analyze.ts'
23
+ export type * from './check.ts'
24
+ export type * from './conditions.ts'
10
25
  export type * from './types.ts'
@@ -0,0 +1,151 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+
6
+ import type { JexlValue } from './types.ts'
7
+
8
+ type Test = (left: JexlValue, right: JexlValue) => boolean
9
+ type Arithmetic = (left: JexlValue, right: JexlValue) => JexlValue
10
+
11
+ export function isList(left: JexlValue, right: JexlValue) {
12
+ return Array.isArray(left) || Array.isArray(right)
13
+ }
14
+
15
+ /**
16
+ * A comparison over list operands: it holds when it holds for any value, as
17
+ * bcftools compares a multi-valued tag, so `AF > 0.05` is true when any
18
+ * allele's frequency is and `CLNSIG == 'Pathogenic'` when any significance is.
19
+ * Each operator calls this only once {@link isList} says it must, so the
20
+ * comparison of two single values stays a direct call V8 can inline.
21
+ */
22
+ export function anyPair(
23
+ test: Test,
24
+ left: JexlValue,
25
+ right: JexlValue
26
+ ): boolean {
27
+ return Array.isArray(left)
28
+ ? left.some((l) => anyPair(test, l, right))
29
+ : Array.isArray(right)
30
+ ? right.some((r) => anyPair(test, left, r))
31
+ : test(left, right)
32
+ }
33
+
34
+ function hasText(value: JexlValue) {
35
+ return (
36
+ typeof value === 'string' ||
37
+ (Array.isArray(value) && value.some((item) => typeof item === 'string'))
38
+ )
39
+ }
40
+
41
+ /**
42
+ * Whether `+` pairs its operands value by value: only when a list is involved
43
+ * and neither side holds text, so `REF + '>' + ALT` still joins as JavaScript
44
+ * joins, `'A>T,C'`, while `DP + 1` adds.
45
+ */
46
+ export function addsPairwise(left: JexlValue, right: JexlValue) {
47
+ return isList(left, right) && !hasText(left) && !hasText(right)
48
+ }
49
+
50
+ /**
51
+ * Arithmetic over list operands, value by value, as bcftools and R apply it:
52
+ * `AC / AN` divides each count, and a list of one, or a lone value, pairs with
53
+ * every value of the other side. Lists of two other lengths have no pairing
54
+ * and yield undefined.
55
+ */
56
+ export function pairwise(
57
+ fn: Arithmetic,
58
+ left: JexlValue,
59
+ right: JexlValue
60
+ ): JexlValue {
61
+ const ls = Array.isArray(left) ? left : [left]
62
+ const rs = Array.isArray(right) ? right : [right]
63
+ if (ls.length !== rs.length && ls.length !== 1 && rs.length !== 1) {
64
+ return undefined
65
+ }
66
+ const length = ls.length === 1 ? rs.length : ls.length
67
+ const out: JexlValue[] = new Array(length)
68
+ for (let i = 0; i < length; i++) {
69
+ out[i] = fn(
70
+ ls.length === 1 ? ls[0] : ls[i],
71
+ rs.length === 1 ? rs[0] : rs[i]
72
+ )
73
+ }
74
+ return out
75
+ }
76
+
77
+ const patterns = new Map<string, RegExp>()
78
+ const MAX_PATTERNS = 1000
79
+
80
+ /**
81
+ * The regular expression a pattern string stands for. A leading `(?i)`, the
82
+ * inline flag PCRE, Python and Go read, makes it case-insensitive.
83
+ */
84
+ function regex(pattern: string) {
85
+ let re = patterns.get(pattern)
86
+ if (!re) {
87
+ try {
88
+ re = pattern.startsWith('(?i)')
89
+ ? new RegExp(pattern.slice(4), 'i')
90
+ : new RegExp(pattern)
91
+ } catch {
92
+ throw new Error(`Invalid regular expression: ${pattern}`)
93
+ }
94
+ if (patterns.size >= MAX_PATTERNS) {
95
+ patterns.clear()
96
+ }
97
+ patterns.set(pattern, re)
98
+ }
99
+ return re
100
+ }
101
+
102
+ /** Whether a value, or any value of a list, matches a pattern string. */
103
+ export function matches(value: JexlValue, pattern: JexlValue): boolean {
104
+ if (typeof pattern !== 'string') {
105
+ return false
106
+ }
107
+ const re = regex(pattern)
108
+ const test = (v: JexlValue) =>
109
+ (typeof v === 'string' ||
110
+ typeof v === 'number' ||
111
+ typeof v === 'boolean') &&
112
+ re.test(String(v))
113
+ return Array.isArray(value) ? value.some(test) : test(value)
114
+ }
115
+
116
+ /**
117
+ * Whether a value is in a container: a substring of a string, a member of a
118
+ * list or Set, or a key of a Map or plain object. A list on the left is in
119
+ * the container when any of its values is.
120
+ */
121
+ export function isIn(left: JexlValue, right: JexlValue): boolean {
122
+ if (Array.isArray(left)) {
123
+ return left.some((l) => isIn(l, right))
124
+ }
125
+ if (typeof right === 'string') {
126
+ // only a primitive has a meaningful substring form. An absent or
127
+ // structured left operand is not "in" a string, and must not be coerced
128
+ // to '' — every string contains the empty string
129
+ return typeof left === 'string' ||
130
+ typeof left === 'number' ||
131
+ typeof left === 'boolean'
132
+ ? right.includes(String(left))
133
+ : false
134
+ }
135
+ if (Array.isArray(right)) {
136
+ return right.includes(left)
137
+ }
138
+ if (right instanceof Set || right instanceof Map) {
139
+ return right.has(left)
140
+ }
141
+ if (
142
+ typeof right === 'object' &&
143
+ right !== null &&
144
+ (typeof left === 'string' ||
145
+ typeof left === 'number' ||
146
+ typeof left === 'boolean')
147
+ ) {
148
+ return Object.hasOwn(right, String(left))
149
+ }
150
+ return false
151
+ }