@atomic-ehr/fhirpath 0.0.1-canary.35b105d.20250724165800
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.
- package/README.md +307 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.js +8185 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/src/analyzer/analyzer.ts +486 -0
- package/src/analyzer/model-provider.ts +244 -0
- package/src/analyzer/schemas/index.ts +2 -0
- package/src/analyzer/schemas/types.ts +40 -0
- package/src/analyzer/types.ts +142 -0
- package/src/api/builder.ts +148 -0
- package/src/api/errors.ts +134 -0
- package/src/api/expression.ts +152 -0
- package/src/api/index.ts +57 -0
- package/src/api/registry.ts +128 -0
- package/src/api/types.ts +154 -0
- package/src/compiler/compiler.ts +579 -0
- package/src/compiler/index.ts +2 -0
- package/src/compiler/prototype-context-adapter.ts +99 -0
- package/src/compiler/types.ts +23 -0
- package/src/index.ts +52 -0
- package/src/interpreter/README.md +78 -0
- package/src/interpreter/interpreter.ts +485 -0
- package/src/interpreter/types.ts +110 -0
- package/src/lexer/char-tables.ts +37 -0
- package/src/lexer/errors.ts +31 -0
- package/src/lexer/index.ts +5 -0
- package/src/lexer/lexer.ts +745 -0
- package/src/lexer/token.ts +104 -0
- package/src/parser/ast.ts +123 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/parser.ts +701 -0
- package/src/parser/pprint.ts +169 -0
- package/src/registry/default-analyzers.ts +257 -0
- package/src/registry/default-compilers.ts +31 -0
- package/src/registry/index.ts +93 -0
- package/src/registry/operations/arithmetic.ts +506 -0
- package/src/registry/operations/collection.ts +425 -0
- package/src/registry/operations/comparison.ts +432 -0
- package/src/registry/operations/existence.ts +703 -0
- package/src/registry/operations/filtering.ts +358 -0
- package/src/registry/operations/literals.ts +341 -0
- package/src/registry/operations/logical.ts +402 -0
- package/src/registry/operations/math.ts +128 -0
- package/src/registry/operations/membership.ts +132 -0
- package/src/registry/operations/string.ts +507 -0
- package/src/registry/operations/subsetting.ts +174 -0
- package/src/registry/operations/type-checking.ts +162 -0
- package/src/registry/operations/type-conversion.ts +404 -0
- package/src/registry/operations/type-operators.ts +307 -0
- package/src/registry/operations/utility.ts +542 -0
- package/src/registry/registry.ts +146 -0
- package/src/registry/types.ts +161 -0
- package/src/registry/utils/evaluation-helpers.ts +93 -0
- package/src/registry/utils/index.ts +3 -0
- package/src/registry/utils/type-system.ts +173 -0
- package/src/runtime/context.ts +179 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { TokenType } from '../../lexer/token';
|
|
2
|
+
import type { Operator } from '../types';
|
|
3
|
+
import { defaultOperatorAnalyze } from '../default-analyzers';
|
|
4
|
+
import { toBoolean, toSingleton } from '../utils';
|
|
5
|
+
|
|
6
|
+
export const andOperator: Operator = {
|
|
7
|
+
name: 'and',
|
|
8
|
+
kind: 'operator',
|
|
9
|
+
|
|
10
|
+
syntax: {
|
|
11
|
+
form: 'infix',
|
|
12
|
+
token: TokenType.AND,
|
|
13
|
+
precedence: 3,
|
|
14
|
+
associativity: 'left',
|
|
15
|
+
notation: 'a and b'
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
signature: {
|
|
19
|
+
parameters: [
|
|
20
|
+
{ name: 'left', types: { kind: 'any' }, cardinality: 'any' },
|
|
21
|
+
{ name: 'right', types: { kind: 'any' }, cardinality: 'any' }
|
|
22
|
+
],
|
|
23
|
+
output: {
|
|
24
|
+
type: 'Boolean',
|
|
25
|
+
cardinality: 'singleton'
|
|
26
|
+
},
|
|
27
|
+
propagatesEmpty: false // Special three-valued logic
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
analyze: defaultOperatorAnalyze,
|
|
31
|
+
|
|
32
|
+
evaluate: (interpreter, context, input, left, right) => {
|
|
33
|
+
// Three-valued logic for and:
|
|
34
|
+
// true and true = true
|
|
35
|
+
// true and false = false
|
|
36
|
+
// true and empty = empty
|
|
37
|
+
// false and anything = false
|
|
38
|
+
// empty and true = empty
|
|
39
|
+
// empty and false = false
|
|
40
|
+
// empty and empty = empty
|
|
41
|
+
|
|
42
|
+
const leftEmpty = left.length === 0;
|
|
43
|
+
const rightEmpty = right.length === 0;
|
|
44
|
+
|
|
45
|
+
if (!leftEmpty) {
|
|
46
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
47
|
+
if (!leftBool) {
|
|
48
|
+
// false and anything = false
|
|
49
|
+
return { value: [false], context };
|
|
50
|
+
}
|
|
51
|
+
// left is true
|
|
52
|
+
if (rightEmpty) {
|
|
53
|
+
// true and empty = empty
|
|
54
|
+
return { value: [], context };
|
|
55
|
+
}
|
|
56
|
+
// true and right
|
|
57
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
58
|
+
return { value: [rightBool], context };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// left is empty
|
|
62
|
+
if (rightEmpty) {
|
|
63
|
+
// empty and empty = empty
|
|
64
|
+
return { value: [], context };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
68
|
+
if (!rightBool) {
|
|
69
|
+
// empty and false = false
|
|
70
|
+
return { value: [false], context };
|
|
71
|
+
}
|
|
72
|
+
// empty and true = empty
|
|
73
|
+
return { value: [], context };
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
compile: (compiler, input, args) => ({
|
|
77
|
+
fn: (ctx) => {
|
|
78
|
+
const left = args[0]?.fn(ctx) || [];
|
|
79
|
+
const right = args[1]?.fn(ctx) || [];
|
|
80
|
+
|
|
81
|
+
const leftEmpty = left.length === 0;
|
|
82
|
+
const rightEmpty = right.length === 0;
|
|
83
|
+
|
|
84
|
+
if (!leftEmpty) {
|
|
85
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
86
|
+
if (!leftBool) return [false];
|
|
87
|
+
if (rightEmpty) return [];
|
|
88
|
+
return [toBoolean(toSingleton(right))];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (rightEmpty) return [];
|
|
92
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
93
|
+
if (!rightBool) return [false];
|
|
94
|
+
return [];
|
|
95
|
+
},
|
|
96
|
+
type: compiler.resolveType('Boolean'),
|
|
97
|
+
isSingleton: true,
|
|
98
|
+
source: `${args[0]?.source || ''} and ${args[1]?.source || ''}`
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const orOperator: Operator = {
|
|
103
|
+
name: 'or',
|
|
104
|
+
kind: 'operator',
|
|
105
|
+
|
|
106
|
+
syntax: {
|
|
107
|
+
form: 'infix',
|
|
108
|
+
token: TokenType.OR,
|
|
109
|
+
precedence: 2,
|
|
110
|
+
associativity: 'left',
|
|
111
|
+
notation: 'a or b'
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
signature: {
|
|
115
|
+
parameters: [
|
|
116
|
+
{ name: 'left', types: { kind: 'any' }, cardinality: 'any' },
|
|
117
|
+
{ name: 'right', types: { kind: 'any' }, cardinality: 'any' }
|
|
118
|
+
],
|
|
119
|
+
output: {
|
|
120
|
+
type: 'Boolean',
|
|
121
|
+
cardinality: 'singleton'
|
|
122
|
+
},
|
|
123
|
+
propagatesEmpty: false // Special three-valued logic
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
analyze: defaultOperatorAnalyze,
|
|
127
|
+
|
|
128
|
+
evaluate: (interpreter, context, input, left, right) => {
|
|
129
|
+
// Three-valued logic for or:
|
|
130
|
+
// true or anything = true
|
|
131
|
+
// false or true = true
|
|
132
|
+
// false or false = false
|
|
133
|
+
// false or empty = empty
|
|
134
|
+
// empty or true = true
|
|
135
|
+
// empty or false = empty
|
|
136
|
+
// empty or empty = empty
|
|
137
|
+
|
|
138
|
+
const leftEmpty = left.length === 0;
|
|
139
|
+
const rightEmpty = right.length === 0;
|
|
140
|
+
|
|
141
|
+
if (!leftEmpty) {
|
|
142
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
143
|
+
if (leftBool) {
|
|
144
|
+
// true or anything = true
|
|
145
|
+
return { value: [true], context };
|
|
146
|
+
}
|
|
147
|
+
// left is false
|
|
148
|
+
if (rightEmpty) {
|
|
149
|
+
// false or empty = empty
|
|
150
|
+
return { value: [], context };
|
|
151
|
+
}
|
|
152
|
+
// false or right
|
|
153
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
154
|
+
return { value: [rightBool], context };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// left is empty
|
|
158
|
+
if (rightEmpty) {
|
|
159
|
+
// empty or empty = empty
|
|
160
|
+
return { value: [], context };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
164
|
+
if (rightBool) {
|
|
165
|
+
// empty or true = true
|
|
166
|
+
return { value: [true], context };
|
|
167
|
+
}
|
|
168
|
+
// empty or false = empty
|
|
169
|
+
return { value: [], context };
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
compile: (compiler, input, args) => ({
|
|
173
|
+
fn: (ctx) => {
|
|
174
|
+
const left = args[0]?.fn(ctx) || [];
|
|
175
|
+
const right = args[1]?.fn(ctx) || [];
|
|
176
|
+
|
|
177
|
+
const leftEmpty = left.length === 0;
|
|
178
|
+
const rightEmpty = right.length === 0;
|
|
179
|
+
|
|
180
|
+
if (!leftEmpty) {
|
|
181
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
182
|
+
if (leftBool) return [true];
|
|
183
|
+
if (rightEmpty) return [];
|
|
184
|
+
return [toBoolean(toSingleton(right))];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (rightEmpty) return [];
|
|
188
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
189
|
+
if (rightBool) return [true];
|
|
190
|
+
return [];
|
|
191
|
+
},
|
|
192
|
+
type: compiler.resolveType('Boolean'),
|
|
193
|
+
isSingleton: true,
|
|
194
|
+
source: `${args[0]?.source || ''} or ${args[1]?.source || ''}`
|
|
195
|
+
})
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export const notOperator: Operator = {
|
|
199
|
+
name: 'not',
|
|
200
|
+
kind: 'operator',
|
|
201
|
+
|
|
202
|
+
syntax: {
|
|
203
|
+
form: 'prefix',
|
|
204
|
+
token: TokenType.NOT,
|
|
205
|
+
precedence: 10, // High precedence for unary operators
|
|
206
|
+
notation: 'not a'
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
signature: {
|
|
210
|
+
parameters: [
|
|
211
|
+
{ name: 'operand', types: { kind: 'any' }, cardinality: 'any' }
|
|
212
|
+
],
|
|
213
|
+
output: {
|
|
214
|
+
type: 'Boolean',
|
|
215
|
+
cardinality: 'preserve-input'
|
|
216
|
+
},
|
|
217
|
+
propagatesEmpty: false // not empty = true
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
analyze: defaultOperatorAnalyze,
|
|
221
|
+
|
|
222
|
+
evaluate: (interpreter, context, input, operand) => {
|
|
223
|
+
if (operand.length === 0) {
|
|
224
|
+
// not empty = true
|
|
225
|
+
return { value: [true], context };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Apply not to each element
|
|
229
|
+
const result = operand.map((item: any) => !toBoolean(item));
|
|
230
|
+
return { value: result, context };
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
compile: (compiler, input, args) => ({
|
|
234
|
+
fn: (ctx) => {
|
|
235
|
+
const operand = args[0]?.fn(ctx) || [];
|
|
236
|
+
if (operand.length === 0) return [true];
|
|
237
|
+
return operand.map((item: any) => !toBoolean(item));
|
|
238
|
+
},
|
|
239
|
+
type: compiler.resolveType('Boolean'),
|
|
240
|
+
isSingleton: args[0]?.isSingleton ?? false,
|
|
241
|
+
source: `not ${args[0]?.source || ''}`
|
|
242
|
+
})
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const xorOperator: Operator = {
|
|
246
|
+
name: 'xor',
|
|
247
|
+
kind: 'operator',
|
|
248
|
+
|
|
249
|
+
syntax: {
|
|
250
|
+
form: 'infix',
|
|
251
|
+
token: TokenType.XOR,
|
|
252
|
+
precedence: 2,
|
|
253
|
+
associativity: 'left',
|
|
254
|
+
notation: 'a xor b'
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
signature: {
|
|
258
|
+
parameters: [
|
|
259
|
+
{ name: 'left', types: { kind: 'any' }, cardinality: 'any' },
|
|
260
|
+
{ name: 'right', types: { kind: 'any' }, cardinality: 'any' }
|
|
261
|
+
],
|
|
262
|
+
output: {
|
|
263
|
+
type: 'Boolean',
|
|
264
|
+
cardinality: 'singleton'
|
|
265
|
+
},
|
|
266
|
+
propagatesEmpty: true
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
analyze: defaultOperatorAnalyze,
|
|
270
|
+
|
|
271
|
+
evaluate: (interpreter, context, input, left, right) => {
|
|
272
|
+
if (left.length === 0 || right.length === 0) return { value: [], context };
|
|
273
|
+
|
|
274
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
275
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
276
|
+
|
|
277
|
+
// XOR: true when exactly one is true
|
|
278
|
+
return { value: [leftBool !== rightBool], context };
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
compile: (compiler, input, args) => ({
|
|
282
|
+
fn: (ctx) => {
|
|
283
|
+
const left = args[0]?.fn(ctx) || [];
|
|
284
|
+
const right = args[1]?.fn(ctx) || [];
|
|
285
|
+
|
|
286
|
+
if (left.length === 0 || right.length === 0) return [];
|
|
287
|
+
|
|
288
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
289
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
290
|
+
|
|
291
|
+
return [leftBool !== rightBool];
|
|
292
|
+
},
|
|
293
|
+
type: compiler.resolveType('Boolean'),
|
|
294
|
+
isSingleton: true,
|
|
295
|
+
source: `${args[0]?.source || ''} xor ${args[1]?.source || ''}`
|
|
296
|
+
})
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export const impliesOperator: Operator = {
|
|
300
|
+
name: 'implies',
|
|
301
|
+
kind: 'operator',
|
|
302
|
+
|
|
303
|
+
syntax: {
|
|
304
|
+
form: 'infix',
|
|
305
|
+
token: TokenType.IMPLIES,
|
|
306
|
+
precedence: 1, // Lowest precedence
|
|
307
|
+
associativity: 'left',
|
|
308
|
+
notation: 'a implies b'
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
signature: {
|
|
312
|
+
parameters: [
|
|
313
|
+
{ name: 'left', types: { kind: 'any' }, cardinality: 'any' },
|
|
314
|
+
{ name: 'right', types: { kind: 'any' }, cardinality: 'any' }
|
|
315
|
+
],
|
|
316
|
+
output: {
|
|
317
|
+
type: 'Boolean',
|
|
318
|
+
cardinality: 'singleton'
|
|
319
|
+
},
|
|
320
|
+
propagatesEmpty: false // Special three-valued logic
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
analyze: defaultOperatorAnalyze,
|
|
324
|
+
|
|
325
|
+
evaluate: (interpreter, context, input, left, right) => {
|
|
326
|
+
// Implies truth table:
|
|
327
|
+
// true implies true = true
|
|
328
|
+
// true implies false = false
|
|
329
|
+
// true implies empty = empty
|
|
330
|
+
// false implies anything = true
|
|
331
|
+
// empty implies true = true
|
|
332
|
+
// empty implies false = empty
|
|
333
|
+
// empty implies empty = empty
|
|
334
|
+
|
|
335
|
+
const leftEmpty = left.length === 0;
|
|
336
|
+
const rightEmpty = right.length === 0;
|
|
337
|
+
|
|
338
|
+
if (!leftEmpty) {
|
|
339
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
340
|
+
if (!leftBool) {
|
|
341
|
+
// false implies anything = true
|
|
342
|
+
return { value: [true], context };
|
|
343
|
+
}
|
|
344
|
+
// left is true
|
|
345
|
+
if (rightEmpty) {
|
|
346
|
+
// true implies empty = empty
|
|
347
|
+
return { value: [], context };
|
|
348
|
+
}
|
|
349
|
+
// true implies right
|
|
350
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
351
|
+
return { value: [rightBool], context };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// left is empty
|
|
355
|
+
if (rightEmpty) {
|
|
356
|
+
// empty implies empty = empty
|
|
357
|
+
return { value: [], context };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
361
|
+
if (rightBool) {
|
|
362
|
+
// empty implies true = true
|
|
363
|
+
return { value: [true], context };
|
|
364
|
+
}
|
|
365
|
+
// empty implies false = empty
|
|
366
|
+
return { value: [], context };
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
compile: (compiler, input, args) => ({
|
|
370
|
+
fn: (ctx) => {
|
|
371
|
+
const left = args[0]?.fn(ctx) || [];
|
|
372
|
+
const right = args[1]?.fn(ctx) || [];
|
|
373
|
+
|
|
374
|
+
const leftEmpty = left.length === 0;
|
|
375
|
+
const rightEmpty = right.length === 0;
|
|
376
|
+
|
|
377
|
+
if (!leftEmpty) {
|
|
378
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
379
|
+
if (!leftBool) return [true];
|
|
380
|
+
if (rightEmpty) return [];
|
|
381
|
+
return [toBoolean(toSingleton(right))];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (rightEmpty) return [];
|
|
385
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
386
|
+
if (rightBool) return [true];
|
|
387
|
+
return [];
|
|
388
|
+
},
|
|
389
|
+
type: compiler.resolveType('Boolean'),
|
|
390
|
+
isSingleton: true,
|
|
391
|
+
source: `${args[0]?.source || ''} implies ${args[1]?.source || ''}`
|
|
392
|
+
})
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Export all logical operators
|
|
396
|
+
export const logicalOperators = [
|
|
397
|
+
andOperator,
|
|
398
|
+
orOperator,
|
|
399
|
+
notOperator,
|
|
400
|
+
xorOperator,
|
|
401
|
+
impliesOperator
|
|
402
|
+
];
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Function } from '../types';
|
|
2
|
+
import { defaultFunctionAnalyze } from '../default-analyzers';
|
|
3
|
+
import { defaultFunctionCompile } from '../default-compilers';
|
|
4
|
+
import { CollectionUtils } from '../../interpreter/types';
|
|
5
|
+
|
|
6
|
+
export const absFunction: Function = {
|
|
7
|
+
name: 'abs',
|
|
8
|
+
kind: 'function',
|
|
9
|
+
|
|
10
|
+
syntax: {
|
|
11
|
+
notation: 'abs()'
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
signature: {
|
|
15
|
+
input: {
|
|
16
|
+
types: { kind: 'union', types: ['Integer', 'Decimal', 'Quantity'] },
|
|
17
|
+
cardinality: 'singleton'
|
|
18
|
+
},
|
|
19
|
+
parameters: [],
|
|
20
|
+
output: {
|
|
21
|
+
type: 'preserve-input',
|
|
22
|
+
cardinality: 'singleton'
|
|
23
|
+
},
|
|
24
|
+
propagatesEmpty: true,
|
|
25
|
+
deterministic: true
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
analyze: defaultFunctionAnalyze,
|
|
29
|
+
|
|
30
|
+
evaluate: (interpreter, context, input) => {
|
|
31
|
+
if (input.length === 0) {
|
|
32
|
+
return { value: [], context };
|
|
33
|
+
}
|
|
34
|
+
const num = CollectionUtils.toSingleton(input);
|
|
35
|
+
return { value: [Math.abs(num)], context };
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
compile: defaultFunctionCompile
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const roundFunction: Function = {
|
|
42
|
+
name: 'round',
|
|
43
|
+
kind: 'function',
|
|
44
|
+
|
|
45
|
+
syntax: {
|
|
46
|
+
notation: 'round(precision)'
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
signature: {
|
|
50
|
+
input: {
|
|
51
|
+
types: { kind: 'union', types: ['Decimal'] },
|
|
52
|
+
cardinality: 'singleton'
|
|
53
|
+
},
|
|
54
|
+
parameters: [
|
|
55
|
+
{
|
|
56
|
+
name: 'precision',
|
|
57
|
+
kind: 'value',
|
|
58
|
+
types: { kind: 'primitive', types: ['Integer'] },
|
|
59
|
+
cardinality: 'singleton',
|
|
60
|
+
optional: true
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
output: {
|
|
64
|
+
type: 'Decimal',
|
|
65
|
+
cardinality: 'singleton'
|
|
66
|
+
},
|
|
67
|
+
propagatesEmpty: true,
|
|
68
|
+
deterministic: true
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
analyze: defaultFunctionAnalyze,
|
|
72
|
+
|
|
73
|
+
evaluate: (interpreter, context, input, precision) => {
|
|
74
|
+
if (input.length === 0) {
|
|
75
|
+
return { value: [], context };
|
|
76
|
+
}
|
|
77
|
+
const num = CollectionUtils.toSingleton(input);
|
|
78
|
+
|
|
79
|
+
if (precision === undefined) {
|
|
80
|
+
return { value: [Math.round(num)], context };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const factor = Math.pow(10, precision);
|
|
84
|
+
return { value: [Math.round(num * factor) / factor], context };
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
compile: defaultFunctionCompile
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const sqrtFunction: Function = {
|
|
91
|
+
name: 'sqrt',
|
|
92
|
+
kind: 'function',
|
|
93
|
+
|
|
94
|
+
syntax: {
|
|
95
|
+
notation: 'sqrt()'
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
signature: {
|
|
99
|
+
input: {
|
|
100
|
+
types: { kind: 'union', types: ['Decimal'] },
|
|
101
|
+
cardinality: 'singleton'
|
|
102
|
+
},
|
|
103
|
+
parameters: [],
|
|
104
|
+
output: {
|
|
105
|
+
type: 'Decimal',
|
|
106
|
+
cardinality: 'singleton'
|
|
107
|
+
},
|
|
108
|
+
propagatesEmpty: true,
|
|
109
|
+
deterministic: true
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
analyze: defaultFunctionAnalyze,
|
|
113
|
+
|
|
114
|
+
evaluate: (interpreter, context, input) => {
|
|
115
|
+
if (input.length === 0) {
|
|
116
|
+
return { value: [], context };
|
|
117
|
+
}
|
|
118
|
+
const num = CollectionUtils.toSingleton(input);
|
|
119
|
+
|
|
120
|
+
if (num < 0) {
|
|
121
|
+
return { value: [], context };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { value: [Math.sqrt(num)], context };
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
compile: defaultFunctionCompile
|
|
128
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { TokenType } from '../../lexer/token';
|
|
2
|
+
import type { Operator } from '../types';
|
|
3
|
+
import { defaultOperatorAnalyze } from '../default-analyzers';
|
|
4
|
+
import { defaultOperatorCompile } from '../default-compilers';
|
|
5
|
+
|
|
6
|
+
// Helper function to check if value is in collection
|
|
7
|
+
function isIn(value: any, collection: any[]): boolean {
|
|
8
|
+
return collection.some(item => {
|
|
9
|
+
if (value === item) return true;
|
|
10
|
+
if (value == null || item == null) return false;
|
|
11
|
+
|
|
12
|
+
// Handle complex equality
|
|
13
|
+
if (typeof value === 'object' && typeof item === 'object') {
|
|
14
|
+
return JSON.stringify(value) === JSON.stringify(item);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export const inOperator: Operator = {
|
|
23
|
+
name: 'in',
|
|
24
|
+
kind: 'operator',
|
|
25
|
+
syntax: {
|
|
26
|
+
form: 'infix',
|
|
27
|
+
token: TokenType.IN,
|
|
28
|
+
precedence: 10,
|
|
29
|
+
associativity: 'left',
|
|
30
|
+
notation: 'a in b'
|
|
31
|
+
},
|
|
32
|
+
signature: {
|
|
33
|
+
parameters: [{ name: 'left' }, { name: 'right' }],
|
|
34
|
+
propagatesEmpty: false,
|
|
35
|
+
output: {
|
|
36
|
+
type: 'Boolean',
|
|
37
|
+
cardinality: 'singleton'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
analyze: defaultOperatorAnalyze,
|
|
41
|
+
evaluate: (_interpreter, context, _input, element, collection) => {
|
|
42
|
+
if (element.length === 0 || collection.length === 0) return { value: [], context };
|
|
43
|
+
|
|
44
|
+
// Handle string substring check
|
|
45
|
+
if (collection.length === 1 && typeof collection[0] === 'string' && typeof element[0] === 'string') {
|
|
46
|
+
return { value: [collection[0].includes(element[0])], context };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { value: [isIn(element[0], collection)], context };
|
|
50
|
+
},
|
|
51
|
+
compile: (compiler, _input, args) => {
|
|
52
|
+
const [leftExpr, rightExpr] = args;
|
|
53
|
+
if (!leftExpr || !rightExpr) {
|
|
54
|
+
throw new Error('in operator requires two arguments');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
fn: (ctx) => {
|
|
58
|
+
const left = leftExpr.fn(ctx);
|
|
59
|
+
const right = rightExpr.fn(ctx);
|
|
60
|
+
if (left.length === 0 || right.length === 0) return [];
|
|
61
|
+
|
|
62
|
+
// Handle string substring check
|
|
63
|
+
if (right.length === 1 && typeof right[0] === 'string' && typeof left[0] === 'string') {
|
|
64
|
+
return [right[0].includes(left[0])];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [isIn(left[0], right)];
|
|
68
|
+
},
|
|
69
|
+
type: compiler.resolveType('Boolean'),
|
|
70
|
+
isSingleton: true
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const containsOperator: Operator = {
|
|
76
|
+
name: 'contains',
|
|
77
|
+
kind: 'operator',
|
|
78
|
+
syntax: {
|
|
79
|
+
form: 'infix',
|
|
80
|
+
token: TokenType.CONTAINS,
|
|
81
|
+
precedence: 10,
|
|
82
|
+
associativity: 'left',
|
|
83
|
+
notation: 'a contains b'
|
|
84
|
+
},
|
|
85
|
+
signature: {
|
|
86
|
+
parameters: [{ name: 'left' }, { name: 'right' }],
|
|
87
|
+
propagatesEmpty: false,
|
|
88
|
+
output: {
|
|
89
|
+
type: 'Boolean',
|
|
90
|
+
cardinality: 'singleton'
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
analyze: defaultOperatorAnalyze,
|
|
94
|
+
evaluate: (_interpreter, context, _input, collection, element) => {
|
|
95
|
+
if (collection.length === 0 || element.length === 0) return { value: [], context };
|
|
96
|
+
|
|
97
|
+
// Handle string substring check
|
|
98
|
+
if (collection.length === 1 && typeof collection[0] === 'string' && typeof element[0] === 'string') {
|
|
99
|
+
return { value: [collection[0].includes(element[0])], context };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { value: [isIn(element[0], collection)], context };
|
|
103
|
+
},
|
|
104
|
+
compile: (compiler, _input, args) => {
|
|
105
|
+
const [leftExpr, rightExpr] = args;
|
|
106
|
+
if (!leftExpr || !rightExpr) {
|
|
107
|
+
throw new Error('contains operator requires two arguments');
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
fn: (ctx) => {
|
|
111
|
+
const left = leftExpr.fn(ctx);
|
|
112
|
+
const right = rightExpr.fn(ctx);
|
|
113
|
+
if (left.length === 0 || right.length === 0) return [];
|
|
114
|
+
|
|
115
|
+
// Handle string substring check
|
|
116
|
+
if (left.length === 1 && typeof left[0] === 'string' && typeof right[0] === 'string') {
|
|
117
|
+
return [left[0].includes(right[0])];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return [isIn(right[0], left)];
|
|
121
|
+
},
|
|
122
|
+
type: compiler.resolveType('Boolean'),
|
|
123
|
+
isSingleton: true
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Export membership operators
|
|
129
|
+
export const membershipOperators = [
|
|
130
|
+
inOperator,
|
|
131
|
+
containsOperator
|
|
132
|
+
];
|