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,232 @@
|
|
|
1
|
+
import { Temporal } from '../../values/datetime.ts'
|
|
2
|
+
import { asNumeric } from '../../values/numeric.ts'
|
|
3
|
+
import { coerceQuantityPair, compareQuantities, quantitiesEquivalent } from '../../values/quantity.ts'
|
|
4
|
+
import { compareTemporal } from '../../values/temporal-compare.ts'
|
|
5
|
+
import {
|
|
6
|
+
SYSTEM_BOOLEAN,
|
|
7
|
+
SYSTEM_STRING,
|
|
8
|
+
systemTypeOf,
|
|
9
|
+
type TypedValue,
|
|
10
|
+
typeLocalName,
|
|
11
|
+
} from '../../values/typed-value.ts'
|
|
12
|
+
import type { BinaryOperatorTable } from './index.ts'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Single-item `=` semantics (spec §6.1.1). Undefined means empty: date/time values
|
|
16
|
+
* whose precisions differ, or quantities whose units cannot be compared yet.
|
|
17
|
+
*/
|
|
18
|
+
export function pairEquals(a: TypedValue, b: TypedValue): boolean | undefined {
|
|
19
|
+
if (a.value === undefined || b.value === undefined) {
|
|
20
|
+
// Valueless primitives (extension-only _field elements) never equal anything.
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
const numericA = asNumeric(a)
|
|
24
|
+
const numericB = asNumeric(b)
|
|
25
|
+
if (numericA && numericB) {
|
|
26
|
+
return numericA.value.equals(numericB.value)
|
|
27
|
+
}
|
|
28
|
+
if (a.value instanceof Temporal && b.value instanceof Temporal) {
|
|
29
|
+
const comparison = compareTemporal(a.value, b.value)
|
|
30
|
+
if (comparison === 'differentPrecision') {
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
return comparison === 0
|
|
34
|
+
}
|
|
35
|
+
const quantityPair = coerceQuantityPair(a, b)
|
|
36
|
+
if (quantityPair) {
|
|
37
|
+
const comparison = compareQuantities(quantityPair[0], quantityPair[1])
|
|
38
|
+
return comparison === undefined ? undefined : comparison === 0
|
|
39
|
+
}
|
|
40
|
+
if (systemTypeOf(a) === SYSTEM_STRING && systemTypeOf(b) === SYSTEM_STRING) {
|
|
41
|
+
return a.value === b.value
|
|
42
|
+
}
|
|
43
|
+
if (systemTypeOf(a) === SYSTEM_BOOLEAN && systemTypeOf(b) === SYSTEM_BOOLEAN) {
|
|
44
|
+
return a.value === b.value
|
|
45
|
+
}
|
|
46
|
+
if (isComplex(a) && isComplex(b)) {
|
|
47
|
+
return deepEquals(a.value, b.value)
|
|
48
|
+
}
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Single-item `~` semantics (spec §6.1.3). Never empty. */
|
|
53
|
+
export function pairEquivalent(a: TypedValue, b: TypedValue): boolean {
|
|
54
|
+
if (a.value === undefined || b.value === undefined) {
|
|
55
|
+
// Two valueless primitives are equivalent; a valueless one never matches a value.
|
|
56
|
+
return a.value === undefined && b.value === undefined
|
|
57
|
+
}
|
|
58
|
+
const numericA = asNumeric(a)
|
|
59
|
+
const numericB = asNumeric(b)
|
|
60
|
+
if (numericA && numericB) {
|
|
61
|
+
// Rounded to the least precise operand.
|
|
62
|
+
const scale = Math.min(numericA.value.scale, numericB.value.scale)
|
|
63
|
+
return numericA.value.round(scale).equals(numericB.value.round(scale))
|
|
64
|
+
}
|
|
65
|
+
if (a.value instanceof Temporal && b.value instanceof Temporal) {
|
|
66
|
+
return compareTemporal(a.value, b.value) === 0
|
|
67
|
+
}
|
|
68
|
+
const quantityPair = coerceQuantityPair(a, b)
|
|
69
|
+
if (quantityPair) {
|
|
70
|
+
return quantitiesEquivalent(quantityPair[0], quantityPair[1])
|
|
71
|
+
}
|
|
72
|
+
// One typed side is enough: untyped comparison values (e.g. from %env) still get
|
|
73
|
+
// FHIR semantics when compared against a model-typed Coding/CodeableConcept.
|
|
74
|
+
if ((typeLocalName(a.type) === 'Coding' || typeLocalName(b.type) === 'Coding') && isComplex(a) && isComplex(b)) {
|
|
75
|
+
return codingEquivalent(a.value, b.value)
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
(typeLocalName(a.type) === 'CodeableConcept' || typeLocalName(b.type) === 'CodeableConcept') &&
|
|
79
|
+
isComplex(a) &&
|
|
80
|
+
isComplex(b)
|
|
81
|
+
) {
|
|
82
|
+
return codeableConceptEquivalent(a.value, b.value)
|
|
83
|
+
}
|
|
84
|
+
if (systemTypeOf(a) === SYSTEM_STRING && systemTypeOf(b) === SYSTEM_STRING) {
|
|
85
|
+
return normalizeString(a.value as string) === normalizeString(b.value as string)
|
|
86
|
+
}
|
|
87
|
+
if (systemTypeOf(a) === SYSTEM_BOOLEAN && systemTypeOf(b) === SYSTEM_BOOLEAN) {
|
|
88
|
+
return a.value === b.value
|
|
89
|
+
}
|
|
90
|
+
if (isComplex(a) && isComplex(b)) {
|
|
91
|
+
return deepEquivalent(a.value, b.value)
|
|
92
|
+
}
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** FHIR equivalence for Coding: system and code only (display and id do not matter). */
|
|
97
|
+
function codingEquivalent(a: unknown, b: unknown): boolean {
|
|
98
|
+
const codingA = a as { system?: unknown; code?: unknown }
|
|
99
|
+
const codingB = b as { system?: unknown; code?: unknown }
|
|
100
|
+
return codingA.system === codingB.system && codingA.code === codingB.code
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** FHIR equivalence for CodeableConcept: any pair of equivalent codings. */
|
|
104
|
+
function codeableConceptEquivalent(a: unknown, b: unknown): boolean {
|
|
105
|
+
const codingsA = ((a as { coding?: unknown[] }).coding ?? []) as unknown[]
|
|
106
|
+
const codingsB = ((b as { coding?: unknown[] }).coding ?? []) as unknown[]
|
|
107
|
+
return codingsA.some(one => codingsB.some(other => codingEquivalent(one, other)))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isComplex(item: TypedValue): boolean {
|
|
111
|
+
return typeof item.value === 'object' && item.value !== null && !(item.value instanceof Temporal)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeString(value: string): string {
|
|
115
|
+
// "Normalizing whitespace" (spec §6.1.2) makes tab/newline/space interchangeable;
|
|
116
|
+
// it does not trim or collapse runs — 'a b' and 'a b' stay different.
|
|
117
|
+
return value.replace(/\s/g, ' ').toLowerCase()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function deepEquals(a: unknown, b: unknown): boolean {
|
|
121
|
+
if (a === b) {
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
125
|
+
return (
|
|
126
|
+
Array.isArray(a) &&
|
|
127
|
+
Array.isArray(b) &&
|
|
128
|
+
a.length === b.length &&
|
|
129
|
+
a.every((item, index) => deepEquals(item, b[index]))
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {
|
|
133
|
+
// Equality on FHIR complex values ignores element ids and primitive metadata,
|
|
134
|
+
// same as equivalence: a value's identity is its children, not its annotations.
|
|
135
|
+
const keysA = Object.keys(a).filter(isValueKey)
|
|
136
|
+
const keysB = Object.keys(b).filter(isValueKey)
|
|
137
|
+
return (
|
|
138
|
+
keysA.length === keysB.length &&
|
|
139
|
+
keysA.every(key => deepEquals((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]))
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
return a === b
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function deepEquivalent(a: unknown, b: unknown): boolean {
|
|
146
|
+
if (a === b) {
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
150
|
+
return normalizeString(a) === normalizeString(b)
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
153
|
+
return (
|
|
154
|
+
Array.isArray(a) &&
|
|
155
|
+
Array.isArray(b) &&
|
|
156
|
+
a.length === b.length &&
|
|
157
|
+
a.every((item, index) => deepEquivalent(item, b[index]))
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {
|
|
161
|
+
const keysA = Object.keys(a).filter(isValueKey)
|
|
162
|
+
const keysB = Object.keys(b).filter(isValueKey)
|
|
163
|
+
return (
|
|
164
|
+
keysA.length === keysB.length &&
|
|
165
|
+
keysA.every(key => deepEquivalent((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]))
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
return a === b
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Excludes a complex value's `id` and `_field` (primitive extension) siblings from comparison. */
|
|
172
|
+
function isValueKey(key: string): boolean {
|
|
173
|
+
return key !== 'id' && !key.startsWith('_')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Collection `=`: empty operand → empty; different lengths → false; ordered pairwise. */
|
|
177
|
+
export function collectionEquals(left: TypedValue[], right: TypedValue[]): boolean | undefined {
|
|
178
|
+
if (left.length === 0 || right.length === 0) {
|
|
179
|
+
return undefined
|
|
180
|
+
}
|
|
181
|
+
if (left.length !== right.length) {
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
184
|
+
let result = true
|
|
185
|
+
for (let i = 0; i < left.length; i++) {
|
|
186
|
+
const pair = pairEquals(left[i] as TypedValue, right[i] as TypedValue)
|
|
187
|
+
if (pair === undefined) {
|
|
188
|
+
return undefined
|
|
189
|
+
}
|
|
190
|
+
result = result && pair
|
|
191
|
+
}
|
|
192
|
+
return result
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Collection `~`: both empty → true; order-independent multiset matching. */
|
|
196
|
+
export function collectionEquivalent(left: TypedValue[], right: TypedValue[]): boolean {
|
|
197
|
+
if (left.length === 0 && right.length === 0) {
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
if (left.length !== right.length) {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
const used = new Array<boolean>(right.length).fill(false)
|
|
204
|
+
for (const item of left) {
|
|
205
|
+
let matched = false
|
|
206
|
+
for (let i = 0; i < right.length; i++) {
|
|
207
|
+
if (!used[i] && pairEquivalent(item, right[i] as TypedValue)) {
|
|
208
|
+
used[i] = true
|
|
209
|
+
matched = true
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!matched) {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function wrap(value: boolean | undefined): TypedValue[] {
|
|
221
|
+
return value === undefined ? [] : [{ type: SYSTEM_BOOLEAN, value }]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const equalityOperators = {
|
|
225
|
+
'=': (_context, left, right) => wrap(collectionEquals(left, right)),
|
|
226
|
+
'!=': (_context, left, right) => {
|
|
227
|
+
const result = collectionEquals(left, right)
|
|
228
|
+
return wrap(result === undefined ? undefined : !result)
|
|
229
|
+
},
|
|
230
|
+
'~': (_context, left, right) => wrap(collectionEquivalent(left, right)),
|
|
231
|
+
'!~': (_context, left, right) => wrap(!collectionEquivalent(left, right)),
|
|
232
|
+
} satisfies BinaryOperatorTable
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { BinaryOperator, TypeSpecifier, UnaryOperator } from '../../parser/ast.ts'
|
|
2
|
+
import type { TypedValue } from '../../values/typed-value.ts'
|
|
3
|
+
import type { EvaluationContext } from '../context.ts'
|
|
4
|
+
import { collectionOperators } from './collections.ts'
|
|
5
|
+
import { comparisonOperators } from './comparison.ts'
|
|
6
|
+
import { equalityOperators } from './equality.ts'
|
|
7
|
+
import { logicOperators } from './logic.ts'
|
|
8
|
+
import { arithmeticOperators, unaryArithmeticOperators } from './math.ts'
|
|
9
|
+
import { stringOperators } from './strings.ts'
|
|
10
|
+
import { typeOperator } from './types.ts'
|
|
11
|
+
|
|
12
|
+
export type BinaryOperatorImpl = (context: EvaluationContext, left: TypedValue[], right: TypedValue[]) => TypedValue[]
|
|
13
|
+
|
|
14
|
+
/** What each operator module exports; modules import this type-only, so no cycle. */
|
|
15
|
+
export type BinaryOperatorTable = Partial<Record<BinaryOperator, BinaryOperatorImpl>>
|
|
16
|
+
|
|
17
|
+
export type UnaryOperatorImpl = (context: EvaluationContext, operand: TypedValue[]) => TypedValue[]
|
|
18
|
+
|
|
19
|
+
export type TypeOperatorImpl = (
|
|
20
|
+
context: EvaluationContext,
|
|
21
|
+
operator: 'is' | 'as',
|
|
22
|
+
operand: TypedValue[],
|
|
23
|
+
type: TypeSpecifier
|
|
24
|
+
) => TypedValue[]
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* One entry per BinaryOperator union member, assembled statically — a missing
|
|
28
|
+
* operator is a compile error here, not a runtime throw.
|
|
29
|
+
*/
|
|
30
|
+
export const binaryOperators: Readonly<Record<BinaryOperator, BinaryOperatorImpl>> = {
|
|
31
|
+
...equalityOperators,
|
|
32
|
+
...comparisonOperators,
|
|
33
|
+
...logicOperators,
|
|
34
|
+
...arithmeticOperators,
|
|
35
|
+
...collectionOperators,
|
|
36
|
+
...stringOperators,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const unaryOperators: Readonly<Record<UnaryOperator, UnaryOperatorImpl>> = unaryArithmeticOperators
|
|
40
|
+
|
|
41
|
+
export function evaluateBinary(
|
|
42
|
+
context: EvaluationContext,
|
|
43
|
+
operator: BinaryOperator,
|
|
44
|
+
left: TypedValue[],
|
|
45
|
+
right: TypedValue[]
|
|
46
|
+
): TypedValue[] {
|
|
47
|
+
return binaryOperators[operator](context, left, right)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function evaluateUnary(
|
|
51
|
+
context: EvaluationContext,
|
|
52
|
+
operator: UnaryOperator,
|
|
53
|
+
operand: TypedValue[]
|
|
54
|
+
): TypedValue[] {
|
|
55
|
+
return unaryOperators[operator](context, operand)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function evaluateTypeOp(
|
|
59
|
+
context: EvaluationContext,
|
|
60
|
+
operator: 'is' | 'as',
|
|
61
|
+
operand: TypedValue[],
|
|
62
|
+
type: TypeSpecifier
|
|
63
|
+
): TypedValue[] {
|
|
64
|
+
return typeOperator(context, operator, operand, type)
|
|
65
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { booleanSingleton, wrapBoolean } from '../../values/collection.ts'
|
|
2
|
+
import type { TypedValue } from '../../values/typed-value.ts'
|
|
3
|
+
import type { BinaryOperatorTable } from './index.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Three-valued logic straight from the spec §6.5 truth tables. `undefined` is the
|
|
7
|
+
* empty collection. Notable asymmetric cells: `false and empty` is false,
|
|
8
|
+
* `true or empty` is true, `empty implies false` is empty, `false implies x` is true.
|
|
9
|
+
*/
|
|
10
|
+
type Tri = boolean | undefined
|
|
11
|
+
|
|
12
|
+
const and = (a: Tri, b: Tri): Tri => {
|
|
13
|
+
if (a === false || b === false) {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
return a === true && b === true ? true : undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const or = (a: Tri, b: Tri): Tri => {
|
|
20
|
+
if (a === true || b === true) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
return a === false && b === false ? false : undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const xor = (a: Tri, b: Tri): Tri => {
|
|
27
|
+
if (a === undefined || b === undefined) {
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
return a !== b
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const implies = (a: Tri, b: Tri): Tri => {
|
|
34
|
+
if (a === false) {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
if (a === true) {
|
|
38
|
+
return b
|
|
39
|
+
}
|
|
40
|
+
return b === true ? true : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function logicOperator(table: (a: Tri, b: Tri) => Tri) {
|
|
44
|
+
return (_context: unknown, left: TypedValue[], right: TypedValue[]): TypedValue[] =>
|
|
45
|
+
wrapBoolean(table(booleanSingleton(left), booleanSingleton(right)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const logicOperators = {
|
|
49
|
+
and: logicOperator(and),
|
|
50
|
+
or: logicOperator(or),
|
|
51
|
+
xor: logicOperator(xor),
|
|
52
|
+
implies: logicOperator(implies),
|
|
53
|
+
} satisfies BinaryOperatorTable
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { FhirPathTypeError } from '../../errors.ts'
|
|
2
|
+
import type { UnaryOperator } from '../../parser/ast.ts'
|
|
3
|
+
import { singleton } from '../../values/collection.ts'
|
|
4
|
+
import { Temporal } from '../../values/datetime.ts'
|
|
5
|
+
import type { Decimal } from '../../values/decimal.ts'
|
|
6
|
+
import { asNumeric, widerKind, wrapNumeric } from '../../values/numeric.ts'
|
|
7
|
+
import {
|
|
8
|
+
alignQuantities,
|
|
9
|
+
calendarToUcumLoose,
|
|
10
|
+
coerceQuantity,
|
|
11
|
+
composeUnits,
|
|
12
|
+
promoteQuantity,
|
|
13
|
+
} from '../../values/quantity.ts'
|
|
14
|
+
import { addDuration } from '../../values/temporal-arithmetic.ts'
|
|
15
|
+
import {
|
|
16
|
+
type QuantityValue,
|
|
17
|
+
SYSTEM_QUANTITY,
|
|
18
|
+
SYSTEM_STRING,
|
|
19
|
+
systemTypeOf,
|
|
20
|
+
type TypedValue,
|
|
21
|
+
} from '../../values/typed-value.ts'
|
|
22
|
+
import { canonicalizeUnit, sameDimensions } from '../../values/ucum.ts'
|
|
23
|
+
import type { BinaryOperatorTable, UnaryOperatorImpl } from './index.ts'
|
|
24
|
+
|
|
25
|
+
type ArithmeticOperator = '+' | '-' | '*' | '/' | 'div' | 'mod'
|
|
26
|
+
|
|
27
|
+
function numericArithmetic(operator: ArithmeticOperator, a: TypedValue, b: TypedValue): TypedValue[] {
|
|
28
|
+
const left = asNumeric(a) as NonNullable<ReturnType<typeof asNumeric>>
|
|
29
|
+
const right = asNumeric(b) as NonNullable<ReturnType<typeof asNumeric>>
|
|
30
|
+
let result: Decimal | undefined
|
|
31
|
+
let kind = widerKind(left.kind, right.kind)
|
|
32
|
+
switch (operator) {
|
|
33
|
+
case '+':
|
|
34
|
+
result = left.value.add(right.value)
|
|
35
|
+
break
|
|
36
|
+
case '-':
|
|
37
|
+
result = left.value.subtract(right.value)
|
|
38
|
+
break
|
|
39
|
+
case '*':
|
|
40
|
+
result = left.value.multiply(right.value)
|
|
41
|
+
break
|
|
42
|
+
case '/':
|
|
43
|
+
result = left.value.divide(right.value)
|
|
44
|
+
kind = 'Decimal'
|
|
45
|
+
break
|
|
46
|
+
case 'div':
|
|
47
|
+
result = left.value.integerDivide(right.value)
|
|
48
|
+
kind = kind === 'Decimal' ? 'Integer' : kind
|
|
49
|
+
break
|
|
50
|
+
default:
|
|
51
|
+
result = left.value.modulo(right.value)
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
if (result === undefined) {
|
|
55
|
+
return []
|
|
56
|
+
}
|
|
57
|
+
return [wrapNumeric(result, kind)]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function temporalArithmetic(operator: ArithmeticOperator, a: TypedValue, b: TypedValue): TypedValue[] {
|
|
61
|
+
if (operator !== '+' && operator !== '-') {
|
|
62
|
+
throw new FhirPathTypeError(`Operator '${operator}' is not defined for date/time values`)
|
|
63
|
+
}
|
|
64
|
+
const result = addDuration(a.value as Temporal, b.value as QuantityValue, operator === '+' ? 1 : -1)
|
|
65
|
+
return result === undefined ? [] : [{ type: a.type, value: result }]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function quantityArithmetic(operator: ArithmeticOperator, left: QuantityValue, right: QuantityValue): TypedValue[] {
|
|
69
|
+
if (operator === '+' || operator === '-') {
|
|
70
|
+
// Convertible units align on the more granular one (spec §6.6); else empty.
|
|
71
|
+
const aligned = alignQuantities(left, right)
|
|
72
|
+
if (!aligned) {
|
|
73
|
+
return []
|
|
74
|
+
}
|
|
75
|
+
const value = operator === '+' ? aligned.left.add(aligned.right) : aligned.left.subtract(aligned.right)
|
|
76
|
+
return [{ type: SYSTEM_QUANTITY, value: { value, unit: aligned.unit, calendar: aligned.calendar } }]
|
|
77
|
+
}
|
|
78
|
+
if (operator === '*' || operator === '/') {
|
|
79
|
+
if (left.calendar || right.calendar) {
|
|
80
|
+
return calendarMultiplicative(operator, left, right)
|
|
81
|
+
}
|
|
82
|
+
const value = operator === '*' ? left.value.multiply(right.value) : left.value.divide(right.value)
|
|
83
|
+
if (value === undefined) {
|
|
84
|
+
return []
|
|
85
|
+
}
|
|
86
|
+
const unit = composeUnits(operator, left.unit, right.unit)
|
|
87
|
+
return [{ type: SYSTEM_QUANTITY, value: { value, unit, calendar: false } }]
|
|
88
|
+
}
|
|
89
|
+
throw new FhirPathTypeError(`Operator '${operator}' is not defined for quantities`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Calendar durations have no general unit algebra, but two cases are exact:
|
|
94
|
+
* scaling by a dimensionless number (2 year * 3 = 6 years, 1 year / 2 = 6 months)
|
|
95
|
+
* and the ratio of two same-family durations (185 months / 185 months = 1 '1').
|
|
96
|
+
* Everything else is undefined → empty.
|
|
97
|
+
*/
|
|
98
|
+
function calendarMultiplicative(operator: '*' | '/', left: QuantityValue, right: QuantityValue): TypedValue[] {
|
|
99
|
+
if (operator === '/') {
|
|
100
|
+
if (left.calendar) {
|
|
101
|
+
const factor = right.calendar ? undefined : dimensionlessValue(right)
|
|
102
|
+
if (factor !== undefined) {
|
|
103
|
+
// Scaling a duration keeps its calendar unit: 1 year / 2 = 0.5 years.
|
|
104
|
+
const scaled = left.value.divide(factor)
|
|
105
|
+
return scaled === undefined ? [] : [{ type: SYSTEM_QUANTITY, value: { ...left, value: scaled } }]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Ratios of durations are well defined; both sides drop to their UCUM twins
|
|
109
|
+
// (185 months / 185 'mo' = 1 '1'), computing through exact canonical factors.
|
|
110
|
+
const leftUcum = calendarAsUcum(left)
|
|
111
|
+
const rightUcum = calendarAsUcum(right)
|
|
112
|
+
const leftCanonical = canonicalizeUnit(leftUcum.unit)
|
|
113
|
+
const rightCanonical = canonicalizeUnit(rightUcum.unit)
|
|
114
|
+
if (leftCanonical && rightCanonical && sameDimensions(leftCanonical, rightCanonical)) {
|
|
115
|
+
const ratio = leftUcum.value
|
|
116
|
+
.multiply(leftCanonical.factor)
|
|
117
|
+
.divide(rightUcum.value.multiply(rightCanonical.factor))
|
|
118
|
+
return ratio === undefined ? [] : [{ type: SYSTEM_QUANTITY, value: { value: ratio, unit: '1', calendar: false } }]
|
|
119
|
+
}
|
|
120
|
+
return quantityArithmetic('/', leftUcum, rightUcum)
|
|
121
|
+
}
|
|
122
|
+
const [calendar, scalar] = left.calendar ? [left, right] : [right, left]
|
|
123
|
+
if (scalar.calendar) {
|
|
124
|
+
// year * year has no meaning.
|
|
125
|
+
return []
|
|
126
|
+
}
|
|
127
|
+
const factor = dimensionlessValue(scalar)
|
|
128
|
+
if (factor === undefined) {
|
|
129
|
+
return []
|
|
130
|
+
}
|
|
131
|
+
const value = calendar.value.multiply(factor)
|
|
132
|
+
return [{ type: SYSTEM_QUANTITY, value: { ...calendar, value } }]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The scalar value of a dimensionless quantity (2000 'g/kg' → 2); else undefined. */
|
|
136
|
+
function dimensionlessValue(quantity: QuantityValue): Decimal | undefined {
|
|
137
|
+
const canonical = canonicalizeUnit(quantity.unit)
|
|
138
|
+
if (!canonical || Object.keys(canonical.dimensions).length > 0) {
|
|
139
|
+
return undefined
|
|
140
|
+
}
|
|
141
|
+
return quantity.value.multiply(canonical.factor)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function calendarAsUcum(quantity: QuantityValue): QuantityValue {
|
|
145
|
+
return quantity.calendar ? calendarToUcumLoose(quantity) : quantity
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isTemporalType(item: TypedValue): boolean {
|
|
149
|
+
return item.value instanceof Temporal
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function arithmeticOperator(operator: ArithmeticOperator) {
|
|
153
|
+
return (_context: unknown, leftInput: TypedValue[], rightInput: TypedValue[]): TypedValue[] => {
|
|
154
|
+
const a = singleton(leftInput)
|
|
155
|
+
const b = singleton(rightInput)
|
|
156
|
+
if (a === undefined || b === undefined) {
|
|
157
|
+
return []
|
|
158
|
+
}
|
|
159
|
+
if (systemTypeOf(a) === SYSTEM_STRING || systemTypeOf(b) === SYSTEM_STRING) {
|
|
160
|
+
if (operator === '+' && systemTypeOf(a) === SYSTEM_STRING && systemTypeOf(b) === SYSTEM_STRING) {
|
|
161
|
+
if (a.value === undefined || b.value === undefined) {
|
|
162
|
+
// A primitive present only through its _field sibling has no value; +
|
|
163
|
+
// propagates that like an empty operand.
|
|
164
|
+
return []
|
|
165
|
+
}
|
|
166
|
+
return [{ type: SYSTEM_STRING, value: (a.value as string) + (b.value as string) }]
|
|
167
|
+
}
|
|
168
|
+
throw new FhirPathTypeError(`Operator '${operator}' is not defined for strings`)
|
|
169
|
+
}
|
|
170
|
+
if (isTemporalType(a) && coerceQuantity(b)) {
|
|
171
|
+
return temporalArithmetic(operator, a, { type: SYSTEM_QUANTITY, value: coerceQuantity(b) })
|
|
172
|
+
}
|
|
173
|
+
if (isTemporalType(b) && coerceQuantity(a) && operator === '+') {
|
|
174
|
+
// Addition commutes: 1 year + @2016 works like @2016 + 1 year.
|
|
175
|
+
return temporalArithmetic(operator, b, { type: SYSTEM_QUANTITY, value: coerceQuantity(a) })
|
|
176
|
+
}
|
|
177
|
+
if (asNumeric(a) && asNumeric(b)) {
|
|
178
|
+
return numericArithmetic(operator, a, b)
|
|
179
|
+
}
|
|
180
|
+
if (coerceQuantity(a) || coerceQuantity(b)) {
|
|
181
|
+
const left = promoteQuantity(a)
|
|
182
|
+
const right = promoteQuantity(b)
|
|
183
|
+
if (left && right && operator !== 'div' && operator !== 'mod') {
|
|
184
|
+
return quantityArithmetic(operator, left, right)
|
|
185
|
+
}
|
|
186
|
+
throw new FhirPathTypeError(`Operator '${operator}' is not defined for these quantity operands`)
|
|
187
|
+
}
|
|
188
|
+
throw new FhirPathTypeError(`Operator '${operator}' is not defined for ${a.type} and ${b.type}`)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const arithmeticOperators = {
|
|
193
|
+
'+': arithmeticOperator('+'),
|
|
194
|
+
'-': arithmeticOperator('-'),
|
|
195
|
+
'*': arithmeticOperator('*'),
|
|
196
|
+
'/': arithmeticOperator('/'),
|
|
197
|
+
div: arithmeticOperator('div'),
|
|
198
|
+
mod: arithmeticOperator('mod'),
|
|
199
|
+
} satisfies BinaryOperatorTable
|
|
200
|
+
|
|
201
|
+
function unaryOperator(sign: 1 | -1) {
|
|
202
|
+
return (_context: unknown, input: TypedValue[]): TypedValue[] => {
|
|
203
|
+
const item = singleton(input)
|
|
204
|
+
if (item === undefined) {
|
|
205
|
+
return []
|
|
206
|
+
}
|
|
207
|
+
const numeric = asNumeric(item)
|
|
208
|
+
if (numeric) {
|
|
209
|
+
if (sign === 1) {
|
|
210
|
+
return [item]
|
|
211
|
+
}
|
|
212
|
+
return [wrapNumeric(numeric.value.negate(), numeric.kind)]
|
|
213
|
+
}
|
|
214
|
+
if (item.type === SYSTEM_QUANTITY) {
|
|
215
|
+
const quantity = item.value as QuantityValue
|
|
216
|
+
return sign === 1 ? [item] : [{ type: SYSTEM_QUANTITY, value: { ...quantity, value: quantity.value.negate() } }]
|
|
217
|
+
}
|
|
218
|
+
throw new FhirPathTypeError(`Unary '${sign === 1 ? '+' : '-'}' is not defined for ${item.type}`)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const unaryArithmeticOperators: Readonly<Record<UnaryOperator, UnaryOperatorImpl>> = {
|
|
223
|
+
'+': unaryOperator(1),
|
|
224
|
+
'-': unaryOperator(-1),
|
|
225
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FhirPathTypeError } from '../../errors.ts'
|
|
2
|
+
import { singleton } from '../../values/collection.ts'
|
|
3
|
+
import { SYSTEM_STRING, systemTypeOf, type TypedValue } from '../../values/typed-value.ts'
|
|
4
|
+
import type { BinaryOperatorTable } from './index.ts'
|
|
5
|
+
|
|
6
|
+
/** `&` concatenates strings and, unlike `+`, treats an empty operand as `''` (spec §6.6.2). */
|
|
7
|
+
function concat(_context: unknown, left: TypedValue[], right: TypedValue[]): TypedValue[] {
|
|
8
|
+
return [{ type: SYSTEM_STRING, value: stringOrEmpty(left) + stringOrEmpty(right) }]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function stringOrEmpty(input: TypedValue[]): string {
|
|
12
|
+
const item = singleton(input)
|
|
13
|
+
if (item === undefined || item.value === undefined) {
|
|
14
|
+
// Empty operand, or a primitive present only through its _field sibling.
|
|
15
|
+
return ''
|
|
16
|
+
}
|
|
17
|
+
if (systemTypeOf(item) !== SYSTEM_STRING) {
|
|
18
|
+
throw new FhirPathTypeError(`Operator '&' expects strings, got ${item.type}`)
|
|
19
|
+
}
|
|
20
|
+
return item.value as string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const stringOperators = {
|
|
24
|
+
'&': concat,
|
|
25
|
+
} satisfies BinaryOperatorTable
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { singleton, wrapBoolean } from '../../values/collection.ts'
|
|
2
|
+
import { itemMatchesType } from '../type-matching.ts'
|
|
3
|
+
import type { TypeOperatorImpl } from './index.ts'
|
|
4
|
+
|
|
5
|
+
export const typeOperator: TypeOperatorImpl = (context, operator, operand, type) => {
|
|
6
|
+
const item = singleton(operand)
|
|
7
|
+
if (item === undefined) {
|
|
8
|
+
return []
|
|
9
|
+
}
|
|
10
|
+
// `is` walks subtypes; the `as` cast demands the exact type (spec + official tests).
|
|
11
|
+
const matches = itemMatchesType(context, item, type.parts, { exact: operator === 'as' })
|
|
12
|
+
if (operator === 'is') {
|
|
13
|
+
return wrapBoolean(matches)
|
|
14
|
+
}
|
|
15
|
+
return matches ? [item] : []
|
|
16
|
+
}
|