diff --git a/docs/conformance/cql.md b/docs/conformance/cql.md index 6d1b24564..2d8e89517 100644 --- a/docs/conformance/cql.md +++ b/docs/conformance/cql.md @@ -85,7 +85,7 @@ The section numbers refer to the documentation of the [ELM Specification](https: |------|---------------|------------|---------------------------------------------|-------| | 9.1. | ExpressionDef | ✓ | | | | 9.2. | ExpressionRef | ! | only inside same library | | -| 9.3. | FunctionDef | ✗ | | | +| 9.3. | FunctionDef | ✓ | | | | 9.4. | FunctionRef | ! | hard coded implementation of some functions | | ### 10. Queries @@ -322,38 +322,38 @@ The section numbers refer to the documentation of the [ELM Specification](https: ### 22. Type Operators | Num | Group | Expression | State | Notes | -|--------|--------------------|-----------|---------------|-------| -| 22.1. | As | ! | no strictness | | -| 22.2. | CanConvert | ✗ | | | -| 22.3. | CanConvertQuantity | ✓ | | | -| 22.4. | Children | ✓ | | | -| 22.5. | Convert | ✗ | | | -| 22.6. | ConvertQuantity | ✓ | | | -| 22.7. | ConvertsToBoolean | ✓ | | | -| 22.8. | ConvertsToDate | ✓ | | | -| 22.9. | ConvertsToDateTime | ✓ | | | -| 22.10. | ConvertsToDecimal | ✓ | | | -| 22.11. | ConvertsToLong | ✓ | | | -| 22.12. | ConvertsToInteger | ✓ | | | -| 22.13. | ConvertsToQuantity | ✓ | | | -| 22.14. | ConvertsToRatio | ✗ | | | -| 22.15. | ConvertsToString | ✓ | | | -| 22.16. | ConvertsToTime | ✓ | | | -| 22.17. | Descendents | ✓ | | | -| 22.18. | Is | ✗ | | | -| 22.19. | ToBoolean | ✓ | | | -| 22.20. | ToChars | ✓ | | | -| 22.21. | ToConcept | ✗ | | | -| 22.22. | ToDate | ✓ | | | -| 22.23. | ToDateTime | ✓ | | | -| 22.24. | ToDecimal | ✓ | | | -| 22.25. | ToInteger | ✓ | | | -| 22.26. | ToList | ✓ | | | -| 22.27. | ToLong | ✓ | | | -| 22.28. | ToQuantity | ✓ | | | -| 22.29. | ToRatio | ✗ | | | -| 22.30. | ToString | ✓ | | | -| 22.31. | ToTime | ✓ | | | +|--------|--------------------|------------|---------------|-------| +| 22.1. | As | ! | no strictness | | +| 22.2. | CanConvert | ✗ | | | +| 22.3. | CanConvertQuantity | ✓ | | | +| 22.4. | Children | ✓ | | | +| 22.5. | Convert | ✗ | | | +| 22.6. | ConvertQuantity | ✓ | | | +| 22.7. | ConvertsToBoolean | ✓ | | | +| 22.8. | ConvertsToDate | ✓ | | | +| 22.9. | ConvertsToDateTime | ✓ | | | +| 22.10. | ConvertsToDecimal | ✓ | | | +| 22.11. | ConvertsToLong | ✓ | | | +| 22.12. | ConvertsToInteger | ✓ | | | +| 22.13. | ConvertsToQuantity | ✓ | | | +| 22.14. | ConvertsToRatio | ✗ | | | +| 22.15. | ConvertsToString | ✓ | | | +| 22.16. | ConvertsToTime | ✓ | | | +| 22.17. | Descendents | ✓ | | | +| 22.18. | Is | ✗ | | | +| 22.19. | ToBoolean | ✓ | | | +| 22.20. | ToChars | ✓ | | | +| 22.21. | ToConcept | ✗ | | | +| 22.22. | ToDate | ✓ | | | +| 22.23. | ToDateTime | ✓ | | | +| 22.24. | ToDecimal | ✓ | | | +| 22.25. | ToInteger | ✓ | | | +| 22.26. | ToList | ✓ | | | +| 22.27. | ToLong | ✓ | | | +| 22.28. | ToQuantity | ✓ | | | +| 22.29. | ToRatio | ✗ | | | +| 22.30. | ToString | ✓ | | | +| 22.31. | ToTime | ✓ | | | ### 23. Clinical Operators diff --git a/modules/cql/src/blaze/elm/compiler/core.clj b/modules/cql/src/blaze/elm/compiler/core.clj index 0bdc4e32c..6d5521da1 100644 --- a/modules/cql/src/blaze/elm/compiler/core.clj +++ b/modules/cql/src/blaze/elm/compiler/core.clj @@ -41,7 +41,11 @@ (defmulti compile* - "Compiles `expression` in `context`." + "Compiles `expression` in `context`. + + Context consists of: + * :library - the library in it's ELM form + * :node - the database node" {:arglists '([context expression])} (fn [_ {:keys [type] :as expr}] (assert (string? type) (format "Missing :type in expression `%s`." (pr-str expr))) diff --git a/modules/cql/src/blaze/elm/compiler/external_data.clj b/modules/cql/src/blaze/elm/compiler/external_data.clj index e659c46aa..0693b3c5a 100644 --- a/modules/cql/src/blaze/elm/compiler/external_data.clj +++ b/modules/cql/src/blaze/elm/compiler/external_data.clj @@ -18,7 +18,9 @@ (defrecord CompartmentListRetrieveExpression [context data-type] core/Expression (-eval [_ {:keys [db]} {:keys [id]} _] - (d/list-compartment-resource-handles db context id data-type))) + (d/list-compartment-resource-handles db context id data-type)) + (-form [_] + `(~'compartment-list-retrieve ~data-type))) (defrecord CompartmentQueryRetrieveExpression [query data-type clauses] @@ -93,7 +95,9 @@ (defrecord ResourceRetrieveExpression [] core/Expression (-eval [_ _ resource _] - [resource])) + [resource]) + (-form [_] + (list 'retrieve-resource))) (def ^:private resource-expr diff --git a/modules/cql/src/blaze/elm/compiler/function.clj b/modules/cql/src/blaze/elm/compiler/function.clj new file mode 100644 index 000000000..ece770ad5 --- /dev/null +++ b/modules/cql/src/blaze/elm/compiler/function.clj @@ -0,0 +1,12 @@ +(ns blaze.elm.compiler.function + (:require + [blaze.elm.compiler.core :as core])) + + +(defn arity-n [name fn-expr operand-names operands] + (reify core/Expression + (-eval [_ context resource scope] + (let [values (map #(core/-eval % context resource scope) operands)] + (core/-eval fn-expr context resource (merge scope (zipmap operand-names values))))) + (-form [_] + `(~'call ~name ~@(map core/-form operands))))) diff --git a/modules/cql/src/blaze/elm/compiler/library.clj b/modules/cql/src/blaze/elm/compiler/library.clj index f78367791..6b5d5bbc6 100644 --- a/modules/cql/src/blaze/elm/compiler/library.clj +++ b/modules/cql/src/blaze/elm/compiler/library.clj @@ -1,39 +1,57 @@ (ns blaze.elm.compiler.library (:require - [blaze.anomaly :as ba :refer [when-ok]] + [blaze.anomaly :as ba :refer [if-ok when-ok]] [blaze.elm.compiler :as compiler] + [blaze.elm.compiler.function :as function] [blaze.elm.deps-infer :as deps-infer] [blaze.elm.equiv-relationships :as equiv-relationships] [blaze.elm.normalizer :as normalizer])) (defn- compile-expression-def - "Compiles the expression of `expression-def` in `context` and associates the - resulting compiled expression under ::compiler/expression to the - `expression-def` which itself is returned. - - Returns an anomaly on errors." - {:arglists '([context expression-def])} - [context {:keys [expression] :as expression-def}] - (let [context (assoc context :eval-context (:context expression-def))] - (-> (ba/try-anomaly - (assoc expression-def - ::compiler/expression (compiler/compile context expression))) + "Compiles the expression of `def` in `context` and returns a tuple of + `[name compiled-expression]` or an anomaly on errors." + [context {:keys [name expression] :as def}] + (let [context (assoc context :eval-context (:context def))] + (-> (ba/try-anomaly [name (compiler/compile context expression)]) (ba/exceptionally #(assoc % :context context :elm/expression expression))))) -(defn- expr-defs [context library] +(defn- compile-function-def + "Compiles the function of `def` in `context`. + + Returns the compiled function or an anomaly on errors." + [context {:keys [name operand] :as def}] + (when-ok [[_ expression] (compile-expression-def context def)] + (partial function/arity-n name expression (mapv :name operand)))) + + +(defn- compile-function-defs [context library] (transduce - (comp (map (partial compile-expression-def context)) - (halt-when ba/anomaly?)) + (filter (comp #{"FunctionDef"} :type)) (completing - (fn [r {:keys [name] ::compiler/keys [expression]}] - (assoc r name expression))) - {} + (fn [context {:keys [name] :as def}] + (if-ok [function (compile-function-def context def)] + (assoc-in context [:functions name] function) + reduced))) + context (-> library :statements :def))) +(defn- expression-defs [context library] + (when-ok [context (compile-function-defs context library)] + (transduce + (comp (filter (comp nil? :type)) + (map (partial compile-expression-def context)) + (halt-when ba/anomaly?)) + (completing + (fn [r [name expression]] + (assoc r name expression))) + {} + (-> library :statements :def)))) + + (defn- compile-parameter-def "Compiles the default value of `parameter-def` in `context` and associates the resulting compiled default value under :default to the `parameter-def` which @@ -71,7 +89,7 @@ equiv-relationships/find-equiv-rels-library deps-infer/infer-library-deps) context (assoc opts :node node :library library)] - (when-ok [expr-defs (expr-defs context library) + (when-ok [expression-defs (expression-defs context library) parameter-default-values (parameter-default-values context library)] - {:compiled-expression-defs expr-defs + {:compiled-expression-defs expression-defs :parameter-default-values parameter-default-values}))) diff --git a/modules/cql/src/blaze/elm/compiler/queries.clj b/modules/cql/src/blaze/elm/compiler/queries.clj index 1abcfc388..9aa10f81b 100644 --- a/modules/cql/src/blaze/elm/compiler/queries.clj +++ b/modules/cql/src/blaze/elm/compiler/queries.clj @@ -298,18 +298,6 @@ (throw (Exception. (str "Unsupported number of " (count sources) " sources in query."))))) -;; ?.? IdentifierRef -;; -;; The IdentifierRef type defines an expression that references an identifier -;; that is either unresolved, or has been resolved to an attribute in an -;; unambiguous iteration scope such as a sort. Implementations should attempt to -;; resolve the identifier, only throwing an error at compile-time (or run-time -;; for an interpretive system) if the identifier reference cannot be resolved. -(defmethod core/compile* :elm.compiler.type/identifier-ref - [_ {:keys [name]}] - (structured-values/->SingleScopePropertyExpression (keyword name))) - - ;; 10.3. AliasRef ;; ;; The AliasRef expression allows for the reference of a specific source within @@ -336,7 +324,13 @@ (->AliasRefExpression name))) -;; 10.12. With +;; 10.7 IdentifierRef +(defmethod core/compile* :elm.compiler.type/identifier-ref + [_ {:keys [name]}] + (structured-values/->SingleScopePropertyExpression (keyword name))) + + +;; 10.14. With ;; ;; The With clause restricts the elements of a given source to only those ;; elements that have elements in the related source that satisfy the suchThat @@ -386,4 +380,4 @@ (format "Unsupported call without single query scope."))))) -;; TODO 10.13. Without +;; TODO 10.15. Without diff --git a/modules/cql/src/blaze/elm/compiler/reusing_logic.clj b/modules/cql/src/blaze/elm/compiler/reusing_logic.clj index 8100b5678..e3a074302 100644 --- a/modules/cql/src/blaze/elm/compiler/reusing_logic.clj +++ b/modules/cql/src/blaze/elm/compiler/reusing_logic.clj @@ -40,11 +40,17 @@ `(~'expr-ref ~name))) -(defn- find-expression-def - "Returns the expression-def with `name` from `library` or nil if not found." +(defn- find-def + "Returns the def with `name` from `library` or nil if not found." {:arglists '([library name])} - [{{expr-defs :def} :statements} name] - (some #(when (= name (:name %)) %) expr-defs)) + [{{defs :def} :statements} name] + (some #(when (= name (:name %)) %) defs)) + + +(defn- find-expression-def [library name] + (when-let [def (find-def library name)] + (when (nil? (:type def)) + def))) (defn- expression-def-not-found-anom [context name] @@ -157,6 +163,18 @@ `(~'call "ToInterval" ~(core/-form operand)))) +(defn- function-def-not-found-anom [context name] + (ba/incorrect + (format "Function definition `%s` not found." name) + :context context)) + + +(defn compile-function [{:keys [functions] :as context} name operands] + (if-let [function (get functions name)] + (function operands) + (throw-anom (function-def-not-found-anom context name)))) + + ;; 9.4. FunctionRef (defmethod core/compile* :elm.compiler.type/function-ref [context {:keys [name] operands :operand}] @@ -184,4 +202,14 @@ "ToInterval" (->ToIntervalFunctionExpression (first operands)) - (throw (Exception. (str "Unsupported function `" name "` in `FunctionRef` expression.")))))) + (compile-function context name operands)))) + + +;; 9.5 OperandRef +(defmethod core/compile* :elm.compiler.type/operand-ref + [_ {:keys [name]}] + (reify core/Expression + (-eval [_ _ _ scope] + (scope name)) + (-form [_] + `(~'operand-ref ~name)))) diff --git a/modules/cql/src/blaze/elm/compiler/spec.clj b/modules/cql/src/blaze/elm/compiler/spec.clj index 9fab2fc57..896730d14 100644 --- a/modules/cql/src/blaze/elm/compiler/spec.clj +++ b/modules/cql/src/blaze/elm/compiler/spec.clj @@ -10,5 +10,9 @@ core/expr?) +(s/def :blaze.elm.compiler/function + fn?) + + (s/def :elm/compile-context (s/keys :req-un [:elm/library :blaze.db/node])) diff --git a/modules/cql/src/blaze/elm/spec.clj b/modules/cql/src/blaze/elm/spec.clj index 87fa7ffef..61d35e8cf 100644 --- a/modules/cql/src/blaze/elm/spec.clj +++ b/modules/cql/src/blaze/elm/spec.clj @@ -622,8 +622,8 @@ (s/keys :opt-un [:elm/name :elm/libraryName :elm.nary-expression/operand])) -;; ?.? IdentifierRef -(defmethod expression :elm.spec.type/identifier-ref [_] +;; 9.5 OperandRef +(defmethod expression :elm.spec.type/operand-ref [_] (s/keys :opt-un [:elm/name])) @@ -669,14 +669,23 @@ :elm.sort-by-item.by-expression/expression])) -;; 10.9. RelationshipClause +;; 10.7 IdentifierRef +(defmethod expression :elm.spec.type/identifier-ref [_] + (s/keys :req-un [:elm/name] :opt-un [:elm/libraryName])) + + +;; TODO: 10.8. LetClause + +;; TODO 10.9. QueryLetRef + +;; 10.10. RelationshipClause (defmulti relationship-clause :type) (s/def :elm/relationship-clause (s/multi-spec relationship-clause :type)) -;; 10.10. ReturnClause +;; 10.11. ReturnClause (s/def :elm.return-clause/expression :elm/expression) @@ -690,9 +699,9 @@ :opt-un [:elm.return-clause/distinct])) -;; TODO: 10.11. AggregateClause +;; TODO: 10.12. AggregateClause -;; 10.12. SortClause +;; 10.13. SortClause (s/def :elm.sort-clause/by (s/coll-of :elm/sort-by-item :min-count 1)) @@ -701,7 +710,7 @@ (s/keys :req-un [:elm.sort-clause/by])) -;; 10.13. With +;; 10.14. With (defmethod relationship-clause "With" [_] (s/keys :req-un [:elm/expression :elm/alias :elm.query/suchThat])) @@ -711,7 +720,7 @@ :opt-un [:elm.query/suchThat])) -;; 10.14. Without +;; 10.15. Without (defmethod relationship-clause "Without" [_] (s/keys :req-un [:elm/expression :elm/alias :elm.query/suchThat])) diff --git a/modules/cql/test/blaze/elm/compiler/external_data_test.clj b/modules/cql/test/blaze/elm/compiler/external_data_test.clj index d6d38bed8..129077f8f 100644 --- a/modules/cql/test/blaze/elm/compiler/external_data_test.clj +++ b/modules/cql/test/blaze/elm/compiler/external_data_test.clj @@ -75,9 +75,13 @@ db (d/db node) patient (d/resource-handle db "Patient" "0")] - (given (core/-eval expr {:db db} patient nil) - [0 fhir-spec/fhir-type] := :fhir/Patient - [0 :id] := "0")))) + (testing "eval" + (given (core/-eval expr {:db db} patient nil) + [0 fhir-spec/fhir-type] := :fhir/Patient + [0 :id] := "0")) + + (testing "form" + (is (= '(retrieve-resource) (core/-form expr))))))) (testing "Observation" (with-system-data [{:blaze.db/keys [node]} mem-node-system] @@ -94,9 +98,13 @@ db (d/db node) patient (d/resource-handle db "Patient" "0")] - (given (core/-eval expr {:db db} patient nil) - [0 fhir-spec/fhir-type] := :fhir/Observation - [0 :id] := "1"))) + (testing "eval" + (given (core/-eval expr {:db db} patient nil) + [0 fhir-spec/fhir-type] := :fhir/Observation + [0 :id] := "1")) + + (testing "form" + (is (= '(compartment-list-retrieve "Observation") (core/-form expr)))))) (testing "with one code" (with-system-data [{:blaze.db/keys [node]} mem-node-system] @@ -107,10 +115,10 @@ [:put {:fhir/type :fhir/Observation :id "1" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-192253" - :code #fhir/code"code-192300"}]} + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-192300"}]} :subject #fhir/Reference{:reference "Patient/0"}}]]] @@ -123,9 +131,9 @@ [{:name "sys-def-131750" :id "system-192253"}]}}} elm #elm/retrieve - {:type "Observation" - :codes #elm/list [#elm/code ["sys-def-131750" - "code-192300"]]} + {:type "Observation" + :codes #elm/list [#elm/code ["sys-def-131750" + "code-192300"]]} expr (c/compile context elm) db (d/db node) patient (d/resource-handle db "Patient" "0")] @@ -149,19 +157,19 @@ [:put {:fhir/type :fhir/Observation :id "1" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-192253" - :code #fhir/code"code-192300"}]} + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-192300"}]} :subject #fhir/Reference{:reference "Patient/0"}}] [:put {:fhir/type :fhir/Observation :id "2" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-192253" - :code #fhir/code"code-140541"}]} + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-140541"}]} :subject #fhir/Reference{:reference "Patient/0"}}]]] @@ -174,10 +182,10 @@ [{:name "sys-def-131750" :id "system-192253"}]}}} elm #elm/retrieve - {:type "Observation" - :codes - #elm/list [#elm/code ["sys-def-131750" "code-192300"] - #elm/code ["sys-def-131750" "code-140541"]]} + {:type "Observation" + :codes + #elm/list [#elm/code ["sys-def-131750" "code-192300"] + #elm/code ["sys-def-131750" "code-140541"]]} expr (c/compile context elm) db (d/db node) patient (d/resource-handle db "Patient" "0")] @@ -215,10 +223,10 @@ [[[:put {:fhir/type :fhir/Medication :id "0" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-225806" - :code #fhir/code"code-225809"}]}}]]] + {:coding + [#fhir/Coding + {:system #fhir/uri"system-225806" + :code #fhir/code"code-225809"}]}}]]] (let [context {:node node @@ -229,9 +237,9 @@ [{:name "sys-def-225944" :id "system-225806"}]}}} elm #elm/retrieve - {:type "Medication" - :codes #elm/list [#elm/code ["sys-def-225944" - "code-225809"]]} + {:type "Medication" + :codes #elm/list [#elm/code ["sys-def-225944" + "code-225809"]]} expr (c/compile context elm) db (d/db node)] @@ -251,10 +259,10 @@ [{:name "sys-def-225944" :id "system-225806"}]}}} elm #elm/retrieve - {:type "Medication" - :codes #elm/list [#elm/code ["sys-def-225944" - "code-225809"]] - :code-property "foo"}] + {:type "Medication" + :codes #elm/list [#elm/code ["sys-def-225944" + "code-225809"]] + :code-property "foo"}] (given (ba/try-anomaly (c/compile context elm)) ::anom/category := ::anom/not-found @@ -270,10 +278,10 @@ [{:name "name-174207" :resultTypeName "{http://hl7.org/fhir}Patient"}]}} elm #elm/retrieve - {:type "Observation" - :context #elm/expression-ref "name-174207" - :codes #elm/list [#elm/code ["sys-def-174848" - "code-174911"]]} + {:type "Observation" + :context #elm/expression-ref "name-174207" + :codes #elm/list [#elm/code ["sys-def-174848" + "code-174911"]]} expr (c/compile {:node node :library library} elm)] (given expr type := WithRelatedContextQueryRetrieveExpression)))) @@ -287,11 +295,11 @@ [{:name "name-174207" :resultTypeName "{http://hl7.org/fhir}Patient"}]}} elm #elm/retrieve - {:type "Observation" - :context #elm/expression-ref "name-174207" - :codes #elm/list [#elm/code ["sys-def-174848" - "code-174911"]] - :code-property "foo"}] + {:type "Observation" + :context #elm/expression-ref "name-174207" + :codes #elm/list [#elm/code ["sys-def-174848" + "code-174911"]] + :code-property "foo"}] (given (ba/try-anomaly (c/compile {:node node :library library} elm)) ::anom/category := ::anom/not-found ::anom/message := "The search-param with code `foo` and type `Observation` was not found."))))) diff --git a/modules/cql/test/blaze/elm/compiler/library_test.clj b/modules/cql/test/blaze/elm/compiler/library_test.clj index fe5924a4e..8928d6eb2 100644 --- a/modules/cql/test/blaze/elm/compiler/library_test.clj +++ b/modules/cql/test/blaze/elm/compiler/library_test.clj @@ -2,6 +2,7 @@ (:require [blaze.cql-translator :as t] [blaze.db.api-stub :refer [mem-node-system]] + [blaze.elm.compiler :as compiler] [blaze.elm.compiler.library :as library] [blaze.elm.compiler.library-spec] [blaze.fhir.spec.type.system :as system] @@ -37,23 +38,52 @@ (given (library/compile-library node library {}) :compiled-expression-defs := {"Foo" true})))) - (testing "one static expression" + (testing "one dynamic expression" (let [library (t/translate "library Test - using FHIR version '4.0.0' - context Patient - define Gender: Patient.gender")] + using FHIR version '4.0.0' + context Patient + define Gender: Patient.gender")] (with-system [{:blaze.db/keys [node]} mem-node-system] (given (library/compile-library node library {}) - [:compiled-expression-defs "Gender" :key] := :gender - [:compiled-expression-defs "Gender" :source :name] := "Patient")))) + [:compiled-expression-defs "Gender" compiler/form] := '(:gender (expr-ref "Patient")))))) - (testing "with compile-time error" + (testing "one function" (let [library (t/translate "library Test - define Error: singleton from {1, 2}")] + using FHIR version '4.0.0' + context Patient + define function Gender(P Patient): P.gender + define InInitialPopulation: Gender(Patient)")] (with-system [{:blaze.db/keys [node]} mem-node-system] (given (library/compile-library node library {}) - ::anom/category := ::anom/conflict - ::anom/message := "More than one element in `SingletonFrom` expression.")))) + [:compiled-expression-defs "InInitialPopulation" compiler/form] := '(call "Gender" (expr-ref "Patient")))))) + + (testing "two functions, one calling the other" + (let [library (t/translate "library Test + using FHIR version '4.0.0' + context Patient + define function Inc(i System.Integer): i + 1 + define function Inc2(i System.Integer): Inc(i) + 1 + define InInitialPopulation: Inc2(1)")] + (with-system [{:blaze.db/keys [node]} mem-node-system] + (given (library/compile-library node library {}) + [:compiled-expression-defs "InInitialPopulation" compiler/form] := '(call "Inc2" 1))))) + + (testing "with compile-time error" + (testing "function" + (let [library (t/translate "library Test + define function Error(): singleton from {1, 2}")] + (with-system [{:blaze.db/keys [node]} mem-node-system] + (given (library/compile-library node library {}) + ::anom/category := ::anom/conflict + ::anom/message := "More than one element in `SingletonFrom` expression.")))) + + (testing "expression" + (let [library (t/translate "library Test + define Error: singleton from {1, 2}")] + (with-system [{:blaze.db/keys [node]} mem-node-system] + (given (library/compile-library node library {}) + ::anom/category := ::anom/conflict + ::anom/message := "More than one element in `SingletonFrom` expression."))))) (testing "with parameter default" (let [library (t/translate "library Test diff --git a/modules/cql/test/blaze/elm/compiler/queries_test.clj b/modules/cql/test/blaze/elm/compiler/queries_test.clj index 4cbba0382..cee6b06bb 100644 --- a/modules/cql/test/blaze/elm/compiler/queries_test.clj +++ b/modules/cql/test/blaze/elm/compiler/queries_test.clj @@ -232,7 +232,21 @@ ::result)) -;; 10.12. With +;; 10.7 IdentifierRef +;; +;; The IdentifierRef type defines an expression that references an identifier +;; that is either unresolved, or has been resolved to an attribute in an +;; unambiguous iteration scope such as a sort. Implementations should attempt to +;; resolve the identifier, only throwing an error at compile-time (or run-time +;; for an interpretive system) if the identifier reference cannot be resolved. +(deftest compile-identifier-ref-test + (let [expr (c/compile {} {:type "IdentifierRef" :name "foo"})] + + (testing "form" + (is (= '(:foo default) (core/-form expr)))))) + + +;; 10.14. With (deftest compile-with-clause-test (testing "Equiv With with two Observations comparing there subjects." (with-system-data [{:blaze.db/keys [node]} mem-node-system] diff --git a/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj b/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj index e715f2551..0b19d9bbd 100644 --- a/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj +++ b/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj @@ -7,6 +7,7 @@ [blaze.anomaly :as ba] [blaze.elm.compiler :as c] [blaze.elm.compiler.core :as core] + [blaze.elm.compiler.function :as function] [blaze.elm.compiler.test-util :as tu] [blaze.elm.interval :as interval] [blaze.elm.literal :as elm] @@ -66,9 +67,59 @@ ;; The FunctionRef type defines an expression that invokes a previously defined ;; function. The result of evaluating each operand is passed to the function. (deftest compile-function-ref-test + (testing "Throws error on missing function" + (given (ba/try-anomaly (c/compile {} #elm/function-ref ["name-175844"])) + ::anom/category := ::anom/incorrect + ::anom/message := "Function definition `name-175844` not found." + :context := {})) + + (testing "Custom function with arity 0" + (let [function-name "name-210650" + fn-expr (c/compile {} #elm/integer "1") + compile-ctx {:functions {function-name (partial function/arity-n function-name fn-expr [])}} + elm (elm/function-ref [function-name]) + expr (c/compile compile-ctx elm)] + (testing "eval" + (is (= 1 (core/-eval expr {} nil nil)))) + + (testing "form" + (is (= `(~'call ~function-name) (core/-form expr)))))) + + (testing "Custom function with arity 1" + (let [function-name "name-180815" + fn-expr (c/compile {} #elm/negate #elm/operand-ref"x") + compile-ctx {:library {:parameters {:def [{:name "a"}]}} + :functions {function-name (partial function/arity-n function-name fn-expr ["x"])}} + elm (elm/function-ref [function-name #elm/parameter-ref "a"]) + expr (c/compile compile-ctx elm)] + (testing "eval" + (are [a res] (= res (core/-eval expr {:parameters {"a" a}} nil nil)) + 1 -1 + -1 1 + 0 0)) + + (testing "form" + (is (= `(~'call ~function-name (~'param-ref "a")) (core/-form expr)))))) + + (testing "Custom function with arity 2" + (let [function-name "name-184652" + fn-expr (c/compile {} #elm/add [#elm/operand-ref"x" #elm/operand-ref"y"]) + compile-ctx {:library {:parameters {:def [{:name "a"} {:name "b"}]}} + :functions {function-name (partial function/arity-n function-name fn-expr ["x" "y"])}} + elm (elm/function-ref [function-name #elm/parameter-ref "a" #elm/parameter-ref "b"]) + expr (c/compile compile-ctx elm)] + (testing "eval" + (are [a b res] (= res (core/-eval expr {:parameters {"a" a "b" b}} nil nil)) + 1 1 2 + 1 0 1 + 0 1 1)) + + (testing "form" + (is (= `(~'call ~function-name (~'param-ref "a") (~'param-ref "b")) (core/-form expr)))))) + (testing "ToQuantity" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToQuantity" #elm/parameter-ref "x") + elm #elm/function-ref ["ToQuantity" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm)] (testing "eval" (are [x res] (= res (core/-eval expr {:parameters {"x" x}} nil nil)) @@ -81,7 +132,7 @@ (testing "ToDateTime" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToDateTime" #elm/parameter-ref "x") + elm #elm/function-ref ["ToDateTime" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm) eval-ctx (fn [x] {:now tu/now :parameters {"x" x}})] (testing "eval" @@ -98,7 +149,7 @@ (testing "ToString" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToString" #elm/parameter-ref "x") + elm #elm/function-ref ["ToString" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm)] (testing "eval" (are [x res] (= res (core/-eval expr {:parameters {"x" x}} nil nil)) @@ -110,29 +161,38 @@ (testing "ToInterval" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToInterval" #elm/parameter-ref "x") + elm #elm/function-ref ["ToInterval" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm) eval-ctx (fn [x] {:now tu/now :parameters {"x" x}})] (testing "eval" (are [x res] (= res (core/-eval expr (eval-ctx x) nil nil)) #fhir/Period - {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" - :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} + {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" + :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} (interval/interval (system/date-time 2021 2 23 14 12 45) (system/date-time 2021 2 23 15 0 0)) #fhir/Period - {:start nil - :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} + {:start nil + :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} (interval/interval nil (system/date-time 2021 2 23 15 0 0)) #fhir/Period - {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" - :end nil} + {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" + :end nil} (interval/interval (system/date-time 2021 2 23 14 12 45) nil))) (testing "form" (is (= '(call "ToInterval" (param-ref "x")) (core/-form expr))))))) + + +;; 9.5 OperandRef +;; +;; The OperandRef expression allows the value of an operand to be referenced as +;; part of an expression within the body of a function definition. +(deftest compile-operand-ref-test + (testing "form" + (is (= '(operand-ref "x") (core/-form (c/compile {} #elm/operand-ref"x")))))) diff --git a/modules/cql/test/blaze/elm/literal.clj b/modules/cql/test/blaze/elm/literal.clj index 04f3f769e..cc3f9635d 100644 --- a/modules/cql/test/blaze/elm/literal.clj +++ b/modules/cql/test/blaze/elm/literal.clj @@ -110,10 +110,16 @@ ;; 9.4. FunctionRef -(defn function-ref [name & ops] +(defn function-ref [[name & ops]] {:type "FunctionRef" :name name - :operand ops}) + :operand (vec ops)}) + + +;; 9.5. OperandRef +(defn operand-ref [name] + {:type "OperandRef" + :name name}) diff --git a/modules/cql/test/data_readers.clj b/modules/cql/test/data_readers.clj index 750db50ac..33ef2a785 100644 --- a/modules/cql/test/data_readers.clj +++ b/modules/cql/test/data_readers.clj @@ -10,6 +10,8 @@ elm/quantity blaze.elm.literal/quantity elm/parameter-ref blaze.elm.literal/parameter-ref elm/expression-ref blaze.elm.literal/expression-ref + elm/function-ref blaze.elm.literal/function-ref + elm/operand-ref blaze.elm.literal/operand-ref elm/retrieve blaze.elm.literal/retrieve elm/equal blaze.elm.literal/equal elm/equivalent blaze.elm.literal/equivalent diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num-query.cql index c06674931..d4f62ff66 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num-query.cql @@ -4,12 +4,11 @@ include FHIRHelpers version '4.0.0' context Patient +define function IntegerParts(s String): + from (Split(s, '-')) S where ConvertsToInteger(S) + define SocialSecurityNumber: First(Patient.identifier.where(system = 'http://hl7.org/fhir/sid/us-ssn').value) -define IntegerParts: - from (Split(SocialSecurityNumber, '-')) S - where ConvertsToInteger(S) - define InInitialPopulation: - Count(IntegerParts) = 3 + Count(IntegerParts(SocialSecurityNumber)) = 3