@atomic-ehr/fhirpath 0.0.1-canary.0c6931e.20250727185306
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 +473 -0
- package/dist/index.d.ts +462 -0
- package/dist/index.js +10307 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
- package/src/analyzer/analyzer.ts +499 -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 +157 -0
- package/src/api/errors.ts +145 -0
- package/src/api/expression.ts +156 -0
- package/src/api/index.ts +122 -0
- package/src/api/inspect.ts +99 -0
- package/src/api/registry.ts +128 -0
- package/src/api/types.ts +210 -0
- package/src/compiler/compiler.ts +546 -0
- package/src/compiler/index.ts +2 -0
- package/src/compiler/prototype-context-adapter.ts +99 -0
- package/src/compiler/types.ts +24 -0
- package/src/index.ts +107 -0
- package/src/interpreter/README.md +78 -0
- package/src/interpreter/interpreter.ts +475 -0
- package/src/interpreter/types.ts +108 -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/lexer2/index.md +232 -0
- package/src/lexer2/index.perf.test.ts +68 -0
- package/src/lexer2/index.test.ts +549 -0
- package/src/lexer2/index.ts +1251 -0
- package/src/lexer2/notes.md +173 -0
- package/src/lexer2/optimization-summary.md +718 -0
- package/src/parser/ast-factory.ts +220 -0
- package/src/parser/ast.ts +144 -0
- package/src/parser/collection-parser.ts +89 -0
- package/src/parser/diagnostic-messages.ts +216 -0
- package/src/parser/diagnostics.ts +85 -0
- package/src/parser/error-reporter.ts +230 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/literal-parser.ts +103 -0
- package/src/parser/parse-error.ts +16 -0
- package/src/parser/parser-error-factory.ts +141 -0
- package/src/parser/parser-state.ts +134 -0
- package/src/parser/parser.ts +1272 -0
- package/src/parser/pprint.ts +169 -0
- package/src/parser/precedence-manager.ts +64 -0
- package/src/parser/source-mapper.ts +248 -0
- package/src/parser/special-constructs.ts +142 -0
- package/src/parser/token-navigator.ts +110 -0
- package/src/parser/types.ts +60 -0
- package/src/parser2/index.md +177 -0
- package/src/parser2/index.perf.test.ts +184 -0
- package/src/parser2/index.test.ts +305 -0
- package/src/parser2/index.ts +578 -0
- package/src/parser2/optimization-summary.md +176 -0
- package/src/registry/default-analyzers.ts +257 -0
- package/src/registry/default-compilers.ts +31 -0
- package/src/registry/index.ts +96 -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 +439 -0
- package/src/registry/operations/math.ts +128 -0
- package/src/registry/operations/membership.ts +132 -0
- package/src/registry/operations/navigation.ts +52 -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 +308 -0
- package/src/registry/operations/utility.ts +644 -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 +158 -0
- package/src/runtime/debug-context.ts +135 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { TokenType } from '../../lexer/token';
|
|
2
|
+
import type { Operator, Function } from '../types';
|
|
3
|
+
import { defaultOperatorAnalyze, defaultFunctionAnalyze } 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, // AND precedence
|
|
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, // OR precedence
|
|
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 notFunction: Function = {
|
|
199
|
+
name: 'not',
|
|
200
|
+
kind: 'function',
|
|
201
|
+
|
|
202
|
+
syntax: {
|
|
203
|
+
notation: 'not()'
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
signature: {
|
|
207
|
+
input: {
|
|
208
|
+
types: { kind: 'any' },
|
|
209
|
+
cardinality: 'any'
|
|
210
|
+
},
|
|
211
|
+
parameters: [], // not() takes no parameters
|
|
212
|
+
output: {
|
|
213
|
+
type: 'Boolean',
|
|
214
|
+
cardinality: 'singleton'
|
|
215
|
+
},
|
|
216
|
+
propagatesEmpty: false, // not() on empty returns true
|
|
217
|
+
deterministic: true
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
analyze: defaultFunctionAnalyze,
|
|
221
|
+
|
|
222
|
+
evaluate: (interpreter, context, input, ...args) => {
|
|
223
|
+
// not() should not accept any parameters
|
|
224
|
+
if (args.length > 0) {
|
|
225
|
+
throw new Error('not() function does not accept any parameters');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// not() behavior per spec:
|
|
229
|
+
// not(true) = false
|
|
230
|
+
// not(false) = true
|
|
231
|
+
// not(empty) = true
|
|
232
|
+
// not(single non-boolean) = false
|
|
233
|
+
// not(multiple values) = empty
|
|
234
|
+
|
|
235
|
+
if (input.length === 0) {
|
|
236
|
+
// not empty = true
|
|
237
|
+
return { value: [true], context };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (input.length > 1) {
|
|
241
|
+
// not(multiple values) = empty
|
|
242
|
+
return { value: [], context };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Single value - check if it's a boolean
|
|
246
|
+
const singleValue = input[0];
|
|
247
|
+
if (typeof singleValue === 'boolean') {
|
|
248
|
+
// Negate boolean value
|
|
249
|
+
return { value: [!singleValue], context };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Non-boolean single value returns false
|
|
253
|
+
return { value: [false], context };
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
compile: (compiler, input) => ({
|
|
257
|
+
fn: (ctx) => {
|
|
258
|
+
const inputValue = input?.fn(ctx) || [];
|
|
259
|
+
|
|
260
|
+
// Match interpreter behavior
|
|
261
|
+
if (inputValue.length === 0) return [true];
|
|
262
|
+
if (inputValue.length > 1) return [];
|
|
263
|
+
|
|
264
|
+
const singleValue = inputValue[0];
|
|
265
|
+
if (typeof singleValue === 'boolean') {
|
|
266
|
+
return [!singleValue];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Non-boolean single value returns false
|
|
270
|
+
return [false];
|
|
271
|
+
},
|
|
272
|
+
type: compiler.resolveType('Boolean'),
|
|
273
|
+
isSingleton: true,
|
|
274
|
+
source: `${input?.source || ''}.not()`
|
|
275
|
+
})
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const xorOperator: Operator = {
|
|
279
|
+
name: 'xor',
|
|
280
|
+
kind: 'operator',
|
|
281
|
+
|
|
282
|
+
syntax: {
|
|
283
|
+
form: 'infix',
|
|
284
|
+
token: TokenType.XOR,
|
|
285
|
+
precedence: 2, // OR precedence
|
|
286
|
+
associativity: 'left',
|
|
287
|
+
notation: 'a xor b'
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
signature: {
|
|
291
|
+
parameters: [
|
|
292
|
+
{ name: 'left', types: { kind: 'any' }, cardinality: 'any' },
|
|
293
|
+
{ name: 'right', types: { kind: 'any' }, cardinality: 'any' }
|
|
294
|
+
],
|
|
295
|
+
output: {
|
|
296
|
+
type: 'Boolean',
|
|
297
|
+
cardinality: 'singleton'
|
|
298
|
+
},
|
|
299
|
+
propagatesEmpty: true
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
analyze: defaultOperatorAnalyze,
|
|
303
|
+
|
|
304
|
+
evaluate: (interpreter, context, input, left, right) => {
|
|
305
|
+
if (left.length === 0 || right.length === 0) return { value: [], context };
|
|
306
|
+
|
|
307
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
308
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
309
|
+
|
|
310
|
+
// XOR: true when exactly one is true
|
|
311
|
+
return { value: [leftBool !== rightBool], context };
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
compile: (compiler, input, args) => ({
|
|
315
|
+
fn: (ctx) => {
|
|
316
|
+
const left = args[0]?.fn(ctx) || [];
|
|
317
|
+
const right = args[1]?.fn(ctx) || [];
|
|
318
|
+
|
|
319
|
+
if (left.length === 0 || right.length === 0) return [];
|
|
320
|
+
|
|
321
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
322
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
323
|
+
|
|
324
|
+
return [leftBool !== rightBool];
|
|
325
|
+
},
|
|
326
|
+
type: compiler.resolveType('Boolean'),
|
|
327
|
+
isSingleton: true,
|
|
328
|
+
source: `${args[0]?.source || ''} xor ${args[1]?.source || ''}`
|
|
329
|
+
})
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export const impliesOperator: Operator = {
|
|
333
|
+
name: 'implies',
|
|
334
|
+
kind: 'operator',
|
|
335
|
+
|
|
336
|
+
syntax: {
|
|
337
|
+
form: 'infix',
|
|
338
|
+
token: TokenType.IMPLIES,
|
|
339
|
+
precedence: 1, // IMPLIES - lowest precedence
|
|
340
|
+
associativity: 'left',
|
|
341
|
+
notation: 'a implies b'
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
signature: {
|
|
345
|
+
parameters: [
|
|
346
|
+
{ name: 'left', types: { kind: 'any' }, cardinality: 'any' },
|
|
347
|
+
{ name: 'right', types: { kind: 'any' }, cardinality: 'any' }
|
|
348
|
+
],
|
|
349
|
+
output: {
|
|
350
|
+
type: 'Boolean',
|
|
351
|
+
cardinality: 'singleton'
|
|
352
|
+
},
|
|
353
|
+
propagatesEmpty: false // Special three-valued logic
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
analyze: defaultOperatorAnalyze,
|
|
357
|
+
|
|
358
|
+
evaluate: (interpreter, context, input, left, right) => {
|
|
359
|
+
// Implies truth table:
|
|
360
|
+
// true implies true = true
|
|
361
|
+
// true implies false = false
|
|
362
|
+
// true implies empty = empty
|
|
363
|
+
// false implies anything = true
|
|
364
|
+
// empty implies true = true
|
|
365
|
+
// empty implies false = empty
|
|
366
|
+
// empty implies empty = empty
|
|
367
|
+
|
|
368
|
+
const leftEmpty = left.length === 0;
|
|
369
|
+
const rightEmpty = right.length === 0;
|
|
370
|
+
|
|
371
|
+
if (!leftEmpty) {
|
|
372
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
373
|
+
if (!leftBool) {
|
|
374
|
+
// false implies anything = true
|
|
375
|
+
return { value: [true], context };
|
|
376
|
+
}
|
|
377
|
+
// left is true
|
|
378
|
+
if (rightEmpty) {
|
|
379
|
+
// true implies empty = empty
|
|
380
|
+
return { value: [], context };
|
|
381
|
+
}
|
|
382
|
+
// true implies right
|
|
383
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
384
|
+
return { value: [rightBool], context };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// left is empty
|
|
388
|
+
if (rightEmpty) {
|
|
389
|
+
// empty implies empty = empty
|
|
390
|
+
return { value: [], context };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
394
|
+
if (rightBool) {
|
|
395
|
+
// empty implies true = true
|
|
396
|
+
return { value: [true], context };
|
|
397
|
+
}
|
|
398
|
+
// empty implies false = empty
|
|
399
|
+
return { value: [], context };
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
compile: (compiler, input, args) => ({
|
|
403
|
+
fn: (ctx) => {
|
|
404
|
+
const left = args[0]?.fn(ctx) || [];
|
|
405
|
+
const right = args[1]?.fn(ctx) || [];
|
|
406
|
+
|
|
407
|
+
const leftEmpty = left.length === 0;
|
|
408
|
+
const rightEmpty = right.length === 0;
|
|
409
|
+
|
|
410
|
+
if (!leftEmpty) {
|
|
411
|
+
const leftBool = toBoolean(toSingleton(left));
|
|
412
|
+
if (!leftBool) return [true];
|
|
413
|
+
if (rightEmpty) return [];
|
|
414
|
+
return [toBoolean(toSingleton(right))];
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (rightEmpty) return [];
|
|
418
|
+
const rightBool = toBoolean(toSingleton(right));
|
|
419
|
+
if (rightBool) return [true];
|
|
420
|
+
return [];
|
|
421
|
+
},
|
|
422
|
+
type: compiler.resolveType('Boolean'),
|
|
423
|
+
isSingleton: true,
|
|
424
|
+
source: `${args[0]?.source || ''} implies ${args[1]?.source || ''}`
|
|
425
|
+
})
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// Export all logical operators
|
|
429
|
+
export const logicalOperators = [
|
|
430
|
+
andOperator,
|
|
431
|
+
orOperator,
|
|
432
|
+
xorOperator,
|
|
433
|
+
impliesOperator
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
// Export logical functions
|
|
437
|
+
export const logicalFunctions = [
|
|
438
|
+
notFunction
|
|
439
|
+
];
|
|
@@ -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: 4, // MEMBERSHIP precedence
|
|
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: 4, // MEMBERSHIP precedence
|
|
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
|
+
];
|