@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.
Files changed (57) hide show
  1. package/README.md +307 -0
  2. package/dist/index.d.ts +225 -0
  3. package/dist/index.js +8185 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +51 -0
  6. package/src/analyzer/analyzer.ts +486 -0
  7. package/src/analyzer/model-provider.ts +244 -0
  8. package/src/analyzer/schemas/index.ts +2 -0
  9. package/src/analyzer/schemas/types.ts +40 -0
  10. package/src/analyzer/types.ts +142 -0
  11. package/src/api/builder.ts +148 -0
  12. package/src/api/errors.ts +134 -0
  13. package/src/api/expression.ts +152 -0
  14. package/src/api/index.ts +57 -0
  15. package/src/api/registry.ts +128 -0
  16. package/src/api/types.ts +154 -0
  17. package/src/compiler/compiler.ts +579 -0
  18. package/src/compiler/index.ts +2 -0
  19. package/src/compiler/prototype-context-adapter.ts +99 -0
  20. package/src/compiler/types.ts +23 -0
  21. package/src/index.ts +52 -0
  22. package/src/interpreter/README.md +78 -0
  23. package/src/interpreter/interpreter.ts +485 -0
  24. package/src/interpreter/types.ts +110 -0
  25. package/src/lexer/char-tables.ts +37 -0
  26. package/src/lexer/errors.ts +31 -0
  27. package/src/lexer/index.ts +5 -0
  28. package/src/lexer/lexer.ts +745 -0
  29. package/src/lexer/token.ts +104 -0
  30. package/src/parser/ast.ts +123 -0
  31. package/src/parser/index.ts +3 -0
  32. package/src/parser/parser.ts +701 -0
  33. package/src/parser/pprint.ts +169 -0
  34. package/src/registry/default-analyzers.ts +257 -0
  35. package/src/registry/default-compilers.ts +31 -0
  36. package/src/registry/index.ts +93 -0
  37. package/src/registry/operations/arithmetic.ts +506 -0
  38. package/src/registry/operations/collection.ts +425 -0
  39. package/src/registry/operations/comparison.ts +432 -0
  40. package/src/registry/operations/existence.ts +703 -0
  41. package/src/registry/operations/filtering.ts +358 -0
  42. package/src/registry/operations/literals.ts +341 -0
  43. package/src/registry/operations/logical.ts +402 -0
  44. package/src/registry/operations/math.ts +128 -0
  45. package/src/registry/operations/membership.ts +132 -0
  46. package/src/registry/operations/string.ts +507 -0
  47. package/src/registry/operations/subsetting.ts +174 -0
  48. package/src/registry/operations/type-checking.ts +162 -0
  49. package/src/registry/operations/type-conversion.ts +404 -0
  50. package/src/registry/operations/type-operators.ts +307 -0
  51. package/src/registry/operations/utility.ts +542 -0
  52. package/src/registry/registry.ts +146 -0
  53. package/src/registry/types.ts +161 -0
  54. package/src/registry/utils/evaluation-helpers.ts +93 -0
  55. package/src/registry/utils/index.ts +3 -0
  56. package/src/registry/utils/type-system.ts +173 -0
  57. package/src/runtime/context.ts +179 -0
