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,209 @@
|
|
|
1
|
+
import { FhirPathTypeError } from '../errors.ts'
|
|
2
|
+
import { singleton } from '../values/collection.ts'
|
|
3
|
+
import { daysInMonth, Temporal } from '../values/datetime.ts'
|
|
4
|
+
import { Decimal } from '../values/decimal.ts'
|
|
5
|
+
import {
|
|
6
|
+
type QuantityValue,
|
|
7
|
+
SYSTEM_DATE,
|
|
8
|
+
SYSTEM_DATETIME,
|
|
9
|
+
SYSTEM_DECIMAL,
|
|
10
|
+
SYSTEM_INTEGER,
|
|
11
|
+
SYSTEM_QUANTITY,
|
|
12
|
+
SYSTEM_TIME,
|
|
13
|
+
systemTypeOf,
|
|
14
|
+
} from '../values/typed-value.ts'
|
|
15
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
16
|
+
|
|
17
|
+
type Edge = 'low' | 'high'
|
|
18
|
+
|
|
19
|
+
const MAX_DECIMAL_PRECISION = 28
|
|
20
|
+
// Spec default for decimal boundaries, and the digit count of a full date (YYYYMMDD).
|
|
21
|
+
const DEFAULT_DECIMAL_BOUNDARY_PRECISION = 8
|
|
22
|
+
const FULL_DATE_DIGITS = 8
|
|
23
|
+
const MAX_DATETIME_DIGITS = 17
|
|
24
|
+
const MAX_TIME_DIGITS = 9
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Decimal boundary: value ± half of the last-digit step, then floored (low) or
|
|
28
|
+
* ceiled (high) to the target precision. Integers behave as scale-0 decimals
|
|
29
|
+
* (`1.lowBoundary(0)` is 0, per the official suite).
|
|
30
|
+
*/
|
|
31
|
+
function decimalBoundary(value: Decimal, edge: Edge, targetScale: number): Decimal {
|
|
32
|
+
const halfStep = new Decimal(5n, value.scale + 1)
|
|
33
|
+
const boundary = edge === 'low' ? value.subtract(halfStep) : value.add(halfStep)
|
|
34
|
+
if (targetScale >= boundary.scale) {
|
|
35
|
+
return boundary.withScale(targetScale)
|
|
36
|
+
}
|
|
37
|
+
return edge === 'low' ? floorAt(boundary, targetScale) : ceilAt(boundary, targetScale)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function floorAt(value: Decimal, scale: number): Decimal {
|
|
41
|
+
const factor = 10n ** BigInt(value.scale - scale)
|
|
42
|
+
let digits = value.digits / factor
|
|
43
|
+
if (value.digits < 0n && value.digits % factor !== 0n) {
|
|
44
|
+
digits -= 1n
|
|
45
|
+
}
|
|
46
|
+
return new Decimal(digits, scale)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ceilAt(value: Decimal, scale: number): Decimal {
|
|
50
|
+
const factor = 10n ** BigInt(value.scale - scale)
|
|
51
|
+
let digits = value.digits / factor
|
|
52
|
+
if (value.digits > 0n && value.digits % factor !== 0n) {
|
|
53
|
+
digits += 1n
|
|
54
|
+
}
|
|
55
|
+
return new Decimal(digits, scale)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Digit count of the value's text form, timezone excluded — the spec's precision measure. */
|
|
59
|
+
function temporalPrecisionDigits(value: Temporal): number {
|
|
60
|
+
const text = value.toString().replace(/(Z|[+-]\d{2}:\d{2})$/, '')
|
|
61
|
+
return (text.match(/\d/g) ?? []).length
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The earliest ('low') or latest ('high') moment a partial date/time may mean:
|
|
66
|
+
* missing components fill with their extremes down to `targetDigits`, components
|
|
67
|
+
* beyond the target truncate away, and a dateTime reaching time precision with no
|
|
68
|
+
* timezone gets the earliest (+14:00) or latest (-12:00) offset.
|
|
69
|
+
*/
|
|
70
|
+
function temporalBoundary(value: Temporal, edge: Edge, targetDigits: number | undefined): Temporal | undefined {
|
|
71
|
+
const isTime = value.kind === 'time'
|
|
72
|
+
const digits = targetDigits ?? (isTime ? MAX_TIME_DIGITS : MAX_DATETIME_DIGITS)
|
|
73
|
+
if (digits > (isTime ? MAX_TIME_DIGITS : MAX_DATETIME_DIGITS)) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
const low = edge === 'low'
|
|
77
|
+
const dateDigits = isTime ? 0 : FULL_DATE_DIGITS
|
|
78
|
+
const wantsMonth = !isTime && digits >= 6
|
|
79
|
+
const wantsDay = !isTime && digits >= FULL_DATE_DIGITS
|
|
80
|
+
const wantsHour = digits >= dateDigits + 2 && (isTime || value.kind === 'dateTime')
|
|
81
|
+
const wantsMinute = digits >= dateDigits + 4
|
|
82
|
+
const wantsSecond = digits >= dateDigits + 6
|
|
83
|
+
const wantsFraction = digits >= dateDigits + 9
|
|
84
|
+
const year = value.year
|
|
85
|
+
const month = wantsMonth ? (value.month ?? (low ? 1 : 12)) : undefined
|
|
86
|
+
const day =
|
|
87
|
+
wantsDay && month !== undefined ? (value.day ?? (low ? 1 : daysInMonth(year as number, month))) : undefined
|
|
88
|
+
const hour = wantsHour ? (value.hour ?? (low ? 0 : 23)) : undefined
|
|
89
|
+
const minute = wantsMinute && hour !== undefined ? (value.minute ?? (low ? 0 : 59)) : undefined
|
|
90
|
+
const second = wantsSecond && minute !== undefined ? (value.second ?? (low ? 0 : 59)) : undefined
|
|
91
|
+
const fraction = wantsFraction && second !== undefined ? (value.fraction ?? (low ? '000' : '999')) : undefined
|
|
92
|
+
let timezoneOffsetMinutes = value.timezoneOffsetMinutes
|
|
93
|
+
if (value.kind === 'dateTime' && hour !== undefined && timezoneOffsetMinutes === undefined) {
|
|
94
|
+
timezoneOffsetMinutes = low ? 14 * 60 : -12 * 60
|
|
95
|
+
}
|
|
96
|
+
if (value.kind === 'dateTime' && hour === undefined) {
|
|
97
|
+
timezoneOffsetMinutes = undefined
|
|
98
|
+
}
|
|
99
|
+
const kind = value.kind === 'dateTime' && hour === undefined ? 'date' : value.kind
|
|
100
|
+
return Temporal.fromFields(kind, {
|
|
101
|
+
year,
|
|
102
|
+
month,
|
|
103
|
+
day,
|
|
104
|
+
hour,
|
|
105
|
+
minute,
|
|
106
|
+
second,
|
|
107
|
+
fraction,
|
|
108
|
+
timezoneOffsetMinutes,
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function boundaryFunction(name: string, edge: Edge): void {
|
|
113
|
+
registerFunction(name, {
|
|
114
|
+
minArity: 0,
|
|
115
|
+
maxArity: 1,
|
|
116
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
117
|
+
const item = singleton(input)
|
|
118
|
+
if (item === undefined) {
|
|
119
|
+
return []
|
|
120
|
+
}
|
|
121
|
+
let precision: number | undefined
|
|
122
|
+
if (args.length === 1) {
|
|
123
|
+
const argument = singleton(evaluateNode(argAt(args, 0), context, input), SYSTEM_INTEGER)
|
|
124
|
+
if (argument === undefined || typeof argument.value !== 'number') {
|
|
125
|
+
throw new FhirPathTypeError(`${name}() expects an integer precision`)
|
|
126
|
+
}
|
|
127
|
+
// Out-of-range precision means the boundary cannot be represented: empty.
|
|
128
|
+
if (argument.value < 0) {
|
|
129
|
+
return []
|
|
130
|
+
}
|
|
131
|
+
precision = argument.value
|
|
132
|
+
}
|
|
133
|
+
switch (systemTypeOf(item) ?? item.type) {
|
|
134
|
+
case SYSTEM_INTEGER:
|
|
135
|
+
case 'System.Long':
|
|
136
|
+
case SYSTEM_DECIMAL: {
|
|
137
|
+
if (precision !== undefined && precision > MAX_DECIMAL_PRECISION) {
|
|
138
|
+
return []
|
|
139
|
+
}
|
|
140
|
+
const value = item.value instanceof Decimal ? item.value : (Decimal.fromString(String(item.value)) as Decimal)
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
type: SYSTEM_DECIMAL,
|
|
144
|
+
value: decimalBoundary(value, edge, precision ?? DEFAULT_DECIMAL_BOUNDARY_PRECISION),
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
case SYSTEM_QUANTITY: {
|
|
149
|
+
if (precision !== undefined && precision > MAX_DECIMAL_PRECISION) {
|
|
150
|
+
return []
|
|
151
|
+
}
|
|
152
|
+
const quantity = item.value as QuantityValue
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
type: SYSTEM_QUANTITY,
|
|
156
|
+
value: {
|
|
157
|
+
...quantity,
|
|
158
|
+
value: decimalBoundary(quantity.value, edge, precision ?? DEFAULT_DECIMAL_BOUNDARY_PRECISION),
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
case SYSTEM_DATE:
|
|
164
|
+
case SYSTEM_DATETIME:
|
|
165
|
+
case SYSTEM_TIME: {
|
|
166
|
+
const boundary = temporalBoundary(item.value as Temporal, edge, precision)
|
|
167
|
+
if (boundary === undefined) {
|
|
168
|
+
return []
|
|
169
|
+
}
|
|
170
|
+
const type = boundary.kind === 'date' ? SYSTEM_DATE : boundary.kind === 'time' ? SYSTEM_TIME : SYSTEM_DATETIME
|
|
171
|
+
return [{ type, value: boundary }]
|
|
172
|
+
}
|
|
173
|
+
default:
|
|
174
|
+
throw new FhirPathTypeError(`${name}() is not defined for ${item.type}`)
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
boundaryFunction('lowBoundary', 'low')
|
|
181
|
+
boundaryFunction('highBoundary', 'high')
|
|
182
|
+
|
|
183
|
+
registerFunction('precision', {
|
|
184
|
+
minArity: 0,
|
|
185
|
+
maxArity: 0,
|
|
186
|
+
evaluate: (_context, input) => {
|
|
187
|
+
const item = singleton(input)
|
|
188
|
+
if (item === undefined) {
|
|
189
|
+
return []
|
|
190
|
+
}
|
|
191
|
+
switch (systemTypeOf(item) ?? item.type) {
|
|
192
|
+
case SYSTEM_DECIMAL:
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
type: SYSTEM_INTEGER,
|
|
196
|
+
value: item.value instanceof Decimal ? item.value.scale : 0,
|
|
197
|
+
},
|
|
198
|
+
]
|
|
199
|
+
case SYSTEM_INTEGER:
|
|
200
|
+
return [{ type: SYSTEM_INTEGER, value: 0 }]
|
|
201
|
+
case SYSTEM_DATE:
|
|
202
|
+
case SYSTEM_DATETIME:
|
|
203
|
+
case SYSTEM_TIME:
|
|
204
|
+
return [{ type: SYSTEM_INTEGER, value: temporalPrecisionDigits(item.value as Temporal) }]
|
|
205
|
+
default:
|
|
206
|
+
throw new FhirPathTypeError(`precision() is not defined for ${item.type}`)
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { unionCollections } from '../engine/operators/collections.ts'
|
|
2
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
3
|
+
|
|
4
|
+
registerFunction('union', {
|
|
5
|
+
minArity: 1,
|
|
6
|
+
maxArity: 1,
|
|
7
|
+
evaluate: (context, input, args, evaluateNode) =>
|
|
8
|
+
unionCollections(input, evaluateNode(argAt(args, 0), context, input)),
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
registerFunction('combine', {
|
|
12
|
+
minArity: 1,
|
|
13
|
+
maxArity: 1,
|
|
14
|
+
evaluate: (context, input, args, evaluateNode) => [...input, ...evaluateNode(argAt(args, 0), context, input)],
|
|
15
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { withFrame } from '../engine/context.ts'
|
|
2
|
+
import { FhirPathRuntimeError } from '../errors.ts'
|
|
3
|
+
import { booleanSingleton } from '../values/collection.ts'
|
|
4
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
5
|
+
|
|
6
|
+
registerFunction('iif', {
|
|
7
|
+
minArity: 2,
|
|
8
|
+
maxArity: 3,
|
|
9
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
10
|
+
if (input.length > 1) {
|
|
11
|
+
throw new FhirPathRuntimeError('iif() expects a collection with at most one item as input')
|
|
12
|
+
}
|
|
13
|
+
// $this is the (optional) single input item; branches evaluate lazily.
|
|
14
|
+
return withFrame(context, { thisValue: input }, frameContext => {
|
|
15
|
+
const criterion = booleanSingleton(evaluateNode(argAt(args, 0), frameContext, input))
|
|
16
|
+
if (criterion === true) {
|
|
17
|
+
return evaluateNode(argAt(args, 1), frameContext, input)
|
|
18
|
+
}
|
|
19
|
+
if (args.length === 3) {
|
|
20
|
+
return evaluateNode(argAt(args, 2), frameContext, input)
|
|
21
|
+
}
|
|
22
|
+
return []
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
})
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { FhirPathTypeError } from '../errors.ts'
|
|
2
|
+
import { CALENDAR_DURATION_UNITS } from '../lexer/tokens.ts'
|
|
3
|
+
import { singleton, wrapBoolean } from '../values/collection.ts'
|
|
4
|
+
import { Temporal } from '../values/datetime.ts'
|
|
5
|
+
import { Decimal } from '../values/decimal.ts'
|
|
6
|
+
import { LONG_MAX, LONG_MIN } from '../values/numeric.ts'
|
|
7
|
+
import { coerceQuantity, convertQuantity } from '../values/quantity.ts'
|
|
8
|
+
import {
|
|
9
|
+
type QuantityValue,
|
|
10
|
+
SYSTEM_BOOLEAN,
|
|
11
|
+
SYSTEM_DATE,
|
|
12
|
+
SYSTEM_DATETIME,
|
|
13
|
+
SYSTEM_DECIMAL,
|
|
14
|
+
SYSTEM_INTEGER,
|
|
15
|
+
SYSTEM_LONG,
|
|
16
|
+
SYSTEM_QUANTITY,
|
|
17
|
+
SYSTEM_STRING,
|
|
18
|
+
SYSTEM_TIME,
|
|
19
|
+
systemTypeOf,
|
|
20
|
+
type TypedValue,
|
|
21
|
+
} from '../values/typed-value.ts'
|
|
22
|
+
import type { FhirPathFunction } from './registry.ts'
|
|
23
|
+
import { registerFunction } from './registry.ts'
|
|
24
|
+
|
|
25
|
+
type Converter = (item: TypedValue) => TypedValue | undefined
|
|
26
|
+
|
|
27
|
+
/** Registers `to<Type>()` plus its `convertsTo<Type>()` twin (spec §5.5). */
|
|
28
|
+
function conversionPair(typeName: string, convert: Converter): void {
|
|
29
|
+
registerFunction(`to${typeName}`, {
|
|
30
|
+
minArity: 0,
|
|
31
|
+
maxArity: 0,
|
|
32
|
+
evaluate: (_context, input) => {
|
|
33
|
+
const item = singleton(input)
|
|
34
|
+
if (item === undefined) {
|
|
35
|
+
return []
|
|
36
|
+
}
|
|
37
|
+
const converted = convert(item)
|
|
38
|
+
return converted === undefined ? [] : [converted]
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
registerFunction(`convertsTo${typeName}`, {
|
|
42
|
+
minArity: 0,
|
|
43
|
+
maxArity: 0,
|
|
44
|
+
evaluate: (_context, input) => {
|
|
45
|
+
const item = singleton(input)
|
|
46
|
+
if (item === undefined) {
|
|
47
|
+
return []
|
|
48
|
+
}
|
|
49
|
+
return wrapBoolean(convert(item) !== undefined)
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const TRUE_STRINGS = new Set(['true', 't', 'yes', 'y', '1', '1.0'])
|
|
55
|
+
const FALSE_STRINGS = new Set(['false', 'f', 'no', 'n', '0', '0.0'])
|
|
56
|
+
|
|
57
|
+
conversionPair('Boolean', item => {
|
|
58
|
+
switch (systemTypeOf(item)) {
|
|
59
|
+
case SYSTEM_BOOLEAN:
|
|
60
|
+
return item
|
|
61
|
+
case SYSTEM_INTEGER: {
|
|
62
|
+
if (item.value === 1) {
|
|
63
|
+
return { type: SYSTEM_BOOLEAN, value: true }
|
|
64
|
+
}
|
|
65
|
+
return item.value === 0 ? { type: SYSTEM_BOOLEAN, value: false } : undefined
|
|
66
|
+
}
|
|
67
|
+
case SYSTEM_LONG: {
|
|
68
|
+
if (item.value === 1n) {
|
|
69
|
+
return { type: SYSTEM_BOOLEAN, value: true }
|
|
70
|
+
}
|
|
71
|
+
return item.value === 0n ? { type: SYSTEM_BOOLEAN, value: false } : undefined
|
|
72
|
+
}
|
|
73
|
+
case SYSTEM_DECIMAL: {
|
|
74
|
+
const value = item.value as Decimal
|
|
75
|
+
if (value.equals(new Decimal(1n, 0))) {
|
|
76
|
+
return { type: SYSTEM_BOOLEAN, value: true }
|
|
77
|
+
}
|
|
78
|
+
return value.isZero() ? { type: SYSTEM_BOOLEAN, value: false } : undefined
|
|
79
|
+
}
|
|
80
|
+
case SYSTEM_STRING: {
|
|
81
|
+
const text = (item.value as string).toLowerCase()
|
|
82
|
+
if (TRUE_STRINGS.has(text)) {
|
|
83
|
+
return { type: SYSTEM_BOOLEAN, value: true }
|
|
84
|
+
}
|
|
85
|
+
return FALSE_STRINGS.has(text) ? { type: SYSTEM_BOOLEAN, value: false } : undefined
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
return undefined
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
conversionPair('Integer', item => {
|
|
93
|
+
switch (systemTypeOf(item)) {
|
|
94
|
+
case SYSTEM_INTEGER:
|
|
95
|
+
return item
|
|
96
|
+
case SYSTEM_LONG: {
|
|
97
|
+
const value = item.value as bigint
|
|
98
|
+
if (value >= -2147483648n && value <= 2147483647n) {
|
|
99
|
+
return { type: SYSTEM_INTEGER, value: Number(value) }
|
|
100
|
+
}
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
case SYSTEM_BOOLEAN:
|
|
104
|
+
return { type: SYSTEM_INTEGER, value: item.value === true ? 1 : 0 }
|
|
105
|
+
case SYSTEM_STRING: {
|
|
106
|
+
if (!/^[+-]?\d+$/.test(item.value as string)) {
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
|
109
|
+
const value = Number.parseInt(item.value as string, 10)
|
|
110
|
+
return Number.isSafeInteger(value) ? { type: SYSTEM_INTEGER, value } : undefined
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
return undefined
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
conversionPair('Long', item => {
|
|
118
|
+
switch (systemTypeOf(item)) {
|
|
119
|
+
case SYSTEM_LONG:
|
|
120
|
+
return item
|
|
121
|
+
case SYSTEM_INTEGER:
|
|
122
|
+
return { type: SYSTEM_LONG, value: BigInt(item.value as number) }
|
|
123
|
+
case SYSTEM_BOOLEAN:
|
|
124
|
+
return { type: SYSTEM_LONG, value: item.value === true ? 1n : 0n }
|
|
125
|
+
case SYSTEM_STRING: {
|
|
126
|
+
if (!/^[+-]?\d+$/.test(item.value as string)) {
|
|
127
|
+
return undefined
|
|
128
|
+
}
|
|
129
|
+
const value = BigInt(item.value as string)
|
|
130
|
+
return value >= LONG_MIN && value <= LONG_MAX ? { type: SYSTEM_LONG, value } : undefined
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
return undefined
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
conversionPair('Decimal', item => {
|
|
138
|
+
switch (systemTypeOf(item)) {
|
|
139
|
+
case SYSTEM_DECIMAL:
|
|
140
|
+
return item
|
|
141
|
+
case SYSTEM_INTEGER:
|
|
142
|
+
return { type: SYSTEM_DECIMAL, value: Decimal.fromString(String(item.value as number)) }
|
|
143
|
+
case SYSTEM_LONG:
|
|
144
|
+
return { type: SYSTEM_DECIMAL, value: Decimal.fromString((item.value as bigint).toString()) }
|
|
145
|
+
case SYSTEM_BOOLEAN:
|
|
146
|
+
return { type: SYSTEM_DECIMAL, value: Decimal.fromString(item.value === true ? '1.0' : '0.0') }
|
|
147
|
+
case SYSTEM_STRING: {
|
|
148
|
+
if (!/^[+-]?\d+(\.\d+)?$/.test(item.value as string)) {
|
|
149
|
+
return undefined
|
|
150
|
+
}
|
|
151
|
+
return { type: SYSTEM_DECIMAL, value: Decimal.fromString(item.value as string) }
|
|
152
|
+
}
|
|
153
|
+
default:
|
|
154
|
+
return undefined
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
export function valueToString(item: TypedValue): string | undefined {
|
|
159
|
+
if (item.value === undefined || item.value === null) {
|
|
160
|
+
// A primitive present only through its _field sibling has no value to render.
|
|
161
|
+
return undefined
|
|
162
|
+
}
|
|
163
|
+
switch (systemTypeOf(item) ?? item.type) {
|
|
164
|
+
case SYSTEM_STRING:
|
|
165
|
+
return item.value as string
|
|
166
|
+
case SYSTEM_BOOLEAN:
|
|
167
|
+
return item.value === true ? 'true' : 'false'
|
|
168
|
+
case SYSTEM_INTEGER:
|
|
169
|
+
return String(item.value as number)
|
|
170
|
+
case SYSTEM_LONG:
|
|
171
|
+
return (item.value as bigint).toString()
|
|
172
|
+
case SYSTEM_DECIMAL:
|
|
173
|
+
return (item.value as Decimal).toString()
|
|
174
|
+
case SYSTEM_DATE:
|
|
175
|
+
case SYSTEM_DATETIME:
|
|
176
|
+
case SYSTEM_TIME:
|
|
177
|
+
return (item.value as Temporal).toString()
|
|
178
|
+
case SYSTEM_QUANTITY: {
|
|
179
|
+
const quantity = item.value as QuantityValue
|
|
180
|
+
return quantity.calendar
|
|
181
|
+
? `${quantity.value.toString()} ${quantity.unit}`
|
|
182
|
+
: `${quantity.value.toString()} '${quantity.unit}'`
|
|
183
|
+
}
|
|
184
|
+
default:
|
|
185
|
+
return undefined
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
conversionPair('String', item => {
|
|
190
|
+
const value = valueToString(item)
|
|
191
|
+
return value === undefined ? undefined : { type: SYSTEM_STRING, value }
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
conversionPair('Date', item => {
|
|
195
|
+
switch (systemTypeOf(item)) {
|
|
196
|
+
case SYSTEM_DATE:
|
|
197
|
+
return item
|
|
198
|
+
case SYSTEM_DATETIME: {
|
|
199
|
+
const value = item.value as Temporal
|
|
200
|
+
const truncated = Temporal.fromFields('date', { year: value.year, month: value.month, day: value.day })
|
|
201
|
+
return truncated === undefined ? undefined : { type: SYSTEM_DATE, value: truncated }
|
|
202
|
+
}
|
|
203
|
+
case SYSTEM_STRING: {
|
|
204
|
+
const parsed = Temporal.parseDate(item.value as string)
|
|
205
|
+
return parsed === undefined ? undefined : { type: SYSTEM_DATE, value: parsed }
|
|
206
|
+
}
|
|
207
|
+
default:
|
|
208
|
+
return undefined
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
conversionPair('DateTime', item => {
|
|
213
|
+
switch (systemTypeOf(item)) {
|
|
214
|
+
case SYSTEM_DATETIME:
|
|
215
|
+
return item
|
|
216
|
+
case SYSTEM_DATE: {
|
|
217
|
+
const value = item.value as Temporal
|
|
218
|
+
const widened = Temporal.fromFields('dateTime', { year: value.year, month: value.month, day: value.day })
|
|
219
|
+
return widened === undefined ? undefined : { type: SYSTEM_DATETIME, value: widened }
|
|
220
|
+
}
|
|
221
|
+
case SYSTEM_STRING: {
|
|
222
|
+
const parsed = Temporal.parseDateTime(item.value as string)
|
|
223
|
+
return parsed === undefined ? undefined : { type: SYSTEM_DATETIME, value: parsed }
|
|
224
|
+
}
|
|
225
|
+
default:
|
|
226
|
+
return undefined
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
conversionPair('Time', item => {
|
|
231
|
+
switch (systemTypeOf(item)) {
|
|
232
|
+
case SYSTEM_TIME:
|
|
233
|
+
return item
|
|
234
|
+
case SYSTEM_STRING: {
|
|
235
|
+
// The conversion format mirrors the literal form, so the leading T is optional.
|
|
236
|
+
const text = item.value as string
|
|
237
|
+
const parsed = Temporal.parseTime(text.startsWith('T') ? text.slice(1) : text)
|
|
238
|
+
return parsed === undefined ? undefined : { type: SYSTEM_TIME, value: parsed }
|
|
239
|
+
}
|
|
240
|
+
default:
|
|
241
|
+
return undefined
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
const QUANTITY_STRING = /^([+-]?\d+(?:\.\d+)?)\s*(?:'([^']+)'|([a-zA-Z]+))?$/
|
|
246
|
+
|
|
247
|
+
function stringToQuantity(text: string): QuantityValue | undefined {
|
|
248
|
+
const match = QUANTITY_STRING.exec(text.trim())
|
|
249
|
+
if (!match) {
|
|
250
|
+
return undefined
|
|
251
|
+
}
|
|
252
|
+
const [, number, ucumUnit, wordUnit] = match
|
|
253
|
+
const value = Decimal.fromString(number as string)
|
|
254
|
+
/* v8 ignore next 3 -- the regex guarantees a parseable number */
|
|
255
|
+
if (!value) {
|
|
256
|
+
return undefined
|
|
257
|
+
}
|
|
258
|
+
if (ucumUnit !== undefined) {
|
|
259
|
+
return { value, unit: ucumUnit, calendar: false }
|
|
260
|
+
}
|
|
261
|
+
if (wordUnit !== undefined) {
|
|
262
|
+
return CALENDAR_DURATION_UNITS.has(wordUnit) ? { value, unit: wordUnit, calendar: true } : undefined
|
|
263
|
+
}
|
|
264
|
+
return { value, unit: '1', calendar: false }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function quantityConverter(item: TypedValue): TypedValue | undefined {
|
|
268
|
+
// FHIR Quantity values coerce like they do for operators (incl. UCUM
|
|
269
|
+
// time-valued codes becoming calendar durations).
|
|
270
|
+
const coerced = coerceQuantity(item)
|
|
271
|
+
if (coerced !== undefined) {
|
|
272
|
+
return item.type === SYSTEM_QUANTITY ? item : { type: SYSTEM_QUANTITY, value: coerced }
|
|
273
|
+
}
|
|
274
|
+
switch (systemTypeOf(item) ?? item.type) {
|
|
275
|
+
case SYSTEM_QUANTITY:
|
|
276
|
+
return item
|
|
277
|
+
case SYSTEM_INTEGER:
|
|
278
|
+
case SYSTEM_DECIMAL:
|
|
279
|
+
case SYSTEM_LONG: {
|
|
280
|
+
const text =
|
|
281
|
+
item.type === SYSTEM_LONG ? (item.value as bigint).toString() : String(item.value as number | Decimal)
|
|
282
|
+
const value = Decimal.fromString(text)
|
|
283
|
+
/* v8 ignore next 3 -- numeric values always render parseable text */
|
|
284
|
+
if (!value) {
|
|
285
|
+
return undefined
|
|
286
|
+
}
|
|
287
|
+
return { type: SYSTEM_QUANTITY, value: { value, unit: '1', calendar: false } }
|
|
288
|
+
}
|
|
289
|
+
case SYSTEM_BOOLEAN:
|
|
290
|
+
return {
|
|
291
|
+
type: SYSTEM_QUANTITY,
|
|
292
|
+
value: {
|
|
293
|
+
value: Decimal.fromString(item.value === true ? '1.0' : '0.0') as Decimal,
|
|
294
|
+
unit: '1',
|
|
295
|
+
calendar: false,
|
|
296
|
+
},
|
|
297
|
+
}
|
|
298
|
+
case SYSTEM_STRING: {
|
|
299
|
+
const parsed = stringToQuantity(item.value as string)
|
|
300
|
+
return parsed === undefined ? undefined : { type: SYSTEM_QUANTITY, value: parsed }
|
|
301
|
+
}
|
|
302
|
+
default:
|
|
303
|
+
return undefined
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const toQuantityImpl: FhirPathFunction['evaluate'] = (context, input, args, evaluateNode) => {
|
|
308
|
+
const item = singleton(input)
|
|
309
|
+
if (item === undefined) {
|
|
310
|
+
return []
|
|
311
|
+
}
|
|
312
|
+
const converted = quantityConverter(item)
|
|
313
|
+
if (converted === undefined) {
|
|
314
|
+
return []
|
|
315
|
+
}
|
|
316
|
+
if (args.length === 1) {
|
|
317
|
+
const unitArg = singleton(evaluateNode(args[0] as NonNullable<(typeof args)[0]>, context, input))
|
|
318
|
+
if (unitArg === undefined || systemTypeOf(unitArg) !== SYSTEM_STRING) {
|
|
319
|
+
throw new FhirPathTypeError('toQuantity() expects a String unit argument')
|
|
320
|
+
}
|
|
321
|
+
const quantity = converted.value as QuantityValue
|
|
322
|
+
const target = unitArg.value as string
|
|
323
|
+
if (quantity.unit !== target || quantity.calendar) {
|
|
324
|
+
const convertedQuantity = convertQuantity(quantity, target)
|
|
325
|
+
return convertedQuantity === undefined ? [] : [{ type: SYSTEM_QUANTITY, value: convertedQuantity }]
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return [converted]
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
registerFunction('toQuantity', {
|
|
332
|
+
minArity: 0,
|
|
333
|
+
maxArity: 1,
|
|
334
|
+
evaluate: toQuantityImpl,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
registerFunction('convertsToQuantity', {
|
|
338
|
+
minArity: 0,
|
|
339
|
+
maxArity: 1,
|
|
340
|
+
// Same path as toQuantity so the toX/convertsToX contract holds with a unit
|
|
341
|
+
// argument too: convertible exactly when toQuantity(unit) is non-empty.
|
|
342
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
343
|
+
const item = singleton(input)
|
|
344
|
+
if (item === undefined) {
|
|
345
|
+
return []
|
|
346
|
+
}
|
|
347
|
+
return wrapBoolean(toQuantityImpl(context, input, args, evaluateNode).length > 0)
|
|
348
|
+
},
|
|
349
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { singleton } from '../values/collection.ts'
|
|
2
|
+
import { Temporal } from '../values/datetime.ts'
|
|
3
|
+
import { Decimal } from '../values/decimal.ts'
|
|
4
|
+
import { SYSTEM_DATE, SYSTEM_DECIMAL, SYSTEM_INTEGER, SYSTEM_TIME, type TypedValue } from '../values/typed-value.ts'
|
|
5
|
+
import { registerFunction } from './registry.ts'
|
|
6
|
+
|
|
7
|
+
function temporalInput(input: TypedValue[]): Temporal | undefined {
|
|
8
|
+
const item = singleton(input)
|
|
9
|
+
if (item === undefined || !(item.value instanceof Temporal)) {
|
|
10
|
+
// Component extraction from a non-temporal value is not applicable → empty.
|
|
11
|
+
return undefined
|
|
12
|
+
}
|
|
13
|
+
return item.value
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Component extractors (ballot §5.8): empty when the value's precision omits the component. */
|
|
17
|
+
function componentFunction(name: string, extract: (value: Temporal) => number | undefined): void {
|
|
18
|
+
registerFunction(name, {
|
|
19
|
+
minArity: 0,
|
|
20
|
+
maxArity: 0,
|
|
21
|
+
evaluate: (_context, input) => {
|
|
22
|
+
const temporal = temporalInput(input)
|
|
23
|
+
if (temporal === undefined) {
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
const component = extract(temporal)
|
|
27
|
+
return component === undefined ? [] : [{ type: SYSTEM_INTEGER, value: component }]
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
componentFunction('yearOf', value => value.year)
|
|
33
|
+
componentFunction('monthOf', value => value.month)
|
|
34
|
+
componentFunction('dayOf', value => value.day)
|
|
35
|
+
componentFunction('hourOf', value => value.hour)
|
|
36
|
+
componentFunction('minuteOf', value => value.minute)
|
|
37
|
+
componentFunction('secondOf', value => value.second)
|
|
38
|
+
componentFunction('millisecondOf', value =>
|
|
39
|
+
value.fraction === undefined ? undefined : Number.parseInt(value.fraction.padEnd(3, '0').slice(0, 3), 10)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
registerFunction('timezoneOffsetOf', {
|
|
43
|
+
minArity: 0,
|
|
44
|
+
maxArity: 0,
|
|
45
|
+
evaluate: (_context, input) => {
|
|
46
|
+
const temporal = temporalInput(input)
|
|
47
|
+
if (temporal === undefined || temporal.timezoneOffsetMinutes === undefined) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
// Offset in hours as a Decimal: +05:30 is 5.5.
|
|
51
|
+
const hours = Decimal.fromString(String(temporal.timezoneOffsetMinutes / 60))
|
|
52
|
+
/* v8 ignore next -- minutes divided by 60 always renders a parseable number */
|
|
53
|
+
return hours === undefined ? [] : [{ type: SYSTEM_DECIMAL, value: hours }]
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
registerFunction('dateOf', {
|
|
58
|
+
minArity: 0,
|
|
59
|
+
maxArity: 0,
|
|
60
|
+
evaluate: (_context, input) => {
|
|
61
|
+
const temporal = temporalInput(input)
|
|
62
|
+
if (temporal === undefined || temporal.kind === 'time') {
|
|
63
|
+
return []
|
|
64
|
+
}
|
|
65
|
+
const date = Temporal.fromFields('date', { year: temporal.year, month: temporal.month, day: temporal.day })
|
|
66
|
+
/* v8 ignore next -- components come from an already-valid Temporal */
|
|
67
|
+
return date === undefined ? [] : [{ type: SYSTEM_DATE, value: date }]
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
registerFunction('timeOf', {
|
|
72
|
+
minArity: 0,
|
|
73
|
+
maxArity: 0,
|
|
74
|
+
evaluate: (_context, input) => {
|
|
75
|
+
const temporal = temporalInput(input)
|
|
76
|
+
if (temporal === undefined || temporal.kind !== 'dateTime' || temporal.hour === undefined) {
|
|
77
|
+
return []
|
|
78
|
+
}
|
|
79
|
+
const time = Temporal.fromFields('time', {
|
|
80
|
+
hour: temporal.hour,
|
|
81
|
+
minute: temporal.minute,
|
|
82
|
+
second: temporal.second,
|
|
83
|
+
fraction: temporal.fraction,
|
|
84
|
+
})
|
|
85
|
+
/* v8 ignore next -- components come from an already-valid Temporal */
|
|
86
|
+
return time === undefined ? [] : [{ type: SYSTEM_TIME, value: time }]
|
|
87
|
+
},
|
|
88
|
+
})
|