@atomic-ehr/fhirpath 0.0.1-canary.69eb286.20250724163205

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 (56) hide show
  1. package/README.md +307 -0
  2. package/dist/index.d.ts +225 -0
  3. package/dist/index.js +8256 -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 +149 -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 +589 -0
  18. package/src/compiler/index.ts +2 -0
  19. package/src/compiler/types.ts +23 -0
  20. package/src/index.ts +52 -0
  21. package/src/interpreter/README.md +78 -0
  22. package/src/interpreter/context.ts +181 -0
  23. package/src/interpreter/interpreter.ts +484 -0
  24. package/src/interpreter/types.ts +132 -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 +422 -0
  39. package/src/registry/operations/comparison.ts +432 -0
  40. package/src/registry/operations/existence.ts +719 -0
  41. package/src/registry/operations/filtering.ts +374 -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 +553 -0
  52. package/src/registry/registry.ts +146 -0
  53. package/src/registry/types.ts +162 -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
@@ -0,0 +1,162 @@
1
+ import type { TokenType } from '../lexer/token';
2
+ import type { TypeRef, ModelProvider } from '../analyzer/types';
3
+ import type { Context, EvaluationResult } from '../interpreter/types';
4
+
5
+ // Type information returned by analyze
6
+ export interface TypeInfo {
7
+ type: TypeRef;
8
+ isSingleton: boolean;
9
+ }
10
+
11
+ // Base interface for all operations
12
+ export interface BaseOperation {
13
+ name: string;
14
+
15
+ // Common lifecycle methods
16
+ analyze: (analyzer: Analyzer, input: TypeInfo, args: TypeInfo[]) => TypeInfo;
17
+ evaluate: (interpreter: Interpreter, context: Context, input: any[], ...args: any[]) => EvaluationResult;
18
+ compile: (compiler: Compiler, input: CompiledExpression, args: CompiledExpression[]) => CompiledExpression;
19
+ }
20
+
21
+ // Operator-specific interface
22
+ export interface Operator extends BaseOperation {
23
+ kind: 'operator';
24
+
25
+ syntax: {
26
+ form: 'prefix' | 'infix' | 'postfix';
27
+ token: TokenType;
28
+ precedence: number;
29
+ associativity?: 'left' | 'right'; // For infix operators
30
+ notation: string; // e.g., "a + b", "not a"
31
+ special?: boolean; // For special forms like . and []
32
+ endToken?: TokenType; // For bracketed operators like []
33
+ };
34
+
35
+ signature: {
36
+ parameters: [OperatorParameter] | [OperatorParameter, OperatorParameter]; // Unary or binary
37
+ output: {
38
+ type: TypeInferenceRule;
39
+ cardinality: CardinalityInferenceRule;
40
+ };
41
+ propagatesEmpty?: boolean;
42
+ };
43
+ }
44
+
45
+ // Function-specific interface
46
+ export interface Function extends BaseOperation {
47
+ kind: 'function';
48
+
49
+ syntax: {
50
+ notation: string; // e.g., "substring(start [, length])"
51
+ };
52
+
53
+ signature: {
54
+ input?: {
55
+ types?: TypeConstraint;
56
+ cardinality?: 'singleton' | 'collection' | 'any';
57
+ };
58
+ parameters: FunctionParameter[];
59
+ output: {
60
+ type: TypeInferenceRule;
61
+ cardinality: CardinalityInferenceRule;
62
+ };
63
+ propagatesEmpty?: boolean;
64
+ deterministic?: boolean;
65
+ };
66
+ }
67
+
68
+ // Literal-specific interface
69
+ export interface Literal extends BaseOperation {
70
+ kind: 'literal';
71
+
72
+ syntax: {
73
+ pattern?: RegExp; // For complex literals like dates
74
+ keywords?: string[]; // For keyword literals like 'true', 'false'
75
+ notation: string; // Example: "123", "@2023-01-01"
76
+ };
77
+
78
+ // Literals have fixed output type
79
+ signature: {
80
+ output: {
81
+ type: string; // Always a specific type
82
+ cardinality: 'singleton'; // Literals are always singleton
83
+ };
84
+ };
85
+
86
+ // Parse the literal value from source text
87
+ parse: (value: string) => any;
88
+ }
89
+
90
+ // Union type for registry
91
+ export type Operation = Operator | Function | Literal;
92
+
93
+ // Specialized parameter types
94
+ export interface OperatorParameter {
95
+ name: 'left' | 'right' | 'operand';
96
+ types?: TypeConstraint;
97
+ cardinality?: 'singleton' | 'collection' | 'any'; // Required cardinality for this parameter
98
+ }
99
+
100
+ export interface FunctionParameter {
101
+ name: string;
102
+ kind: 'value' | 'expression' | 'type-specifier';
103
+ types?: TypeConstraint;
104
+ cardinality?: 'singleton' | 'collection' | 'any';
105
+ optional?: boolean;
106
+ default?: any;
107
+ }
108
+
109
+ export interface TypeConstraint {
110
+ kind: 'primitive' | 'class' | 'union' | 'any';
111
+ types?: string[]; // ['Integer', 'Decimal'] for numeric, ['Resource'] for FHIR types
112
+ }
113
+
114
+ export type TypeInferenceRule =
115
+ | string // Fixed type like 'Boolean'
116
+ | 'preserve-input' // Returns input type
117
+ | 'promote-numeric' // Integer + Decimal = Decimal
118
+ | ((input: TypeRef, args: TypeRef[], provider: ModelProvider) => TypeRef);
119
+
120
+ export type CardinalityInferenceRule =
121
+ | 'singleton' | 'collection'
122
+ | 'preserve-input' // Same as input
123
+ | 'all-singleton' // Singleton only if all inputs are singleton
124
+ | ((input: boolean, args: boolean[]) => boolean);
125
+
126
+ // Closure-based compiled expression
127
+ export interface CompiledExpression {
128
+ // The compiled function
129
+ fn: (context: RuntimeContext) => any[];
130
+
131
+ // Type information for optimization
132
+ type: TypeRef;
133
+ isSingleton: boolean;
134
+
135
+ // For debugging/tracing
136
+ source?: string;
137
+ }
138
+
139
+ export interface RuntimeContext {
140
+ input: any[];
141
+ env: Record<string, any>;
142
+ focus?: any;
143
+ }
144
+
145
+ // Interfaces for components that use the registry
146
+ export interface Analyzer {
147
+ error(message: string): void;
148
+ warning(message: string): void;
149
+ resolveType(typeName: string): TypeRef;
150
+ }
151
+
152
+ export interface Interpreter {
153
+ evaluate(node: any, input: any[], context: Context): EvaluationResult;
154
+ }
155
+
156
+ export interface Compiler {
157
+ compile(node: any, input: CompiledExpression): CompiledExpression;
158
+ resolveType(typeName: string): TypeRef;
159
+ }
160
+
161
+ // Re-export TypeRef from analyzer
162
+ export type { TypeRef } from '../analyzer/types';
@@ -0,0 +1,93 @@
1
+ import { CollectionUtils } from '../../interpreter/types';
2
+
3
+ /**
4
+ * Convert collection to singleton value
5
+ */
6
+ export function toSingleton(collection: any[]): any {
7
+ return CollectionUtils.toSingleton(collection);
8
+ }
9
+
10
+ /**
11
+ * Convert value to boolean according to FHIRPath rules
12
+ */
13
+ export function toBoolean(value: any): boolean {
14
+ // Handle direct boolean
15
+ if (typeof value === 'boolean') {
16
+ return value;
17
+ }
18
+
19
+ // Handle null/undefined as false
20
+ if (value == null) {
21
+ return false;
22
+ }
23
+
24
+ // Handle empty string as false
25
+ if (value === '') {
26
+ return false;
27
+ }
28
+
29
+ // Handle zero as false
30
+ if (value === 0) {
31
+ return false;
32
+ }
33
+
34
+ // Everything else is true
35
+ return true;
36
+ }
37
+
38
+ /**
39
+ * Determines if a value is truthy according to FHIRPath rules
40
+ */
41
+ export function isTruthy(value: any[]): boolean {
42
+ if (value.length === 0) {
43
+ return false;
44
+ }
45
+
46
+ // Convert to singleton
47
+ const singleton = toSingleton(value);
48
+
49
+ // Rule: true if singleton is true, false if singleton is false
50
+ if (typeof singleton === 'boolean') {
51
+ return singleton;
52
+ }
53
+
54
+ // Rule: singleton exists and is not false = true
55
+ return singleton !== undefined;
56
+ }
57
+
58
+ /**
59
+ * Checks if two values are equivalent for FHIRPath comparison
60
+ */
61
+ export function isEquivalent(a: any, b: any): boolean {
62
+ // Handle null/undefined
63
+ if (a === b) return true;
64
+ if (a == null || b == null) return false;
65
+
66
+ // Handle different types
67
+ if (typeof a !== typeof b) return false;
68
+
69
+ // Handle primitives
70
+ if (typeof a !== 'object') return a === b;
71
+
72
+ // Handle arrays
73
+ if (Array.isArray(a) && Array.isArray(b)) {
74
+ if (a.length !== b.length) return false;
75
+ for (let i = 0; i < a.length; i++) {
76
+ if (!isEquivalent(a[i], b[i])) return false;
77
+ }
78
+ return true;
79
+ }
80
+
81
+ // Handle objects (deep comparison)
82
+ const keysA = Object.keys(a);
83
+ const keysB = Object.keys(b);
84
+
85
+ if (keysA.length !== keysB.length) return false;
86
+
87
+ for (const key of keysA) {
88
+ if (!keysB.includes(key)) return false;
89
+ if (!isEquivalent(a[key], b[key])) return false;
90
+ }
91
+
92
+ return true;
93
+ }
@@ -0,0 +1,3 @@
1
+ export { TypeSystem } from './type-system';
2
+ export { isTruthy, isEquivalent, toSingleton, toBoolean } from './evaluation-helpers';
3
+ export type { TypeInfo, PrimitiveType } from './type-system';
@@ -0,0 +1,173 @@
1
+ /**
2
+ * FHIRPath Type System
3
+ *
4
+ * Handles type checking and casting for FHIRPath expressions.
5
+ * Supports primitive types and FHIR resource types.
6
+ */
7
+
8
+ export enum PrimitiveType {
9
+ Boolean = 'Boolean',
10
+ String = 'String',
11
+ Integer = 'Integer',
12
+ Decimal = 'Decimal',
13
+ Date = 'Date',
14
+ DateTime = 'DateTime',
15
+ Time = 'Time',
16
+ Quantity = 'Quantity'
17
+ }
18
+
19
+ /**
20
+ * Type information for a value
21
+ */
22
+ export interface TypeInfo {
23
+ name: string;
24
+ isPrimitive: boolean;
25
+ isResource?: boolean;
26
+ isList?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Type registry for checking and casting
31
+ */
32
+ export class TypeSystem {
33
+ private static primitiveTypes = new Set<string>(Object.values(PrimitiveType));
34
+
35
+ /**
36
+ * Check if a value is of a specific type
37
+ */
38
+ static isType(value: any, typeName: string): boolean {
39
+ // Handle primitive types
40
+ if (this.primitiveTypes.has(typeName)) {
41
+ return this.isPrimitiveType(value, typeName as PrimitiveType);
42
+ }
43
+
44
+ // Handle FHIR resource types
45
+ // For now, check if the object has a resourceType property matching the type name
46
+ if (typeof value === 'object' && value !== null) {
47
+ return value.resourceType === typeName;
48
+ }
49
+
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * Check if a value is a primitive type
55
+ */
56
+ private static isPrimitiveType(value: any, type: PrimitiveType): boolean {
57
+ switch (type) {
58
+ case PrimitiveType.Boolean:
59
+ return typeof value === 'boolean';
60
+
61
+ case PrimitiveType.String:
62
+ return typeof value === 'string';
63
+
64
+ case PrimitiveType.Integer:
65
+ return typeof value === 'number' && Number.isInteger(value);
66
+
67
+ case PrimitiveType.Decimal:
68
+ return typeof value === 'number';
69
+
70
+ case PrimitiveType.Date:
71
+ // Check if it's a date string in FHIR format (YYYY-MM-DD)
72
+ if (typeof value === 'string') {
73
+ return /^\d{4}(-\d{2}(-\d{2})?)?$/.test(value);
74
+ }
75
+ return false;
76
+
77
+ case PrimitiveType.DateTime:
78
+ // Check if it's a datetime string in FHIR format
79
+ if (typeof value === 'string') {
80
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?$/.test(value);
81
+ }
82
+ return false;
83
+
84
+ case PrimitiveType.Time:
85
+ // Check if it's a time string in FHIR format (HH:MM:SS)
86
+ if (typeof value === 'string') {
87
+ return /^\d{2}:\d{2}:\d{2}(\.\d{3})?$/.test(value);
88
+ }
89
+ return false;
90
+
91
+ case PrimitiveType.Quantity:
92
+ // Check if it's a quantity object with value and optional unit
93
+ if (typeof value === 'object' && value !== null) {
94
+ return typeof value.value === 'number' &&
95
+ (value.unit === undefined || typeof value.unit === 'string');
96
+ }
97
+ return false;
98
+
99
+ default:
100
+ return false;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Cast a value to a specific type (returns empty if cast fails)
106
+ */
107
+ static cast(value: any, typeName: string): any | null {
108
+ // If already the correct type, return as-is
109
+ if (this.isType(value, typeName)) {
110
+ return value;
111
+ }
112
+
113
+ // Handle FHIR resource casting
114
+ if (!this.primitiveTypes.has(typeName)) {
115
+ // Can't cast between different resource types
116
+ return null;
117
+ }
118
+
119
+ // Handle primitive type casting
120
+ return this.castToPrimitive(value, typeName as PrimitiveType);
121
+ }
122
+
123
+ /**
124
+ * Cast to primitive type
125
+ */
126
+ private static castToPrimitive(value: any, type: PrimitiveType): any | null {
127
+ // For now, return null for failed casts
128
+ // Later we can implement actual conversions (e.g., string to number)
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * Get type information for a value
134
+ */
135
+ static getType(value: any): TypeInfo {
136
+ // Check primitive types
137
+ if (typeof value === 'boolean') {
138
+ return { name: PrimitiveType.Boolean, isPrimitive: true };
139
+ }
140
+ if (typeof value === 'string') {
141
+ // Check for specific string formats
142
+ if (/^\d{4}(-\d{2}(-\d{2})?)?$/.test(value)) {
143
+ return { name: PrimitiveType.Date, isPrimitive: true };
144
+ }
145
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
146
+ return { name: PrimitiveType.DateTime, isPrimitive: true };
147
+ }
148
+ if (/^\d{2}:\d{2}:\d{2}/.test(value)) {
149
+ return { name: PrimitiveType.Time, isPrimitive: true };
150
+ }
151
+ return { name: PrimitiveType.String, isPrimitive: true };
152
+ }
153
+ if (typeof value === 'number') {
154
+ return {
155
+ name: Number.isInteger(value) ? PrimitiveType.Integer : PrimitiveType.Decimal,
156
+ isPrimitive: true
157
+ };
158
+ }
159
+
160
+ // Check for FHIR resources
161
+ if (typeof value === 'object' && value !== null) {
162
+ if (value.resourceType) {
163
+ return { name: value.resourceType, isPrimitive: false, isResource: true };
164
+ }
165
+ if (typeof value.value === 'number' && (value.unit || value.code)) {
166
+ return { name: PrimitiveType.Quantity, isPrimitive: true };
167
+ }
168
+ }
169
+
170
+ // Unknown type
171
+ return { name: 'Unknown', isPrimitive: false };
172
+ }
173
+ }