@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,358 @@
|
|
|
1
|
+
import type { Function } from '../types';
|
|
2
|
+
import { defaultFunctionAnalyze } from '../default-analyzers';
|
|
3
|
+
import { defaultFunctionCompile } from '../default-compilers';
|
|
4
|
+
import { RuntimeContextManager } from '../../runtime/context';
|
|
5
|
+
import { isTruthy } from '../utils';
|
|
6
|
+
|
|
7
|
+
export const whereFunction: Function = {
|
|
8
|
+
name: 'where',
|
|
9
|
+
kind: 'function',
|
|
10
|
+
|
|
11
|
+
syntax: {
|
|
12
|
+
notation: 'where(criteria)'
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
signature: {
|
|
16
|
+
input: {
|
|
17
|
+
types: { kind: 'any' },
|
|
18
|
+
cardinality: 'any'
|
|
19
|
+
},
|
|
20
|
+
parameters: [
|
|
21
|
+
{
|
|
22
|
+
name: 'criteria',
|
|
23
|
+
kind: 'expression',
|
|
24
|
+
types: { kind: 'any' },
|
|
25
|
+
cardinality: 'any',
|
|
26
|
+
optional: false
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
output: {
|
|
30
|
+
type: 'preserve-input',
|
|
31
|
+
cardinality: 'collection'
|
|
32
|
+
},
|
|
33
|
+
propagatesEmpty: true,
|
|
34
|
+
deterministic: true
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
analyze: defaultFunctionAnalyze,
|
|
38
|
+
|
|
39
|
+
evaluate: (interpreter, context, input, criteria) => {
|
|
40
|
+
const results: any[] = [];
|
|
41
|
+
if (!criteria) {
|
|
42
|
+
throw new Error('where() requires a predicate expression');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < input.length; i++) {
|
|
46
|
+
const item = input[i];
|
|
47
|
+
const iterContext = RuntimeContextManager.withIterator(context, item, i);
|
|
48
|
+
const result = interpreter.evaluate(criteria, [item], iterContext);
|
|
49
|
+
|
|
50
|
+
if (isTruthy(result.value)) {
|
|
51
|
+
results.push(item);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { value: results, context };
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
compile: (compiler, input, args) => {
|
|
59
|
+
const criteria = args[0];
|
|
60
|
+
if (!criteria) {
|
|
61
|
+
throw new Error('where() requires a predicate expression');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
fn: (ctx) => {
|
|
66
|
+
const inputValue = input.fn(ctx);
|
|
67
|
+
const results: any[] = [];
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < inputValue.length; i++) {
|
|
70
|
+
const item = inputValue[i];
|
|
71
|
+
const iterCtx = RuntimeContextManager.withIterator(ctx, item, i);
|
|
72
|
+
const predicateResult = criteria.fn(iterCtx);
|
|
73
|
+
|
|
74
|
+
if (isTruthy(predicateResult)) {
|
|
75
|
+
results.push(item);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return results;
|
|
80
|
+
},
|
|
81
|
+
type: input.type,
|
|
82
|
+
isSingleton: false,
|
|
83
|
+
source: `${input.source || ''}.where(${criteria.source || ''})`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const selectFunction: Function = {
|
|
89
|
+
name: 'select',
|
|
90
|
+
kind: 'function',
|
|
91
|
+
|
|
92
|
+
syntax: {
|
|
93
|
+
notation: 'select(expression)'
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
signature: {
|
|
97
|
+
input: {
|
|
98
|
+
types: { kind: 'any' },
|
|
99
|
+
cardinality: 'any'
|
|
100
|
+
},
|
|
101
|
+
parameters: [
|
|
102
|
+
{
|
|
103
|
+
name: 'expression',
|
|
104
|
+
kind: 'expression',
|
|
105
|
+
types: { kind: 'any' },
|
|
106
|
+
cardinality: 'any',
|
|
107
|
+
optional: false
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
output: {
|
|
111
|
+
type: 'any',
|
|
112
|
+
cardinality: 'collection'
|
|
113
|
+
},
|
|
114
|
+
propagatesEmpty: true,
|
|
115
|
+
deterministic: true
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
analyze: function(analyzer, input, args) {
|
|
119
|
+
// First run default validation
|
|
120
|
+
defaultFunctionAnalyze.call(this, analyzer, input, args);
|
|
121
|
+
|
|
122
|
+
// For select(), the output type is determined by the expression result
|
|
123
|
+
const expressionInfo = args[0];
|
|
124
|
+
if (!expressionInfo) {
|
|
125
|
+
return { type: analyzer.resolveType('Any'), isSingleton: false };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// select() always returns a collection
|
|
129
|
+
return { type: expressionInfo.type, isSingleton: false };
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
evaluate: (interpreter, context, input, expression) => {
|
|
133
|
+
const results: any[] = [];
|
|
134
|
+
if (!expression) {
|
|
135
|
+
throw new Error('select() requires an expression');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < input.length; i++) {
|
|
139
|
+
const item = input[i];
|
|
140
|
+
const iterContext = RuntimeContextManager.withIterator(context, item, i);
|
|
141
|
+
const result = interpreter.evaluate(expression, [item], iterContext);
|
|
142
|
+
results.push(...result.value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { value: results, context };
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
compile: (compiler, input, args) => {
|
|
149
|
+
const expression = args[0];
|
|
150
|
+
if (!expression) {
|
|
151
|
+
throw new Error('select() requires an expression');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
fn: (ctx) => {
|
|
156
|
+
const inputValue = input.fn(ctx);
|
|
157
|
+
const results: any[] = [];
|
|
158
|
+
|
|
159
|
+
for (let i = 0; i < inputValue.length; i++) {
|
|
160
|
+
const item = inputValue[i];
|
|
161
|
+
const iterCtx = RuntimeContextManager.withIterator(ctx, item, i);
|
|
162
|
+
const exprResult = expression.fn(iterCtx);
|
|
163
|
+
results.push(...exprResult);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return results;
|
|
167
|
+
},
|
|
168
|
+
type: expression.type,
|
|
169
|
+
isSingleton: false,
|
|
170
|
+
source: `${input.source || ''}.select(${expression.source || ''})`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const ofTypeFunction: Function = {
|
|
176
|
+
name: 'ofType',
|
|
177
|
+
kind: 'function',
|
|
178
|
+
|
|
179
|
+
syntax: {
|
|
180
|
+
notation: 'ofType(type)'
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
signature: {
|
|
184
|
+
input: {
|
|
185
|
+
types: { kind: 'any' },
|
|
186
|
+
cardinality: 'any'
|
|
187
|
+
},
|
|
188
|
+
parameters: [
|
|
189
|
+
{
|
|
190
|
+
name: 'type',
|
|
191
|
+
kind: 'expression',
|
|
192
|
+
types: { kind: 'any' },
|
|
193
|
+
cardinality: 'any',
|
|
194
|
+
optional: false
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
output: {
|
|
198
|
+
type: 'preserve-input',
|
|
199
|
+
cardinality: 'collection'
|
|
200
|
+
},
|
|
201
|
+
propagatesEmpty: true,
|
|
202
|
+
deterministic: true
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
analyze: defaultFunctionAnalyze,
|
|
206
|
+
|
|
207
|
+
evaluate: (interpreter, context, input, typeNode) => {
|
|
208
|
+
const results: any[] = [];
|
|
209
|
+
|
|
210
|
+
// Extract type name from AST node
|
|
211
|
+
let typeName: string;
|
|
212
|
+
if (typeNode && typeNode.type === 11) { // TypeReference (correct enum value)
|
|
213
|
+
typeName = typeNode.typeName;
|
|
214
|
+
} else if (typeNode && typeNode.type === 1) { // TypeOrIdentifier
|
|
215
|
+
typeName = typeNode.name;
|
|
216
|
+
} else {
|
|
217
|
+
throw new Error('ofType() requires a type reference');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for (const item of input) {
|
|
221
|
+
if (isOfType(item, typeName)) {
|
|
222
|
+
results.push(item);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { value: results, context };
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
compile: (compiler, input, args) => {
|
|
230
|
+
// The TypeSystem module with isOfType helper
|
|
231
|
+
const { TypeSystem } = require('../utils/type-system');
|
|
232
|
+
|
|
233
|
+
// The argument should be a TypeReference node compiled to return the type name
|
|
234
|
+
const typeArg = args[0];
|
|
235
|
+
if (!typeArg) {
|
|
236
|
+
throw new Error('ofType() requires a type reference');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
fn: (ctx) => {
|
|
241
|
+
const inputValue = input.fn(ctx);
|
|
242
|
+
const results: any[] = [];
|
|
243
|
+
|
|
244
|
+
// Get the type name
|
|
245
|
+
let typeName: string;
|
|
246
|
+
try {
|
|
247
|
+
const typeResult = typeArg.fn(ctx);
|
|
248
|
+
if (typeResult.length === 1 && typeof typeResult[0] === 'string') {
|
|
249
|
+
typeName = typeResult[0];
|
|
250
|
+
} else {
|
|
251
|
+
throw new Error('Type reference must evaluate to a string');
|
|
252
|
+
}
|
|
253
|
+
} catch (e: any) {
|
|
254
|
+
// If it's a type reference that cannot be evaluated, extract from source
|
|
255
|
+
if (typeArg.source && /^[A-Z]/.test(typeArg.source)) {
|
|
256
|
+
typeName = typeArg.source;
|
|
257
|
+
} else {
|
|
258
|
+
throw new Error(`Cannot determine type name: ${e.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Filter by type
|
|
263
|
+
for (const item of inputValue) {
|
|
264
|
+
if (TypeSystem.isType(item, typeName)) {
|
|
265
|
+
results.push(item);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return results;
|
|
270
|
+
},
|
|
271
|
+
type: input.type,
|
|
272
|
+
isSingleton: false,
|
|
273
|
+
source: `${input.source || ''}.ofType(${typeArg.source || ''})`
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const repeatFunction: Function = {
|
|
279
|
+
name: 'repeat',
|
|
280
|
+
kind: 'function',
|
|
281
|
+
|
|
282
|
+
syntax: {
|
|
283
|
+
notation: 'repeat(expression)'
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
signature: {
|
|
287
|
+
input: {
|
|
288
|
+
types: { kind: 'any' },
|
|
289
|
+
cardinality: 'any'
|
|
290
|
+
},
|
|
291
|
+
parameters: [
|
|
292
|
+
{
|
|
293
|
+
name: 'expression',
|
|
294
|
+
kind: 'expression',
|
|
295
|
+
types: { kind: 'any' },
|
|
296
|
+
cardinality: 'any',
|
|
297
|
+
optional: false
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
output: {
|
|
301
|
+
type: 'preserve-input',
|
|
302
|
+
cardinality: 'collection'
|
|
303
|
+
},
|
|
304
|
+
propagatesEmpty: true,
|
|
305
|
+
deterministic: true
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
analyze: defaultFunctionAnalyze,
|
|
309
|
+
|
|
310
|
+
evaluate: (interpreter, context, input, expression) => {
|
|
311
|
+
let current = input;
|
|
312
|
+
const seen = new Set();
|
|
313
|
+
|
|
314
|
+
while (current.length > 0) {
|
|
315
|
+
const nextResults: any[] = [];
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i < current.length; i++) {
|
|
318
|
+
const item = current[i];
|
|
319
|
+
const itemKey = JSON.stringify(item);
|
|
320
|
+
|
|
321
|
+
if (!seen.has(itemKey)) {
|
|
322
|
+
seen.add(itemKey);
|
|
323
|
+
const iterContext = RuntimeContextManager.withIterator(context, item, i);
|
|
324
|
+
const result = interpreter.evaluate(expression, [item], iterContext);
|
|
325
|
+
nextResults.push(...result.value);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
current = nextResults;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { value: Array.from(seen).map(key => JSON.parse(key as string)), context };
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
compile: defaultFunctionCompile
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Helper function for type checking
|
|
339
|
+
function isOfType(item: any, typeName: string): boolean {
|
|
340
|
+
// Handle FHIR resource types
|
|
341
|
+
if (item && typeof item === 'object' && item.resourceType === typeName) {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Handle primitive types
|
|
346
|
+
switch (typeName) {
|
|
347
|
+
case 'String':
|
|
348
|
+
return typeof item === 'string';
|
|
349
|
+
case 'Boolean':
|
|
350
|
+
return typeof item === 'boolean';
|
|
351
|
+
case 'Integer':
|
|
352
|
+
return typeof item === 'number' && Number.isInteger(item);
|
|
353
|
+
case 'Decimal':
|
|
354
|
+
return typeof item === 'number';
|
|
355
|
+
default:
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import type { Literal } from '../types';
|
|
2
|
+
import { defaultLiteralAnalyze } from '../default-analyzers';
|
|
3
|
+
|
|
4
|
+
export const integerLiteral: Literal = {
|
|
5
|
+
name: 'integer-literal',
|
|
6
|
+
kind: 'literal',
|
|
7
|
+
|
|
8
|
+
syntax: {
|
|
9
|
+
pattern: /^-?\d+$/,
|
|
10
|
+
notation: '123'
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
signature: {
|
|
14
|
+
output: {
|
|
15
|
+
type: 'Integer',
|
|
16
|
+
cardinality: 'singleton'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
parse: (value: string) => parseInt(value, 10),
|
|
21
|
+
|
|
22
|
+
analyze: defaultLiteralAnalyze,
|
|
23
|
+
|
|
24
|
+
evaluate: (interpreter, context, input, ...args) => {
|
|
25
|
+
// Value is passed as first argument from parser
|
|
26
|
+
const value = args[0];
|
|
27
|
+
return { value: [value], context };
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
compile: (compiler, input, args) => {
|
|
31
|
+
// Value is captured from parser
|
|
32
|
+
const value = (input as any).value || 0; // Parser stores parsed value
|
|
33
|
+
return {
|
|
34
|
+
fn: (ctx) => [value],
|
|
35
|
+
type: compiler.resolveType('Integer'),
|
|
36
|
+
isSingleton: true,
|
|
37
|
+
source: value.toString()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const decimalLiteral: Literal = {
|
|
43
|
+
name: 'decimal-literal',
|
|
44
|
+
kind: 'literal',
|
|
45
|
+
|
|
46
|
+
syntax: {
|
|
47
|
+
pattern: /^-?\d+\.\d+$/,
|
|
48
|
+
notation: '123.45'
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
signature: {
|
|
52
|
+
output: {
|
|
53
|
+
type: 'Decimal',
|
|
54
|
+
cardinality: 'singleton'
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
parse: (value: string) => parseFloat(value),
|
|
59
|
+
|
|
60
|
+
analyze: defaultLiteralAnalyze,
|
|
61
|
+
|
|
62
|
+
evaluate: (interpreter, context, input, ...args) => {
|
|
63
|
+
const value = args[0];
|
|
64
|
+
return { value: [value], context };
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
compile: (compiler, input, args) => {
|
|
68
|
+
const value = (input as any).value || 0.0;
|
|
69
|
+
return {
|
|
70
|
+
fn: (ctx) => [value],
|
|
71
|
+
type: compiler.resolveType('Decimal'),
|
|
72
|
+
isSingleton: true,
|
|
73
|
+
source: value.toString()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const trueLiteral: Literal = {
|
|
79
|
+
name: 'true',
|
|
80
|
+
kind: 'literal',
|
|
81
|
+
|
|
82
|
+
syntax: {
|
|
83
|
+
keywords: ['true'],
|
|
84
|
+
notation: 'true'
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
signature: {
|
|
88
|
+
output: {
|
|
89
|
+
type: 'Boolean',
|
|
90
|
+
cardinality: 'singleton'
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
parse: () => true,
|
|
95
|
+
|
|
96
|
+
analyze: defaultLiteralAnalyze,
|
|
97
|
+
|
|
98
|
+
evaluate: (interpreter, context) => ({ value: [true], context }),
|
|
99
|
+
|
|
100
|
+
compile: (compiler) => ({
|
|
101
|
+
fn: (ctx) => [true],
|
|
102
|
+
type: compiler.resolveType('Boolean'),
|
|
103
|
+
isSingleton: true,
|
|
104
|
+
source: 'true'
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const falseLiteral: Literal = {
|
|
109
|
+
name: 'false',
|
|
110
|
+
kind: 'literal',
|
|
111
|
+
|
|
112
|
+
syntax: {
|
|
113
|
+
keywords: ['false'],
|
|
114
|
+
notation: 'false'
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
signature: {
|
|
118
|
+
output: {
|
|
119
|
+
type: 'Boolean',
|
|
120
|
+
cardinality: 'singleton'
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
parse: () => false,
|
|
125
|
+
|
|
126
|
+
analyze: defaultLiteralAnalyze,
|
|
127
|
+
|
|
128
|
+
evaluate: (interpreter, context) => ({ value: [false], context }),
|
|
129
|
+
|
|
130
|
+
compile: (compiler) => ({
|
|
131
|
+
fn: (ctx) => [false],
|
|
132
|
+
type: compiler.resolveType('Boolean'),
|
|
133
|
+
isSingleton: true,
|
|
134
|
+
source: 'false'
|
|
135
|
+
})
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const stringLiteral: Literal = {
|
|
139
|
+
name: 'string-literal',
|
|
140
|
+
kind: 'literal',
|
|
141
|
+
|
|
142
|
+
syntax: {
|
|
143
|
+
pattern: /^'([^'\\]|\\.)*'$/,
|
|
144
|
+
notation: "'hello'"
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
signature: {
|
|
148
|
+
output: {
|
|
149
|
+
type: 'String',
|
|
150
|
+
cardinality: 'singleton'
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
parse: (value: string) => {
|
|
155
|
+
// Remove quotes and unescape
|
|
156
|
+
const content = value.slice(1, -1);
|
|
157
|
+
return content
|
|
158
|
+
.replace(/\\'/g, "'")
|
|
159
|
+
.replace(/\\\\/g, "\\")
|
|
160
|
+
.replace(/\\n/g, "\n")
|
|
161
|
+
.replace(/\\r/g, "\r")
|
|
162
|
+
.replace(/\\t/g, "\t")
|
|
163
|
+
.replace(/\\f/g, "\f")
|
|
164
|
+
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
analyze: defaultLiteralAnalyze,
|
|
168
|
+
|
|
169
|
+
evaluate: (interpreter, context, input, ...args) => {
|
|
170
|
+
const value = args[0];
|
|
171
|
+
return { value: [value], context };
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
compile: (compiler, input, args) => {
|
|
175
|
+
const value = (input as any).value || '';
|
|
176
|
+
return {
|
|
177
|
+
fn: (ctx) => [value],
|
|
178
|
+
type: compiler.resolveType('String'),
|
|
179
|
+
isSingleton: true,
|
|
180
|
+
source: `'${value.replace(/'/g, "\\'")}'`
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const dateTimeLiteral: Literal = {
|
|
186
|
+
name: 'datetime-literal',
|
|
187
|
+
kind: 'literal',
|
|
188
|
+
|
|
189
|
+
syntax: {
|
|
190
|
+
pattern: /@\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?)?)?/,
|
|
191
|
+
notation: '@2023-01-01T12:00:00Z'
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
signature: {
|
|
195
|
+
output: {
|
|
196
|
+
type: 'DateTime',
|
|
197
|
+
cardinality: 'singleton'
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
parse: (value: string) => {
|
|
202
|
+
// Remove @ prefix
|
|
203
|
+
const dateStr = value.substring(1);
|
|
204
|
+
|
|
205
|
+
// Parse partial dates by padding with defaults
|
|
206
|
+
const parts = dateStr.match(/^(\d{4})(?:-(\d{2})(?:-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:\d{2})?)?)?)?$/);
|
|
207
|
+
if (!parts) throw new Error(`Invalid DateTime literal: ${value}`);
|
|
208
|
+
|
|
209
|
+
const [, year, month = '01', day = '01', hour = '00', minute = '00', second = '00', ms = '0', tz = ''] = parts;
|
|
210
|
+
|
|
211
|
+
const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms.padEnd(3, '0')}${tz || 'Z'}`;
|
|
212
|
+
return new Date(isoString);
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
analyze: defaultLiteralAnalyze,
|
|
216
|
+
|
|
217
|
+
evaluate: (interpreter, context, input, ...args) => {
|
|
218
|
+
const value = args[0];
|
|
219
|
+
return { value: [value], context };
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
compile: (compiler, input, args) => {
|
|
223
|
+
const value = (input as any).value || new Date();
|
|
224
|
+
return {
|
|
225
|
+
fn: (ctx) => [value],
|
|
226
|
+
type: compiler.resolveType('DateTime'),
|
|
227
|
+
isSingleton: true,
|
|
228
|
+
source: `@${value.toISOString()}`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const timeLiteral: Literal = {
|
|
234
|
+
name: 'time-literal',
|
|
235
|
+
kind: 'literal',
|
|
236
|
+
|
|
237
|
+
syntax: {
|
|
238
|
+
pattern: /@T\d{2}:\d{2}(:\d{2}(\.\d+)?)?/,
|
|
239
|
+
notation: '@T12:00:00'
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
signature: {
|
|
243
|
+
output: {
|
|
244
|
+
type: 'Time',
|
|
245
|
+
cardinality: 'singleton'
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
parse: (value: string) => {
|
|
250
|
+
// Remove @T prefix
|
|
251
|
+
const timeStr = value.substring(2);
|
|
252
|
+
|
|
253
|
+
// Parse time components
|
|
254
|
+
const parts = timeStr.match(/^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?$/);
|
|
255
|
+
if (!parts) throw new Error(`Invalid Time literal: ${value}`);
|
|
256
|
+
|
|
257
|
+
const [, hour, minute, second = '00', ms = '0'] = parts as string[];
|
|
258
|
+
|
|
259
|
+
// Store as object with time components
|
|
260
|
+
return {
|
|
261
|
+
hour: parseInt(hour || '0', 10),
|
|
262
|
+
minute: parseInt(minute || '0', 10),
|
|
263
|
+
second: parseInt(second || '0', 10),
|
|
264
|
+
millisecond: parseInt(ms.padEnd(3, '0'), 10)
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
analyze: defaultLiteralAnalyze,
|
|
269
|
+
|
|
270
|
+
evaluate: (interpreter, context, input, ...args) => {
|
|
271
|
+
const value = args[0];
|
|
272
|
+
return { value: [value], context };
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
compile: (compiler, input, args) => {
|
|
276
|
+
const value = (input as any).value || { hour: 0, minute: 0, second: 0 };
|
|
277
|
+
return {
|
|
278
|
+
fn: (ctx) => [value],
|
|
279
|
+
type: compiler.resolveType('Time'),
|
|
280
|
+
isSingleton: true,
|
|
281
|
+
source: `@T${String(value.hour).padStart(2, '0')}:${String(value.minute).padStart(2, '0')}:${String(value.second).padStart(2, '0')}`
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export const quantityLiteral: Literal = {
|
|
287
|
+
name: 'quantity-literal',
|
|
288
|
+
kind: 'literal',
|
|
289
|
+
|
|
290
|
+
syntax: {
|
|
291
|
+
pattern: /^-?\d+(\.\d+)?\s*'[^']+'$/,
|
|
292
|
+
notation: "5.4 'mg'"
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
signature: {
|
|
296
|
+
output: {
|
|
297
|
+
type: 'Quantity',
|
|
298
|
+
cardinality: 'singleton'
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
parse: (value: string) => {
|
|
303
|
+
const match = value.match(/^(-?\d+(?:\.\d+)?)\s*'([^']+)'$/);
|
|
304
|
+
if (!match) throw new Error(`Invalid Quantity literal: ${value}`);
|
|
305
|
+
|
|
306
|
+
const [, num, unit] = match as [string, string, string];
|
|
307
|
+
return {
|
|
308
|
+
value: parseFloat(num || '0'),
|
|
309
|
+
unit: unit || ''
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
analyze: defaultLiteralAnalyze,
|
|
314
|
+
|
|
315
|
+
evaluate: (interpreter, context, input, ...args) => {
|
|
316
|
+
const value = args[0];
|
|
317
|
+
return { value: [value], context };
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
compile: (compiler, input, args) => {
|
|
321
|
+
const value = (input as any).value || { value: 0, unit: '' };
|
|
322
|
+
return {
|
|
323
|
+
fn: (ctx) => [value],
|
|
324
|
+
type: compiler.resolveType('Quantity'),
|
|
325
|
+
isSingleton: true,
|
|
326
|
+
source: `${value.value} '${value.unit}'`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// Export all literals
|
|
332
|
+
export const literals = [
|
|
333
|
+
integerLiteral,
|
|
334
|
+
decimalLiteral,
|
|
335
|
+
trueLiteral,
|
|
336
|
+
falseLiteral,
|
|
337
|
+
stringLiteral,
|
|
338
|
+
dateTimeLiteral,
|
|
339
|
+
timeLiteral,
|
|
340
|
+
quantityLiteral
|
|
341
|
+
];
|