@atomic-ehr/fhirpath 0.0.2 → 0.0.3
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 +716 -238
- package/dist/index.d.ts +225 -119
- package/dist/index.js +10911 -5600
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/src/analyzer/augmentor.ts +242 -0
- package/src/analyzer/cursor-services.ts +75 -0
- package/src/analyzer/scope-manager.ts +57 -0
- package/src/analyzer/trivia-indexer.ts +58 -0
- package/src/analyzer/type-compat.ts +157 -0
- package/src/analyzer/utils.ts +132 -0
- package/src/analyzer.ts +921 -1208
- package/src/completion-provider.ts +209 -191
- package/src/{quantity-value.ts → complex-types/quantity-value.ts} +112 -22
- package/src/complex-types/temporal.ts +1737 -0
- package/src/errors.ts +25 -3
- package/src/index.ts +17 -104
- package/src/inspect.ts +4 -4
- package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
- package/src/interpreter/navigator.ts +94 -0
- package/src/interpreter/runtime-context.ts +273 -0
- package/src/interpreter.ts +435 -469
- package/src/lexer.ts +188 -210
- package/src/model-provider.ts +71 -43
- package/src/operations/abs-function.ts +1 -1
- package/src/operations/aggregate-function.ts +84 -5
- package/src/operations/all-function.ts +4 -3
- package/src/operations/allFalse-function.ts +2 -1
- package/src/operations/allTrue-function.ts +2 -1
- package/src/operations/and-operator.ts +2 -1
- package/src/operations/anyFalse-function.ts +2 -1
- package/src/operations/anyTrue-function.ts +2 -1
- package/src/operations/as-function.ts +58 -0
- package/src/operations/as-operator.ts +57 -19
- package/src/operations/ceiling-function.ts +1 -1
- package/src/operations/children-function.ts +14 -5
- package/src/operations/combine-function.ts +6 -3
- package/src/operations/combine-operator.ts +6 -7
- package/src/operations/comparison.ts +692 -0
- package/src/operations/contains-function.ts +1 -1
- package/src/operations/contains-operator.ts +2 -1
- package/src/operations/convertsToBoolean-function.ts +78 -0
- package/src/operations/convertsToDecimal-function.ts +82 -0
- package/src/operations/convertsToInteger-function.ts +71 -0
- package/src/operations/convertsToLong-function.ts +89 -0
- package/src/operations/convertsToQuantity-function.ts +116 -0
- package/src/operations/convertsToString-function.ts +88 -0
- package/src/operations/count-function.ts +2 -1
- package/src/operations/dateOf-function.ts +69 -0
- package/src/operations/dayOf-function.ts +66 -0
- package/src/operations/decimal-boundaries.ts +133 -0
- package/src/operations/defineVariable-function.ts +130 -17
- package/src/operations/distinct-function.ts +1 -1
- package/src/operations/div-operator.ts +1 -1
- package/src/operations/divide-operator.ts +12 -7
- package/src/operations/dot-operator.ts +1 -1
- package/src/operations/empty-function.ts +30 -21
- package/src/operations/endsWith-function.ts +6 -1
- package/src/operations/equal-operator.ts +23 -32
- package/src/operations/equivalent-operator.ts +13 -53
- package/src/operations/exclude-function.ts +2 -1
- package/src/operations/exists-function.ts +4 -3
- package/src/operations/first-function.ts +1 -1
- package/src/operations/floor-function.ts +1 -1
- package/src/operations/greater-operator.ts +20 -3
- package/src/operations/greater-or-equal-operator.ts +20 -3
- package/src/operations/highBoundary-function.ts +120 -0
- package/src/operations/hourOf-function.ts +66 -0
- package/src/operations/iif-function.ts +186 -7
- package/src/operations/implies-operator.ts +1 -1
- package/src/operations/in-operator.ts +2 -1
- package/src/operations/index.ts +41 -0
- package/src/operations/indexOf-function.ts +1 -1
- package/src/operations/intersect-function.ts +1 -1
- package/src/operations/is-function.ts +59 -0
- package/src/operations/is-operator.ts +20 -9
- package/src/operations/isDistinct-function.ts +2 -1
- package/src/operations/join-function.ts +1 -1
- package/src/operations/last-function.ts +1 -1
- package/src/operations/lastIndexOf-function.ts +85 -0
- package/src/operations/length-function.ts +1 -1
- package/src/operations/less-operator.ts +20 -3
- package/src/operations/less-or-equal-operator.ts +20 -3
- package/src/operations/less-than.ts +2 -2
- package/src/operations/lowBoundary-function.ts +120 -0
- package/src/operations/lower-function.ts +1 -1
- package/src/operations/matches-function.ts +86 -0
- package/src/operations/matchesFull-function.ts +96 -0
- package/src/operations/millisecondOf-function.ts +66 -0
- package/src/operations/minus-operator.ts +69 -4
- package/src/operations/minuteOf-function.ts +66 -0
- package/src/operations/mod-operator.ts +1 -1
- package/src/operations/monthOf-function.ts +66 -0
- package/src/operations/multiply-operator.ts +27 -3
- package/src/operations/not-equal-operator.ts +24 -30
- package/src/operations/not-equivalent-operator.ts +13 -53
- package/src/operations/not-function.ts +1 -1
- package/src/operations/ofType-function.ts +8 -12
- package/src/operations/or-operator.ts +2 -1
- package/src/operations/plus-operator.ts +71 -7
- package/src/operations/power-function.ts +35 -10
- package/src/operations/repeat-function.ts +169 -0
- package/src/operations/replace-function.ts +1 -1
- package/src/operations/replaceMatches-function.ts +120 -0
- package/src/operations/round-function.ts +1 -1
- package/src/operations/secondOf-function.ts +66 -0
- package/src/operations/select-function.ts +66 -5
- package/src/operations/single-function.ts +1 -1
- package/src/operations/skip-function.ts +1 -1
- package/src/operations/split-function.ts +1 -1
- package/src/operations/sqrt-function.ts +15 -8
- package/src/operations/startsWith-function.ts +1 -1
- package/src/operations/subsetOf-function.ts +6 -2
- package/src/operations/substring-function.ts +1 -1
- package/src/operations/supersetOf-function.ts +6 -2
- package/src/operations/tail-function.ts +1 -1
- package/src/operations/take-function.ts +1 -1
- package/src/operations/temporal-functions.ts +555 -0
- package/src/operations/timeOf-function.ts +67 -0
- package/src/operations/timezoneOffsetOf-function.ts +69 -0
- package/src/operations/toBoolean-function.ts +27 -8
- package/src/operations/toChars-function.ts +56 -0
- package/src/operations/toDecimal-function.ts +27 -8
- package/src/operations/toInteger-function.ts +15 -3
- package/src/operations/toLong-function.ts +98 -0
- package/src/operations/toQuantity-function.ts +181 -0
- package/src/operations/toString-function.ts +45 -3
- package/src/operations/trace-function.ts +1 -1
- package/src/operations/trim-function.ts +1 -1
- package/src/operations/truncate-function.ts +1 -1
- package/src/operations/unary-minus-operator.ts +2 -2
- package/src/operations/unary-plus-operator.ts +1 -1
- package/src/operations/union-function.ts +1 -1
- package/src/operations/union-operator.ts +16 -26
- package/src/operations/upper-function.ts +1 -1
- package/src/operations/where-function.ts +3 -3
- package/src/operations/xor-operator.ts +1 -1
- package/src/operations/yearOf-function.ts +66 -0
- package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
- package/src/parser.ts +248 -501
- package/src/registry.ts +53 -42
- package/src/types.ts +128 -16
- package/src/utils/pprint.ts +151 -0
package/src/registry.ts
CHANGED
|
@@ -161,7 +161,7 @@ export class Registry {
|
|
|
161
161
|
/**
|
|
162
162
|
* Get functions applicable to a specific type
|
|
163
163
|
*/
|
|
164
|
-
getFunctionsForType(typeName: TypeName | string): FunctionDefinition[] {
|
|
164
|
+
getFunctionsForType = (typeName: TypeName | string): FunctionDefinition[] => {
|
|
165
165
|
const results: FunctionDefinition[] = [];
|
|
166
166
|
|
|
167
167
|
for (const [_, func] of this.functions) {
|
|
@@ -200,53 +200,64 @@ export class Registry {
|
|
|
200
200
|
/**
|
|
201
201
|
* Check if a function is applicable to a type
|
|
202
202
|
*/
|
|
203
|
-
|
|
203
|
+
private getTypeInfoFromString(type: string): TypeInfo {
|
|
204
|
+
const isCollection = type.endsWith('[]');
|
|
205
|
+
const typeName = isCollection ? type.slice(0, -2) : type;
|
|
206
|
+
return {
|
|
207
|
+
type: typeName as TypeName,
|
|
208
|
+
singleton: !isCollection,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public isTypeCompatible(inputType: TypeInfo, requiredType: TypeInfo): boolean {
|
|
213
|
+
// Check singleton compatibility
|
|
214
|
+
if (requiredType.singleton && !inputType.singleton) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const numericTypes: TypeName[] = ['Integer', 'Decimal'];
|
|
219
|
+
const temporalTypes: TypeName[] = ['Date', 'DateTime', 'Time'];
|
|
220
|
+
|
|
221
|
+
// 'Any' type is always compatible
|
|
222
|
+
if (requiredType.type === 'Any') {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Direct type match
|
|
227
|
+
if (inputType.type === requiredType.type) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Numeric types are compatible with each other
|
|
232
|
+
if (numericTypes.includes(inputType.type) && numericTypes.includes(requiredType.type)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Temporal types are compatible with each other
|
|
237
|
+
if (temporalTypes.includes(inputType.type) && temporalTypes.includes(requiredType.type)) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// TODO: Add model-aware subtype checking here
|
|
242
|
+
// For now, we only handle the above cases.
|
|
243
|
+
// A full implementation would require a ModelProvider to check subtype relationships.
|
|
244
|
+
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
isFunctionApplicableToType(functionName: string, type: string): boolean {
|
|
204
249
|
const func = this.getFunction(functionName);
|
|
205
250
|
if (!func) return false;
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
if (!func.signatures || func.signatures.length === 0) return true;
|
|
209
|
-
|
|
210
|
-
// Check if we're dealing with a collection type
|
|
211
|
-
const isCollection = typeof typeName === 'string' && typeName.endsWith('[]');
|
|
212
|
-
|
|
213
|
-
// Check if ANY signature matches the type
|
|
251
|
+
|
|
252
|
+
// New logic: check all signatures
|
|
214
253
|
for (const signature of func.signatures) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const inputType = signature.input.type;
|
|
219
|
-
const requiresSingleton = signature.input.singleton;
|
|
220
|
-
|
|
221
|
-
// If function requires singleton but we have a collection, skip this signature
|
|
222
|
-
if (requiresSingleton && isCollection) {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// 'Any' type accepts all inputs (but still respects singleton constraint checked above)
|
|
227
|
-
if (inputType === 'Any') return true;
|
|
228
|
-
|
|
229
|
-
// Direct type match
|
|
230
|
-
if (inputType === typeName) return true;
|
|
231
|
-
|
|
232
|
-
// For collection types, check if function can work with collections
|
|
233
|
-
if (typeof typeName === 'string' && typeName.endsWith('[]')) {
|
|
234
|
-
const itemType = typeName.slice(0, -2);
|
|
235
|
-
// Only allow if function doesn't require singleton
|
|
236
|
-
if (inputType === itemType && !requiresSingleton) {
|
|
237
|
-
return true;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Check if it's a numeric type and function accepts numeric types
|
|
242
|
-
const numericTypes = ['Integer', 'Decimal', 'Number'];
|
|
243
|
-
if (numericTypes.includes(typeName as string) && numericTypes.includes(inputType as string)) {
|
|
254
|
+
if (!signature.input) {
|
|
255
|
+
// No input constraint, so it's applicable
|
|
244
256
|
return true;
|
|
245
257
|
}
|
|
246
258
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (temporalTypes.includes(typeName as string) && temporalTypes.includes(inputType as string)) {
|
|
259
|
+
const inputType = this.getTypeInfoFromString(type);
|
|
260
|
+
if (this.isTypeCompatible(inputType, signature.input)) {
|
|
250
261
|
return true;
|
|
251
262
|
}
|
|
252
263
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Token, TokenType } from './lexer';
|
|
2
|
+
import type { AnyCursorNode } from './parser/cursor-nodes';
|
|
2
3
|
|
|
3
4
|
// Precedence levels (higher number = higher precedence)
|
|
4
5
|
export enum PRECEDENCE {
|
|
@@ -25,6 +26,9 @@ export interface TypeInfo<TypeContext = unknown> {
|
|
|
25
26
|
// FHIRPath type
|
|
26
27
|
type: TypeName;
|
|
27
28
|
singleton?: boolean;
|
|
29
|
+
|
|
30
|
+
// Indicates this is definitely an empty collection
|
|
31
|
+
isEmpty?: boolean;
|
|
28
32
|
|
|
29
33
|
// Model type information FHIR.Patient; FHIR.string;
|
|
30
34
|
namespace?: string;
|
|
@@ -77,6 +81,7 @@ export interface OperatorSignature {
|
|
|
77
81
|
export interface OperatorDefinition {
|
|
78
82
|
symbol: string;
|
|
79
83
|
name: string;
|
|
84
|
+
doesNotPropagateEmpty?: boolean;
|
|
80
85
|
category: string[];
|
|
81
86
|
precedence: PRECEDENCE;
|
|
82
87
|
associativity: 'left' | 'right';
|
|
@@ -97,6 +102,7 @@ export interface FunctionSignature {
|
|
|
97
102
|
optional?: boolean;
|
|
98
103
|
type: TypeInfo;
|
|
99
104
|
expression?: boolean;
|
|
105
|
+
typeReference?: boolean; // When true, this parameter expects a type name (e.g., ofType(Patient))
|
|
100
106
|
}>;
|
|
101
107
|
result: TypeInfo | 'inputType' | 'inputTypeSingleton' | 'parameterType';
|
|
102
108
|
}
|
|
@@ -108,6 +114,16 @@ export interface FunctionDefinition {
|
|
|
108
114
|
examples: string[];
|
|
109
115
|
signatures: FunctionSignature[];
|
|
110
116
|
evaluate: FunctionEvaluator;
|
|
117
|
+
inferResultType?: (
|
|
118
|
+
analyzer: any,
|
|
119
|
+
node: any,
|
|
120
|
+
inputType?: TypeInfo
|
|
121
|
+
) => Promise<TypeInfo>;
|
|
122
|
+
analyze?: (
|
|
123
|
+
context: AnalysisContext,
|
|
124
|
+
args: ASTNode[]
|
|
125
|
+
) => Promise<InternalAnalysisResult> | InternalAnalysisResult;
|
|
126
|
+
doesNotPropagateEmpty?: boolean; // When true, function doesn't propagate empty collections
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
// Node types enum - string-based for better debugging
|
|
@@ -115,9 +131,9 @@ export enum NodeType {
|
|
|
115
131
|
EOF = 'EOF',
|
|
116
132
|
Binary = 'Binary',
|
|
117
133
|
Unary = 'Unary',
|
|
118
|
-
TypeOrIdentifier = 'TypeOrIdentifier',
|
|
119
134
|
Identifier = 'Identifier',
|
|
120
135
|
Literal = 'Literal',
|
|
136
|
+
TemporalLiteral = 'TemporalLiteral',
|
|
121
137
|
Function = 'Function',
|
|
122
138
|
Variable = 'Variable',
|
|
123
139
|
Index = 'Index',
|
|
@@ -170,7 +186,7 @@ export interface ParserOptions {
|
|
|
170
186
|
// Base structure for all AST nodes
|
|
171
187
|
export interface BaseASTNode {
|
|
172
188
|
// Core properties - always present
|
|
173
|
-
type: NodeType | 'Error';
|
|
189
|
+
type: NodeType | 'Error' | 'CursorNode';
|
|
174
190
|
|
|
175
191
|
// LSP-compatible range - always present for LSP features
|
|
176
192
|
range: Range;
|
|
@@ -211,17 +227,19 @@ export interface IdentifierNode extends BaseASTNode {
|
|
|
211
227
|
symbolKind?: SymbolKind.Variable | SymbolKind.Function | SymbolKind.Property;
|
|
212
228
|
}
|
|
213
229
|
|
|
214
|
-
export interface TypeOrIdentifierNode extends BaseASTNode {
|
|
215
|
-
type: NodeType.TypeOrIdentifier;
|
|
216
|
-
name: string;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
230
|
export interface LiteralNode extends BaseASTNode {
|
|
220
231
|
type: NodeType.Literal;
|
|
221
232
|
value: any;
|
|
222
233
|
valueType: 'string' | 'number' | 'boolean' | 'date' | 'time' | 'datetime' | 'null';
|
|
223
234
|
}
|
|
224
235
|
|
|
236
|
+
export interface TemporalLiteralNode extends BaseASTNode {
|
|
237
|
+
type: NodeType.TemporalLiteral;
|
|
238
|
+
value: any; // The parsed temporal object
|
|
239
|
+
valueType: 'date' | 'time' | 'datetime';
|
|
240
|
+
rawValue: string; // The original string value (without @)
|
|
241
|
+
}
|
|
242
|
+
|
|
225
243
|
export interface BinaryNode extends BaseASTNode {
|
|
226
244
|
type: NodeType.Binary;
|
|
227
245
|
operator: string;
|
|
@@ -281,14 +299,13 @@ export interface QuantityNode extends BaseASTNode {
|
|
|
281
299
|
type: NodeType.Quantity;
|
|
282
300
|
value: number;
|
|
283
301
|
unit: string;
|
|
284
|
-
isCalendarUnit?: boolean;
|
|
285
302
|
}
|
|
286
303
|
|
|
287
304
|
// Unified ASTNode type - discriminated union
|
|
288
305
|
export type ASTNode =
|
|
289
306
|
| IdentifierNode
|
|
290
|
-
| TypeOrIdentifierNode
|
|
291
307
|
| LiteralNode
|
|
308
|
+
| TemporalLiteralNode
|
|
292
309
|
| BinaryNode
|
|
293
310
|
| UnaryNode
|
|
294
311
|
| FunctionNode
|
|
@@ -299,7 +316,8 @@ export type ASTNode =
|
|
|
299
316
|
| CollectionNode
|
|
300
317
|
| TypeReferenceNode
|
|
301
318
|
| QuantityNode
|
|
302
|
-
| ErrorNode
|
|
319
|
+
| ErrorNode
|
|
320
|
+
| AnyCursorNode;
|
|
303
321
|
|
|
304
322
|
export interface RuntimeContext {
|
|
305
323
|
input: any[];
|
|
@@ -311,7 +329,7 @@ export interface RuntimeContext {
|
|
|
311
329
|
|
|
312
330
|
// Evaluation result - everything is a collection of boxed values
|
|
313
331
|
export interface EvaluationResult {
|
|
314
|
-
value: import('./boxing').FHIRPathValue[];
|
|
332
|
+
value: import('./interpreter/boxing').FHIRPathValue[];
|
|
315
333
|
context: RuntimeContext;
|
|
316
334
|
}
|
|
317
335
|
|
|
@@ -337,6 +355,8 @@ export interface Diagnostic {
|
|
|
337
355
|
export interface AnalysisResult {
|
|
338
356
|
diagnostics: Diagnostic[];
|
|
339
357
|
ast: ASTNode;
|
|
358
|
+
type?: TypeInfo;
|
|
359
|
+
userVariables?: Map<string, TypeInfo>;
|
|
340
360
|
}
|
|
341
361
|
|
|
342
362
|
// Parse error type
|
|
@@ -353,7 +373,7 @@ export interface ParseResult {
|
|
|
353
373
|
errors: ParseError[];
|
|
354
374
|
indexes?: {
|
|
355
375
|
nodeById: Map<string, ASTNode>;
|
|
356
|
-
nodesByType: Map<NodeType | 'Error', ASTNode[]>;
|
|
376
|
+
nodesByType: Map<NodeType | 'Error' | 'CursorNode', ASTNode[]>;
|
|
357
377
|
identifiers: Map<string, ASTNode[]>;
|
|
358
378
|
};
|
|
359
379
|
cursorContext?: {
|
|
@@ -363,15 +383,15 @@ export interface ParseResult {
|
|
|
363
383
|
};
|
|
364
384
|
}
|
|
365
385
|
|
|
366
|
-
export type NodeEvaluator = (node: ASTNode, input: import('./boxing').FHIRPathValue[], context: RuntimeContext) => Promise<EvaluationResult>;
|
|
386
|
+
export type NodeEvaluator = (node: ASTNode, input: import('./interpreter/boxing').FHIRPathValue[], context: RuntimeContext) => Promise<EvaluationResult>;
|
|
367
387
|
|
|
368
|
-
export type OperationEvaluator = (input: import('./boxing').FHIRPathValue[], context: RuntimeContext, ...args: any[]) => Promise<EvaluationResult>;
|
|
388
|
+
export type OperationEvaluator = (input: import('./interpreter/boxing').FHIRPathValue[], context: RuntimeContext, ...args: any[]) => Promise<EvaluationResult>;
|
|
369
389
|
|
|
370
390
|
export type FunctionEvaluator = (
|
|
371
|
-
input: import('./boxing').FHIRPathValue[],
|
|
391
|
+
input: import('./interpreter/boxing').FHIRPathValue[],
|
|
372
392
|
context: RuntimeContext,
|
|
373
393
|
args: ASTNode[],
|
|
374
|
-
evaluator: (node: ASTNode, input: import('./boxing').FHIRPathValue[], context: RuntimeContext) => Promise<EvaluationResult>
|
|
394
|
+
evaluator: (node: ASTNode, input: import('./interpreter/boxing').FHIRPathValue[], context: RuntimeContext) => Promise<EvaluationResult>
|
|
375
395
|
) => Promise<EvaluationResult>;
|
|
376
396
|
|
|
377
397
|
// Type guards for optional properties
|
|
@@ -403,3 +423,95 @@ export function isFunctionNode(node: ASTNode): node is FunctionNode {
|
|
|
403
423
|
return node.type === NodeType.Function;
|
|
404
424
|
}
|
|
405
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Result of analyzing a single AST node in the context-flow architecture.
|
|
428
|
+
*/
|
|
429
|
+
export interface InternalAnalysisResult {
|
|
430
|
+
type: TypeInfo;
|
|
431
|
+
diagnostics: Diagnostic[];
|
|
432
|
+
context?: AnalysisContext;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Immutable context that flows through the analysis tree.
|
|
437
|
+
* Carries variable scopes and input types through the AST.
|
|
438
|
+
*/
|
|
439
|
+
export class AnalysisContext {
|
|
440
|
+
constructor(
|
|
441
|
+
public readonly inputType: TypeInfo,
|
|
442
|
+
public readonly systemVariables: ReadonlyMap<string, TypeInfo>,
|
|
443
|
+
public readonly userVariables: ReadonlyMap<string, TypeInfo>,
|
|
444
|
+
private readonly analyzeNodeCallback: (node: ASTNode, ctx: AnalysisContext) => Promise<InternalAnalysisResult>,
|
|
445
|
+
public readonly modelProvider?: ModelProvider,
|
|
446
|
+
public readonly hasDynamicVariables: boolean = false,
|
|
447
|
+
public readonly _chainHead: boolean = true
|
|
448
|
+
) {}
|
|
449
|
+
|
|
450
|
+
withUserVariable(name: string, type: TypeInfo): AnalysisContext {
|
|
451
|
+
const newUserVars = new Map(this.userVariables);
|
|
452
|
+
newUserVars.set(name, type);
|
|
453
|
+
return new AnalysisContext(
|
|
454
|
+
this.inputType,
|
|
455
|
+
this.systemVariables,
|
|
456
|
+
newUserVars,
|
|
457
|
+
this.analyzeNodeCallback,
|
|
458
|
+
this.modelProvider,
|
|
459
|
+
this.hasDynamicVariables,
|
|
460
|
+
this._chainHead
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
withSystemVariable(name: string, type: TypeInfo): AnalysisContext {
|
|
465
|
+
const newSystemVars = new Map(this.systemVariables);
|
|
466
|
+
newSystemVars.set(name, type);
|
|
467
|
+
return new AnalysisContext(
|
|
468
|
+
this.inputType,
|
|
469
|
+
newSystemVars,
|
|
470
|
+
this.userVariables,
|
|
471
|
+
this.analyzeNodeCallback,
|
|
472
|
+
this.modelProvider,
|
|
473
|
+
this.hasDynamicVariables,
|
|
474
|
+
this._chainHead
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
withInputType(type: TypeInfo): AnalysisContext {
|
|
479
|
+
return new AnalysisContext(
|
|
480
|
+
type,
|
|
481
|
+
this.systemVariables,
|
|
482
|
+
this.userVariables,
|
|
483
|
+
this.analyzeNodeCallback,
|
|
484
|
+
this.modelProvider,
|
|
485
|
+
this.hasDynamicVariables,
|
|
486
|
+
false
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
withDynamicVariables(): AnalysisContext {
|
|
491
|
+
return new AnalysisContext(
|
|
492
|
+
this.inputType,
|
|
493
|
+
this.systemVariables,
|
|
494
|
+
this.userVariables,
|
|
495
|
+
this.analyzeNodeCallback,
|
|
496
|
+
this.modelProvider,
|
|
497
|
+
true,
|
|
498
|
+
this._chainHead
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
fork(): AnalysisContext {
|
|
503
|
+
return new AnalysisContext(
|
|
504
|
+
this.inputType,
|
|
505
|
+
new Map(this.systemVariables),
|
|
506
|
+
new Map(this.userVariables),
|
|
507
|
+
this.analyzeNodeCallback,
|
|
508
|
+
this.modelProvider,
|
|
509
|
+
this.hasDynamicVariables,
|
|
510
|
+
this._chainHead
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
analyzeNode(node: ASTNode): Promise<InternalAnalysisResult> {
|
|
515
|
+
return this.analyzeNodeCallback(node, this);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { NodeType } from '../types';
|
|
2
|
+
import type {
|
|
3
|
+
ASTNode,
|
|
4
|
+
IdentifierNode,
|
|
5
|
+
LiteralNode,
|
|
6
|
+
UnaryNode,
|
|
7
|
+
BinaryNode,
|
|
8
|
+
FunctionNode,
|
|
9
|
+
IndexNode,
|
|
10
|
+
MembershipTestNode,
|
|
11
|
+
TypeCastNode,
|
|
12
|
+
CollectionNode,
|
|
13
|
+
TypeReferenceNode,
|
|
14
|
+
QuantityNode,
|
|
15
|
+
} from '../types';
|
|
16
|
+
|
|
17
|
+
export function pprint(node: ASTNode, indent: number = 0): string {
|
|
18
|
+
const spaces = ' '.repeat(indent);
|
|
19
|
+
|
|
20
|
+
switch (node.type) {
|
|
21
|
+
case NodeType.Literal: {
|
|
22
|
+
const lit = node as LiteralNode;
|
|
23
|
+
if (lit.valueType === 'string') {
|
|
24
|
+
return `"${lit.value}"`;
|
|
25
|
+
} else if (lit.valueType === 'null') {
|
|
26
|
+
return 'null';
|
|
27
|
+
}
|
|
28
|
+
return String(lit.value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
case NodeType.Identifier: {
|
|
32
|
+
const id = node as IdentifierNode;
|
|
33
|
+
return id.name;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
case NodeType.Variable: {
|
|
37
|
+
return (node as any).name;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
case NodeType.Binary: {
|
|
41
|
+
const bin = node as BinaryNode;
|
|
42
|
+
const op = bin.operator;
|
|
43
|
+
|
|
44
|
+
const leftStr = pprint(bin.left, 0);
|
|
45
|
+
const rightStr = pprint(bin.right, 0);
|
|
46
|
+
|
|
47
|
+
if (leftStr.length + rightStr.length + op.length + 4 < 60 &&
|
|
48
|
+
!leftStr.includes('\n') && !rightStr.includes('\n')) {
|
|
49
|
+
return `(${op} ${leftStr} ${rightStr})`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return `(${op}\n${spaces} ${pprint(bin.left, indent + 2)}\n${spaces} ${pprint(bin.right, indent + 2)})`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case NodeType.Unary: {
|
|
56
|
+
const un = node as UnaryNode;
|
|
57
|
+
const operandStr = pprint(un.operand, 0);
|
|
58
|
+
|
|
59
|
+
if (operandStr.length < 40 && !operandStr.includes('\n')) {
|
|
60
|
+
return `(${un.operator} ${operandStr})`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return `(${un.operator}\n${spaces} ${pprint(un.operand, indent + 2)})`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case NodeType.Function: {
|
|
67
|
+
const fn = node as FunctionNode;
|
|
68
|
+
const nameStr = pprint(fn.name, 0);
|
|
69
|
+
|
|
70
|
+
if (fn.arguments.length === 0) {
|
|
71
|
+
return `(${nameStr})`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const argStrs = fn.arguments.map(arg => pprint(arg, 0));
|
|
75
|
+
const totalLen = nameStr.length + argStrs.reduce((sum, s) => sum + s.length + 1, 0) + 2;
|
|
76
|
+
|
|
77
|
+
if (totalLen < 60 && argStrs.every(s => !s.includes('\n'))) {
|
|
78
|
+
return `(${nameStr} ${argStrs.join(' ')})`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const argLines = fn.arguments.map(arg => `${spaces} ${pprint(arg, indent + 2)}`);
|
|
82
|
+
return `(${nameStr}\n${argLines.join('\n')})`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case NodeType.Index: {
|
|
86
|
+
const idx = node as IndexNode;
|
|
87
|
+
const exprStr = pprint(idx.expression, 0);
|
|
88
|
+
const indexStr = pprint(idx.index, 0);
|
|
89
|
+
|
|
90
|
+
if (exprStr.length + indexStr.length < 50 &&
|
|
91
|
+
!exprStr.includes('\n') && !indexStr.includes('\n')) {
|
|
92
|
+
return `([] ${exprStr} ${indexStr})`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return `([]\n${spaces} ${pprint(idx.expression, indent + 2)}\n${spaces} ${pprint(idx.index, indent + 2)})`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case NodeType.MembershipTest: {
|
|
99
|
+
const mt = node as MembershipTestNode;
|
|
100
|
+
const exprStr = pprint(mt.expression, 0);
|
|
101
|
+
|
|
102
|
+
if (exprStr.length + mt.targetType.length < 50 && !exprStr.includes('\n')) {
|
|
103
|
+
return `(is ${exprStr} ${mt.targetType})`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return `(is\n${spaces} ${pprint(mt.expression, indent + 2)}\n${spaces} ${mt.targetType})`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
case NodeType.TypeCast: {
|
|
110
|
+
const tc = node as TypeCastNode;
|
|
111
|
+
const exprStr = pprint(tc.expression, 0);
|
|
112
|
+
|
|
113
|
+
if (exprStr.length + tc.targetType.length < 50 && !exprStr.includes('\n')) {
|
|
114
|
+
return `(as ${exprStr} ${tc.targetType})`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return `(as\n${spaces} ${pprint(tc.expression, indent + 2)}\n${spaces} ${tc.targetType})`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case NodeType.Collection: {
|
|
121
|
+
const coll = node as CollectionNode;
|
|
122
|
+
|
|
123
|
+
if (coll.elements.length === 0) {
|
|
124
|
+
return '{}';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const elemStrs = coll.elements.map(e => pprint(e, 0));
|
|
128
|
+
const totalLen = elemStrs.reduce((sum, s) => sum + s.length + 1, 2);
|
|
129
|
+
|
|
130
|
+
if (totalLen < 60 && elemStrs.every(s => !s.includes('\n'))) {
|
|
131
|
+
return `{${elemStrs.join(' ')}}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const elemLines = coll.elements.map(e => `${spaces} ${pprint(e, indent + 2)}`);
|
|
135
|
+
return `{\n${elemLines.join('\n')}\n${spaces}}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case NodeType.TypeReference: {
|
|
139
|
+
const tr = node as TypeReferenceNode;
|
|
140
|
+
return `Type[${tr.typeName}]`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case NodeType.Quantity: {
|
|
144
|
+
const q = node as QuantityNode;
|
|
145
|
+
return `${q.value} '${q.unit}'`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
return `<unknown:${(node as any).type}>`;
|
|
150
|
+
}
|
|
151
|
+
}
|