@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,78 @@
1
+ # FHIRPath Interpreter
2
+
3
+ A stream-processing based FHIRPath interpreter implementation following the mental model described in `ideas/fhirpath-mental-model-3.md`.
4
+
5
+ ## Architecture
6
+
7
+ The interpreter follows the core principle that **everything is a processing node** with a uniform interface:
8
+ - **Input**: Always a collection (even single values are collections of one)
9
+ - **Context**: Variables and environment data flowing parallel to data
10
+ - **Output**: The resulting collection
11
+ - **New Context**: Potentially modified context
12
+
13
+ ## Implementation Status
14
+
15
+ ### ✅ Phase 1: Core Infrastructure
16
+ - `types.ts` - Core interfaces (EvaluationResult, Context, TypeInfo)
17
+ - `context.ts` - Context management (variables, environment)
18
+ - `interpreter.ts` - Base interpreter class with node dispatch
19
+
20
+ ### ✅ Phase 2: Simple Nodes
21
+ - **Literals**: Numbers, strings, booleans, null, collections
22
+ - **Identifiers**: Property navigation with flattening
23
+ - **Variables**: $this, $index, $total, %user-variables, %context
24
+ - **Dot Operator**: Pipeline semantics (left output → right input)
25
+
26
+ ### ✅ Phase 3: Operators
27
+ - **Arithmetic**: +, -, *, /, div, mod with singleton conversion
28
+ - **Comparison**: =, !=, <, >, <=, >= with three-valued logic
29
+ - **Logical**: and, or, not, xor, implies with three-valued logic
30
+ - **Unary**: +, -, not
31
+
32
+ ### 🚧 Phase 4: Basic Functions (Next)
33
+ - Function dispatch mechanism
34
+ - Simple value functions (first, last, count, etc.)
35
+ - Iterator functions (where, select, exists, all)
36
+
37
+ ### 📋 Phase 5: Type System (Planned)
38
+ - Type hierarchy and checking
39
+ - is/as operators
40
+ - Type conversions
41
+
42
+ ### 📋 Phase 6: Advanced Features (Planned)
43
+ - Context modification (defineVariable)
44
+ - Conditional evaluation (iif)
45
+ - Collection operations (union, intersect)
46
+ - Index operations
47
+
48
+ ## Key Design Decisions
49
+
50
+ 1. **Two-Phase Evaluation**: Control flow (top-down) and data flow (bottom-up)
51
+ 2. **Collection Semantics**: Everything is a collection, empty represents null/missing
52
+ 3. **Context Threading**: Context flows through expressions, modified by some operations
53
+ 4. **Three-Valued Logic**: Empty collections represent "unknown" in boolean operations
54
+ 5. **Singleton Rules**: Automatic conversion from single-item collections when needed
55
+
56
+ ## Usage
57
+
58
+ ```typescript
59
+ import { evaluateFHIRPath } from './interpreter/interpreter';
60
+ import { ContextManager } from './interpreter/context';
61
+
62
+ // Simple evaluation
63
+ const result = evaluateFHIRPath('Patient.name.given', patient);
64
+
65
+ // With context
66
+ const context = ContextManager.create();
67
+ const ctxWithVar = ContextManager.setVariable(context, 'threshold', [10]);
68
+ const result2 = evaluateFHIRPath('value > %threshold', data, ctxWithVar);
69
+ ```
70
+
71
+ ## Testing
72
+
73
+ Tests are organized by implementation phase in `test/interpreter.test.ts`:
74
+ - Phase 2: Simple nodes (literals, identifiers, variables, dot)
75
+ - Phase 3: Operators (arithmetic, comparison, logical)
76
+ - Phase 4: Functions (coming soon)
77
+
78
+ Run tests with: `bun test test/interpreter.test.ts`
@@ -0,0 +1,181 @@
1
+ import type { Context } from './types';
2
+
3
+ /**
4
+ * Context management utilities for FHIRPath interpreter.
5
+ * Context flows parallel to data through expressions.
6
+ *
7
+ * Uses JavaScript prototype chain for efficient inheritance.
8
+ */
9
+ export class ContextManager {
10
+ /**
11
+ * Create a new empty context
12
+ */
13
+ static create(initialInput?: any[]): Context {
14
+ // Create base context with null prototype to avoid Object.prototype pollution
15
+ const context = Object.create(null) as Context;
16
+
17
+ // Initialize with prototype-based objects
18
+ context.variables = Object.create(null);
19
+ context.env = Object.create(null);
20
+
21
+ // Set root variables
22
+ context.$context = initialInput;
23
+ context.$resource = initialInput;
24
+ context.$rootResource = initialInput;
25
+
26
+ return context;
27
+ }
28
+
29
+ /**
30
+ * Create a child context inheriting from parent via prototype chain
31
+ * O(1) operation - no copying needed
32
+ */
33
+ static copy(context: Context): Context {
34
+ // Create child context with parent as prototype
35
+ const newContext = Object.create(context) as Context;
36
+
37
+ // Create child objects that inherit from parent's objects
38
+ newContext.variables = Object.create(context.variables);
39
+ newContext.env = Object.create(context.env);
40
+
41
+ // Root variables are inherited automatically through prototype chain
42
+ // No need to copy them unless they change
43
+
44
+ return newContext;
45
+ }
46
+
47
+ /**
48
+ * Add or update a variable in context
49
+ * Only sets on current context level, shadowing parent values
50
+ */
51
+ static setVariable(context: Context, name: string, value: any[]): Context {
52
+ const newContext = this.copy(context);
53
+ newContext.variables[name] = value;
54
+ return newContext;
55
+ }
56
+
57
+ /**
58
+ * Get a variable value from context
59
+ * Uses prototype chain for lookup
60
+ */
61
+ static getVariable(context: Context, name: string): any[] | undefined {
62
+ // Check user-defined variables first (prototype chain handles inheritance)
63
+ if (name in context.variables) {
64
+ return context.variables[name];
65
+ }
66
+
67
+ // Check special root variables
68
+ switch (name) {
69
+ case 'context':
70
+ return context.$context;
71
+ case 'resource':
72
+ return context.$resource;
73
+ case 'rootResource':
74
+ return context.$rootResource;
75
+ default:
76
+ return undefined;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Set iterator context ($this, $index) - used by where(), select(), etc.
82
+ */
83
+ static setIteratorContext(
84
+ context: Context,
85
+ item: any,
86
+ index: number
87
+ ): Context {
88
+ const newContext = this.copy(context);
89
+ // Only set changed values - prototype provides the rest
90
+ newContext.env.$this = [item];
91
+ newContext.env.$index = index;
92
+ return newContext;
93
+ }
94
+
95
+ /**
96
+ * Set aggregate context ($total) - used by aggregate()
97
+ */
98
+ static setAggregateContext(
99
+ context: Context,
100
+ total: any[]
101
+ ): Context {
102
+ const newContext = this.copy(context);
103
+ newContext.env.$total = total;
104
+ return newContext;
105
+ }
106
+
107
+ /**
108
+ * Clear iterator/aggregate context - restore to original
109
+ */
110
+ static clearEnv(context: Context): Context {
111
+ const newContext = this.copy(context);
112
+ // Create fresh env object, effectively hiding parent's env values
113
+ newContext.env = Object.create(null);
114
+ return newContext;
115
+ }
116
+
117
+ /**
118
+ * Get special environment variable
119
+ */
120
+ static getEnvVariable(context: Context, name: '$this' | '$index' | '$total'): any {
121
+ switch (name) {
122
+ case '$this':
123
+ return context.env.$this;
124
+ case '$index':
125
+ return context.env.$index;
126
+ case '$total':
127
+ return context.env.$total;
128
+ default:
129
+ return undefined;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Check if a variable exists in context (including inherited)
135
+ */
136
+ static hasVariable(context: Context, name: string): boolean {
137
+ return (name in context.variables) ||
138
+ ['context', 'resource', 'rootResource'].includes(name);
139
+ }
140
+
141
+ /**
142
+ * Debug helper - get all variables including inherited ones
143
+ * Traverses prototype chain to collect all variables
144
+ */
145
+ static getAllVariables(context: Context): Record<string, any[]> {
146
+ const result: Record<string, any[]> = {};
147
+
148
+ // Traverse prototype chain for user variables
149
+ let currentVars = context.variables;
150
+ while (currentVars) {
151
+ for (const key in currentVars) {
152
+ if (!(key in result) && Object.prototype.hasOwnProperty.call(currentVars, key)) {
153
+ result[`%${key}`] = currentVars[key]!;
154
+ }
155
+ }
156
+ currentVars = Object.getPrototypeOf(currentVars);
157
+ }
158
+
159
+ // Root variables
160
+ if (context.$context) result['%context'] = context.$context;
161
+ if (context.$resource) result['%resource'] = context.$resource;
162
+ if (context.$rootResource) result['%rootResource'] = context.$rootResource;
163
+
164
+ // Environment variables (also traverse prototype chain)
165
+ let currentEnv = context.env;
166
+ while (currentEnv) {
167
+ if (currentEnv.$this !== undefined && !('$this' in result)) {
168
+ result['$this'] = currentEnv.$this;
169
+ }
170
+ if (currentEnv.$index !== undefined && !('$index' in result)) {
171
+ result['$index'] = [currentEnv.$index];
172
+ }
173
+ if (currentEnv.$total !== undefined && !('$total' in result)) {
174
+ result['$total'] = currentEnv.$total;
175
+ }
176
+ currentEnv = Object.getPrototypeOf(currentEnv);
177
+ }
178
+
179
+ return result;
180
+ }
181
+ }