fhirpath-ts 0.0.0-bootstrap.0
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/LICENSE +201 -0
- package/README.md +391 -0
- package/THIRD-PARTY-NOTICES.md +296 -0
- package/dist/analyzer/analyze-dto.d.ts +45 -0
- package/dist/analyzer/analyze-dto.d.ts.map +1 -0
- package/dist/analyzer/analyze-dto.js +147 -0
- package/dist/analyzer/analyze-dto.js.map +1 -0
- package/dist/analyzer/analyze.d.ts +98 -0
- package/dist/analyzer/analyze.d.ts.map +1 -0
- package/dist/analyzer/analyze.js +925 -0
- package/dist/analyzer/analyze.js.map +1 -0
- package/dist/analyzer/expression-policy.d.ts +210 -0
- package/dist/analyzer/expression-policy.d.ts.map +1 -0
- package/dist/analyzer/expression-policy.js +254 -0
- package/dist/analyzer/expression-policy.js.map +1 -0
- package/dist/analyzer/index.d.ts +3 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +6 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/regex-safety.d.ts +10 -0
- package/dist/analyzer/regex-safety.d.ts.map +1 -0
- package/dist/analyzer/regex-safety.js +84 -0
- package/dist/analyzer/regex-safety.js.map +1 -0
- package/dist/analyzer/signatures.d.ts +72 -0
- package/dist/analyzer/signatures.d.ts.map +1 -0
- package/dist/analyzer/signatures.js +195 -0
- package/dist/analyzer/signatures.js.map +1 -0
- package/dist/api/bundle.d.ts +29 -0
- package/dist/api/bundle.d.ts.map +1 -0
- package/dist/api/bundle.js +108 -0
- package/dist/api/bundle.js.map +1 -0
- package/dist/api/cache.d.ts +10 -0
- package/dist/api/cache.d.ts.map +1 -0
- package/dist/api/cache.js +38 -0
- package/dist/api/cache.js.map +1 -0
- package/dist/api/column-signature.d.ts +53 -0
- package/dist/api/column-signature.d.ts.map +1 -0
- package/dist/api/column-signature.js +43 -0
- package/dist/api/column-signature.js.map +1 -0
- package/dist/api/compile.d.ts +120 -0
- package/dist/api/compile.d.ts.map +1 -0
- package/dist/api/compile.js +167 -0
- package/dist/api/compile.js.map +1 -0
- package/dist/api/constraints.d.ts +48 -0
- package/dist/api/constraints.d.ts.map +1 -0
- package/dist/api/constraints.js +52 -0
- package/dist/api/constraints.js.map +1 -0
- package/dist/api/dto.d.ts +134 -0
- package/dist/api/dto.d.ts.map +1 -0
- package/dist/api/dto.js +295 -0
- package/dist/api/dto.js.map +1 -0
- package/dist/api/engine.d.ts +136 -0
- package/dist/api/engine.d.ts.map +1 -0
- package/dist/api/engine.js +183 -0
- package/dist/api/engine.js.map +1 -0
- package/dist/api/evaluate.d.ts +7 -0
- package/dist/api/evaluate.d.ts.map +1 -0
- package/dist/api/evaluate.js +10 -0
- package/dist/api/evaluate.js.map +1 -0
- package/dist/api/project.d.ts +89 -0
- package/dist/api/project.d.ts.map +1 -0
- package/dist/api/project.js +150 -0
- package/dist/api/project.js.map +1 -0
- package/dist/api/tagged.d.ts +14 -0
- package/dist/api/tagged.d.ts.map +1 -0
- package/dist/api/tagged.js +13 -0
- package/dist/api/tagged.js.map +1 -0
- package/dist/cli/dto-check.d.ts +25 -0
- package/dist/cli/dto-check.d.ts.map +1 -0
- package/dist/cli/dto-check.js +77 -0
- package/dist/cli/dto-check.js.map +1 -0
- package/dist/cli/fhirpath-check.d.ts +3 -0
- package/dist/cli/fhirpath-check.d.ts.map +1 -0
- package/dist/cli/fhirpath-check.js +149 -0
- package/dist/cli/fhirpath-check.js.map +1 -0
- package/dist/cli/ts-loader.d.mts +3 -0
- package/dist/cli/ts-loader.d.mts.map +1 -0
- package/dist/cli/ts-loader.mjs +48 -0
- package/dist/cli/ts-loader.mjs.map +1 -0
- package/dist/engine/context.d.ts +173 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +118 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/evaluator.d.ts +7 -0
- package/dist/engine/evaluator.d.ts.map +1 -0
- package/dist/engine/evaluator.js +196 -0
- package/dist/engine/evaluator.js.map +1 -0
- package/dist/engine/navigation.d.ts +11 -0
- package/dist/engine/navigation.d.ts.map +1 -0
- package/dist/engine/navigation.js +102 -0
- package/dist/engine/navigation.js.map +1 -0
- package/dist/engine/operators/collections.d.ts +12 -0
- package/dist/engine/operators/collections.d.ts.map +1 -0
- package/dist/engine/operators/collections.js +31 -0
- package/dist/engine/operators/collections.js.map +1 -0
- package/dist/engine/operators/comparison.d.ts +13 -0
- package/dist/engine/operators/comparison.d.ts.map +1 -0
- package/dist/engine/operators/comparison.js +77 -0
- package/dist/engine/operators/comparison.js.map +1 -0
- package/dist/engine/operators/equality.d.ts +19 -0
- package/dist/engine/operators/equality.d.ts.map +1 -0
- package/dist/engine/operators/equality.js +203 -0
- package/dist/engine/operators/equality.js.map +1 -0
- package/dist/engine/operators/index.d.ts +18 -0
- package/dist/engine/operators/index.d.ts.map +1 -0
- package/dist/engine/operators/index.js +30 -0
- package/dist/engine/operators/index.js.map +1 -0
- package/dist/engine/operators/logic.d.ts +8 -0
- package/dist/engine/operators/logic.d.ts.map +1 -0
- package/dist/engine/operators/logic.js +38 -0
- package/dist/engine/operators/logic.js.map +1 -0
- package/dist/engine/operators/math.d.ts +13 -0
- package/dist/engine/operators/math.d.ts.map +1 -0
- package/dist/engine/operators/math.js +198 -0
- package/dist/engine/operators/math.js.map +1 -0
- package/dist/engine/operators/strings.d.ts +8 -0
- package/dist/engine/operators/strings.d.ts.map +1 -0
- package/dist/engine/operators/strings.js +22 -0
- package/dist/engine/operators/strings.js.map +1 -0
- package/dist/engine/operators/types.d.ts +3 -0
- package/dist/engine/operators/types.d.ts.map +1 -0
- package/dist/engine/operators/types.js +15 -0
- package/dist/engine/operators/types.js.map +1 -0
- package/dist/engine/type-matching.d.ts +20 -0
- package/dist/engine/type-matching.d.ts.map +1 -0
- package/dist/engine/type-matching.js +122 -0
- package/dist/engine/type-matching.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/eslint/index.d.ts +12 -0
- package/dist/eslint/index.d.ts.map +1 -0
- package/dist/eslint/index.js +316 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/fhir/html-checks.d.ts +7 -0
- package/dist/fhir/html-checks.d.ts.map +1 -0
- package/dist/fhir/html-checks.js +256 -0
- package/dist/fhir/html-checks.js.map +1 -0
- package/dist/fhir/model-navigation.d.ts +11 -0
- package/dist/fhir/model-navigation.d.ts.map +1 -0
- package/dist/fhir/model-navigation.js +142 -0
- package/dist/fhir/model-navigation.js.map +1 -0
- package/dist/functions/aggregate.d.ts +2 -0
- package/dist/functions/aggregate.d.ts.map +1 -0
- package/dist/functions/aggregate.js +127 -0
- package/dist/functions/aggregate.js.map +1 -0
- package/dist/functions/boundary.d.ts +2 -0
- package/dist/functions/boundary.d.ts.map +1 -0
- package/dist/functions/boundary.js +189 -0
- package/dist/functions/boundary.js.map +1 -0
- package/dist/functions/combining.d.ts +2 -0
- package/dist/functions/combining.d.ts.map +1 -0
- package/dist/functions/combining.js +13 -0
- package/dist/functions/combining.js.map +1 -0
- package/dist/functions/control.d.ts +2 -0
- package/dist/functions/control.d.ts.map +1 -0
- package/dist/functions/control.js +25 -0
- package/dist/functions/control.js.map +1 -0
- package/dist/functions/conversion.d.ts +3 -0
- package/dist/functions/conversion.d.ts.map +1 -0
- package/dist/functions/conversion.js +316 -0
- package/dist/functions/conversion.js.map +1 -0
- package/dist/functions/date-components.d.ts +2 -0
- package/dist/functions/date-components.d.ts.map +1 -0
- package/dist/functions/date-components.js +81 -0
- package/dist/functions/date-components.js.map +1 -0
- package/dist/functions/existence.d.ts +4 -0
- package/dist/functions/existence.d.ts.map +1 -0
- package/dist/functions/existence.js +97 -0
- package/dist/functions/existence.js.map +1 -0
- package/dist/functions/fhir.d.ts +2 -0
- package/dist/functions/fhir.d.ts.map +1 -0
- package/dist/functions/fhir.js +222 -0
- package/dist/functions/fhir.js.map +1 -0
- package/dist/functions/filtering.d.ts +2 -0
- package/dist/functions/filtering.d.ts.map +1 -0
- package/dist/functions/filtering.js +112 -0
- package/dist/functions/filtering.js.map +1 -0
- package/dist/functions/install.d.ts +18 -0
- package/dist/functions/install.d.ts.map +1 -0
- package/dist/functions/install.js +20 -0
- package/dist/functions/install.js.map +1 -0
- package/dist/functions/iteration.d.ts +7 -0
- package/dist/functions/iteration.d.ts.map +1 -0
- package/dist/functions/iteration.js +9 -0
- package/dist/functions/iteration.js.map +1 -0
- package/dist/functions/math.d.ts +2 -0
- package/dist/functions/math.d.ts.map +1 -0
- package/dist/functions/math.js +152 -0
- package/dist/functions/math.js.map +1 -0
- package/dist/functions/reflection.d.ts +2 -0
- package/dist/functions/reflection.d.ts.map +1 -0
- package/dist/functions/reflection.js +23 -0
- package/dist/functions/reflection.js.map +1 -0
- package/dist/functions/registry.d.ts +28 -0
- package/dist/functions/registry.d.ts.map +1 -0
- package/dist/functions/registry.js +46 -0
- package/dist/functions/registry.js.map +1 -0
- package/dist/functions/sort.d.ts +2 -0
- package/dist/functions/sort.d.ts.map +1 -0
- package/dist/functions/sort.js +48 -0
- package/dist/functions/sort.js.map +1 -0
- package/dist/functions/string.d.ts +2 -0
- package/dist/functions/string.d.ts.map +1 -0
- package/dist/functions/string.js +353 -0
- package/dist/functions/string.js.map +1 -0
- package/dist/functions/subsetting.d.ts +2 -0
- package/dist/functions/subsetting.d.ts.map +1 -0
- package/dist/functions/subsetting.js +72 -0
- package/dist/functions/subsetting.js.map +1 -0
- package/dist/functions/tree.d.ts +8 -0
- package/dist/functions/tree.d.ts.map +1 -0
- package/dist/functions/tree.js +109 -0
- package/dist/functions/tree.js.map +1 -0
- package/dist/functions/type-specifier.d.ts +8 -0
- package/dist/functions/type-specifier.d.ts.map +1 -0
- package/dist/functions/type-specifier.js +27 -0
- package/dist/functions/type-specifier.js.map +1 -0
- package/dist/functions/utility.d.ts +2 -0
- package/dist/functions/utility.d.ts.map +1 -0
- package/dist/functions/utility.js +76 -0
- package/dist/functions/utility.js.map +1 -0
- package/dist/functions/variables.d.ts +2 -0
- package/dist/functions/variables.d.ts.map +1 -0
- package/dist/functions/variables.js +35 -0
- package/dist/functions/variables.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/lexer.d.ts +4 -0
- package/dist/lexer/lexer.d.ts.map +1 -0
- package/dist/lexer/lexer.js +318 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/lexer/tokens.d.ts +23 -0
- package/dist/lexer/tokens.d.ts.map +1 -0
- package/dist/lexer/tokens.js +40 -0
- package/dist/lexer/tokens.js.map +1 -0
- package/dist/model/provider.d.ts +36 -0
- package/dist/model/provider.d.ts.map +1 -0
- package/dist/model/provider.js +2 -0
- package/dist/model/provider.js.map +1 -0
- package/dist/parser/ast.d.ts +104 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +2 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/parser.d.ts +4 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +299 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/precedence.d.ts +24 -0
- package/dist/parser/precedence.d.ts.map +1 -0
- package/dist/parser/precedence.js +52 -0
- package/dist/parser/precedence.js.map +1 -0
- package/dist/parser/printer.d.ts +4 -0
- package/dist/parser/printer.d.ts.map +1 -0
- package/dist/parser/printer.js +112 -0
- package/dist/parser/printer.js.map +1 -0
- package/dist/r4/generated/resources-data.d.ts +3 -0
- package/dist/r4/generated/resources-data.d.ts.map +1 -0
- package/dist/r4/generated/resources-data.js +28628 -0
- package/dist/r4/generated/resources-data.js.map +1 -0
- package/dist/r4/generated/type-maps.d.ts +43153 -0
- package/dist/r4/generated/type-maps.d.ts.map +1 -0
- package/dist/r4/generated/type-maps.js +4 -0
- package/dist/r4/generated/type-maps.js.map +1 -0
- package/dist/r4/generated/types-data.d.ts +3 -0
- package/dist/r4/generated/types-data.d.ts.map +1 -0
- package/dist/r4/generated/types-data.js +2205 -0
- package/dist/r4/generated/types-data.js.map +1 -0
- package/dist/r4/index.d.ts +9 -0
- package/dist/r4/index.d.ts.map +1 -0
- package/dist/r4/index.js +94 -0
- package/dist/r4/index.js.map +1 -0
- package/dist/r4/model-data.d.ts +17 -0
- package/dist/r4/model-data.d.ts.map +1 -0
- package/dist/r4/model-data.js +2 -0
- package/dist/r4/model-data.js.map +1 -0
- package/dist/sites/index.d.ts +31 -0
- package/dist/sites/index.d.ts.map +1 -0
- package/dist/sites/index.js +205 -0
- package/dist/sites/index.js.map +1 -0
- package/dist/typed/infer.d.ts +234 -0
- package/dist/typed/infer.d.ts.map +1 -0
- package/dist/typed/infer.js +85 -0
- package/dist/typed/infer.js.map +1 -0
- package/dist/typed/perf-fixture.types.d.ts +136 -0
- package/dist/typed/perf-fixture.types.d.ts.map +1 -0
- package/dist/typed/perf-fixture.types.js +2 -0
- package/dist/typed/perf-fixture.types.js.map +1 -0
- package/dist/values/collection.d.ts +24 -0
- package/dist/values/collection.d.ts.map +1 -0
- package/dist/values/collection.js +51 -0
- package/dist/values/collection.js.map +1 -0
- package/dist/values/datetime.d.ts +54 -0
- package/dist/values/datetime.d.ts.map +1 -0
- package/dist/values/datetime.js +226 -0
- package/dist/values/datetime.js.map +1 -0
- package/dist/values/decimal.d.ts +48 -0
- package/dist/values/decimal.d.ts.map +1 -0
- package/dist/values/decimal.js +188 -0
- package/dist/values/decimal.js.map +1 -0
- package/dist/values/numeric.d.ts +24 -0
- package/dist/values/numeric.d.ts.map +1 -0
- package/dist/values/numeric.js +53 -0
- package/dist/values/numeric.js.map +1 -0
- package/dist/values/quantity.d.ts +49 -0
- package/dist/values/quantity.d.ts.map +1 -0
- package/dist/values/quantity.js +297 -0
- package/dist/values/quantity.js.map +1 -0
- package/dist/values/temporal-arithmetic.d.ts +17 -0
- package/dist/values/temporal-arithmetic.d.ts.map +1 -0
- package/dist/values/temporal-arithmetic.js +183 -0
- package/dist/values/temporal-arithmetic.js.map +1 -0
- package/dist/values/temporal-compare.d.ts +9 -0
- package/dist/values/temporal-compare.d.ts.map +1 -0
- package/dist/values/temporal-compare.js +59 -0
- package/dist/values/temporal-compare.js.map +1 -0
- package/dist/values/type-compat.d.ts +47 -0
- package/dist/values/type-compat.d.ts.map +1 -0
- package/dist/values/type-compat.js +100 -0
- package/dist/values/type-compat.js.map +1 -0
- package/dist/values/typed-value.d.ts +48 -0
- package/dist/values/typed-value.d.ts.map +1 -0
- package/dist/values/typed-value.js +117 -0
- package/dist/values/typed-value.js.map +1 -0
- package/dist/values/ucum.d.ts +14 -0
- package/dist/values/ucum.d.ts.map +1 -0
- package/dist/values/ucum.js +146 -0
- package/dist/values/ucum.js.map +1 -0
- package/package.json +137 -0
- package/src/analyzer/analyze-dto.ts +195 -0
- package/src/analyzer/analyze.ts +1181 -0
- package/src/analyzer/expression-policy.ts +434 -0
- package/src/analyzer/index.ts +23 -0
- package/src/analyzer/regex-safety.ts +85 -0
- package/src/analyzer/signatures.ts +260 -0
- package/src/api/bundle.ts +132 -0
- package/src/api/cache.ts +40 -0
- package/src/api/column-signature.ts +71 -0
- package/src/api/compile.ts +308 -0
- package/src/api/constraints.ts +104 -0
- package/src/api/dto.ts +445 -0
- package/src/api/engine.ts +328 -0
- package/src/api/evaluate.ts +11 -0
- package/src/api/project.ts +255 -0
- package/src/api/tagged.ts +35 -0
- package/src/cli/dto-check.ts +94 -0
- package/src/cli/fhirpath-check.ts +168 -0
- package/src/cli/ts-loader.mjs +49 -0
- package/src/engine/context.ts +282 -0
- package/src/engine/evaluator.ts +246 -0
- package/src/engine/navigation.ts +106 -0
- package/src/engine/operators/collections.ts +34 -0
- package/src/engine/operators/comparison.ts +82 -0
- package/src/engine/operators/equality.ts +232 -0
- package/src/engine/operators/index.ts +65 -0
- package/src/engine/operators/logic.ts +53 -0
- package/src/engine/operators/math.ts +225 -0
- package/src/engine/operators/strings.ts +25 -0
- package/src/engine/operators/types.ts +16 -0
- package/src/engine/type-matching.ts +148 -0
- package/src/errors.ts +33 -0
- package/src/eslint/index.ts +388 -0
- package/src/fhir/html-checks.ts +266 -0
- package/src/fhir/model-navigation.ts +148 -0
- package/src/functions/aggregate.ts +136 -0
- package/src/functions/boundary.ts +209 -0
- package/src/functions/combining.ts +15 -0
- package/src/functions/control.ts +25 -0
- package/src/functions/conversion.ts +349 -0
- package/src/functions/date-components.ts +88 -0
- package/src/functions/existence.ts +107 -0
- package/src/functions/fhir.ts +247 -0
- package/src/functions/filtering.ts +120 -0
- package/src/functions/install.ts +19 -0
- package/src/functions/iteration.ts +21 -0
- package/src/functions/math.ts +167 -0
- package/src/functions/reflection.ts +24 -0
- package/src/functions/registry.ts +72 -0
- package/src/functions/sort.ts +68 -0
- package/src/functions/string.ts +413 -0
- package/src/functions/subsetting.ts +89 -0
- package/src/functions/tree.ts +112 -0
- package/src/functions/type-specifier.ts +29 -0
- package/src/functions/utility.ts +82 -0
- package/src/functions/variables.ts +36 -0
- package/src/index.ts +30 -0
- package/src/lexer/lexer.ts +341 -0
- package/src/lexer/tokens.ts +69 -0
- package/src/model/provider.ts +36 -0
- package/src/parser/ast.ts +164 -0
- package/src/parser/parser.ts +321 -0
- package/src/parser/precedence.ts +52 -0
- package/src/parser/printer.ts +114 -0
- package/src/r4/generated/resources-data.ts +28631 -0
- package/src/r4/generated/type-maps.ts +22145 -0
- package/src/r4/generated/types-data.ts +2208 -0
- package/src/r4/index.ts +107 -0
- package/src/r4/model-data.ts +17 -0
- package/src/sites/index.ts +285 -0
- package/src/typed/infer.ts +433 -0
- package/src/typed/perf-fixture.types.ts +143 -0
- package/src/values/collection.ts +57 -0
- package/src/values/datetime.ts +285 -0
- package/src/values/decimal.ts +213 -0
- package/src/values/numeric.ts +63 -0
- package/src/values/quantity.ts +358 -0
- package/src/values/temporal-arithmetic.ts +231 -0
- package/src/values/temporal-compare.ts +65 -0
- package/src/values/type-compat.ts +133 -0
- package/src/values/typed-value.ts +151 -0
- package/src/values/ucum.ts +181 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SourceSpan } from '../errors.ts'
|
|
2
|
+
|
|
3
|
+
export type TokenKind =
|
|
4
|
+
| 'identifier'
|
|
5
|
+
| 'delimitedIdentifier'
|
|
6
|
+
| 'string'
|
|
7
|
+
| 'number'
|
|
8
|
+
| 'date'
|
|
9
|
+
| 'dateTime'
|
|
10
|
+
| 'time'
|
|
11
|
+
| 'specialVariable'
|
|
12
|
+
| 'keyword'
|
|
13
|
+
| 'punct'
|
|
14
|
+
| 'operator'
|
|
15
|
+
| 'end'
|
|
16
|
+
|
|
17
|
+
export interface Token {
|
|
18
|
+
kind: TokenKind
|
|
19
|
+
/** Raw source text of the token. */
|
|
20
|
+
text: string
|
|
21
|
+
/**
|
|
22
|
+
* Decoded value where it differs from the raw text: escape sequences resolved for
|
|
23
|
+
* `string` and `delimitedIdentifier`, quotes/backticks/`@`/`$` stripped where applicable.
|
|
24
|
+
*/
|
|
25
|
+
value: string
|
|
26
|
+
span: SourceSpan
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Words the grammar treats as operators or literals. `as`, `contains`, `in`, `is`, `div` and
|
|
31
|
+
* `mod` may still appear as element names (the parser decides by position); calendar duration
|
|
32
|
+
* words (`year`, `months`, ...) lex as plain identifiers.
|
|
33
|
+
*/
|
|
34
|
+
export const KEYWORDS: ReadonlySet<string> = new Set([
|
|
35
|
+
'and',
|
|
36
|
+
'or',
|
|
37
|
+
'xor',
|
|
38
|
+
'implies',
|
|
39
|
+
'div',
|
|
40
|
+
'mod',
|
|
41
|
+
'in',
|
|
42
|
+
'contains',
|
|
43
|
+
'is',
|
|
44
|
+
'as',
|
|
45
|
+
'true',
|
|
46
|
+
'false',
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
export const SPECIAL_VARIABLES: ReadonlySet<string> = new Set(['$this', '$index', '$total'])
|
|
50
|
+
|
|
51
|
+
/** Calendar duration units usable in quantity literals, e.g. `4 days` (spec §4.1.4.7). */
|
|
52
|
+
export const CALENDAR_DURATION_UNITS: ReadonlySet<string> = new Set([
|
|
53
|
+
'year',
|
|
54
|
+
'years',
|
|
55
|
+
'month',
|
|
56
|
+
'months',
|
|
57
|
+
'week',
|
|
58
|
+
'weeks',
|
|
59
|
+
'day',
|
|
60
|
+
'days',
|
|
61
|
+
'hour',
|
|
62
|
+
'hours',
|
|
63
|
+
'minute',
|
|
64
|
+
'minutes',
|
|
65
|
+
'second',
|
|
66
|
+
'seconds',
|
|
67
|
+
'millisecond',
|
|
68
|
+
'milliseconds',
|
|
69
|
+
])
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Type and cardinality of one element of a class, from the model's ModelInfo. */
|
|
2
|
+
export interface ElementInfo {
|
|
3
|
+
/** Canonical type name(s); more than one for choice elements like `Observation.value[x]`. */
|
|
4
|
+
types: string[]
|
|
5
|
+
isCollection: boolean
|
|
6
|
+
/** True for `[x]` elements, whose JSON keys carry a type suffix (`valueQuantity`). */
|
|
7
|
+
isChoice: boolean
|
|
8
|
+
/**
|
|
9
|
+
* Resource type names a Reference-typed element may point to, from
|
|
10
|
+
* `Reference.targetProfile` — what resolve() statically yields. Absent when
|
|
11
|
+
* the element is not a reference or the reference is unconstrained.
|
|
12
|
+
*/
|
|
13
|
+
referenceTargets?: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The spec's ModelInfo concept (§10/§12.2): everything the engine and the static
|
|
18
|
+
* analyzer need to know about a data model. The core never assumes FHIR; the R4
|
|
19
|
+
* model package implements this from generated StructureDefinition data.
|
|
20
|
+
*/
|
|
21
|
+
export interface ModelProvider {
|
|
22
|
+
/** Namespace prefix for the model's types, e.g. `FHIR`. */
|
|
23
|
+
namespace: string
|
|
24
|
+
/** Resolve an unqualified type name to its canonical name, or undefined if unknown. */
|
|
25
|
+
resolveType(name: string): string | undefined
|
|
26
|
+
/** Look up an element on a type, resolving choice elements by their stem name. */
|
|
27
|
+
getElement(type: string, element: string): ElementInfo | undefined
|
|
28
|
+
/** True when `type` equals or derives from `base` (both canonical). */
|
|
29
|
+
isSubtypeOf(type: string, base: string): boolean
|
|
30
|
+
/**
|
|
31
|
+
* All element names of a type, own and inherited, in declaration order.
|
|
32
|
+
* Undefined when the type is unknown to the model — navigation then falls back
|
|
33
|
+
* to raw JSON reads instead of strict unknown-element errors.
|
|
34
|
+
*/
|
|
35
|
+
listElements?(type: string): string[] | undefined
|
|
36
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { SourceSpan } from '../errors.ts'
|
|
2
|
+
|
|
3
|
+
export type BinaryOperator =
|
|
4
|
+
| '*'
|
|
5
|
+
| '/'
|
|
6
|
+
| 'div'
|
|
7
|
+
| 'mod'
|
|
8
|
+
| '+'
|
|
9
|
+
| '-'
|
|
10
|
+
| '&'
|
|
11
|
+
| '|'
|
|
12
|
+
| '<'
|
|
13
|
+
| '>'
|
|
14
|
+
| '<='
|
|
15
|
+
| '>='
|
|
16
|
+
| '='
|
|
17
|
+
| '~'
|
|
18
|
+
| '!='
|
|
19
|
+
| '!~'
|
|
20
|
+
| 'in'
|
|
21
|
+
| 'contains'
|
|
22
|
+
| 'and'
|
|
23
|
+
| 'or'
|
|
24
|
+
| 'xor'
|
|
25
|
+
| 'implies'
|
|
26
|
+
|
|
27
|
+
export type UnaryOperator = '+' | '-'
|
|
28
|
+
|
|
29
|
+
/** A possibly qualified type name, e.g. `Patient`, `FHIR.Patient`, or `System.Boolean`. */
|
|
30
|
+
export interface TypeSpecifier {
|
|
31
|
+
parts: string[]
|
|
32
|
+
span: SourceSpan
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface BaseNode {
|
|
36
|
+
span: SourceSpan
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The empty collection literal `{}`. */
|
|
40
|
+
export interface NullLiteralNode extends BaseNode {
|
|
41
|
+
kind: 'null'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface BooleanLiteralNode extends BaseNode {
|
|
45
|
+
kind: 'boolean'
|
|
46
|
+
value: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface StringLiteralNode extends BaseNode {
|
|
50
|
+
kind: 'string'
|
|
51
|
+
value: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Integer, Long (5L), or Decimal literal; the raw text keeps float precision intact. */
|
|
55
|
+
export interface NumberLiteralNode extends BaseNode {
|
|
56
|
+
kind: 'number'
|
|
57
|
+
text: string
|
|
58
|
+
isDecimal: boolean
|
|
59
|
+
isLong?: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DateLiteralNode extends BaseNode {
|
|
63
|
+
kind: 'date'
|
|
64
|
+
/** Literal text without the `@`, e.g. `2014-01`. */
|
|
65
|
+
text: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DateTimeLiteralNode extends BaseNode {
|
|
69
|
+
kind: 'dateTime'
|
|
70
|
+
/** Literal text without the `@`, e.g. `2014-01-25T14:30:00Z` or `2014T`. */
|
|
71
|
+
text: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TimeLiteralNode extends BaseNode {
|
|
75
|
+
kind: 'time'
|
|
76
|
+
/** Literal text without the `@T`, e.g. `14:30`. */
|
|
77
|
+
text: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface QuantityLiteralNode extends BaseNode {
|
|
81
|
+
kind: 'quantity'
|
|
82
|
+
value: string
|
|
83
|
+
unit: string
|
|
84
|
+
/** `'mg'` style UCUM strings vs bare calendar words like `days`. */
|
|
85
|
+
unitKind: 'ucum' | 'calendar'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A path segment or element name, e.g. `Patient` or `name`. */
|
|
89
|
+
export interface IdentifierNode extends BaseNode {
|
|
90
|
+
kind: 'identifier'
|
|
91
|
+
name: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** `$this`, `$index`, or `$total`. */
|
|
95
|
+
export interface SpecialVariableNode extends BaseNode {
|
|
96
|
+
kind: 'special'
|
|
97
|
+
name: 'this' | 'index' | 'total'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** An environment variable, e.g. `%ucum` or `` %`vs-observation-vitalsignresult` ``. */
|
|
101
|
+
export interface ExternalConstantNode extends BaseNode {
|
|
102
|
+
kind: 'external'
|
|
103
|
+
name: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface DotNode extends BaseNode {
|
|
107
|
+
kind: 'dot'
|
|
108
|
+
left: AstNode
|
|
109
|
+
right: AstNode
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface IndexerNode extends BaseNode {
|
|
113
|
+
kind: 'indexer'
|
|
114
|
+
target: AstNode
|
|
115
|
+
index: AstNode
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface FunctionCallNode extends BaseNode {
|
|
119
|
+
kind: 'call'
|
|
120
|
+
name: string
|
|
121
|
+
args: AstNode[]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface UnaryNode extends BaseNode {
|
|
125
|
+
kind: 'unary'
|
|
126
|
+
operator: UnaryOperator
|
|
127
|
+
operand: AstNode
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface BinaryNode extends BaseNode {
|
|
131
|
+
kind: 'binary'
|
|
132
|
+
operator: BinaryOperator
|
|
133
|
+
left: AstNode
|
|
134
|
+
right: AstNode
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** `expr is Type` or `expr as Type`. */
|
|
138
|
+
export interface TypeOpNode extends BaseNode {
|
|
139
|
+
kind: 'typeOp'
|
|
140
|
+
operator: 'is' | 'as'
|
|
141
|
+
operand: AstNode
|
|
142
|
+
type: TypeSpecifier
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export type AstNode =
|
|
146
|
+
| NullLiteralNode
|
|
147
|
+
| BooleanLiteralNode
|
|
148
|
+
| StringLiteralNode
|
|
149
|
+
| NumberLiteralNode
|
|
150
|
+
| DateLiteralNode
|
|
151
|
+
| DateTimeLiteralNode
|
|
152
|
+
| TimeLiteralNode
|
|
153
|
+
| QuantityLiteralNode
|
|
154
|
+
| IdentifierNode
|
|
155
|
+
| SpecialVariableNode
|
|
156
|
+
| ExternalConstantNode
|
|
157
|
+
| DotNode
|
|
158
|
+
| IndexerNode
|
|
159
|
+
| FunctionCallNode
|
|
160
|
+
| UnaryNode
|
|
161
|
+
| BinaryNode
|
|
162
|
+
| TypeOpNode
|
|
163
|
+
|
|
164
|
+
export type AstNodeKind = AstNode['kind']
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// The Pratt parsing approach (prefix/infix parselets driven by a precedence table) is adapted
|
|
2
|
+
// from Medplum's FHIRPath parser (Apache-2.0):
|
|
3
|
+
// https://github.com/medplum/medplum/blob/main/packages/core/src/fhirlexer/parse.ts
|
|
4
|
+
import { FhirPathSyntaxError, type SourceSpan } from '../errors.ts'
|
|
5
|
+
import { tokenize } from '../lexer/lexer.ts'
|
|
6
|
+
import { CALENDAR_DURATION_UNITS, type Token } from '../lexer/tokens.ts'
|
|
7
|
+
import type { AstNode, BinaryOperator, TypeSpecifier, UnaryOperator } from './ast.ts'
|
|
8
|
+
import { BindingPower, INFIX_BINDING_POWER } from './precedence.ts'
|
|
9
|
+
|
|
10
|
+
/** Keywords the grammar also accepts as element names, e.g. `'abc'.contains('b')`. */
|
|
11
|
+
const KEYWORDS_USABLE_AS_IDENTIFIERS: ReadonlySet<string> = new Set(['as', 'contains', 'in', 'is'])
|
|
12
|
+
|
|
13
|
+
const TYPE_OPERATORS: ReadonlySet<string> = new Set(['is', 'as'])
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Nesting far beyond anything a real expression uses. Without a cap, adversarial
|
|
17
|
+
* input like ((((…)))) overflows the call stack as a native RangeError instead of
|
|
18
|
+
* the FhirPathSyntaxError callers rely on catching.
|
|
19
|
+
*/
|
|
20
|
+
const MAX_DEPTH = 500
|
|
21
|
+
|
|
22
|
+
class Parser {
|
|
23
|
+
private readonly tokens: Token[]
|
|
24
|
+
private pos = 0
|
|
25
|
+
private depth = 0
|
|
26
|
+
/** Parenthesized nodes can no longer take call parentheses: `(a)(b)` is not grammatical. */
|
|
27
|
+
private readonly parenthesized = new WeakSet<AstNode>()
|
|
28
|
+
|
|
29
|
+
constructor(source: string) {
|
|
30
|
+
this.tokens = tokenize(source)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
parse(): AstNode {
|
|
34
|
+
const expression = this.parseExpression(0)
|
|
35
|
+
const trailing = this.peek()
|
|
36
|
+
if (trailing.kind !== 'end') {
|
|
37
|
+
throw this.error(`Unexpected '${trailing.text}' after expression`, trailing)
|
|
38
|
+
}
|
|
39
|
+
return expression
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private parseExpression(minBindingPower: number): AstNode {
|
|
43
|
+
if (++this.depth > MAX_DEPTH) {
|
|
44
|
+
throw this.error(`Expression nesting exceeds ${MAX_DEPTH} levels`, this.peek())
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
let left = this.parsePrefix()
|
|
48
|
+
for (;;) {
|
|
49
|
+
const token = this.peek()
|
|
50
|
+
const key = token.kind === 'operator' || token.kind === 'punct' || token.kind === 'keyword' ? token.text : ''
|
|
51
|
+
const bindingPower = INFIX_BINDING_POWER[key]
|
|
52
|
+
if (bindingPower === undefined || bindingPower <= minBindingPower) {
|
|
53
|
+
return left
|
|
54
|
+
}
|
|
55
|
+
this.advance()
|
|
56
|
+
left = this.parseInfix(left, token, bindingPower)
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
this.depth--
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private parsePrefix(): AstNode {
|
|
64
|
+
const token = this.peek()
|
|
65
|
+
switch (token.kind) {
|
|
66
|
+
case 'end':
|
|
67
|
+
throw this.error('Unexpected end of expression', token)
|
|
68
|
+
case 'number':
|
|
69
|
+
return this.parseNumberOrQuantity()
|
|
70
|
+
case 'string':
|
|
71
|
+
this.advance()
|
|
72
|
+
return { kind: 'string', value: token.value, span: token.span }
|
|
73
|
+
case 'date':
|
|
74
|
+
this.advance()
|
|
75
|
+
return { kind: 'date', text: token.value, span: token.span }
|
|
76
|
+
case 'dateTime':
|
|
77
|
+
this.advance()
|
|
78
|
+
return { kind: 'dateTime', text: token.value, span: token.span }
|
|
79
|
+
case 'time':
|
|
80
|
+
this.advance()
|
|
81
|
+
return { kind: 'time', text: token.value, span: token.span }
|
|
82
|
+
case 'specialVariable':
|
|
83
|
+
this.advance()
|
|
84
|
+
return { kind: 'special', name: token.value as 'this' | 'index' | 'total', span: token.span }
|
|
85
|
+
case 'identifier':
|
|
86
|
+
case 'delimitedIdentifier':
|
|
87
|
+
this.advance()
|
|
88
|
+
return { kind: 'identifier', name: token.value, span: token.span }
|
|
89
|
+
case 'keyword':
|
|
90
|
+
return this.parseKeywordPrefix(token)
|
|
91
|
+
case 'operator':
|
|
92
|
+
return this.parseUnary(token)
|
|
93
|
+
case 'punct':
|
|
94
|
+
return this.parsePunctPrefix(token)
|
|
95
|
+
/* v8 ignore start -- exhaustive fallback, every token kind has a case above */
|
|
96
|
+
default:
|
|
97
|
+
throw this.error(`Unexpected '${token.text}'`, token)
|
|
98
|
+
/* v8 ignore stop */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private parseKeywordPrefix(token: Token): AstNode {
|
|
103
|
+
if (token.text === 'true' || token.text === 'false') {
|
|
104
|
+
this.advance()
|
|
105
|
+
return { kind: 'boolean', value: token.text === 'true', span: token.span }
|
|
106
|
+
}
|
|
107
|
+
if (KEYWORDS_USABLE_AS_IDENTIFIERS.has(token.text)) {
|
|
108
|
+
this.advance()
|
|
109
|
+
return { kind: 'identifier', name: token.value, span: token.span }
|
|
110
|
+
}
|
|
111
|
+
throw this.error(`Unexpected '${token.text}'`, token)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private parseUnary(token: Token): AstNode {
|
|
115
|
+
if (token.text !== '+' && token.text !== '-') {
|
|
116
|
+
throw this.error(`Unexpected '${token.text}'`, token)
|
|
117
|
+
}
|
|
118
|
+
this.advance()
|
|
119
|
+
const operand = this.parseExpression(BindingPower.Unary)
|
|
120
|
+
return {
|
|
121
|
+
kind: 'unary',
|
|
122
|
+
operator: token.text as UnaryOperator,
|
|
123
|
+
operand,
|
|
124
|
+
span: this.spanBetween(token.span, operand.span),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private parsePunctPrefix(token: Token): AstNode {
|
|
129
|
+
switch (token.text) {
|
|
130
|
+
case '(': {
|
|
131
|
+
this.advance()
|
|
132
|
+
const inner = this.parseExpression(0)
|
|
133
|
+
this.expect(')')
|
|
134
|
+
this.parenthesized.add(inner)
|
|
135
|
+
return inner
|
|
136
|
+
}
|
|
137
|
+
case '{': {
|
|
138
|
+
this.advance()
|
|
139
|
+
const close = this.expect('}')
|
|
140
|
+
return { kind: 'null', span: this.spanBetween(token.span, close.span) }
|
|
141
|
+
}
|
|
142
|
+
case '%': {
|
|
143
|
+
this.advance()
|
|
144
|
+
const name = this.peek()
|
|
145
|
+
if (name.kind !== 'identifier' && name.kind !== 'delimitedIdentifier' && name.kind !== 'string') {
|
|
146
|
+
throw this.error('Expected a name after %', name)
|
|
147
|
+
}
|
|
148
|
+
this.advance()
|
|
149
|
+
return { kind: 'external', name: name.value, span: this.spanBetween(token.span, name.span) }
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
throw this.error(`Unexpected '${token.text}'`, token)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private parseNumberOrQuantity(): AstNode {
|
|
157
|
+
const token = this.peek()
|
|
158
|
+
this.advance()
|
|
159
|
+
if (token.value.endsWith('L')) {
|
|
160
|
+
// Long literals take no quantity unit.
|
|
161
|
+
return { kind: 'number', text: token.value.slice(0, -1), isDecimal: false, isLong: true, span: token.span }
|
|
162
|
+
}
|
|
163
|
+
const next = this.peek()
|
|
164
|
+
if (next.kind === 'string') {
|
|
165
|
+
this.advance()
|
|
166
|
+
return {
|
|
167
|
+
kind: 'quantity',
|
|
168
|
+
value: token.value,
|
|
169
|
+
unit: next.value,
|
|
170
|
+
unitKind: 'ucum',
|
|
171
|
+
span: this.spanBetween(token.span, next.span),
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (next.kind === 'identifier' && CALENDAR_DURATION_UNITS.has(next.value)) {
|
|
175
|
+
this.advance()
|
|
176
|
+
return {
|
|
177
|
+
kind: 'quantity',
|
|
178
|
+
value: token.value,
|
|
179
|
+
unit: next.value,
|
|
180
|
+
unitKind: 'calendar',
|
|
181
|
+
span: this.spanBetween(token.span, next.span),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { kind: 'number', text: token.value, isDecimal: token.value.includes('.'), span: token.span }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private parseInfix(left: AstNode, token: Token, bindingPower: number): AstNode {
|
|
188
|
+
switch (token.text) {
|
|
189
|
+
case '.':
|
|
190
|
+
return this.parseDot(left)
|
|
191
|
+
case '[': {
|
|
192
|
+
const index = this.parseExpression(0)
|
|
193
|
+
const close = this.expect(']')
|
|
194
|
+
return { kind: 'indexer', target: left, index, span: this.spanBetween(left.span, close.span) }
|
|
195
|
+
}
|
|
196
|
+
case '(':
|
|
197
|
+
return this.parseCall(left)
|
|
198
|
+
default:
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
if (TYPE_OPERATORS.has(token.text)) {
|
|
202
|
+
const type = this.parseTypeSpecifier()
|
|
203
|
+
return {
|
|
204
|
+
kind: 'typeOp',
|
|
205
|
+
operator: token.text as 'is' | 'as',
|
|
206
|
+
operand: left,
|
|
207
|
+
type,
|
|
208
|
+
span: this.spanBetween(left.span, type.span),
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// All FHIRPath binary operators are left-associative, so the right side
|
|
212
|
+
// parses at this operator's own binding power.
|
|
213
|
+
const right = this.parseExpression(bindingPower)
|
|
214
|
+
return {
|
|
215
|
+
kind: 'binary',
|
|
216
|
+
operator: token.text as BinaryOperator,
|
|
217
|
+
left,
|
|
218
|
+
right,
|
|
219
|
+
span: this.spanBetween(left.span, right.span),
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* After `.`, only an element name, function call, or special variable may follow.
|
|
225
|
+
* Operator keywords are element names here, so `text.div` and `'abc'.contains('b')` work.
|
|
226
|
+
*/
|
|
227
|
+
private parseDot(left: AstNode): AstNode {
|
|
228
|
+
const token = this.peek()
|
|
229
|
+
let right: AstNode
|
|
230
|
+
if (
|
|
231
|
+
token.kind === 'identifier' ||
|
|
232
|
+
token.kind === 'delimitedIdentifier' ||
|
|
233
|
+
(token.kind === 'keyword' && token.text !== 'true' && token.text !== 'false')
|
|
234
|
+
) {
|
|
235
|
+
this.advance()
|
|
236
|
+
right = { kind: 'identifier', name: token.value, span: token.span }
|
|
237
|
+
if (this.peek().kind === 'punct' && this.peek().text === '(') {
|
|
238
|
+
this.advance()
|
|
239
|
+
right = this.parseCall(right)
|
|
240
|
+
}
|
|
241
|
+
} else if (token.kind === 'specialVariable') {
|
|
242
|
+
this.advance()
|
|
243
|
+
right = { kind: 'special', name: token.value as 'this' | 'index' | 'total', span: token.span }
|
|
244
|
+
} else {
|
|
245
|
+
throw this.error(`Expected an element or function name after '.', got '${token.text}'`, token)
|
|
246
|
+
}
|
|
247
|
+
return { kind: 'dot', left, right, span: this.spanBetween(left.span, right.span) }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private parseCall(target: AstNode): AstNode {
|
|
251
|
+
if (target.kind !== 'identifier' || this.parenthesized.has(target)) {
|
|
252
|
+
throw this.error('Unexpected parentheses', this.peek())
|
|
253
|
+
}
|
|
254
|
+
const args: AstNode[] = []
|
|
255
|
+
if (!(this.peek().kind === 'punct' && this.peek().text === ')')) {
|
|
256
|
+
args.push(this.parseExpression(0))
|
|
257
|
+
while (this.peek().kind === 'punct' && this.peek().text === ',') {
|
|
258
|
+
this.advance()
|
|
259
|
+
args.push(this.parseExpression(0))
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const close = this.expect(')')
|
|
263
|
+
return { kind: 'call', name: target.name, args, span: this.spanBetween(target.span, close.span) }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private parseTypeSpecifier(): TypeSpecifier {
|
|
267
|
+
const first = this.expectTypeName()
|
|
268
|
+
const parts = [first.value]
|
|
269
|
+
let end = first.span
|
|
270
|
+
while (this.peek().kind === 'punct' && this.peek().text === '.') {
|
|
271
|
+
this.advance()
|
|
272
|
+
const part = this.expectTypeName()
|
|
273
|
+
parts.push(part.value)
|
|
274
|
+
end = part.span
|
|
275
|
+
}
|
|
276
|
+
return { parts, span: this.spanBetween(first.span, end) }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private expectTypeName(): Token {
|
|
280
|
+
const token = this.peek()
|
|
281
|
+
if (token.kind !== 'identifier' && token.kind !== 'delimitedIdentifier') {
|
|
282
|
+
throw this.error(`Expected a type name, got '${token.text}'`, token)
|
|
283
|
+
}
|
|
284
|
+
this.advance()
|
|
285
|
+
return token
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private peek(): Token {
|
|
289
|
+
return this.tokens[this.pos] as Token
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private advance(): Token {
|
|
293
|
+
const token = this.tokens[this.pos] as Token
|
|
294
|
+
if (token.kind !== 'end') {
|
|
295
|
+
this.pos += 1
|
|
296
|
+
}
|
|
297
|
+
return token
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private expect(text: string): Token {
|
|
301
|
+
const token = this.peek()
|
|
302
|
+
if (token.text !== text) {
|
|
303
|
+
throw this.error(`Expected '${text}', got '${token.kind === 'end' ? 'end of expression' : token.text}'`, token)
|
|
304
|
+
}
|
|
305
|
+
this.advance()
|
|
306
|
+
return token
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private error(message: string, token: Token): FhirPathSyntaxError {
|
|
310
|
+
return new FhirPathSyntaxError(message, token.span)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private spanBetween(start: SourceSpan, end: SourceSpan): SourceSpan {
|
|
314
|
+
return { start: start.start, end: end.end, line: start.line, column: start.column }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Parse a FHIRPath expression into its AST. Throws `FhirPathSyntaxError` with a position on bad input. */
|
|
319
|
+
export function parse(source: string): AstNode {
|
|
320
|
+
return new Parser(source).parse()
|
|
321
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding powers derived from the spec's 13-level operator precedence table
|
|
3
|
+
* (https://hl7.org/fhirpath/#operator-precedence). Higher binds tighter; the
|
|
4
|
+
* spec numbers levels the other way around (1 = `.`, 13 = `implies`).
|
|
5
|
+
*/
|
|
6
|
+
export const BindingPower = {
|
|
7
|
+
Implies: 1,
|
|
8
|
+
OrXor: 2,
|
|
9
|
+
And: 3,
|
|
10
|
+
Membership: 4,
|
|
11
|
+
Equality: 5,
|
|
12
|
+
Comparison: 6,
|
|
13
|
+
Union: 7,
|
|
14
|
+
TypeOps: 8,
|
|
15
|
+
Additive: 9,
|
|
16
|
+
Multiplicative: 10,
|
|
17
|
+
Unary: 11,
|
|
18
|
+
Indexer: 12,
|
|
19
|
+
Dot: 13,
|
|
20
|
+
FunctionCall: 14,
|
|
21
|
+
} as const
|
|
22
|
+
|
|
23
|
+
/** Binding power for every infix (and postfix) construct, keyed by token text. */
|
|
24
|
+
export const INFIX_BINDING_POWER: Readonly<Record<string, number>> = {
|
|
25
|
+
implies: BindingPower.Implies,
|
|
26
|
+
or: BindingPower.OrXor,
|
|
27
|
+
xor: BindingPower.OrXor,
|
|
28
|
+
and: BindingPower.And,
|
|
29
|
+
in: BindingPower.Membership,
|
|
30
|
+
contains: BindingPower.Membership,
|
|
31
|
+
'=': BindingPower.Equality,
|
|
32
|
+
'~': BindingPower.Equality,
|
|
33
|
+
'!=': BindingPower.Equality,
|
|
34
|
+
'!~': BindingPower.Equality,
|
|
35
|
+
'<': BindingPower.Comparison,
|
|
36
|
+
'>': BindingPower.Comparison,
|
|
37
|
+
'<=': BindingPower.Comparison,
|
|
38
|
+
'>=': BindingPower.Comparison,
|
|
39
|
+
'|': BindingPower.Union,
|
|
40
|
+
is: BindingPower.TypeOps,
|
|
41
|
+
as: BindingPower.TypeOps,
|
|
42
|
+
'+': BindingPower.Additive,
|
|
43
|
+
'-': BindingPower.Additive,
|
|
44
|
+
'&': BindingPower.Additive,
|
|
45
|
+
'*': BindingPower.Multiplicative,
|
|
46
|
+
'/': BindingPower.Multiplicative,
|
|
47
|
+
div: BindingPower.Multiplicative,
|
|
48
|
+
mod: BindingPower.Multiplicative,
|
|
49
|
+
'[': BindingPower.Indexer,
|
|
50
|
+
'.': BindingPower.Dot,
|
|
51
|
+
'(': BindingPower.FunctionCall,
|
|
52
|
+
}
|