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,112 @@
|
|
|
1
|
+
import { pairEquals } from '../engine/operators/equality.ts'
|
|
2
|
+
import { readModelProperty } from '../fhir/model-navigation.ts'
|
|
3
|
+
import type { ModelProvider } from '../model/provider.ts'
|
|
4
|
+
import { SYSTEM_BOOLEAN, SYSTEM_STRING, systemTypeOf, toTypedValue, type TypedValue } from '../values/typed-value.ts'
|
|
5
|
+
import { registerFunction } from './registry.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* All immediate child nodes of an item. `resourceType` is a JSON discriminator, not
|
|
9
|
+
* an element, and `_field` keys are primitive-extension metadata handled elsewhere.
|
|
10
|
+
*/
|
|
11
|
+
export function childrenOf(item: TypedValue, model?: ModelProvider): TypedValue[] {
|
|
12
|
+
const value = item.value
|
|
13
|
+
if (typeof value !== 'object' || value === null) {
|
|
14
|
+
// A primitive's children are its element id and extensions (FHIR spec).
|
|
15
|
+
return primitiveMetadataChildren(item)
|
|
16
|
+
}
|
|
17
|
+
// With a model, children keep their element types (so `code as Coding` works
|
|
18
|
+
// downstream) and elements present only through a _field sibling still appear.
|
|
19
|
+
if (model && item.type.startsWith(`${model.namespace}.`)) {
|
|
20
|
+
const elements = model.listElements?.(item.type)
|
|
21
|
+
if (elements !== undefined) {
|
|
22
|
+
const typed: TypedValue[] = []
|
|
23
|
+
for (const element of elements) {
|
|
24
|
+
typed.push(...(readModelProperty(model, item, element) ?? []))
|
|
25
|
+
}
|
|
26
|
+
return typed
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const result: TypedValue[] = []
|
|
30
|
+
for (const [key, child] of Object.entries(value)) {
|
|
31
|
+
if (key === 'resourceType' || key.startsWith('_') || child === null || child === undefined) {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(child)) {
|
|
35
|
+
for (const element of child) {
|
|
36
|
+
if (element !== null && element !== undefined) {
|
|
37
|
+
result.push(toTypedValue(element))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
result.push(toTypedValue(child))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function primitiveMetadataChildren(item: TypedValue): TypedValue[] {
|
|
48
|
+
const metadata = item.primitiveElement as { id?: unknown; extension?: unknown } | undefined
|
|
49
|
+
if (metadata === undefined || metadata === null) {
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
const result: TypedValue[] = []
|
|
53
|
+
if (metadata.id !== undefined && metadata.id !== null) {
|
|
54
|
+
result.push({ type: 'System.String', value: metadata.id })
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(metadata.extension)) {
|
|
57
|
+
result.push(...metadata.extension.map(extension => ({ type: 'FHIR.Extension', value: extension })))
|
|
58
|
+
}
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
registerFunction('children', {
|
|
63
|
+
minArity: 0,
|
|
64
|
+
maxArity: 0,
|
|
65
|
+
evaluate: (context, input) => input.flatMap(item => childrenOf(item, context.model)),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
registerFunction('descendants', {
|
|
69
|
+
minArity: 0,
|
|
70
|
+
maxArity: 0,
|
|
71
|
+
evaluate: (context, input) => {
|
|
72
|
+
// repeat(children()): collect transitively, excluding the input itself. The
|
|
73
|
+
// dedup granularity differs from repeat() on purpose and is pinned by the
|
|
74
|
+
// official suites: descendants() keeps `=`-equal siblings produced in the same
|
|
75
|
+
// round (a batch filter against prior rounds only), where repeat() collapses
|
|
76
|
+
// them (incremental within-round dedup). They cannot share one closure.
|
|
77
|
+
//
|
|
78
|
+
// Dedup is `existing.value === item.value || pairEquals(existing, item)` against
|
|
79
|
+
// prior rounds, indexed for O(1) on the common case: `seenValues` (a Set of raw
|
|
80
|
+
// values) resolves the `===` branch, and it also covers `pairEquals` for
|
|
81
|
+
// String/Boolean, whose equality *is* `===`. The remaining classes (numeric,
|
|
82
|
+
// temporal, quantity, complex) can be pairEquals-equal without being `===`-equal,
|
|
83
|
+
// so those fall back to a scan of same-class prior items. The index updates only
|
|
84
|
+
// between rounds, preserving the batch semantics.
|
|
85
|
+
const collected: TypedValue[] = []
|
|
86
|
+
const seenValues = new Set<unknown>()
|
|
87
|
+
const fallback: TypedValue[] = []
|
|
88
|
+
const needsFallback = (item: TypedValue): boolean => {
|
|
89
|
+
const type = systemTypeOf(item)
|
|
90
|
+
return type !== SYSTEM_STRING && type !== SYSTEM_BOOLEAN
|
|
91
|
+
}
|
|
92
|
+
const isDuplicate = (item: TypedValue): boolean => {
|
|
93
|
+
if (seenValues.has(item.value)) {
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
return needsFallback(item) && fallback.some(existing => pairEquals(existing, item) === true)
|
|
97
|
+
}
|
|
98
|
+
let current = input.flatMap(item => childrenOf(item, context.model))
|
|
99
|
+
while (current.length > 0) {
|
|
100
|
+
const fresh = current.filter(item => !isDuplicate(item))
|
|
101
|
+
for (const item of fresh) {
|
|
102
|
+
seenValues.add(item.value)
|
|
103
|
+
if (needsFallback(item)) {
|
|
104
|
+
fallback.push(item)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
collected.push(...fresh)
|
|
108
|
+
current = fresh.flatMap(item => childrenOf(item, context.model))
|
|
109
|
+
}
|
|
110
|
+
return collected
|
|
111
|
+
},
|
|
112
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FhirPathTypeError } from '../errors.ts'
|
|
2
|
+
import type { AstNode } from '../parser/ast.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `ofType(Quantity)`, `is(System.Boolean)`, and `as(FHIR.Patient)` receive their type
|
|
6
|
+
* as an expression that parsed as an identifier or dotted identifier chain; read it
|
|
7
|
+
* back as a qualified type name.
|
|
8
|
+
*/
|
|
9
|
+
export function typePartsFromArgument(functionName: string, node: AstNode): string[] {
|
|
10
|
+
const parts = collect(node)
|
|
11
|
+
if (!parts) {
|
|
12
|
+
throw new FhirPathTypeError(`Function '${functionName}' expects a type name argument`)
|
|
13
|
+
}
|
|
14
|
+
return parts
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function collect(node: AstNode): string[] | undefined {
|
|
18
|
+
if (node.kind === 'identifier') {
|
|
19
|
+
return [node.name]
|
|
20
|
+
}
|
|
21
|
+
if (node.kind === 'dot') {
|
|
22
|
+
const left = collect(node.left)
|
|
23
|
+
const right = node.right.kind === 'identifier' ? node.right.name : undefined
|
|
24
|
+
if (left && right !== undefined) {
|
|
25
|
+
return [...left, right]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { withFrame } from '../engine/context.ts'
|
|
2
|
+
import { booleanSingleton, wrapBoolean } from '../values/collection.ts'
|
|
3
|
+
import { Temporal } from '../values/datetime.ts'
|
|
4
|
+
import { SYSTEM_DATE, SYSTEM_DATETIME, SYSTEM_TIME, type TypedValue } from '../values/typed-value.ts'
|
|
5
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
6
|
+
|
|
7
|
+
registerFunction('not', {
|
|
8
|
+
minArity: 0,
|
|
9
|
+
maxArity: 0,
|
|
10
|
+
evaluate: (_context, input) => {
|
|
11
|
+
const value = booleanSingleton(input)
|
|
12
|
+
return wrapBoolean(value === undefined ? undefined : !value)
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
registerFunction('trace', {
|
|
17
|
+
minArity: 1,
|
|
18
|
+
maxArity: 2,
|
|
19
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
20
|
+
const name = evaluateNode(argAt(args, 0), context, input)
|
|
21
|
+
const label = typeof name[0]?.value === 'string' ? (name[0].value as string) : ''
|
|
22
|
+
const traced =
|
|
23
|
+
args.length === 2
|
|
24
|
+
? withFrame(context, { thisValue: input }, frameContext => evaluateNode(argAt(args, 1), frameContext, input))
|
|
25
|
+
: input
|
|
26
|
+
context.trace(label, traced)
|
|
27
|
+
return input
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
/** The evaluation clock is fixed per evaluation, so these are deterministic within one run. */
|
|
32
|
+
function clockParts(now: Date): { date: string; time: string; offset: number } {
|
|
33
|
+
const pad = (value: number): string => String(value).padStart(2, '0')
|
|
34
|
+
const date = `${String(now.getFullYear()).padStart(4, '0')}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
|
|
35
|
+
const millis = String(now.getMilliseconds()).padStart(3, '0')
|
|
36
|
+
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${millis}`
|
|
37
|
+
return { date, time, offset: -now.getTimezoneOffset() }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
registerFunction('now', {
|
|
41
|
+
minArity: 0,
|
|
42
|
+
maxArity: 0,
|
|
43
|
+
evaluate: context => {
|
|
44
|
+
const { date, time, offset } = clockParts(context.now)
|
|
45
|
+
const value = Temporal.parseDateTime(`${date}T${time}`)
|
|
46
|
+
/* v8 ignore next 3 -- the clock always renders a valid dateTime */
|
|
47
|
+
if (!value) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
const withOffset = Temporal.fromFields('dateTime', {
|
|
51
|
+
year: value.year,
|
|
52
|
+
month: value.month,
|
|
53
|
+
day: value.day,
|
|
54
|
+
hour: value.hour,
|
|
55
|
+
minute: value.minute,
|
|
56
|
+
second: value.second,
|
|
57
|
+
fraction: value.fraction,
|
|
58
|
+
timezoneOffsetMinutes: offset,
|
|
59
|
+
}) as Temporal
|
|
60
|
+
return [{ type: SYSTEM_DATETIME, value: withOffset }] as TypedValue[]
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
registerFunction('today', {
|
|
65
|
+
minArity: 0,
|
|
66
|
+
maxArity: 0,
|
|
67
|
+
evaluate: context => {
|
|
68
|
+
const { date } = clockParts(context.now)
|
|
69
|
+
const value = Temporal.parseDate(date) as Temporal
|
|
70
|
+
return [{ type: SYSTEM_DATE, value }] as TypedValue[]
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
registerFunction('timeOfDay', {
|
|
75
|
+
minArity: 0,
|
|
76
|
+
maxArity: 0,
|
|
77
|
+
evaluate: context => {
|
|
78
|
+
const { time } = clockParts(context.now)
|
|
79
|
+
const value = Temporal.parseTime(time) as Temporal
|
|
80
|
+
return [{ type: SYSTEM_TIME, value }] as TypedValue[]
|
|
81
|
+
},
|
|
82
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { withFrame } from '../engine/context.ts'
|
|
2
|
+
import { FhirPathTypeError } from '../errors.ts'
|
|
3
|
+
import { singleton } from '../values/collection.ts'
|
|
4
|
+
import { SYSTEM_STRING, systemTypeOf, type TypedValue } from '../values/typed-value.ts'
|
|
5
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* defineVariable(name [, value]) — ballot STU. The variable joins the current
|
|
9
|
+
* expression chain's scope (see EvaluationContext.variables): later links of the
|
|
10
|
+
* same dot chain see it, sibling operator operands and arguments do not.
|
|
11
|
+
*/
|
|
12
|
+
registerFunction('defineVariable', {
|
|
13
|
+
minArity: 1,
|
|
14
|
+
maxArity: 2,
|
|
15
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
16
|
+
const nameValue = singleton(evaluateNode(argAt(args, 0), context, input))
|
|
17
|
+
if (nameValue === undefined || systemTypeOf(nameValue) !== SYSTEM_STRING) {
|
|
18
|
+
throw new FhirPathTypeError('defineVariable() expects a String name')
|
|
19
|
+
}
|
|
20
|
+
const name = nameValue.value as string
|
|
21
|
+
if (context.env.has(name)) {
|
|
22
|
+
throw new FhirPathTypeError(`Cannot override the environment variable %${name}`)
|
|
23
|
+
}
|
|
24
|
+
if (context.variables.has(name)) {
|
|
25
|
+
throw new FhirPathTypeError(`Variable %${name} is already defined in this scope`)
|
|
26
|
+
}
|
|
27
|
+
// The value expression evaluates against the function input, e.g.
|
|
28
|
+
// Patient.name.defineVariable('n2', skip(1).first()).
|
|
29
|
+
const value: TypedValue[] =
|
|
30
|
+
args.length === 2
|
|
31
|
+
? withFrame(context, { thisValue: input }, frameContext => evaluateNode(argAt(args, 1), frameContext, input))
|
|
32
|
+
: input
|
|
33
|
+
context.variables.set(name, value)
|
|
34
|
+
return input
|
|
35
|
+
},
|
|
36
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type { CustomFunctionSignature, ValueArgSpec } from './analyzer/signatures.ts'
|
|
2
|
+
export type { BundleLike } from './api/bundle.ts'
|
|
3
|
+
export type {
|
|
4
|
+
AnyExpression,
|
|
5
|
+
CustomFunction,
|
|
6
|
+
EvaluateOptions,
|
|
7
|
+
OverloadedCustomFunction,
|
|
8
|
+
SingleCustomFunction,
|
|
9
|
+
} from './api/compile.ts'
|
|
10
|
+
export { compile, CompiledExpression, DEFAULT_PARSE_CACHE_SIZE } from './api/compile.ts'
|
|
11
|
+
export type { ConstraintCheckResult, ConstraintIssue, FhirConstraint, OperationOutcome } from './api/constraints.ts'
|
|
12
|
+
export type { ColumnTypeMismatch, DtoBase, DtoClass, DtoEnv, DtoInstance, DtoOptions, DtoRow } from './api/dto.ts'
|
|
13
|
+
export { column, criteria, defineDto } from './api/dto.ts'
|
|
14
|
+
export type { EngineInput, EngineOptions, TypedEvaluateOptions } from './api/engine.ts'
|
|
15
|
+
export { BoundExpression, FhirPathEngine, recordEngines } from './api/engine.ts'
|
|
16
|
+
export { evaluate } from './api/evaluate.ts'
|
|
17
|
+
export type { ColumnOptions, ColumnResult, Projection, ProjectionColumn, ProjectionColumns } from './api/project.ts'
|
|
18
|
+
export { fhirpath } from './api/tagged.ts'
|
|
19
|
+
export type { HostNativeFunction, RegexEngine } from './engine/context.ts'
|
|
20
|
+
export type { SourceSpan } from './errors.ts'
|
|
21
|
+
export { FhirPathError, FhirPathRuntimeError, FhirPathSyntaxError, FhirPathTypeError } from './errors.ts'
|
|
22
|
+
export type { ElementInfo, ModelProvider } from './model/provider.ts'
|
|
23
|
+
export type { AstNode } from './parser/ast.ts'
|
|
24
|
+
export { parse } from './parser/parser.ts'
|
|
25
|
+
export { printExpression } from './parser/printer.ts'
|
|
26
|
+
export type { FhirpathInput, FhirpathResult, FhirpathResultIn, FhirTypeName } from './typed/infer.ts'
|
|
27
|
+
export { Temporal } from './values/datetime.ts'
|
|
28
|
+
export { Decimal } from './values/decimal.ts'
|
|
29
|
+
export type { ValueKind } from './values/type-compat.ts'
|
|
30
|
+
export type { QuantityValue, TypedValue } from './values/typed-value.ts'
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { FhirPathSyntaxError, type SourceSpan } from '../errors.ts'
|
|
2
|
+
import { KEYWORDS, SPECIAL_VARIABLES, type Token, type TokenKind } from './tokens.ts'
|
|
3
|
+
|
|
4
|
+
const isDigit = (ch: string): boolean => ch >= '0' && ch <= '9'
|
|
5
|
+
const isIdentifierStart = (ch: string): boolean => (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch === '_'
|
|
6
|
+
const isIdentifierPart = (ch: string): boolean => isIdentifierStart(ch) || isDigit(ch)
|
|
7
|
+
const isWhitespace = (ch: string): boolean => ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n' || ch === '\f'
|
|
8
|
+
const isHexDigit = (ch: string): boolean => isDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
|
9
|
+
|
|
10
|
+
const ESCAPES: Readonly<Record<string, string>> = {
|
|
11
|
+
"'": "'",
|
|
12
|
+
'"': '"',
|
|
13
|
+
'`': '`',
|
|
14
|
+
'/': '/',
|
|
15
|
+
'\\': '\\',
|
|
16
|
+
f: '\f',
|
|
17
|
+
n: '\n',
|
|
18
|
+
r: '\r',
|
|
19
|
+
t: '\t',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TWO_CHAR_OPERATORS: ReadonlySet<string> = new Set(['!=', '!~', '<=', '>='])
|
|
23
|
+
const ONE_CHAR_OPERATORS: ReadonlySet<string> = new Set(['=', '~', '<', '>', '+', '-', '*', '/', '|', '&'])
|
|
24
|
+
const PUNCT: ReadonlySet<string> = new Set(['(', ')', '[', ']', '{', '}', '.', ',', '%'])
|
|
25
|
+
|
|
26
|
+
class Lexer {
|
|
27
|
+
private readonly source: string
|
|
28
|
+
private pos = 0
|
|
29
|
+
|
|
30
|
+
constructor(source: string) {
|
|
31
|
+
this.source = source
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
tokenize(): Token[] {
|
|
35
|
+
const tokens: Token[] = []
|
|
36
|
+
for (;;) {
|
|
37
|
+
const token = this.next()
|
|
38
|
+
tokens.push(token)
|
|
39
|
+
if (token.kind === 'end') {
|
|
40
|
+
return tokens
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private next(): Token {
|
|
46
|
+
this.skipTrivia()
|
|
47
|
+
if (this.pos >= this.source.length) {
|
|
48
|
+
return this.token('end', this.pos, '')
|
|
49
|
+
}
|
|
50
|
+
const start = this.pos
|
|
51
|
+
const ch = this.source[this.pos] as string
|
|
52
|
+
if (isIdentifierStart(ch)) {
|
|
53
|
+
return this.readIdentifier(start)
|
|
54
|
+
}
|
|
55
|
+
if (isDigit(ch)) {
|
|
56
|
+
return this.readNumber(start)
|
|
57
|
+
}
|
|
58
|
+
switch (ch) {
|
|
59
|
+
case "'":
|
|
60
|
+
return this.readString(start)
|
|
61
|
+
case '`':
|
|
62
|
+
return this.readDelimitedIdentifier(start)
|
|
63
|
+
case '@':
|
|
64
|
+
return this.readDateTimeLiteral(start)
|
|
65
|
+
case '$':
|
|
66
|
+
return this.readSpecialVariable(start)
|
|
67
|
+
default:
|
|
68
|
+
return this.readOperatorOrPunct(start)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private skipTrivia(): void {
|
|
73
|
+
while (this.pos < this.source.length) {
|
|
74
|
+
const ch = this.source[this.pos] as string
|
|
75
|
+
if (isWhitespace(ch)) {
|
|
76
|
+
this.advance()
|
|
77
|
+
} else if (ch === '/' && this.source[this.pos + 1] === '/') {
|
|
78
|
+
while (this.pos < this.source.length && this.source[this.pos] !== '\n') {
|
|
79
|
+
this.advance()
|
|
80
|
+
}
|
|
81
|
+
} else if (ch === '/' && this.source[this.pos + 1] === '*') {
|
|
82
|
+
const start = this.spanFrom(this.pos)
|
|
83
|
+
this.advance()
|
|
84
|
+
this.advance()
|
|
85
|
+
while (this.pos < this.source.length && !(this.source[this.pos] === '*' && this.source[this.pos + 1] === '/')) {
|
|
86
|
+
this.advance()
|
|
87
|
+
}
|
|
88
|
+
if (this.pos >= this.source.length) {
|
|
89
|
+
throw new FhirPathSyntaxError('Unterminated comment', start)
|
|
90
|
+
}
|
|
91
|
+
this.advance()
|
|
92
|
+
this.advance()
|
|
93
|
+
} else {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private readIdentifier(start: number): Token {
|
|
100
|
+
while (this.pos < this.source.length && isIdentifierPart(this.source[this.pos] as string)) {
|
|
101
|
+
this.advance()
|
|
102
|
+
}
|
|
103
|
+
const text = this.source.slice(start, this.pos)
|
|
104
|
+
return this.token(KEYWORDS.has(text) ? 'keyword' : 'identifier', start, text)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private readNumber(start: number): Token {
|
|
108
|
+
while (this.pos < this.source.length && isDigit(this.source[this.pos] as string)) {
|
|
109
|
+
this.advance()
|
|
110
|
+
}
|
|
111
|
+
// A '.' continues the number only when a digit follows; `5.single()` is Integer 5 then '.'.
|
|
112
|
+
if (this.source[this.pos] === '.' && isDigit(this.source[this.pos + 1] ?? '')) {
|
|
113
|
+
this.advance()
|
|
114
|
+
while (this.pos < this.source.length && isDigit(this.source[this.pos] as string)) {
|
|
115
|
+
this.advance()
|
|
116
|
+
}
|
|
117
|
+
} else if (this.source[this.pos] === 'L') {
|
|
118
|
+
// Long literal suffix (ballot STU): 5L is a System.Long.
|
|
119
|
+
this.advance()
|
|
120
|
+
}
|
|
121
|
+
const text = this.source.slice(start, this.pos)
|
|
122
|
+
return this.token('number', start, text)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private readString(start: number): Token {
|
|
126
|
+
const value = this.readQuoted(start, "'", 'string literal')
|
|
127
|
+
return this.token('string', start, value)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private readDelimitedIdentifier(start: number): Token {
|
|
131
|
+
const value = this.readQuoted(start, '`', 'delimited identifier')
|
|
132
|
+
if (value === '') {
|
|
133
|
+
throw new FhirPathSyntaxError('Empty delimited identifier', this.spanFrom(start))
|
|
134
|
+
}
|
|
135
|
+
return this.token('delimitedIdentifier', start, value)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private readQuoted(start: number, quote: string, what: string): string {
|
|
139
|
+
this.advance()
|
|
140
|
+
let value = ''
|
|
141
|
+
while (this.pos < this.source.length) {
|
|
142
|
+
const ch = this.source[this.pos] as string
|
|
143
|
+
if (ch === quote) {
|
|
144
|
+
this.advance()
|
|
145
|
+
return value
|
|
146
|
+
}
|
|
147
|
+
if (ch === '\\') {
|
|
148
|
+
value += this.readEscape()
|
|
149
|
+
} else {
|
|
150
|
+
value += ch
|
|
151
|
+
this.advance()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new FhirPathSyntaxError(`Unterminated ${what}`, this.spanFrom(start))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private readEscape(): string {
|
|
158
|
+
const start = this.pos
|
|
159
|
+
this.advance()
|
|
160
|
+
const ch = this.source[this.pos]
|
|
161
|
+
if (ch === undefined) {
|
|
162
|
+
throw new FhirPathSyntaxError('Unterminated escape sequence', this.spanFrom(start))
|
|
163
|
+
}
|
|
164
|
+
if (ch === 'u') {
|
|
165
|
+
this.advance()
|
|
166
|
+
let code = ''
|
|
167
|
+
for (let i = 0; i < 4; i++) {
|
|
168
|
+
const hex = this.source[this.pos]
|
|
169
|
+
if (hex === undefined || !isHexDigit(hex)) {
|
|
170
|
+
throw new FhirPathSyntaxError('Unicode escape must be \\u followed by 4 hex digits', this.spanFrom(start))
|
|
171
|
+
}
|
|
172
|
+
code += hex
|
|
173
|
+
this.advance()
|
|
174
|
+
}
|
|
175
|
+
return String.fromCharCode(Number.parseInt(code, 16))
|
|
176
|
+
}
|
|
177
|
+
const resolved = ESCAPES[ch]
|
|
178
|
+
if (resolved === undefined) {
|
|
179
|
+
throw new FhirPathSyntaxError(`Invalid escape sequence \\${ch}`, this.spanFrom(start))
|
|
180
|
+
}
|
|
181
|
+
this.advance()
|
|
182
|
+
return resolved
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private readDateTimeLiteral(start: number): Token {
|
|
186
|
+
this.advance()
|
|
187
|
+
if (this.source[this.pos] === 'T') {
|
|
188
|
+
this.advance()
|
|
189
|
+
this.readTimeFormat(start, 'time literal')
|
|
190
|
+
return this.token('time', start, this.source.slice(start + 2, this.pos))
|
|
191
|
+
}
|
|
192
|
+
this.readDigits(2, start, 'date literal')
|
|
193
|
+
this.readDigits(2, start, 'date literal')
|
|
194
|
+
// Like the reference ANTLR lexer, a component is consumed only when complete:
|
|
195
|
+
// `@2014-5` is the date @2014 followed by the operator '-' and the number 5.
|
|
196
|
+
if (this.hasTwoDigitComponent('-')) {
|
|
197
|
+
this.advance()
|
|
198
|
+
this.readDigits(2, start, 'date literal')
|
|
199
|
+
if (this.hasTwoDigitComponent('-')) {
|
|
200
|
+
this.advance()
|
|
201
|
+
this.readDigits(2, start, 'date literal')
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (this.source[this.pos] !== 'T') {
|
|
205
|
+
return this.token('date', start, this.source.slice(start + 1, this.pos))
|
|
206
|
+
}
|
|
207
|
+
this.advance()
|
|
208
|
+
if (isDigit(this.source[this.pos] ?? '')) {
|
|
209
|
+
this.readTimeFormat(start, 'dateTime literal')
|
|
210
|
+
this.readTimezoneOffset()
|
|
211
|
+
}
|
|
212
|
+
return this.token('dateTime', start, this.source.slice(start + 1, this.pos))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private readTimeFormat(start: number, what: string): void {
|
|
216
|
+
this.readDigits(2, start, what)
|
|
217
|
+
if (this.hasTwoDigitComponent(':')) {
|
|
218
|
+
this.advance()
|
|
219
|
+
this.readDigits(2, start, what)
|
|
220
|
+
if (this.hasTwoDigitComponent(':')) {
|
|
221
|
+
this.advance()
|
|
222
|
+
this.readDigits(2, start, what)
|
|
223
|
+
if (this.source[this.pos] === '.' && isDigit(this.source[this.pos + 1] ?? '')) {
|
|
224
|
+
this.advance()
|
|
225
|
+
while (isDigit(this.source[this.pos] ?? '')) {
|
|
226
|
+
this.advance()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private hasTwoDigitComponent(separator: string): boolean {
|
|
234
|
+
return (
|
|
235
|
+
this.source[this.pos] === separator &&
|
|
236
|
+
isDigit(this.source[this.pos + 1] ?? '') &&
|
|
237
|
+
isDigit(this.source[this.pos + 2] ?? '')
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private readTimezoneOffset(): void {
|
|
242
|
+
const ch = this.source[this.pos]
|
|
243
|
+
if (ch === 'Z') {
|
|
244
|
+
this.advance()
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
// '+'/'-' is an offset only in the full hh:mm shape; otherwise it is an operator, e.g. `@2014-01-01T10 - 3 days`.
|
|
248
|
+
if (
|
|
249
|
+
(ch === '+' || ch === '-') &&
|
|
250
|
+
isDigit(this.source[this.pos + 1] ?? '') &&
|
|
251
|
+
isDigit(this.source[this.pos + 2] ?? '') &&
|
|
252
|
+
this.source[this.pos + 3] === ':' &&
|
|
253
|
+
isDigit(this.source[this.pos + 4] ?? '') &&
|
|
254
|
+
isDigit(this.source[this.pos + 5] ?? '')
|
|
255
|
+
) {
|
|
256
|
+
for (let i = 0; i < 6; i++) {
|
|
257
|
+
this.advance()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private readDigits(count: number, start: number, what: string): void {
|
|
263
|
+
for (let i = 0; i < count; i++) {
|
|
264
|
+
const ch = this.source[this.pos]
|
|
265
|
+
if (ch === undefined || !isDigit(ch)) {
|
|
266
|
+
throw new FhirPathSyntaxError(`Invalid ${what}`, this.spanFrom(start))
|
|
267
|
+
}
|
|
268
|
+
this.advance()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private readSpecialVariable(start: number): Token {
|
|
273
|
+
this.advance()
|
|
274
|
+
while (this.pos < this.source.length && isIdentifierPart(this.source[this.pos] as string)) {
|
|
275
|
+
this.advance()
|
|
276
|
+
}
|
|
277
|
+
const text = this.source.slice(start, this.pos)
|
|
278
|
+
if (!SPECIAL_VARIABLES.has(text)) {
|
|
279
|
+
throw new FhirPathSyntaxError(`Unknown special variable ${text}`, this.spanFrom(start))
|
|
280
|
+
}
|
|
281
|
+
return this.token('specialVariable', start, text.slice(1))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private readOperatorOrPunct(start: number): Token {
|
|
285
|
+
const two = this.source.slice(this.pos, this.pos + 2)
|
|
286
|
+
if (TWO_CHAR_OPERATORS.has(two)) {
|
|
287
|
+
this.advance()
|
|
288
|
+
this.advance()
|
|
289
|
+
return this.token('operator', start, two)
|
|
290
|
+
}
|
|
291
|
+
const ch = this.source[this.pos] as string
|
|
292
|
+
if (ONE_CHAR_OPERATORS.has(ch)) {
|
|
293
|
+
this.advance()
|
|
294
|
+
return this.token('operator', start, ch)
|
|
295
|
+
}
|
|
296
|
+
if (PUNCT.has(ch)) {
|
|
297
|
+
this.advance()
|
|
298
|
+
return this.token('punct', start, ch)
|
|
299
|
+
}
|
|
300
|
+
throw new FhirPathSyntaxError(`Unexpected character '${ch}'`, this.spanFrom(start))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private advance(): void {
|
|
304
|
+
this.pos += 1
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private token(kind: TokenKind, start: number, value: string): Token {
|
|
308
|
+
return {
|
|
309
|
+
kind,
|
|
310
|
+
text: this.source.slice(start, this.pos),
|
|
311
|
+
value,
|
|
312
|
+
span: this.spanFrom(start),
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Tokens are emitted in source order, so line/column advance incrementally from
|
|
317
|
+
// the previous span instead of rescanning from offset 0 (which made tokenizing
|
|
318
|
+
// quadratic in expression length).
|
|
319
|
+
private lineCursor = 0
|
|
320
|
+
private cursorLine = 1
|
|
321
|
+
private cursorColumn = 1
|
|
322
|
+
|
|
323
|
+
/** Span from `start` to the current position; line/column point at `start`. */
|
|
324
|
+
private spanFrom(start: number): SourceSpan {
|
|
325
|
+
for (let i = this.lineCursor; i < start; i++) {
|
|
326
|
+
if (this.source[i] === '\n') {
|
|
327
|
+
this.cursorLine += 1
|
|
328
|
+
this.cursorColumn = 1
|
|
329
|
+
} else {
|
|
330
|
+
this.cursorColumn += 1
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
this.lineCursor = Math.max(this.lineCursor, start)
|
|
334
|
+
return { start, end: this.pos, line: this.cursorLine, column: this.cursorColumn }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Split a FHIRPath expression into tokens. The last token always has kind `end`. */
|
|
339
|
+
export function tokenize(source: string): Token[] {
|
|
340
|
+
return new Lexer(source).tokenize()
|
|
341
|
+
}
|