@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,145 @@
|
|
|
1
|
+
import type { Location } from './types';
|
|
2
|
+
|
|
3
|
+
export enum ErrorCode {
|
|
4
|
+
// Parse errors
|
|
5
|
+
PARSE_ERROR = 'PARSE_ERROR',
|
|
6
|
+
SYNTAX_ERROR = 'SYNTAX_ERROR',
|
|
7
|
+
UNEXPECTED_TOKEN = 'UNEXPECTED_TOKEN',
|
|
8
|
+
UNTERMINATED_STRING = 'UNTERMINATED_STRING',
|
|
9
|
+
INVALID_ESCAPE = 'INVALID_ESCAPE',
|
|
10
|
+
|
|
11
|
+
// Parser-specific error codes
|
|
12
|
+
UNCLOSED_PARENTHESIS = 'UNCLOSED_PARENTHESIS',
|
|
13
|
+
UNCLOSED_BRACKET = 'UNCLOSED_BRACKET',
|
|
14
|
+
UNCLOSED_BRACE = 'UNCLOSED_BRACE',
|
|
15
|
+
MISSING_ARGUMENTS = 'MISSING_ARGUMENTS',
|
|
16
|
+
INVALID_OPERATOR = 'INVALID_OPERATOR',
|
|
17
|
+
EXPECTED_EXPRESSION = 'EXPECTED_EXPRESSION',
|
|
18
|
+
EXPECTED_IDENTIFIER = 'EXPECTED_IDENTIFIER',
|
|
19
|
+
MULTIPLE_ERRORS = 'MULTIPLE_ERRORS',
|
|
20
|
+
INVALID_CHARACTER = 'INVALID_CHARACTER',
|
|
21
|
+
|
|
22
|
+
// Type errors
|
|
23
|
+
TYPE_ERROR = 'TYPE_ERROR',
|
|
24
|
+
TYPE_MISMATCH = 'TYPE_MISMATCH',
|
|
25
|
+
UNKNOWN_TYPE = 'UNKNOWN_TYPE',
|
|
26
|
+
CARDINALITY_ERROR = 'CARDINALITY_ERROR',
|
|
27
|
+
|
|
28
|
+
// Runtime errors
|
|
29
|
+
RUNTIME_ERROR = 'RUNTIME_ERROR',
|
|
30
|
+
UNDEFINED_VARIABLE = 'UNDEFINED_VARIABLE',
|
|
31
|
+
UNDEFINED_FUNCTION = 'UNDEFINED_FUNCTION',
|
|
32
|
+
INVALID_ARGUMENT = 'INVALID_ARGUMENT',
|
|
33
|
+
DIVISION_BY_ZERO = 'DIVISION_BY_ZERO',
|
|
34
|
+
|
|
35
|
+
// Analysis errors
|
|
36
|
+
ANALYSIS_ERROR = 'ANALYSIS_ERROR',
|
|
37
|
+
UNREACHABLE_CODE = 'UNREACHABLE_CODE',
|
|
38
|
+
AMBIGUOUS_TYPE = 'AMBIGUOUS_TYPE',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class FHIRPathError extends Error {
|
|
42
|
+
constructor(
|
|
43
|
+
message: string,
|
|
44
|
+
public code: ErrorCode,
|
|
45
|
+
public location?: Location,
|
|
46
|
+
public expression?: string
|
|
47
|
+
) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = 'FHIRPathError';
|
|
50
|
+
|
|
51
|
+
// Ensure proper prototype chain
|
|
52
|
+
Object.setPrototypeOf(this, FHIRPathError.prototype);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override toString(): string {
|
|
56
|
+
let result = `${this.name}: ${this.message}`;
|
|
57
|
+
|
|
58
|
+
if (this.expression && this.location) {
|
|
59
|
+
result += `\n\n${this.expression}`;
|
|
60
|
+
if (this.location.offset >= 0 && this.location.length > 0) {
|
|
61
|
+
const indent = ' '.repeat(this.location.offset);
|
|
62
|
+
const marker = '^'.repeat(this.location.length);
|
|
63
|
+
result += `\n${indent}${marker}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.location) {
|
|
68
|
+
result += `\n\nAt line ${this.location.line}, column ${this.location.column}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Error factory functions
|
|
76
|
+
export function parseError(
|
|
77
|
+
message: string,
|
|
78
|
+
location?: Location,
|
|
79
|
+
expression?: string
|
|
80
|
+
): FHIRPathError {
|
|
81
|
+
return new FHIRPathError(message, ErrorCode.PARSE_ERROR, location, expression);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function syntaxError(
|
|
85
|
+
message: string,
|
|
86
|
+
location?: Location,
|
|
87
|
+
expression?: string
|
|
88
|
+
): FHIRPathError {
|
|
89
|
+
return new FHIRPathError(message, ErrorCode.SYNTAX_ERROR, location, expression);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function typeError(
|
|
93
|
+
message: string,
|
|
94
|
+
location?: Location,
|
|
95
|
+
expression?: string
|
|
96
|
+
): FHIRPathError {
|
|
97
|
+
return new FHIRPathError(message, ErrorCode.TYPE_ERROR, location, expression);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function runtimeError(
|
|
101
|
+
message: string,
|
|
102
|
+
location?: Location,
|
|
103
|
+
expression?: string
|
|
104
|
+
): FHIRPathError {
|
|
105
|
+
return new FHIRPathError(message, ErrorCode.RUNTIME_ERROR, location, expression);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function undefinedVariable(
|
|
109
|
+
name: string,
|
|
110
|
+
location?: Location,
|
|
111
|
+
expression?: string
|
|
112
|
+
): FHIRPathError {
|
|
113
|
+
return new FHIRPathError(
|
|
114
|
+
`Undefined variable: ${name}`,
|
|
115
|
+
ErrorCode.UNDEFINED_VARIABLE,
|
|
116
|
+
location,
|
|
117
|
+
expression
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function undefinedFunction(
|
|
122
|
+
name: string,
|
|
123
|
+
location?: Location,
|
|
124
|
+
expression?: string
|
|
125
|
+
): FHIRPathError {
|
|
126
|
+
return new FHIRPathError(
|
|
127
|
+
`Undefined function: ${name}`,
|
|
128
|
+
ErrorCode.UNDEFINED_FUNCTION,
|
|
129
|
+
location,
|
|
130
|
+
expression
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function invalidArgument(
|
|
135
|
+
message: string,
|
|
136
|
+
location?: Location,
|
|
137
|
+
expression?: string
|
|
138
|
+
): FHIRPathError {
|
|
139
|
+
return new FHIRPathError(
|
|
140
|
+
`Invalid argument: ${message}`,
|
|
141
|
+
ErrorCode.INVALID_ARGUMENT,
|
|
142
|
+
location,
|
|
143
|
+
expression
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ASTNode } from '../parser/ast';
|
|
2
|
+
import type {
|
|
3
|
+
FHIRPathExpression as IFHIRPathExpression,
|
|
4
|
+
CompiledExpression,
|
|
5
|
+
EvaluationContext,
|
|
6
|
+
CompileOptions,
|
|
7
|
+
AnalyzeOptions,
|
|
8
|
+
AnalysisResult
|
|
9
|
+
} from './types';
|
|
10
|
+
import { Interpreter } from '../interpreter/interpreter';
|
|
11
|
+
import { Compiler } from '../compiler/compiler';
|
|
12
|
+
import { analyzeFHIRPath } from '../analyzer/analyzer';
|
|
13
|
+
import { RuntimeContextManager } from '../runtime/context';
|
|
14
|
+
import { pprint } from '../parser/pprint';
|
|
15
|
+
|
|
16
|
+
export class FHIRPathExpression implements IFHIRPathExpression {
|
|
17
|
+
constructor(
|
|
18
|
+
public readonly ast: ASTNode,
|
|
19
|
+
private readonly originalExpression: string
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
evaluate(input?: any, context?: EvaluationContext): any[] {
|
|
23
|
+
const interpreter = new Interpreter();
|
|
24
|
+
const inputArray = input === undefined ? [] : Array.isArray(input) ? input : [input];
|
|
25
|
+
const ctx = this.createContext(context, inputArray);
|
|
26
|
+
|
|
27
|
+
const result = interpreter.evaluate(this.ast, inputArray, ctx);
|
|
28
|
+
|
|
29
|
+
return result.value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
compile(options?: CompileOptions): CompiledExpression {
|
|
33
|
+
const compiler = new Compiler();
|
|
34
|
+
const compiled = compiler.compile(this.ast);
|
|
35
|
+
|
|
36
|
+
// Create the compiled function
|
|
37
|
+
const fn = (input?: any, context?: EvaluationContext): any[] => {
|
|
38
|
+
const inputArray = input === undefined ? [] : Array.isArray(input) ? input : [input];
|
|
39
|
+
const ctx = this.createContext(context, inputArray);
|
|
40
|
+
|
|
41
|
+
return compiled.fn(ctx);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Add source property
|
|
45
|
+
Object.defineProperty(fn, 'source', {
|
|
46
|
+
value: options?.sourceMap ? this.originalExpression : this.toString(),
|
|
47
|
+
writable: false,
|
|
48
|
+
enumerable: true
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return fn as CompiledExpression;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
analyze(options?: AnalyzeOptions): AnalysisResult {
|
|
55
|
+
try {
|
|
56
|
+
// Create analyzer-compatible model provider
|
|
57
|
+
const analyzerModelProvider = options?.modelProvider ? {
|
|
58
|
+
resolveType: options.modelProvider.resolveType.bind(options.modelProvider),
|
|
59
|
+
getPropertyType: (type: any, propName: string) => {
|
|
60
|
+
const props = options.modelProvider!.getProperties(options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(type) : String(type));
|
|
61
|
+
const prop = props.find(p => p.name === propName);
|
|
62
|
+
if (!prop) return undefined;
|
|
63
|
+
return {
|
|
64
|
+
type: options.modelProvider!.resolveType(prop.type),
|
|
65
|
+
isSingleton: !prop.isCollection
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
isAssignable: (from: any, to: any) => {
|
|
69
|
+
const hierarchy = options.modelProvider!.getTypeHierarchy(options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(from) : String(from));
|
|
70
|
+
const toName = options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(to) : String(to);
|
|
71
|
+
return hierarchy.includes(toName);
|
|
72
|
+
},
|
|
73
|
+
getTypeName: (type: any) => String(type)
|
|
74
|
+
} : {
|
|
75
|
+
resolveType: () => undefined,
|
|
76
|
+
getPropertyType: () => undefined,
|
|
77
|
+
isAssignable: () => false,
|
|
78
|
+
getTypeName: () => 'unknown'
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = analyzeFHIRPath(this.ast, analyzerModelProvider);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
type: result.resultType || { kind: 'unknown' },
|
|
85
|
+
isSingleton: result.resultIsSingleton || false,
|
|
86
|
+
errors: result.diagnostics.filter(d => d.severity === 'error').map(d => ({
|
|
87
|
+
message: d.message,
|
|
88
|
+
location: d.position ? {
|
|
89
|
+
line: d.position.line,
|
|
90
|
+
column: d.position.column,
|
|
91
|
+
offset: d.position.offset,
|
|
92
|
+
length: 1 // We don't have length info in Position
|
|
93
|
+
} : undefined,
|
|
94
|
+
code: 'ANALYSIS_ERROR'
|
|
95
|
+
})),
|
|
96
|
+
warnings: result.diagnostics.filter(d => d.severity === 'warning').map(d => ({
|
|
97
|
+
message: d.message,
|
|
98
|
+
location: d.position ? {
|
|
99
|
+
line: d.position.line,
|
|
100
|
+
column: d.position.column,
|
|
101
|
+
offset: d.position.offset,
|
|
102
|
+
length: 1
|
|
103
|
+
} : undefined,
|
|
104
|
+
code: 'ANALYSIS_WARNING'
|
|
105
|
+
}))
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
type: { kind: 'unknown' },
|
|
110
|
+
isSingleton: false,
|
|
111
|
+
errors: [{
|
|
112
|
+
message: error instanceof Error ? error.message : String(error),
|
|
113
|
+
code: 'ANALYSIS_ERROR'
|
|
114
|
+
}],
|
|
115
|
+
warnings: []
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
toString(): string {
|
|
121
|
+
return pprint(this.ast);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private createContext(evalContext?: EvaluationContext, input: any[] = []) {
|
|
125
|
+
let ctx = RuntimeContextManager.create(input);
|
|
126
|
+
|
|
127
|
+
if (evalContext?.variables) {
|
|
128
|
+
for (const [name, value] of Object.entries(evalContext.variables)) {
|
|
129
|
+
ctx = RuntimeContextManager.setVariable(ctx, name, Array.isArray(value) ? value : [value]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (evalContext?.environment) {
|
|
134
|
+
// Environment variables are stored as regular variables with appropriate prefix
|
|
135
|
+
for (const [name, value] of Object.entries(evalContext.environment)) {
|
|
136
|
+
if (name.startsWith('$')) {
|
|
137
|
+
// Special environment variables
|
|
138
|
+
const varName = name.substring(1); // Remove $ prefix
|
|
139
|
+
ctx = RuntimeContextManager.setSpecialVariable(ctx, varName, Array.isArray(value) ? value : [value]);
|
|
140
|
+
} else {
|
|
141
|
+
// Regular environment variables
|
|
142
|
+
ctx = RuntimeContextManager.setVariable(ctx, name, Array.isArray(value) ? value : [value]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Store custom functions in context for interpreter to access
|
|
148
|
+
if (evalContext?.customFunctions) {
|
|
149
|
+
// TODO: Custom functions need to be implemented through the registry
|
|
150
|
+
// For now, we'll store them in env
|
|
151
|
+
(ctx as any).customFunctions = evalContext.customFunctions;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return ctx;
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FHIRPathExpression,
|
|
3
|
+
CompiledExpression,
|
|
4
|
+
EvaluationContext,
|
|
5
|
+
CompileOptions,
|
|
6
|
+
AnalyzeOptions,
|
|
7
|
+
AnalysisResult,
|
|
8
|
+
InspectResult,
|
|
9
|
+
InspectOptions
|
|
10
|
+
} from './types';
|
|
11
|
+
import { FHIRPathParser } from '../parser/parser';
|
|
12
|
+
import {
|
|
13
|
+
type ParserOptions,
|
|
14
|
+
type ParseResult,
|
|
15
|
+
type ParseDiagnostic,
|
|
16
|
+
type TextRange
|
|
17
|
+
} from '../parser/types';
|
|
18
|
+
import type { ASTNode } from '../parser/ast';
|
|
19
|
+
import { FHIRPathExpression as Expression } from './expression';
|
|
20
|
+
import { FHIRPathError, parseError } from './errors';
|
|
21
|
+
import { publicRegistry } from './registry';
|
|
22
|
+
import { inspect as inspectImpl } from './inspect';
|
|
23
|
+
|
|
24
|
+
// Export parser types for API consumers
|
|
25
|
+
export {
|
|
26
|
+
type ParserOptions,
|
|
27
|
+
type ParseResult,
|
|
28
|
+
type ParseDiagnostic,
|
|
29
|
+
type DiagnosticSeverity,
|
|
30
|
+
type TextRange,
|
|
31
|
+
type Position
|
|
32
|
+
} from '../parser/types';
|
|
33
|
+
|
|
34
|
+
// New parse function with mode support
|
|
35
|
+
export function parse(expression: string, options: ParserOptions = {}): ParseResult {
|
|
36
|
+
const parser = new FHIRPathParser(expression, options);
|
|
37
|
+
return parser.parse();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Convenience function for evaluation (throws on error)
|
|
41
|
+
export function parseForEvaluation(expression: string): ASTNode {
|
|
42
|
+
try {
|
|
43
|
+
const result = parse(expression, { throwOnError: true });
|
|
44
|
+
return result.ast;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error instanceof Error) {
|
|
47
|
+
throw parseError(error.message, undefined, expression);
|
|
48
|
+
}
|
|
49
|
+
throw parseError(String(error), undefined, expression);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Type guards for result types
|
|
54
|
+
|
|
55
|
+
export function isStandardResult(result: ParseResult): result is ParseResult {
|
|
56
|
+
return 'ast' in result && 'diagnostics' in result && 'hasErrors' in result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isDiagnosticResult(result: ParseResult): result is ParseResult & { isPartial: boolean; ranges: Map<ASTNode, TextRange> } {
|
|
60
|
+
return isStandardResult(result) && 'isPartial' in result && 'ranges' in result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate function - alternative to removed Validate mode
|
|
64
|
+
export function validate(expression: string): { valid: boolean; diagnostics: ParseDiagnostic[] } {
|
|
65
|
+
const result = parse(expression);
|
|
66
|
+
if (isStandardResult(result)) {
|
|
67
|
+
return {
|
|
68
|
+
valid: !result.hasErrors,
|
|
69
|
+
diagnostics: result.diagnostics
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Should not happen, but handle gracefully
|
|
73
|
+
return { valid: true, diagnostics: [] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// Evaluate expression directly
|
|
78
|
+
export function evaluate(
|
|
79
|
+
expression: string | FHIRPathExpression,
|
|
80
|
+
input?: any,
|
|
81
|
+
context?: EvaluationContext
|
|
82
|
+
): any[] {
|
|
83
|
+
const expr = typeof expression === 'string'
|
|
84
|
+
? new Expression(parseForEvaluation(expression), expression)
|
|
85
|
+
: expression;
|
|
86
|
+
return expr.evaluate(input, context);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Compile to optimized function
|
|
90
|
+
export function compile(
|
|
91
|
+
expression: string | FHIRPathExpression,
|
|
92
|
+
options?: CompileOptions
|
|
93
|
+
): CompiledExpression {
|
|
94
|
+
const expr = typeof expression === 'string'
|
|
95
|
+
? new Expression(parseForEvaluation(expression), expression)
|
|
96
|
+
: expression;
|
|
97
|
+
return expr.compile(options);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Analyze expression for validation
|
|
101
|
+
export function analyze(
|
|
102
|
+
expression: string | FHIRPathExpression,
|
|
103
|
+
options?: AnalyzeOptions
|
|
104
|
+
): AnalysisResult {
|
|
105
|
+
const expr = typeof expression === 'string'
|
|
106
|
+
? new Expression(parseForEvaluation(expression), expression)
|
|
107
|
+
: expression;
|
|
108
|
+
return expr.analyze(options);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Inspect expression with debugging information
|
|
112
|
+
export function inspect(
|
|
113
|
+
expression: string | FHIRPathExpression,
|
|
114
|
+
input?: any,
|
|
115
|
+
context?: EvaluationContext,
|
|
116
|
+
options?: InspectOptions
|
|
117
|
+
): InspectResult {
|
|
118
|
+
return inspectImpl(expression, input, context, options);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default registry instance
|
|
122
|
+
export const registry = publicRegistry;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FHIRPathExpression,
|
|
3
|
+
EvaluationContext,
|
|
4
|
+
InspectResult,
|
|
5
|
+
InspectOptions,
|
|
6
|
+
ErrorInfo,
|
|
7
|
+
WarningInfo
|
|
8
|
+
} from './types';
|
|
9
|
+
import { parseForEvaluation } from './index';
|
|
10
|
+
import { FHIRPathExpression as Expression } from './expression';
|
|
11
|
+
import { Interpreter } from '../interpreter/interpreter';
|
|
12
|
+
import { RuntimeContextManager } from '../runtime/context';
|
|
13
|
+
import { createDebugContext, isDebugContext } from '../runtime/debug-context';
|
|
14
|
+
import { FHIRPathError } from './errors';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Inspect a FHIRPath expression, providing rich debugging information
|
|
18
|
+
* including traces, AST, execution time, and optionally step-by-step evaluation
|
|
19
|
+
*/
|
|
20
|
+
export function inspect(
|
|
21
|
+
expression: string | FHIRPathExpression,
|
|
22
|
+
input?: any,
|
|
23
|
+
context?: EvaluationContext,
|
|
24
|
+
options?: InspectOptions
|
|
25
|
+
): InspectResult {
|
|
26
|
+
const startTime = performance.now();
|
|
27
|
+
|
|
28
|
+
// Parse if string
|
|
29
|
+
const expr = typeof expression === 'string'
|
|
30
|
+
? new Expression(parseForEvaluation(expression), expression)
|
|
31
|
+
: expression;
|
|
32
|
+
const exprString = typeof expression === 'string' ? expression : expr.toString();
|
|
33
|
+
|
|
34
|
+
// Prepare input (default to empty array)
|
|
35
|
+
const inputValue = input === undefined ? [] : Array.isArray(input) ? input : [input];
|
|
36
|
+
|
|
37
|
+
// Create runtime context
|
|
38
|
+
const runtimeContext = RuntimeContextManager.create(inputValue, context?.variables);
|
|
39
|
+
|
|
40
|
+
// Convert to debug context
|
|
41
|
+
const debugContext = createDebugContext(runtimeContext, options);
|
|
42
|
+
|
|
43
|
+
// Initialize result
|
|
44
|
+
const errors: ErrorInfo[] = [];
|
|
45
|
+
const warnings: WarningInfo[] = [];
|
|
46
|
+
let result: any[] = [];
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Create interpreter
|
|
50
|
+
const interpreter = new Interpreter();
|
|
51
|
+
|
|
52
|
+
// Evaluate with debug context
|
|
53
|
+
const evalResult = interpreter.evaluate(expr.ast, inputValue, debugContext);
|
|
54
|
+
result = evalResult.value;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// Capture error information
|
|
57
|
+
const errorInfo: ErrorInfo = {
|
|
58
|
+
message: error instanceof Error ? error.message : String(error),
|
|
59
|
+
type: error instanceof FHIRPathError ? error.name : 'Error'
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (error instanceof Error && error.stack) {
|
|
63
|
+
errorInfo.stack = error.stack;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
errors.push(errorInfo);
|
|
67
|
+
|
|
68
|
+
// Re-throw if it's a critical error
|
|
69
|
+
if (error instanceof FHIRPathError && error.name === 'ParseError') {
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const executionTime = performance.now() - startTime;
|
|
75
|
+
|
|
76
|
+
// Build result
|
|
77
|
+
const inspectResult: InspectResult = {
|
|
78
|
+
result,
|
|
79
|
+
expression: exprString,
|
|
80
|
+
ast: expr.ast,
|
|
81
|
+
executionTime,
|
|
82
|
+
traces: debugContext.traces
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Add optional fields
|
|
86
|
+
if (debugContext.steps && debugContext.steps.length > 0) {
|
|
87
|
+
inspectResult.evaluationSteps = debugContext.steps;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (errors.length > 0) {
|
|
91
|
+
inspectResult.errors = errors;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (warnings.length > 0) {
|
|
95
|
+
inspectResult.warnings = warnings;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return inspectResult;
|
|
99
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RegistryAPI,
|
|
3
|
+
OperationMetadata,
|
|
4
|
+
OperationInfo
|
|
5
|
+
} from './types';
|
|
6
|
+
import { Registry } from '../registry/registry';
|
|
7
|
+
import type { Operation } from '../registry/types';
|
|
8
|
+
|
|
9
|
+
export class PublicRegistryAPI implements RegistryAPI {
|
|
10
|
+
listFunctions(): OperationMetadata[] {
|
|
11
|
+
return Registry.getAllFunctions().map(op => this.toMetadata(op));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
listOperators(): OperationMetadata[] {
|
|
15
|
+
const operators = [
|
|
16
|
+
...Registry.getOperatorsByForm('infix'),
|
|
17
|
+
...Registry.getOperatorsByForm('prefix'),
|
|
18
|
+
...Registry.getOperatorsByForm('postfix')
|
|
19
|
+
];
|
|
20
|
+
return operators.map(op => this.toMetadata(op));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
listAllOperations(): OperationMetadata[] {
|
|
24
|
+
return Registry.getAllOperations().map(op => this.toMetadata(op));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
hasOperation(name: string): boolean {
|
|
28
|
+
return Registry.get(name) !== undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
hasFunction(name: string): boolean {
|
|
32
|
+
const op = Registry.get(name);
|
|
33
|
+
return op !== undefined && op.kind === 'function';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
hasOperator(symbol: string): boolean {
|
|
37
|
+
const op = Registry.get(symbol);
|
|
38
|
+
return op !== undefined && op.kind === 'operator';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getOperationInfo(name: string): OperationInfo | undefined {
|
|
42
|
+
const op = Registry.get(name);
|
|
43
|
+
if (!op) return undefined;
|
|
44
|
+
|
|
45
|
+
return this.toOperationInfo(op);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
canRegisterFunction(name: string): boolean {
|
|
49
|
+
// Check if name is valid and not already taken
|
|
50
|
+
if (!name || typeof name !== 'string') return false;
|
|
51
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) return false;
|
|
52
|
+
|
|
53
|
+
// Check if already exists as built-in
|
|
54
|
+
return !this.hasOperation(name);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private toMetadata(op: Operation): OperationMetadata {
|
|
58
|
+
return {
|
|
59
|
+
name: op.name,
|
|
60
|
+
kind: op.kind,
|
|
61
|
+
syntax: {
|
|
62
|
+
notation: op.syntax.notation
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private toOperationInfo(op: Operation): OperationInfo {
|
|
68
|
+
const info: OperationInfo = {
|
|
69
|
+
...this.toMetadata(op),
|
|
70
|
+
signature: {}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Add input signature for functions
|
|
74
|
+
if (op.kind === 'function' && op.signature.input) {
|
|
75
|
+
info.signature.input = {
|
|
76
|
+
types: this.extractTypes(op.signature.input.types),
|
|
77
|
+
cardinality: op.signature.input.cardinality
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Add parameters
|
|
82
|
+
if ('parameters' in op.signature && op.signature.parameters && op.signature.parameters.length > 0) {
|
|
83
|
+
info.signature.parameters = op.signature.parameters.map((param: any) => ({
|
|
84
|
+
name: param.name,
|
|
85
|
+
types: this.extractTypes(param.types),
|
|
86
|
+
cardinality: param.cardinality,
|
|
87
|
+
optional: param.optional
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Add output signature
|
|
92
|
+
if (op.signature.output) {
|
|
93
|
+
info.signature.output = {
|
|
94
|
+
type: typeof op.signature.output.type === 'string'
|
|
95
|
+
? op.signature.output.type
|
|
96
|
+
: 'dynamic',
|
|
97
|
+
cardinality: typeof op.signature.output.cardinality === 'string'
|
|
98
|
+
? (op.signature.output.cardinality === 'all-singleton' ? 'singleton' : op.signature.output.cardinality as any)
|
|
99
|
+
: undefined
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Add description if available (for now, operations don't have description in the type)
|
|
104
|
+
// TODO: Add description to Operation type if needed
|
|
105
|
+
|
|
106
|
+
// Add examples if available (for now, operations don't have examples in the type)
|
|
107
|
+
// TODO: Add examples to Operation type if needed
|
|
108
|
+
|
|
109
|
+
return info;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private extractTypes(constraint?: any): string[] | undefined {
|
|
113
|
+
if (!constraint) return undefined;
|
|
114
|
+
|
|
115
|
+
if (constraint.kind === 'primitive' || constraint.kind === 'class') {
|
|
116
|
+
return constraint.types;
|
|
117
|
+
} else if (constraint.kind === 'union' && constraint.types) {
|
|
118
|
+
return constraint.types;
|
|
119
|
+
} else if (constraint.kind === 'any') {
|
|
120
|
+
return ['any'];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Singleton instance
|
|
128
|
+
export const publicRegistry = new PublicRegistryAPI();
|