@@ -0,0 +1,152 @@
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({
42
+ input: inputArray,
43
+ focus: inputArray,
44
+ env: ctx.env || {}
45
+ });
46
+ };
47
+
48
+ // Add source property
49
+ Object.defineProperty(fn, 'source', {
50
+ value: options?.sourceMap ? this.originalExpression : this.toString(),
51
+ writable: false,
52
+ enumerable: true
53
+ });
54
+
55
+ return fn as CompiledExpression;
56
+ }
57
+
58
+ analyze(options?: AnalyzeOptions): AnalysisResult {
59
+ try {
60
+ // Create analyzer-compatible model provider
61
+ const analyzerModelProvider = options?.modelProvider ? {
62
+ resolveType: options.modelProvider.resolveType.bind(options.modelProvider),
63
+ getPropertyType: (type: any, propName: string) => {
64
+ const props = options.modelProvider!.getProperties(options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(type) : String(type));
65
+ const prop = props.find(p => p.name === propName);
66
+ if (!prop) return undefined;
67
+ return {
68
+ type: options.modelProvider!.resolveType(prop.type),
69
+ isSingleton: !prop.isCollection
70
+ };
71
+ },
72
+ isAssignable: (from: any, to: any) => {
73
+ const hierarchy = options.modelProvider!.getTypeHierarchy(options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(from) : String(from));
74
+ const toName = options.modelProvider!.getTypeName ? options.modelProvider!.getTypeName(to) : String(to);
75
+ return hierarchy.includes(toName);
76
+ },
77
+ getTypeName: (type: any) => String(type)
78
+ } : {
79
+ resolveType: () => undefined,
80
+ getPropertyType: () => undefined,
81
+ isAssignable: () => false,
82
+ getTypeName: () => 'unknown'
83
+ };
84
+
85
+ const result = analyzeFHIRPath(this.ast, analyzerModelProvider);
86
+
87
+ return {
88
+ type: result.resultType || { kind: 'unknown' },
89
+ isSingleton: result.resultIsSingleton || false,
90
+ errors: result.diagnostics.filter(d => d.severity === 'error').map(d => ({
91
+ message: d.message,
92
+ location: d.position ? {
93
+ line: d.position.line,
94
+ column: d.position.column,
95
+ offset: d.position.offset,
96
+ length: 1 // We don't have length info in Position
97
+ } : undefined,
98
+ code: 'ANALYSIS_ERROR'
99
+ })),
100
+ warnings: result.diagnostics.filter(d => d.severity === 'warning').map(d => ({
101
+ message: d.message,
102
+ location: d.position ? {
103
+ line: d.position.line,
104
+ column: d.position.column,
105
+ offset: d.position.offset,
106
+ length: 1
107
+ } : undefined,
108
+ code: 'ANALYSIS_WARNING'
109
+ }))
110
+ };
111
+ } catch (error) {
112
+ return {
113
+ type: { kind: 'unknown' },
114
+ isSingleton: false,
115
+ errors: [{
116
+ message: error instanceof Error ? error.message : String(error),
117
+ code: 'ANALYSIS_ERROR'
118
+ }],
119
+ warnings: []
120
+ };
121
+ }
122
+ }
123
+
124
+ toString(): string {
125
+ return pprint(this.ast);
126
+ }
127
+
128
+ private createContext(evalContext?: EvaluationContext, input: any[] = []) {
129
+ let ctx = RuntimeContextManager.create(input);
130
+
131
+ if (evalContext?.variables) {
132
+ for (const [name, value] of Object.entries(evalContext.variables)) {
133
+ ctx = RuntimeContextManager.setVariable(ctx, name, Array.isArray(value) ? value : [value]);
134
+ }
135
+ }
136
+
137
+ if (evalContext?.environment) {
138
+ // Environment variables would need to be handled through a different mechanism
139
+ // as the context.env only supports specific FHIRPath environment variables
140
+ // TODO: Consider how to handle custom environment variables
141
+ }
142
+
143
+ // Store custom functions in context for interpreter to access
144
+ if (evalContext?.customFunctions) {
145
+ // TODO: Custom functions need to be implemented through the registry
146
+ // For now, we'll store them in env
147
+ (ctx as any).customFunctions = evalContext.customFunctions;
148
+ }
149
+
150
+ return ctx;
151
+ }
152
+ }
@@ -0,0 +1,57 @@
1
+ import type {
2
+ FHIRPathExpression,
3
+ CompiledExpression,
4
+ EvaluationContext,
5
+ CompileOptions,
6
+ AnalyzeOptions,
7
+ AnalysisResult
8
+ } from './types';
9
+ import { FHIRPathParser } from '../parser/parser';
10
+ import { FHIRPathExpression as Expression } from './expression';
11
+ import { FHIRPathError, parseError } from './errors';
12
+ import { publicRegistry } from './registry';
13
+
14
+ // Parse expression into AST
15
+ export function parse(expression: string): FHIRPathExpression {
16
+ try {
17
+ const parser = new FHIRPathParser(expression);
18
+ const ast = parser.parse();
19
+ return new Expression(ast, expression);
20
+ } catch (error) {
21
+ if (error instanceof Error) {
22
+ throw parseError(error.message, undefined, expression);
23
+ }
24
+ throw parseError(String(error), undefined, expression);
25
+ }
26
+ }
27
+
28
+ // Evaluate expression directly
29
+ export function evaluate(
30
+ expression: string | FHIRPathExpression,
31
+ input?: any,
32
+ context?: EvaluationContext
33
+ ): any[] {
34
+ const expr = typeof expression === 'string' ? parse(expression) : expression;
35
+ return expr.evaluate(input, context);
36
+ }
37
+
38
+ // Compile to optimized function
39
+ export function compile(
40
+ expression: string | FHIRPathExpression,
41
+ options?: CompileOptions
42
+ ): CompiledExpression {
43
+ const expr = typeof expression === 'string' ? parse(expression) : expression;
44
+ return expr.compile(options);
45
+ }
46
+
47
+ // Analyze expression for validation
48
+ export function analyze(
49
+ expression: string | FHIRPathExpression,
50
+ options?: AnalyzeOptions
51
+ ): AnalysisResult {
52
+ const expr = typeof expression === 'string' ? parse(expression) : expression;
53
+ return expr.analyze(options);
54
+ }
55
+
56
+ // Default registry instance
57
+ export const registry = publicRegistry;
@@ -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();
@@ -0,0 +1,154 @@
1
+ import type { ASTNode } from '../parser/ast';
2
+ import type { TypeRef } from '../analyzer/types';
3
+ import type { Context } from '../interpreter/types';
4
+
5
+ // Core expression interface
6
+ export interface FHIRPathExpression {
7
+ readonly ast: ASTNode;
8
+ evaluate(input?: any, context?: EvaluationContext): any[];
9
+ compile(options?: CompileOptions): CompiledExpression;
10
+ analyze(options?: AnalyzeOptions): AnalysisResult;
11
+ toString(): string;
12
+ }
13
+
14
+ // Compiled expression function
15
+ export interface CompiledExpression {
16
+ (input?: any, context?: EvaluationContext): any[];
17
+ readonly source: string;
18
+ }
19
+
20
+ // Evaluation context for expressions
21
+ export interface EvaluationContext {
22
+ variables?: Record<string, any>;
23
+ environment?: Record<string, any>;
24
+ modelProvider?: ModelProvider;
25
+ customFunctions?: CustomFunctionMap;
26
+ }
27
+
28
+ // Custom function definition
29
+ export type CustomFunction = (
30
+ context: Context,
31
+ input: any[],
32
+ ...args: any[]
33
+ ) => any[];
34
+
35
+ export type CustomFunctionMap = Record<string, CustomFunction>;
36
+
37
+ // Model provider interface
38
+ export interface ModelProvider {
39
+ resolveType(typeName: string): TypeRef | undefined;
40
+ getTypeHierarchy(typeName: string): string[];
41
+ getProperties(typeName: string): PropertyDefinition[];
42
+ getTypeName?(type: TypeRef): string;
43
+ }
44
+
45
+ export interface PropertyDefinition {
46
+ name: string;
47
+ type: string;
48
+ isCollection: boolean;
49
+ isRequired: boolean;
50
+ }
51
+
52
+ // Compilation options
53
+ export interface CompileOptions {
54
+ optimize?: boolean;
55
+ sourceMap?: boolean;
56
+ }
57
+
58
+ // Analysis options
59
+ export interface AnalyzeOptions {
60
+ modelProvider?: ModelProvider;
61
+ strict?: boolean;
62
+ }
63
+
64
+ // Analysis result
65
+ export interface AnalysisResult {
66
+ type: TypeRef;
67
+ isSingleton: boolean;
68
+ errors: AnalysisError[];
69
+ warnings: AnalysisWarning[];
70
+ }
71
+
72
+ export interface AnalysisError {
73
+ message: string;
74
+ location?: Location;
75
+ code: string;
76
+ }
77
+
78
+ export interface AnalysisWarning {
79
+ message: string;
80
+ location?: Location;
81
+ code: string;
82
+ }
83
+
84
+ export interface Location {
85
+ line: number;
86
+ column: number;
87
+ offset: number;
88
+ length: number;
89
+ }
90
+
91
+ // Registry API types
92
+ export interface RegistryAPI {
93
+ // List operations by type
94
+ listFunctions(): OperationMetadata[];
95
+ listOperators(): OperationMetadata[];
96
+ listAllOperations(): OperationMetadata[];
97
+
98
+ // Check existence
99
+ hasOperation(name: string): boolean;
100
+ hasFunction(name: string): boolean;
101
+ hasOperator(symbol: string): boolean;
102
+
103
+ // Get operation metadata (read-only view)
104
+ getOperationInfo(name: string): OperationInfo | undefined;
105
+
106
+ // Extension validation
107
+ canRegisterFunction(name: string): boolean;
108
+ }
109
+
110
+ // Simplified metadata for public consumption
111
+ export interface OperationMetadata {
112
+ name: string;
113
+ kind: 'function' | 'operator' | 'literal';
114
+ syntax: {
115
+ notation: string; // e.g., "a + b", "substring(start, length)"
116
+ };
117
+ }
118
+
119
+ export interface OperationInfo extends OperationMetadata {
120
+ signature: {
121
+ input?: {
122
+ types?: string[];
123
+ cardinality?: 'singleton' | 'collection' | 'any';
124
+ };
125
+ parameters?: Array<{
126
+ name: string;
127
+ types?: string[];
128
+ cardinality?: 'singleton' | 'collection' | 'any';
129
+ optional?: boolean;
130
+ }>;
131
+ output?: {
132
+ type?: string | 'dynamic';
133
+ cardinality?: 'singleton' | 'collection' | 'preserve-input';
134
+ };
135
+ };
136
+ description?: string;
137
+ examples?: string[];
138
+ }
139
+
140
+ // Builder interfaces
141
+ export interface FHIRPathBuilder {
142
+ withModelProvider(provider: ModelProvider): this;
143
+ withCustomFunction(name: string, fn: CustomFunction): this;
144
+ withVariable(name: string, value: any): this;
145
+ build(): FHIRPathAPI;
146
+ }
147
+
148
+ export interface FHIRPathAPI {
149
+ parse(expression: string): FHIRPathExpression;
150
+ evaluate(expression: string | FHIRPathExpression, input?: any): any[];
151
+ compile(expression: string | FHIRPathExpression): CompiledExpression;
152
+ analyze(expression: string | FHIRPathExpression): AnalysisResult;
153
+ registry: RegistryAPI;
154
+ }