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,107 @@
|
|
|
1
|
+
import { pairEquals } from '../engine/operators/equality.ts'
|
|
2
|
+
import { FhirPathTypeError } from '../errors.ts'
|
|
3
|
+
import { booleanSingleton, wrapBoolean } from '../values/collection.ts'
|
|
4
|
+
import { SYSTEM_BOOLEAN, SYSTEM_INTEGER, systemTypeOf, type TypedValue } from '../values/typed-value.ts'
|
|
5
|
+
import { perItem } from './iteration.ts'
|
|
6
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
7
|
+
|
|
8
|
+
registerFunction('empty', {
|
|
9
|
+
minArity: 0,
|
|
10
|
+
maxArity: 0,
|
|
11
|
+
evaluate: (_context, input) => wrapBoolean(input.length === 0),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
registerFunction('exists', {
|
|
15
|
+
minArity: 0,
|
|
16
|
+
maxArity: 1,
|
|
17
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
18
|
+
if (args.length === 0) {
|
|
19
|
+
return wrapBoolean(input.length > 0)
|
|
20
|
+
}
|
|
21
|
+
let found = false
|
|
22
|
+
perItem(context, input, argAt(args, 0), evaluateNode, (_item, result) => {
|
|
23
|
+
found = found || booleanSingleton(result) === true
|
|
24
|
+
})
|
|
25
|
+
return wrapBoolean(found)
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
registerFunction('all', {
|
|
30
|
+
minArity: 1,
|
|
31
|
+
maxArity: 1,
|
|
32
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
33
|
+
let all = true
|
|
34
|
+
perItem(context, input, argAt(args, 0), evaluateNode, (_item, result) => {
|
|
35
|
+
all = all && booleanSingleton(result) === true
|
|
36
|
+
})
|
|
37
|
+
return wrapBoolean(all)
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function booleanAggregate(name: string, fold: (values: boolean[]) => boolean) {
|
|
42
|
+
registerFunction(name, {
|
|
43
|
+
minArity: 0,
|
|
44
|
+
maxArity: 0,
|
|
45
|
+
evaluate: (_context, input) => {
|
|
46
|
+
const values = input.map(item => {
|
|
47
|
+
if (systemTypeOf(item) !== SYSTEM_BOOLEAN) {
|
|
48
|
+
throw new FhirPathTypeError(`${name}() expects a collection of booleans, found ${item.type}`)
|
|
49
|
+
}
|
|
50
|
+
return item.value as boolean
|
|
51
|
+
})
|
|
52
|
+
return wrapBoolean(fold(values))
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
booleanAggregate('allTrue', values => values.every(value => value))
|
|
58
|
+
booleanAggregate('anyTrue', values => values.some(value => value))
|
|
59
|
+
booleanAggregate('allFalse', values => values.every(value => !value))
|
|
60
|
+
booleanAggregate('anyFalse', values => values.some(value => !value))
|
|
61
|
+
|
|
62
|
+
registerFunction('count', {
|
|
63
|
+
minArity: 0,
|
|
64
|
+
maxArity: 0,
|
|
65
|
+
evaluate: (_context, input) => [{ type: SYSTEM_INTEGER, value: input.length }],
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
/** Duplicate elimination with `=` semantics; shared with the `|` operator and isDistinct(). */
|
|
69
|
+
export function distinctItems(input: TypedValue[]): TypedValue[] {
|
|
70
|
+
const result: TypedValue[] = []
|
|
71
|
+
for (const item of input) {
|
|
72
|
+
if (!result.some(existing => pairEquals(existing, item) === true)) {
|
|
73
|
+
result.push(item)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
registerFunction('distinct', {
|
|
80
|
+
minArity: 0,
|
|
81
|
+
maxArity: 0,
|
|
82
|
+
evaluate: (_context, input) => distinctItems(input),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
registerFunction('isDistinct', {
|
|
86
|
+
minArity: 0,
|
|
87
|
+
maxArity: 0,
|
|
88
|
+
evaluate: (_context, input) => wrapBoolean(distinctItems(input).length === input.length),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
registerFunction('subsetOf', {
|
|
92
|
+
minArity: 1,
|
|
93
|
+
maxArity: 1,
|
|
94
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
95
|
+
const other = evaluateNode(argAt(args, 0), context, input)
|
|
96
|
+
return wrapBoolean(input.every(item => other.some(candidate => pairEquals(item, candidate) === true)))
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
registerFunction('supersetOf', {
|
|
101
|
+
minArity: 1,
|
|
102
|
+
maxArity: 1,
|
|
103
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
104
|
+
const other = evaluateNode(argAt(args, 0), context, input)
|
|
105
|
+
return wrapBoolean(other.every(item => input.some(candidate => pairEquals(item, candidate) === true)))
|
|
106
|
+
},
|
|
107
|
+
})
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { FhirPathRuntimeError, FhirPathTypeError } from '../errors.ts'
|
|
2
|
+
import { validateNarrative } from '../fhir/html-checks.ts'
|
|
3
|
+
import { singleton, wrapBoolean } from '../values/collection.ts'
|
|
4
|
+
import { calendarToUcumLoose, compareQuantities, promoteQuantity } from '../values/quantity.ts'
|
|
5
|
+
import { SYSTEM_QUANTITY, SYSTEM_STRING, systemTypeOf, toTypedValue, type TypedValue } from '../values/typed-value.ts'
|
|
6
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
7
|
+
|
|
8
|
+
/** extension(url): extensions of each item, including primitive `_field` extensions. */
|
|
9
|
+
registerFunction('extension', {
|
|
10
|
+
minArity: 1,
|
|
11
|
+
maxArity: 1,
|
|
12
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
13
|
+
const urlValue = singleton(evaluateNode(argAt(args, 0), context, input))
|
|
14
|
+
if (urlValue === undefined) {
|
|
15
|
+
return []
|
|
16
|
+
}
|
|
17
|
+
if (systemTypeOf(urlValue) !== SYSTEM_STRING) {
|
|
18
|
+
throw new FhirPathTypeError('extension() expects a String url argument')
|
|
19
|
+
}
|
|
20
|
+
const url = urlValue.value as string
|
|
21
|
+
const result: TypedValue[] = []
|
|
22
|
+
for (const item of input) {
|
|
23
|
+
for (const extension of extensionsOf(item)) {
|
|
24
|
+
if ((extension as { url?: unknown }).url === url) {
|
|
25
|
+
result.push({ type: 'FHIR.Extension', value: extension })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
function extensionsOf(item: TypedValue): unknown[] {
|
|
34
|
+
const containers: unknown[] = [item.value, item.primitiveElement]
|
|
35
|
+
const result: unknown[] = []
|
|
36
|
+
for (const container of containers) {
|
|
37
|
+
if (typeof container === 'object' && container !== null) {
|
|
38
|
+
const extensions = (container as { extension?: unknown }).extension
|
|
39
|
+
if (Array.isArray(extensions)) {
|
|
40
|
+
result.push(...extensions)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* True when the input holds a single primitive with an actual value; a multi-item
|
|
49
|
+
* input is "not a single value" (FHIR spec wording), so false — not an error.
|
|
50
|
+
*/
|
|
51
|
+
registerFunction('hasValue', {
|
|
52
|
+
minArity: 0,
|
|
53
|
+
maxArity: 0,
|
|
54
|
+
evaluate: (_context, input) => {
|
|
55
|
+
if (input.length === 0) {
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
if (input.length > 1) {
|
|
59
|
+
return wrapBoolean(false)
|
|
60
|
+
}
|
|
61
|
+
const item = input[0] as TypedValue
|
|
62
|
+
return wrapBoolean(
|
|
63
|
+
systemTypeOf(item) !== undefined &&
|
|
64
|
+
item.type !== SYSTEM_QUANTITY &&
|
|
65
|
+
item.value !== undefined &&
|
|
66
|
+
item.value !== null
|
|
67
|
+
)
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
registerFunction('getValue', {
|
|
72
|
+
minArity: 0,
|
|
73
|
+
maxArity: 0,
|
|
74
|
+
evaluate: (_context, input) => {
|
|
75
|
+
if (input.length !== 1) {
|
|
76
|
+
return []
|
|
77
|
+
}
|
|
78
|
+
const item = input[0] as TypedValue
|
|
79
|
+
if (systemTypeOf(item) === undefined || item.value === undefined) {
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
return [{ type: systemTypeOf(item) as string, value: item.value }]
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Resolves contained references and Bundle entries against the evaluation root.
|
|
88
|
+
* External references return empty. A contained reference inside a Bundle entry
|
|
89
|
+
* also returns empty because resolution does not change root for each entry.
|
|
90
|
+
*/
|
|
91
|
+
registerFunction('resolve', {
|
|
92
|
+
minArity: 0,
|
|
93
|
+
maxArity: 0,
|
|
94
|
+
evaluate: (context, input) => {
|
|
95
|
+
const result: TypedValue[] = []
|
|
96
|
+
const scope = context.root[0]?.value as ResolveScope | undefined
|
|
97
|
+
for (const item of input) {
|
|
98
|
+
const reference = referenceStringOf(item)
|
|
99
|
+
if (reference === undefined) {
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
const resolved = resolveReference(reference, scope)
|
|
103
|
+
if (resolved !== undefined) {
|
|
104
|
+
result.push(toTypedValue(resolved))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
function referenceStringOf(item: TypedValue): string | undefined {
|
|
112
|
+
if (typeof item.value === 'string') {
|
|
113
|
+
return item.value
|
|
114
|
+
}
|
|
115
|
+
if (typeof item.value === 'object' && item.value !== null) {
|
|
116
|
+
const reference = (item.value as { reference?: unknown }).reference
|
|
117
|
+
return typeof reference === 'string' ? reference : undefined
|
|
118
|
+
}
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface BundleEntry {
|
|
123
|
+
fullUrl?: unknown
|
|
124
|
+
resource?: { resourceType?: unknown; id?: unknown }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface ResolveScope {
|
|
128
|
+
resourceType?: unknown
|
|
129
|
+
contained?: unknown
|
|
130
|
+
entry?: unknown
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function resolveReference(reference: string, scope: ResolveScope | undefined): unknown {
|
|
134
|
+
if (!scope) {
|
|
135
|
+
return undefined
|
|
136
|
+
}
|
|
137
|
+
if (reference.startsWith('#')) {
|
|
138
|
+
const id = reference.slice(1)
|
|
139
|
+
if (id === '') {
|
|
140
|
+
return scope
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(scope.contained)) {
|
|
143
|
+
return scope.contained.find(resource => (resource as { id?: unknown } | null)?.id === id)
|
|
144
|
+
}
|
|
145
|
+
return undefined
|
|
146
|
+
}
|
|
147
|
+
if (scope.resourceType === 'Bundle' && Array.isArray(scope.entry)) {
|
|
148
|
+
const isAbsolute = reference.includes('://') || reference.startsWith('urn:')
|
|
149
|
+
for (const entry of scope.entry) {
|
|
150
|
+
if (!isObject(entry)) {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
const { fullUrl, resource } = entry as BundleEntry
|
|
154
|
+
// Absolute references match a fullUrl exactly. Relative references
|
|
155
|
+
// (Type/id) match a resource's own type/id — a fullUrl suffix match alone
|
|
156
|
+
// would wrongly resolve Patient/123 against a different base whose resource
|
|
157
|
+
// id is not 123, so the resource must confirm the type/id.
|
|
158
|
+
if (isAbsolute && fullUrl === reference) {
|
|
159
|
+
return resource
|
|
160
|
+
}
|
|
161
|
+
if (resource && `${String(resource.resourceType)}/${String(resource.id)}` === reference) {
|
|
162
|
+
return resource
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return undefined
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
170
|
+
return typeof value === 'object' && value !== null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Validate narrative xhtml against the FHIR rules (real implementation, not a stub). */
|
|
174
|
+
registerFunction('htmlChecks', {
|
|
175
|
+
minArity: 0,
|
|
176
|
+
maxArity: 0,
|
|
177
|
+
evaluate: (_context, input) => {
|
|
178
|
+
const item = singleton(input)
|
|
179
|
+
if (item === undefined) {
|
|
180
|
+
return []
|
|
181
|
+
}
|
|
182
|
+
return wrapBoolean(typeof item.value === 'string' && validateNarrative(item.value))
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
/** Quantities are comparable when their units share a dimension (FHIR R5 addition). */
|
|
187
|
+
registerFunction('comparable', {
|
|
188
|
+
minArity: 1,
|
|
189
|
+
maxArity: 1,
|
|
190
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
191
|
+
const left = singleton(input)
|
|
192
|
+
const right = singleton(evaluateNode(argAt(args, 0), context, input))
|
|
193
|
+
if (left === undefined || right === undefined) {
|
|
194
|
+
return []
|
|
195
|
+
}
|
|
196
|
+
const a = promoteQuantity(left)
|
|
197
|
+
const b = promoteQuantity(right)
|
|
198
|
+
if (!(a && b)) {
|
|
199
|
+
throw new FhirPathTypeError('comparable() expects Quantity operands')
|
|
200
|
+
}
|
|
201
|
+
// Dimensions decide comparability, so calendar durations count as their
|
|
202
|
+
// loose UCUM twins: 1 year.comparable(1 second) is true.
|
|
203
|
+
return wrapBoolean(compareQuantities(calendarToUcumLoose(a), calendarToUcumLoose(b)) !== undefined)
|
|
204
|
+
},
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Base-StructureDefinition conformance only: the url must name a core resource type.
|
|
209
|
+
* Real profile validation is a deferred feature (README register).
|
|
210
|
+
*/
|
|
211
|
+
registerFunction('conformsTo', {
|
|
212
|
+
minArity: 1,
|
|
213
|
+
maxArity: 1,
|
|
214
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
215
|
+
const item = singleton(input)
|
|
216
|
+
if (item === undefined) {
|
|
217
|
+
return []
|
|
218
|
+
}
|
|
219
|
+
const urlValue = singleton(evaluateNode(argAt(args, 0), context, input))
|
|
220
|
+
const url = typeof urlValue?.value === 'string' ? urlValue.value : ''
|
|
221
|
+
const match = /^http:\/\/hl7\.org\/fhir\/StructureDefinition\/([A-Za-z]+)$/.exec(url)
|
|
222
|
+
if (!match) {
|
|
223
|
+
throw new FhirPathRuntimeError(`conformsTo() only supports base StructureDefinition urls, got '${url}'`)
|
|
224
|
+
}
|
|
225
|
+
const resourceType = (item.value as { resourceType?: unknown } | undefined)?.resourceType
|
|
226
|
+
return wrapBoolean(resourceType === match[1])
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
/** Deferred features (see the README register): registered so they fail with a clear message. */
|
|
231
|
+
function unsupported(name: string, minArity: number, maxArity: number, reason: string): void {
|
|
232
|
+
registerFunction(name, {
|
|
233
|
+
minArity,
|
|
234
|
+
maxArity,
|
|
235
|
+
evaluate: () => {
|
|
236
|
+
throw new FhirPathRuntimeError(`${name}() is not supported in v1: ${reason}`)
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
unsupported('memberOf', 1, 1, 'terminology functions need a terminology service')
|
|
242
|
+
unsupported('subsumes', 1, 1, 'terminology functions need a terminology service')
|
|
243
|
+
unsupported('subsumedBy', 1, 1, 'terminology functions need a terminology service')
|
|
244
|
+
unsupported('slice', 2, 2, 'profile slicing needs profile definitions')
|
|
245
|
+
unsupported('elementDefinition', 0, 0, 'element definitions need profile definitions')
|
|
246
|
+
unsupported('checkModifiers', 0, 1, 'modifier checking needs profile definitions')
|
|
247
|
+
unsupported('weight', 0, 0, 'item weights need code-system lookups')
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { pairEquals } from '../engine/operators/equality.ts'
|
|
2
|
+
import { isKnownTypeName, itemMatchesType } from '../engine/type-matching.ts'
|
|
3
|
+
import { FhirPathRuntimeError } from '../errors.ts'
|
|
4
|
+
import { booleanSingleton, singleton, wrapBoolean } from '../values/collection.ts'
|
|
5
|
+
import type { TypedValue } from '../values/typed-value.ts'
|
|
6
|
+
import { perItem } from './iteration.ts'
|
|
7
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
8
|
+
import { typePartsFromArgument } from './type-specifier.ts'
|
|
9
|
+
|
|
10
|
+
registerFunction('where', {
|
|
11
|
+
minArity: 1,
|
|
12
|
+
maxArity: 1,
|
|
13
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
14
|
+
const result: TypedValue[] = []
|
|
15
|
+
perItem(context, input, argAt(args, 0), evaluateNode, (item, criteria) => {
|
|
16
|
+
if (booleanSingleton(criteria) === true) {
|
|
17
|
+
result.push(item)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
return result
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
registerFunction('select', {
|
|
25
|
+
minArity: 1,
|
|
26
|
+
maxArity: 1,
|
|
27
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
28
|
+
const result: TypedValue[] = []
|
|
29
|
+
perItem(context, input, argAt(args, 0), evaluateNode, (_item, projected) => {
|
|
30
|
+
result.push(...projected)
|
|
31
|
+
})
|
|
32
|
+
return result
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
registerFunction('repeat', {
|
|
37
|
+
minArity: 1,
|
|
38
|
+
maxArity: 1,
|
|
39
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
40
|
+
const expression = argAt(args, 0)
|
|
41
|
+
const collected: TypedValue[] = []
|
|
42
|
+
let current = input
|
|
43
|
+
while (current.length > 0) {
|
|
44
|
+
const produced: TypedValue[] = []
|
|
45
|
+
perItem(context, current, expression, evaluateNode, (_item, projected) => {
|
|
46
|
+
produced.push(...projected)
|
|
47
|
+
})
|
|
48
|
+
// Only never-seen items continue the loop (including duplicates produced in
|
|
49
|
+
// the same round), so cyclic data terminates and results stay distinct.
|
|
50
|
+
const fresh: typeof produced = []
|
|
51
|
+
for (const item of produced) {
|
|
52
|
+
if (!collected.some(existing => existing.value === item.value || pairEquals(existing, item) === true)) {
|
|
53
|
+
collected.push(item)
|
|
54
|
+
fresh.push(item)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
current = fresh
|
|
58
|
+
}
|
|
59
|
+
return collected
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/** coalesce(...) — ballot STU: the first argument that evaluates non-empty. */
|
|
64
|
+
registerFunction('coalesce', {
|
|
65
|
+
minArity: 0,
|
|
66
|
+
maxArity: 99,
|
|
67
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
68
|
+
for (const arg of args) {
|
|
69
|
+
const value = evaluateNode(arg, context, input)
|
|
70
|
+
if (value.length > 0) {
|
|
71
|
+
return value
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return []
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
registerFunction('ofType', {
|
|
79
|
+
minArity: 1,
|
|
80
|
+
maxArity: 1,
|
|
81
|
+
evaluate: (context, input, args) => {
|
|
82
|
+
const parts = typePartsFromArgument('ofType', argAt(args, 0))
|
|
83
|
+
requireKnownType(context, 'ofType', parts)
|
|
84
|
+
return input.filter(item => itemMatchesType(context, item, parts, { exact: true }))
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
function requireKnownType(context: Parameters<typeof isKnownTypeName>[0], name: string, parts: string[]): void {
|
|
89
|
+
// Without a model there is no authority on type names; stay lenient.
|
|
90
|
+
if (context.model && !isKnownTypeName(context, parts)) {
|
|
91
|
+
throw new FhirPathRuntimeError(`${name}() received an unknown type name '${parts.join('.')}'`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Deprecated function forms of the `is` and `as` operators (spec §6.3).
|
|
96
|
+
registerFunction('is', {
|
|
97
|
+
minArity: 1,
|
|
98
|
+
maxArity: 1,
|
|
99
|
+
evaluate: (context, input, args) => {
|
|
100
|
+
const item = singleton(input)
|
|
101
|
+
if (item === undefined) {
|
|
102
|
+
return []
|
|
103
|
+
}
|
|
104
|
+
return wrapBoolean(itemMatchesType(context, item, typePartsFromArgument('is', argAt(args, 0))))
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
registerFunction('as', {
|
|
109
|
+
minArity: 1,
|
|
110
|
+
maxArity: 1,
|
|
111
|
+
evaluate: (context, input, args) => {
|
|
112
|
+
const parts = typePartsFromArgument('as', argAt(args, 0))
|
|
113
|
+
requireKnownType(context, 'as', parts)
|
|
114
|
+
const item = singleton(input)
|
|
115
|
+
if (item === undefined) {
|
|
116
|
+
return []
|
|
117
|
+
}
|
|
118
|
+
return itemMatchesType(context, item, parts, { exact: true }) ? [item] : []
|
|
119
|
+
},
|
|
120
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Importing this module registers the function library into the registry table.
|
|
2
|
+
// The evaluator imports it for its side effects; later phases append more modules.
|
|
3
|
+
import './aggregate.ts'
|
|
4
|
+
import './boundary.ts'
|
|
5
|
+
import './combining.ts'
|
|
6
|
+
import './conversion.ts'
|
|
7
|
+
import './date-components.ts'
|
|
8
|
+
import './control.ts'
|
|
9
|
+
import './existence.ts'
|
|
10
|
+
import './fhir.ts'
|
|
11
|
+
import './filtering.ts'
|
|
12
|
+
import './math.ts'
|
|
13
|
+
import './reflection.ts'
|
|
14
|
+
import './sort.ts'
|
|
15
|
+
import './string.ts'
|
|
16
|
+
import './subsetting.ts'
|
|
17
|
+
import './tree.ts'
|
|
18
|
+
import './variables.ts'
|
|
19
|
+
import './utility.ts'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type EvaluationContext, withFrame } from '../engine/context.ts'
|
|
2
|
+
import type { AstNode } from '../parser/ast.ts'
|
|
3
|
+
import type { TypedValue } from '../values/typed-value.ts'
|
|
4
|
+
|
|
5
|
+
export type NodeEvaluator = (node: AstNode, context: EvaluationContext, input: TypedValue[]) => TypedValue[]
|
|
6
|
+
|
|
7
|
+
/** Evaluate `expression` once per item with `$this` and `$index` bound. */
|
|
8
|
+
export function perItem(
|
|
9
|
+
context: EvaluationContext,
|
|
10
|
+
input: TypedValue[],
|
|
11
|
+
expression: AstNode,
|
|
12
|
+
evaluateNode: NodeEvaluator,
|
|
13
|
+
each: (item: TypedValue, result: TypedValue[], index: number) => void
|
|
14
|
+
): void {
|
|
15
|
+
input.forEach((item, index) => {
|
|
16
|
+
const result = withFrame(context, { thisValue: [item], index }, frameContext =>
|
|
17
|
+
evaluateNode(expression, frameContext, [item])
|
|
18
|
+
)
|
|
19
|
+
each(item, result, index)
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { FhirPathTypeError } from '../errors.ts'
|
|
2
|
+
import { singleton } from '../values/collection.ts'
|
|
3
|
+
import { Decimal } from '../values/decimal.ts'
|
|
4
|
+
import { asNumeric, wrapNumeric } from '../values/numeric.ts'
|
|
5
|
+
import {
|
|
6
|
+
type QuantityValue,
|
|
7
|
+
SYSTEM_DECIMAL,
|
|
8
|
+
SYSTEM_INTEGER,
|
|
9
|
+
SYSTEM_QUANTITY,
|
|
10
|
+
type TypedValue,
|
|
11
|
+
} from '../values/typed-value.ts'
|
|
12
|
+
import { argAt, registerFunction } from './registry.ts'
|
|
13
|
+
|
|
14
|
+
function numericInput(name: string, input: TypedValue[]): { item: TypedValue; value: Decimal } | undefined {
|
|
15
|
+
const item = singleton(input)
|
|
16
|
+
if (item === undefined) {
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
const numeric = asNumeric(item)
|
|
20
|
+
if (!numeric) {
|
|
21
|
+
throw new FhirPathTypeError(`${name}() is not defined for ${item.type}`)
|
|
22
|
+
}
|
|
23
|
+
return { item, value: numeric.value }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
registerFunction('abs', {
|
|
27
|
+
minArity: 0,
|
|
28
|
+
maxArity: 0,
|
|
29
|
+
evaluate: (_context, input) => {
|
|
30
|
+
const item = singleton(input)
|
|
31
|
+
if (item === undefined) {
|
|
32
|
+
return []
|
|
33
|
+
}
|
|
34
|
+
if (item.type === SYSTEM_QUANTITY) {
|
|
35
|
+
const quantity = item.value as QuantityValue
|
|
36
|
+
return [{ type: SYSTEM_QUANTITY, value: { ...quantity, value: quantity.value.abs() } }]
|
|
37
|
+
}
|
|
38
|
+
const numeric = asNumeric(item)
|
|
39
|
+
if (!numeric) {
|
|
40
|
+
throw new FhirPathTypeError(`abs() is not defined for ${item.type}`)
|
|
41
|
+
}
|
|
42
|
+
return [wrapNumeric(numeric.value.abs(), numeric.kind)]
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
registerFunction('ceiling', {
|
|
47
|
+
minArity: 0,
|
|
48
|
+
maxArity: 0,
|
|
49
|
+
evaluate: (_context, input) => {
|
|
50
|
+
const numeric = numericInput('ceiling', input)
|
|
51
|
+
if (!numeric) {
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
return [wrapNumeric(numeric.value.ceiling(), 'Integer')]
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
registerFunction('floor', {
|
|
59
|
+
minArity: 0,
|
|
60
|
+
maxArity: 0,
|
|
61
|
+
evaluate: (_context, input) => {
|
|
62
|
+
const numeric = numericInput('floor', input)
|
|
63
|
+
if (!numeric) {
|
|
64
|
+
return []
|
|
65
|
+
}
|
|
66
|
+
return [wrapNumeric(numeric.value.floor(), 'Integer')]
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
registerFunction('truncate', {
|
|
71
|
+
minArity: 0,
|
|
72
|
+
maxArity: 0,
|
|
73
|
+
evaluate: (_context, input) => {
|
|
74
|
+
const numeric = numericInput('truncate', input)
|
|
75
|
+
if (!numeric) {
|
|
76
|
+
return []
|
|
77
|
+
}
|
|
78
|
+
return [wrapNumeric(numeric.value.truncate(), 'Integer')]
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
registerFunction('round', {
|
|
83
|
+
minArity: 0,
|
|
84
|
+
maxArity: 1,
|
|
85
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
86
|
+
const numeric = numericInput('round', input)
|
|
87
|
+
if (!numeric) {
|
|
88
|
+
return []
|
|
89
|
+
}
|
|
90
|
+
let precision = 0
|
|
91
|
+
if (args.length === 1) {
|
|
92
|
+
const argument = singleton(evaluateNode(argAt(args, 0), context, input), SYSTEM_INTEGER)
|
|
93
|
+
if (argument === undefined || typeof argument.value !== 'number' || argument.value < 0) {
|
|
94
|
+
throw new FhirPathTypeError('round() expects a non-negative integer precision')
|
|
95
|
+
}
|
|
96
|
+
precision = argument.value
|
|
97
|
+
}
|
|
98
|
+
return [{ type: SYSTEM_DECIMAL, value: numeric.value.round(precision) }]
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
/** Number-backed transcendental; empty when the JS result is not finite (domain errors). */
|
|
103
|
+
function transcendental(name: string, compute: (value: number) => number): void {
|
|
104
|
+
registerFunction(name, {
|
|
105
|
+
minArity: 0,
|
|
106
|
+
maxArity: 0,
|
|
107
|
+
evaluate: (_context, input) => {
|
|
108
|
+
const numeric = numericInput(name, input)
|
|
109
|
+
if (!numeric) {
|
|
110
|
+
return []
|
|
111
|
+
}
|
|
112
|
+
const result = compute(numeric.value.toNumber())
|
|
113
|
+
const decimal = Decimal.fromNumber(result)
|
|
114
|
+
return decimal === undefined ? [] : [{ type: SYSTEM_DECIMAL, value: decimal }]
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
transcendental('exp', Math.exp)
|
|
120
|
+
transcendental('ln', Math.log)
|
|
121
|
+
transcendental('sqrt', Math.sqrt)
|
|
122
|
+
|
|
123
|
+
registerFunction('log', {
|
|
124
|
+
minArity: 1,
|
|
125
|
+
maxArity: 1,
|
|
126
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
127
|
+
const numeric = numericInput('log', input)
|
|
128
|
+
if (!numeric) {
|
|
129
|
+
return []
|
|
130
|
+
}
|
|
131
|
+
const baseInput = singleton(evaluateNode(argAt(args, 0), context, input))
|
|
132
|
+
const base = baseInput === undefined ? undefined : asNumeric(baseInput)
|
|
133
|
+
if (!base) {
|
|
134
|
+
return []
|
|
135
|
+
}
|
|
136
|
+
const result = Math.log(numeric.value.toNumber()) / Math.log(base.value.toNumber())
|
|
137
|
+
const decimal = Decimal.fromNumber(result)
|
|
138
|
+
// NaN and infinities (log of a non-positive value, base 1) become empty.
|
|
139
|
+
return decimal === undefined ? [] : [{ type: SYSTEM_DECIMAL, value: decimal }]
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
registerFunction('power', {
|
|
144
|
+
minArity: 1,
|
|
145
|
+
maxArity: 1,
|
|
146
|
+
evaluate: (context, input, args, evaluateNode) => {
|
|
147
|
+
const numeric = numericInput('power', input)
|
|
148
|
+
if (!numeric) {
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
151
|
+
const exponentInput = singleton(evaluateNode(argAt(args, 0), context, input))
|
|
152
|
+
const exponent = exponentInput === undefined ? undefined : asNumeric(exponentInput)
|
|
153
|
+
if (!exponent) {
|
|
154
|
+
return []
|
|
155
|
+
}
|
|
156
|
+
const result = numeric.value.toNumber() ** exponent.value.toNumber()
|
|
157
|
+
const decimal = Decimal.fromNumber(result)
|
|
158
|
+
if (decimal === undefined) {
|
|
159
|
+
return []
|
|
160
|
+
}
|
|
161
|
+
// Integer ^ Integer stays an Integer when the result is whole (spec §5.7.7).
|
|
162
|
+
if (numeric.item.type === SYSTEM_INTEGER && exponentInput?.type === SYSTEM_INTEGER && decimal.isInteger()) {
|
|
163
|
+
return [wrapNumeric(decimal, 'Integer')]
|
|
164
|
+
}
|
|
165
|
+
return [{ type: SYSTEM_DECIMAL, value: decimal }]
|
|
166
|
+
},
|
|
167
|
+
})
|