diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bc23aae --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# How to contribute + +We'd love to accept your patches and contributions to this project. + +## Before you begin + +### Sign our Contributor License Agreement + +Contributions to this project must be accompanied by a +[Contributor License Agreement](https://cla.developers.google.com/about) (CLA). +You (or your employer) retain the copyright to your contribution; this simply +gives us permission to use and redistribute your contributions as part of the +project. + +If you or your current employer have already signed the Google CLA (even if it +was for a different project), you probably don't need to do it again. + +Visit to see your current agreements or to +sign a new one. + +### Review our community guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). + +## Contribution process + +### Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e94f86c --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# Experimental Clinical Quality Language Engine + +A work in progress CQL execution engine for analyzing FHIR healthcare data at +scale. + +CQL is a domain specific language designed for querying and executing logic on +healthcare data. CQL excels at healthcare data analysis such as defining quality +measures, clinical decision support, cohorts or preparing data for dashboards. +CQL is designed for healthcare with first class support for terminologies, easy +querying of FHIR via FHIRPath, graceful handling of mixed precision or missing +data and built in clinical helper functions. You can find an intro to the CQL +Language at https://cql.hl7.org. + +## Features + +Notable features of this engine include: + +- Built in explainability - for each CQL expression definition we produce a tree +that traces through the data and expressions involved in calculating the final +result +- Built in custom CQL parser, reducing project dependencies and allowing +optimizations between the parser and interpreter +- Benchmarked and optimized to be fast and memory efficient + +In addition to the engine this repository has several tools that make it easy to +launch and productionize CQL: + +- A scalable Beam job for running CQL over large patient populations +- A CLI for easy configuration +- Integration into Google Cloud Storage + +## Limitations + +This CQL Engine is **experimental** and not an officially supported Google +Product. The API to call the engine and the format of the results returned are +subject to change. There is limited support of certain parts of the CQL +Language: + +- Only the FHIR version 4.0.1 data model is supported +- Only the Patient Context is supported +- Not all system operators are supported +- No support for Quantities with UCUM units +- No support for Interval/List Promotion and Demotion +- No support for related context retrieves +- No support for uncertainties +- No support for importing or exporting ELM + +## Getting Started + +There are several different ways to use this engine. For quick experimentation +we have a Command Line Interface and REPL. For executing CQL over a large +patient population there is a Beam job. Finally, the CQL Golang Module allows +you to execute CQL by implementing your own connector to a database and +terminology server. + +> ⚠️⚠️ **Warning** ⚠️⚠️ +> +> When using these tools with protected health information (PHI), please be sure +to follow your organization's policies with respect to PHI. + +## CLI + +When intending to run the CQL engine locally over small populations or for quick +experimentation use the CLI located at [cmd/cli](cmd/cli). For documentation +and examples see [cmd/cli/README.md](cmd/cli/README.md). + +## Apache Beam Pipeline + +The Beam pipeline is recommended when running CQL over large patient populations. +More information and usage examples are documented at +[beam/README.md](beam/README.md). + +## Golang Module + +The engine can be used via the CQL golang module documented in the +[godoc](https://pkg.go.dev/github.com/google/cql). +The [Retriever interface](retriever/retriever.go) can be implemented to connect +to a custom database or FHIR server. The +[Terminology Provider interface](terminology/provider.go) can be implemented to +connect to a custom Terminology server. + +## REPL + +For quick experiments with our CQL Engine we have a REPL. More information and +usage examples are documented at [cmd/repl/README.md](cmd/repl/README.md). + +## Documentation + +If you are interested in how this engine was implemented see +[docs/implementation.md](docs/implementation.md) for an overview of the +codebase. diff --git a/beam/README.md b/beam/README.md new file mode 100644 index 0000000..f4dd391 --- /dev/null +++ b/beam/README.md @@ -0,0 +1,74 @@ +# CQL on Beam + +The CQL on Beam pipeline is designed for running CQL on large patient +populations. Apache Beam is an open source, unified model for defining both +batch and streaming pipelines. Using the Apache Beam SDKs, you build a program +that defines the pipeline. Then, you execute the pipeline on a specific platform +such as Google Cloud's Dataflow. Apache Beam insulates you from the low-level +details of distributed processing, such as coordinating individual workers, +sharding datasets, and other such tasks. See these +[docs](https://cloud.google.com/dataflow/docs/concepts/beam-programming-model) +for more information on Apache Beam. + +## Running + +The CQL on Beam pipeline is currently limited to local file system IO. The +pipeline can read FHIR bundles and output NDJSON CQL results to the file system. +Future work will add more IO options, namely reading from FHIR Store and +outputting to BigQuery. Once those IOs are complete the CQL on Beam pipeline can +be run on [Google Cloud's Dataflow](https://cloud.google.com/dataflow/docs/quickstarts/create-pipeline-go). + +To build the program from source run the following from the root of the +repository (note you must have [Go](https://go.dev/dl/) installed): + +```bash +cd beam +go build -o beam main.go +``` + +This will build the `beam` binary and write it out in your current directory. You +can then run the binary: + +```bash +./beam \ + -cql_dir="path/to/cql/dir/" \ + -fhir_bundle_dir="path/to/bundle/dir/" \ + -fhir_terminology_dir="path/to/terminology/dir/" \ + -ndjson_output_dir="path/to/output/" +``` + +## Flags + +**--cql_dir** Required. The path to a directory containing one or more CQL +files. The engine only reads files ending in a `.cql` suffix. ELM inputs are +not currently supported. + +**--evaluation_timestamp** The timestamp to use for evaluating CQL. If not +provided EvaluationTimestamp will default to time.Now() called at the start of +the pipeline. + +Example: + +```bash +--evaluation_timestamp="@2018-02-02T15:02:03.000-04:00" +``` + +**--fhir_bundle_dir** Required. The path containing one or more FHIR bundles. +Each file should have one FHIR Bundle containing all of the FHIR resources for a +particular patient. + +**--fhir_terminology_dir** Optional. The path to a directory containing json +definitions of FHIR ValueSets. + +**--ndjson_output_dir** Required. Output directory that the CQL results will be +written to. The results for each patient are converted to JSON and written as a +line in the NDJSON. + +**--return_private_defs** If true will include the output of all private CQL +expression definitions. By default only public definitions are outputted. + + + + + + diff --git a/beam/main.go b/beam/main.go new file mode 100644 index 0000000..6231030 --- /dev/null +++ b/beam/main.go @@ -0,0 +1,189 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Beam pipeline for computing CQL at scale. +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + log "github.com/golang/glog" + "github.com/apache/beam/sdks/v2/go/pkg/beam/x/beamx" + "github.com/google/cql/beam/transforms" + "github.com/apache/beam/sdks/v2/go/pkg/beam" + + // The following import is required for accessing local files. + "github.com/apache/beam/sdks/v2/go/pkg/beam/io/fileio" + _ "github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/local" + "github.com/apache/beam/sdks/v2/go/pkg/beam/io/textio" +) + +// TODO(b/317813865): Add input and output options as needed, such as FHIR Store or NDJSON inputs +// and BigQuery outputs. + +// flags holds the values of the flags largely to assist in easier testing without having to change +// global variables. +type beamFlags struct { + CQLDir string + FHIRBundleDir string + FHIRTerminologyDir string + EvaluationTimestamp string + ReturnPrivateDefs bool + NDJSONOutputDir string +} + +var flags beamFlags + +func init() { + flag.StringVar(&flags.CQLDir, "cql_dir", "", "(Required) Directory holding one or more CQL files.") + flag.StringVar(&flags.FHIRBundleDir, "fhir_bundle_dir", "", "(Required) Directory holding FHIR Bundle JSON files, which are used to create a retriever for the CQL engine.") + flag.StringVar(&flags.FHIRTerminologyDir, "fhir_terminology_dir", "", "(Optional) Directory holding FHIR Valueset JSONs, which are used to create a terminology provider for the CQL engine.") + flag.StringVar(&flags.EvaluationTimestamp, "evaluation_timestamp", "", "(Optional) The timestamp to use for evaluating CQL. If not provided EvaluationTimestamp will default to time.Now() called at the start of the eval request.") + flag.BoolVar(&flags.ReturnPrivateDefs, "return_private_defs", false, "(Optional) If true will include the output of all private CQL expression definitions. By default only public definitions are outputted.") + // TODO b/339070720: Add CQL parameters. + flag.StringVar(&flags.NDJSONOutputDir, "ndjson_output_dir", "", "(Required) Output directory that the NDJSON files will be written to.") +} + +// pipelineConfig holds the validated configuration for the pipeline. +type pipelineConfig struct { + // TODO: b/339070720 - Instead of parsing on each worker, if we could serialize the cql.ELM struct + // we could parse once before execution and pass it to each worker. + CQL []string + FHIRBundleDir string + ValueSets []string + EvaluationTimestamp time.Time + ReturnPrivateDefs bool + NDJSONOutputDir string +} + +func buildPipelineConfig(flags *beamFlags) (*pipelineConfig, error) { + if flags == nil { + return nil, fmt.Errorf("flags must not be nil") + } + + cfg := &pipelineConfig{ + FHIRBundleDir: flags.FHIRBundleDir, + ReturnPrivateDefs: flags.ReturnPrivateDefs, + NDJSONOutputDir: flags.NDJSONOutputDir, + } + + if flags.EvaluationTimestamp != "" { + var err error + cfg.EvaluationTimestamp, err = time.Parse(time.RFC3339, flags.EvaluationTimestamp) + if err != nil { + return nil, fmt.Errorf("evaluation_timestamp must be in RFC3339 format: %v", err) + } + } else { + cfg.EvaluationTimestamp = time.Now() + } + + if flags.CQLDir == "" { + return nil, fmt.Errorf("cql_dir must be set") + } + if flags.FHIRBundleDir == "" { + return nil, fmt.Errorf("fhir_bundle_dir must be set") + } + if flags.NDJSONOutputDir == "" { + return nil, fmt.Errorf("ndjson_output_dir must be set") + } + + var err error + cfg.CQL, err = readFilesWithSuffix(flags.CQLDir, ".cql") + if err != nil { + return nil, err + } + if len(cfg.CQL) == 0 { + return nil, fmt.Errorf("must be at least one CQL file") + } + + cfg.ValueSets, err = readFilesWithSuffix(flags.FHIRTerminologyDir, ".json") + if err != nil { + return nil, err + } + + return cfg, nil +} + +// readFilesWithSuffix reads all files from a directory with the given suffix. +func readFilesWithSuffix(dir, allowedFileSuffix string) ([]string, error) { + if dir == "" { + return nil, nil + } + files, err := os.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf("failed to read directory %s: %w", dir, err) + } + strs := make([]string, 0, len(files)) + for _, file := range files { + if file.IsDir() || !strings.HasSuffix(file.Name(), allowedFileSuffix) { + continue + } + bytes, err := os.ReadFile(filepath.Join(dir, file.Name())) + if err != nil { + return nil, fmt.Errorf("failed to read file %s: %w", filepath.Join(dir, file.Name()), err) + } + strs = append(strs, string(bytes)) + } + return strs, nil +} + +// buildPipeline uses the config to construct the pipeline. Results and errors are returned for +// tests. +func buildPipeline(s beam.Scope, cfg *pipelineConfig) (results, errors beam.PCollection) { + matches := fileio.MatchFiles(s, filepath.Join(cfg.FHIRBundleDir, "*.json")) + files := fileio.ReadMatches(s, matches) + bundles, loadErrors := beam.ParDo2(s, transforms.FileToBundle, files) + + var evalErrors beam.PCollection + fn := &transforms.CQLEvalFn{ + CQL: cfg.CQL, + ValueSets: cfg.ValueSets, + EvaluationTimestamp: cfg.EvaluationTimestamp, + ReturnPrivateDefs: cfg.ReturnPrivateDefs, + } + results, evalErrors = beam.ParDo2(s, fn, bundles) + + ndjsonRows, writeErrors := beam.ParDo2(s, transforms.NDJSONSink, results) + // TODO: b/339070720: Shard the output files. + textio.Write(s, filepath.Join(cfg.NDJSONOutputDir, "results.ndjson"), ndjsonRows) + + errors = beam.Flatten(s, loadErrors, evalErrors, writeErrors) + errorRows := beam.ParDo(s, transforms.ErrorsNDJSONSink, errors) + textio.Write(s, filepath.Join(cfg.NDJSONOutputDir, "errors.ndjson"), errorRows) + + return results, errors +} + +func main() { + flag.Parse() + beam.Init() + + cfg, err := buildPipelineConfig(&flags) + if err != nil { + log.Exit(err) + } + + p, s := beam.NewPipelineWithRoot() + _, _ = buildPipeline(s, cfg) + + if err := beamx.Run(context.Background(), p); err != nil { + log.Exitf("Failed to execute job: %v", err) + } +} diff --git a/beam/main_test.go b/beam/main_test.go new file mode 100644 index 0000000..1484806 --- /dev/null +++ b/beam/main_test.go @@ -0,0 +1,402 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + cbpb "github.com/google/cql/protos/cql_beam_go_proto" + crpb "github.com/google/cql/protos/cql_result_go_proto" + "github.com/apache/beam/sdks/v2/go/pkg/beam" + "github.com/apache/beam/sdks/v2/go/pkg/beam/testing/ptest" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestPipeline(t *testing.T) { + _, _, fhirBundleDir := directorySetup(t, cqlLibs, valueSets, fhirBundles) + + tests := []struct { + name string + cfg *pipelineConfig + wantOutput []*cbpb.BeamResult + wantError []*cbpb.BeamError + }{ + { + name: "Successful CQL Eval", + cfg: &pipelineConfig{ + CQL: []string{dedent.Dedent( + `library EvalTest version '1.0' + using FHIR version '4.0.1' + valueset "DiabetesVS": 'https://example.com/vs/glucose' + define HasDiabetes: exists([Condition: "DiabetesVS"]) + `, + )}, + ValueSets: valueSets, + FHIRBundleDir: fhirBundleDir, + NDJSONOutputDir: t.TempDir(), + EvaluationTimestamp: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + wantOutput: []*cbpb.BeamResult{ + &cbpb.BeamResult{ + Id: proto.String("1"), + EvaluationTimestamp: timestamppb.New(time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)), + Result: &crpb.Libraries{ + Libraries: []*crpb.Library{ + &crpb.Library{ + Name: proto.String("BeamMetadata"), + Version: proto.String("1.0.0"), + ExprDefs: map[string]*crpb.Value{ + "ID": &crpb.Value{ + Value: &crpb.Value_StringValue{StringValue: "1"}, + }, + }, + }, + &crpb.Library{ + Name: proto.String("EvalTest"), + Version: proto.String("1.0"), + ExprDefs: map[string]*crpb.Value{ + "HasDiabetes": &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: true}, + }, + "DiabetesVS": &crpb.Value{ + Value: &crpb.Value_ValueSetValue{ + ValueSetValue: &crpb.ValueSet{Id: proto.String("https://example.com/vs/glucose"), Version: proto.String("")}, + }, + }, + }, + }, + }, + }, + }, + }, + wantError: []*cbpb.BeamError{}, + }, + { + name: "CQL Eval Error", + cfg: &pipelineConfig{ + CQL: []string{dedent.Dedent( + `library "EvalTest" version '1.0' + using FHIR version '4.0.1' + valueset "DiabetesVS": 'urn:example:nosuchvalueset' + define HasDiabetes: exists([Condition: "DiabetesVS"])`)}, + ValueSets: valueSets, + FHIRBundleDir: fhirBundleDir, + NDJSONOutputDir: t.TempDir(), + EvaluationTimestamp: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + wantOutput: []*cbpb.BeamResult{}, + wantError: []*cbpb.BeamError{ + &cbpb.BeamError{ + ErrorMessage: proto.String("failed during CQL evaluation: EvalTest 1.0, could not find ValueSet{urn:example:nosuchvalueset, } resource not loaded"), + SourceUri: proto.String("bundle:bundle1"), + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + p, s := beam.NewPipelineWithRoot() + result, errors := buildPipeline(s, test.cfg) + beam.ParDo0(s, diffEvalResults, beam.Impulse(s), beam.SideInput{Input: beam.CreateList(s, test.wantOutput)}, beam.SideInput{Input: result}) + beam.ParDo0(s, diffEvalErrors, beam.Impulse(s), beam.SideInput{Input: beam.CreateList(s, test.wantError)}, beam.SideInput{Input: errors}) + if err := ptest.Run(p); err != nil { + t.Fatal(err) + } + }) + } +} + +func diffEvalResults(_ []byte, iterWant, iterGot func(**cbpb.BeamResult) bool) error { + var got, want []*cbpb.BeamResult + var v *cbpb.BeamResult + for iterGot(&v) { + got = append(got, v) + } + for iterWant(&v) { + want = append(want, v) + } + + sortOutputs := func(a, b *cbpb.BeamResult) bool { + if a.GetEvaluationTimestamp().GetSeconds() != b.GetEvaluationTimestamp().GetSeconds() { + return a.GetEvaluationTimestamp().GetSeconds() < b.GetEvaluationTimestamp().GetSeconds() + } + return false + } + + if diff := cmp.Diff(want, got, cmpopts.SortSlices(sortOutputs), protocmp.Transform(), protocmp.SortRepeatedFields(&crpb.Libraries{}, "libraries")); diff != "" { + return fmt.Errorf("BeamResult unexpected differences (-want +got):\n%s", diff) + } + return nil +} + +func diffEvalErrors(_ []byte, iterWant, iterGot func(**cbpb.BeamError) bool) error { + var got, want []*cbpb.BeamError + var v *cbpb.BeamError + for iterGot(&v) { + got = append(got, v) + } + for iterWant(&v) { + want = append(want, v) + } + + if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { + return fmt.Errorf("BeamError unexpected differences (-want +got):\n%s", diff) + } + return nil +} + +func TestBuildConfig(t *testing.T) { + cqlDir, terminologyDir, _ := directorySetup(t, cqlLibs, valueSets, fhirBundles) + + tests := []struct { + name string + flags *beamFlags + want *pipelineConfig + }{ + { + name: "with evaluation timestamp", + flags: &beamFlags{ + CQLDir: cqlDir, + FHIRTerminologyDir: terminologyDir, + FHIRBundleDir: "fhirBundleDir", + EvaluationTimestamp: "2024-01-01T00:00:00Z", + ReturnPrivateDefs: true, + NDJSONOutputDir: "ndjsonOutputDir", + }, + want: &pipelineConfig{ + CQL: cqlLibs, + ValueSets: valueSets, + FHIRBundleDir: "fhirBundleDir", + EvaluationTimestamp: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + ReturnPrivateDefs: true, + NDJSONOutputDir: "ndjsonOutputDir", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := buildPipelineConfig(test.flags) + if err != nil { + t.Fatalf("buildConfig() failed: %v", err) + } + if diff := cmp.Diff(got, test.want); diff != "" { + t.Errorf("buildConfig() unexpected diff (-got +want):\n %s", diff) + } + }) + } +} + +func TestBuildConfig_Failure(t *testing.T) { + cqlDir, _, fhirBundleDir := directorySetup(t, cqlLibs, valueSets, fhirBundles) + + tests := []struct { + name string + flags *beamFlags + wantError string + }{ + { + name: "cql_dir not set", + flags: &beamFlags{}, + wantError: "cql_dir must be set", + }, + { + name: "fhir_bundle_dir not set", + flags: &beamFlags{ + CQLDir: cqlDir, + }, + wantError: "fhir_bundle_dir must be set", + }, + { + name: "ndjson_output_dir not set", + flags: &beamFlags{ + CQLDir: cqlDir, + FHIRBundleDir: fhirBundleDir, + }, + wantError: "ndjson_output_dir must be set", + }, + { + name: "invalid cql_dir", + flags: &beamFlags{ + CQLDir: "baddir", + FHIRBundleDir: fhirBundleDir, + NDJSONOutputDir: "output", + }, + wantError: "failed to read directory baddir", + }, + { + name: "invalid terminology_dir", + flags: &beamFlags{ + CQLDir: cqlDir, + FHIRBundleDir: fhirBundleDir, + NDJSONOutputDir: "output", + FHIRTerminologyDir: "baddir", + }, + wantError: "failed to read directory baddir", + }, + { + name: "invalid evaluation timestamp", + flags: &beamFlags{ + EvaluationTimestamp: "invalid", + }, + wantError: "evaluation_timestamp must be in RFC3339 format", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := buildPipelineConfig(test.flags) + if err == nil { + t.Fatalf("buildConfig() succeeded, want error") + } + if !strings.Contains(err.Error(), test.wantError) { + t.Errorf("Unexpected error contents (%v) want (%v)", err.Error(), test.wantError) + } + }) + } +} + +func directorySetup(t *testing.T, cqlLibs []string, valueSets []string, fhirBundles []string) (string, string, string) { + t.Helper() + tempDir := t.TempDir() + + cqlDir := filepath.Join(tempDir, "cqlDir") + err := os.Mkdir(cqlDir, 0755) + if err != nil { + t.Fatalf("Failed to create directory %s: %v", cqlDir, err) + } + for i, cql := range cqlLibs { + err = os.WriteFile(filepath.Join(cqlDir, fmt.Sprintf("cql-%d.cql", i)), []byte(cql), 0644) + if err != nil { + t.Fatalf("Failed to write file %s: %v", filepath.Join(cqlDir, cql), err) + } + } + // Write a non .CQL file to test that we don't read it. + err = os.WriteFile(filepath.Join(cqlDir, "not-cql.txt"), []byte("Should not read this"), 0644) + if err != nil { + t.Fatalf("Failed to write file %s: %v", filepath.Join(cqlDir, "not-cql.txt"), err) + } + + terminologyDir := filepath.Join(tempDir, "terminologyDir") + err = os.Mkdir(terminologyDir, 0755) + if err != nil { + t.Fatalf("Failed to create directory %s: %v", terminologyDir, err) + } + for i, vs := range valueSets { + err = os.WriteFile(filepath.Join(terminologyDir, fmt.Sprintf("vs-%d.json", i)), []byte(vs), 0644) + if err != nil { + t.Fatalf("Failed to write file %s: %v", filepath.Join(terminologyDir, vs), err) + } + } + // Write a non .json file to test that we don't read it. + err = os.WriteFile(filepath.Join(terminologyDir, "not-json.txt"), []byte("Should not read this"), 0644) + if err != nil { + t.Fatalf("Failed to write file %s: %v", filepath.Join(terminologyDir, "not-json.txt"), err) + } + + fhirBundleDir := filepath.Join(tempDir, "fhirBundleDir") + err = os.Mkdir(fhirBundleDir, 0755) + if err != nil { + t.Fatalf("Failed to create directory %s: %v", fhirBundleDir, err) + } + for i, fb := range fhirBundles { + err = os.WriteFile(filepath.Join(fhirBundleDir, fmt.Sprintf("bundle-%d.json", i)), []byte(fb), 0644) + if err != nil { + t.Fatalf("Failed to write file %s: %v", filepath.Join(fhirBundleDir, fb), err) + } + } + // Write a non .json file to test that we don't read it. + err = os.WriteFile(filepath.Join(fhirBundleDir, "not-json.txt"), []byte("Should not read this"), 0644) + if err != nil { + t.Fatalf("Failed to write file %s: %v", filepath.Join(fhirBundleDir, "not-json.txt"), err) + } + + return cqlDir, terminologyDir, fhirBundleDir +} + +var cqlLibs = []string{ + `library lib1`, + `library lib2`, +} + +var valueSets = []string{ + `{ + "resourceType": "ValueSet", + "url": "https://example.com/vs/blood_pressure", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "https://example.com/system", "code": "12345" } + ] + } + }`, + `{ + "resourceType": "ValueSet", + "url": "https://example.com/vs/glucose", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "https://example.com/system", "code": "54321" } + ] + } + }`, +} + +var fhirBundles = []string{ + `{ + "resourceType": "Bundle", + "id": "bundle1", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "1" + } + }, + { + "resource": { + "resourceType": "Condition", + "id": "1", + "code": { + "coding": [{ "system": "https://example.com/system", "code": "12345", "display": "Hypertension"}] + }, + "onsetDateTime" : "2023-10-01" + } + }, + { + "resource": { + "resourceType": "Condition", + "id": "2", + "code": { + "coding": [{ "system": "https://example.com/system", "code": "54321", "display": "Diabetes"}] + }, + "onsetDateTime" : "2024-01-02" + } + } + ] + }`, +} + +func TestMain(m *testing.M) { + ptest.MainWithDefault(m, "direct") +} diff --git a/beam/transforms/eval.go b/beam/transforms/eval.go new file mode 100644 index 0000000..3c91654 --- /dev/null +++ b/beam/transforms/eval.go @@ -0,0 +1,144 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transforms + +import ( + "context" + "reflect" + "time" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + "github.com/google/cql" + cbpb "github.com/google/cql/protos/cql_beam_go_proto" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/google/cql/terminology" + bpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + "github.com/apache/beam/sdks/v2/go/pkg/beam" + "github.com/apache/beam/sdks/v2/go/pkg/beam/register" + "google.golang.org/protobuf/proto" +) + +const counterPrefix = "beam_cql" + +var ( + bundleCount = beam.NewCounter(counterPrefix, "fhir_bundles") + bundleErrorCount = beam.NewCounter(counterPrefix, "fhir_bundle_read_errors") + eventCount = beam.NewCounter(counterPrefix, "events") + errCount = beam.NewCounter(counterPrefix, "errors") +) + +func init() { + register.DoFn4x1[context.Context, *bpb.Bundle, func(*cbpb.BeamResult), func(*cbpb.BeamError), error](&CQLEvalFn{}) + beam.RegisterType(reflect.TypeOf((*cbpb.BeamResult)(nil))) + beam.RegisterType(reflect.TypeOf((*cbpb.BeamError)(nil))) +} + +// BeamMetadata produces the ID when running the CQL. +const BeamMetadata = ` +library BeamMetadata version '1.0.0' +using FHIR version '4.0.1' +context Patient +define ID: Patient.id.value +` + +// CQLEvalFn is a DoFn that parses and evaluates CQL. +type CQLEvalFn struct { + // Only exported fields are serialized. + CQL []string + ValueSets []string + EvaluationTimestamp time.Time + ReturnPrivateDefs bool + elm *cql.ELM + terminology terminology.Provider +} + +// Setup parses the CQL and initializes the terminology provider. +func (fn *CQLEvalFn) Setup() error { + var err error + fhirDM, err := cql.FHIRDataModel("4.0.1") + if err != nil { + return err + } + fn.elm, err = cql.Parse(context.Background(), append(fn.CQL, BeamMetadata), cql.ParseConfig{DataModels: [][]byte{fhirDM}}) + if err != nil { + return err + } + fn.terminology, err = terminology.NewInMemoryFHIRProvider(fn.ValueSets) + return err +} + +func (fn *CQLEvalFn) ProcessElement(ctx context.Context, bundle *bpb.Bundle, emit func(*cbpb.BeamResult), emitError func(*cbpb.BeamError)) error { + retriever, err := local.NewRetrieverFromR4BundleProto(bundle) + if err != nil { + errCount.Inc(ctx, 1) + emitError(&cbpb.BeamError{ + ErrorMessage: proto.String(err.Error()), + SourceUri: proto.String(sourceURI(bundle)), + }) + return err + } + + res, err := fn.elm.Eval(ctx, retriever, cql.EvalConfig{Terminology: fn.terminology, EvaluationTimestamp: fn.EvaluationTimestamp, ReturnPrivateDefs: fn.ReturnPrivateDefs}) + if err != nil { + errCount.Inc(ctx, 1) + emitError(&cbpb.BeamError{ + ErrorMessage: proto.String(err.Error()), + SourceUri: proto.String(sourceURI(bundle)), + }) + return nil + } + + var patientID string + p := res[result.LibKey{Name: "BeamMetadata", Version: "1.0.0"}]["ID"] + if !result.IsNull(p) { + patientID, err = result.ToString(p) + if err != nil { + errCount.Inc(ctx, 1) + emitError(&cbpb.BeamError{ + ErrorMessage: proto.String(err.Error()), + SourceUri: proto.String(sourceURI(bundle)), + }) + return nil + } + } + + pbResult, err := res.Proto() + if err != nil { + errCount.Inc(ctx, 1) + emitError(&cbpb.BeamError{ + ErrorMessage: proto.String(err.Error()), + SourceUri: proto.String(sourceURI(bundle)), + }) + return nil + } + + evalRes := &cbpb.BeamResult{ + Id: proto.String(patientID), + EvaluationTimestamp: timestamppb.New(fn.EvaluationTimestamp), + Result: pbResult, + } + emit(evalRes) + return nil +} + +func sourceURI(bundle *bpb.Bundle) string { + // TODO(b/317813865): Fall back to different source identifiers like the first patient id + // if the bundle has no id. + if bundle.GetId().GetValue() != "" { + return "bundle:" + bundle.GetId().GetValue() + } + return "" +} diff --git a/beam/transforms/eval_test.go b/beam/transforms/eval_test.go new file mode 100644 index 0000000..7c3c0a0 --- /dev/null +++ b/beam/transforms/eval_test.go @@ -0,0 +1,226 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transforms + +import ( + "context" + "testing" + "time" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + cbpb "github.com/google/cql/protos/cql_beam_go_proto" + crpb "github.com/google/cql/protos/cql_result_go_proto" + "github.com/google/fhir/go/fhirversion" + "github.com/google/fhir/go/jsonformat" + bpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestCQLEvalFn(t *testing.T) { + tests := []struct { + name string + evalFn *CQLEvalFn + input *bpb.Bundle + wantResult []*cbpb.BeamResult + wantError []*cbpb.BeamError + }{ + { + name: "Successful Eval", + evalFn: &CQLEvalFn{ + CQL: []string{dedent.Dedent( + `library EvalTest version '1.0' + using FHIR version '4.0.1' + valueset "HypertensionVS": 'urn:example:hypertension' + valueset "DiabetesVS": 'urn:example:diabetes' + define HasHypertension: exists([Condition: "HypertensionVS"]) + define HasDiabetes: exists([Condition: "DiabetesVS"])`)}, + ValueSets: valueSets, + EvaluationTimestamp: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + input: parseOrFatal(t, `{ + "resourceType": "Bundle", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "1" + } + }, + { + "resource": { + "resourceType": "Condition", + "id": "1", + "code": { + "coding": [{ "system": "http://example.com", "code": "11111", "display": "Hypertension"}] + }, + "onsetDateTime" : "2023-10-01" + } + }, + { + "resource": { + "resourceType": "Condition", + "id": "2", + "code": { + "coding": [{ "system": "http://example.com", "code": "22222", "display": "Diabetes"}] + }, + "onsetDateTime" : "2023-11-01" + } + } + ] + }`).GetBundle(), + wantResult: []*cbpb.BeamResult{ + &cbpb.BeamResult{ + Id: proto.String("1"), + EvaluationTimestamp: timestamppb.New(time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)), + Result: &crpb.Libraries{ + Libraries: []*crpb.Library{ + &crpb.Library{ + Name: proto.String("EvalTest"), + Version: proto.String("1.0"), + ExprDefs: map[string]*crpb.Value{ + "HasDiabetes": &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: true}, + }, + "HasHypertension": &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: true}, + }, + "DiabetesVS": &crpb.Value{ + Value: &crpb.Value_ValueSetValue{ + ValueSetValue: &crpb.ValueSet{Id: proto.String("urn:example:diabetes"), Version: proto.String("")}, + }, + }, + "HypertensionVS": &crpb.Value{ + Value: &crpb.Value_ValueSetValue{ + ValueSetValue: &crpb.ValueSet{Id: proto.String("urn:example:hypertension"), Version: proto.String("")}, + }, + }, + }, + }, + &crpb.Library{ + Name: proto.String("BeamMetadata"), + Version: proto.String("1.0.0"), + ExprDefs: map[string]*crpb.Value{ + "ID": &crpb.Value{ + Value: &crpb.Value_StringValue{StringValue: "1"}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "EvalError from Bad ValueSet", + evalFn: &CQLEvalFn{ + CQL: []string{dedent.Dedent( + `library "EvalTest" version '1.0' + using FHIR version '4.0.1' + valueset "DiabetesVS": 'urn:example:nosuchvalueset' + define HasDiabetes: exists([Condition: "DiabetesVS"])`)}, + ValueSets: valueSets, + EvaluationTimestamp: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + input: parseOrFatal(t, `{ + "resourceType": "Bundle", + "id": "bundle1", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "1" + } + }, + { + "resource": { + "resourceType": "Condition", + "id": "1", + "code": { + "coding": [{ "system": "http://example.com", "code": "11111", "display": "Hypertension"}] + }, + "onsetDateTime" : "2023-10-01" + } + } + ] + }`).GetBundle(), + wantError: []*cbpb.BeamError{ + &cbpb.BeamError{ + ErrorMessage: proto.String("failed during CQL evaluation: EvalTest 1.0, could not find ValueSet{urn:example:nosuchvalueset, } resource not loaded"), + SourceUri: proto.String("bundle:bundle1"), + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var gotOutput []*cbpb.BeamResult + var gotError []*cbpb.BeamError + emitOutput := func(e *cbpb.BeamResult) { gotOutput = append(gotOutput, e) } + emitError := func(e *cbpb.BeamError) { gotError = append(gotError, e) } + + if err := test.evalFn.Setup(); err != nil { + t.Fatalf("Setup() failed: %v", err) + } + test.evalFn.ProcessElement(context.Background(), test.input, emitOutput, emitError) + + if diff := cmp.Diff(test.wantResult, gotOutput, protocmp.Transform(), protocmp.SortRepeatedFields(&crpb.Libraries{}, "libraries")); diff != "" { + t.Errorf("ProcessElement() returned diff (-want +got):\n%s", diff) + } + if diff := cmp.Diff(test.wantError, gotError, protocmp.Transform()); diff != "" { + t.Errorf("ProcessElement() returned diff (-want +got):\n%s", diff) + } + }) + } +} + +var valueSets = []string{ + `{ + "resourceType": "ValueSet", + "url": "urn:example:hypertension", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "http://example.com", "code": "11111" } + ] + } + }`, + `{ + "resourceType": "ValueSet", + "url": "urn:example:diabetes", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "http://example.com", "code": "22222" } + ] + } + }`, +} + +func parseOrFatal(t *testing.T, json string) *bpb.ContainedResource { + t.Helper() + unmarshaller, err := jsonformat.NewUnmarshallerWithoutValidation("UTC", fhirversion.R4) + if err != nil { + t.Fatalf("jsonformat.NewUnmarshallerWithoutValidation() failed: %v", err) + } + cr, err := unmarshaller.UnmarshalR4([]byte(json)) + if err != nil { + t.Fatalf("UnmarshalR4(%q) failed: %v", json, err) + } + + return cr +} diff --git a/beam/transforms/sink.go b/beam/transforms/sink.go new file mode 100644 index 0000000..9b937bc --- /dev/null +++ b/beam/transforms/sink.go @@ -0,0 +1,76 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package transforms provides Dofns for Beam jobs processing CQL. +package transforms + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + cbpb "github.com/google/cql/protos/cql_beam_go_proto" + "github.com/google/cql/result" + "github.com/apache/beam/sdks/v2/go/pkg/beam" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +var ( + ndjsonSinkToProtoErrorCount = beam.NewCounter(counterPrefix, "ndjson_sink_to_proto_errors") + ndjsonSinkToJSONErrorCount = beam.NewCounter(counterPrefix, "ndjson_sink_to_json_errors") +) + +func quote(s string) string { + if len(s) == 0 { + return s + } + return strconv.Quote(s) +} + +// NDJSONSink marshals BeamResult to JSON and writes it to a NDJSON file. +func NDJSONSink(ctx context.Context, output *cbpb.BeamResult, emitValue func(string), emitError func(*cbpb.BeamError)) { + libs, err := result.LibrariesFromProto(output.Result) + if err != nil { + ndjsonSinkToProtoErrorCount.Inc(ctx, 1) + emitError(&cbpb.BeamError{ErrorMessage: proto.String(err.Error())}) + return + } + + evalTime := output.EvaluationTimestamp.AsTime().In(time.UTC) + jMap := map[string]any{ + "ID": output.Id, + "EvaluationTimestamp": evalTime.Format(time.RFC3339), + "Result": libs, + } + + jResult, err := json.Marshal(jMap) + if err != nil { + ndjsonSinkToJSONErrorCount.Inc(ctx, 1) + emitError(&cbpb.BeamError{ErrorMessage: proto.String(err.Error())}) + return + } + emitValue(fmt.Sprintf("%v\n", string(jResult))) +} + +// ErrorsNDJSONSink writes processing errors to an NDJSON file for troubleshooting. +func ErrorsNDJSONSink(beamErr *cbpb.BeamError, emitError func(string)) { + jsonBytes, err := protojson.Marshal(beamErr) + if err != nil { + emitError(fmt.Sprintf("Failed to marshal BeamError to JSON: %v", err)) + } + emitError(fmt.Sprintf("%v\n", string(jsonBytes))) +} diff --git a/beam/transforms/sink_test.go b/beam/transforms/sink_test.go new file mode 100644 index 0000000..c93e6cd --- /dev/null +++ b/beam/transforms/sink_test.go @@ -0,0 +1,103 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transforms + +import ( + "context" + "testing" + "time" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + cbpb "github.com/google/cql/protos/cql_beam_go_proto" + crpb "github.com/google/cql/protos/cql_result_go_proto" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/protobuf/proto" +) + +func TestNDJSONSink(t *testing.T) { + tests := []struct { + name string + outputs []*cbpb.BeamResult + wantValueRows []string + wantErrors []*cbpb.BeamError + }{ + { + name: "Successful Rows", + outputs: []*cbpb.BeamResult{ + &cbpb.BeamResult{ + Id: proto.String("1"), + EvaluationTimestamp: timestamppb.New(time.Date(2023, time.November, 1, 1, 20, 30, 1e8, time.UTC)), + Result: &crpb.Libraries{ + Libraries: []*crpb.Library{ + &crpb.Library{ + Name: proto.String("TESTLIB"), + Version: proto.String("1.0.0"), + ExprDefs: map[string]*crpb.Value{ + "HasDiabetes": &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: false}, + }, + "HasHypertension": &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: false}, + }, + }, + }, + }, + }, + }, + &cbpb.BeamResult{ + Id: proto.String("2"), + EvaluationTimestamp: timestamppb.New(time.Date(2023, time.December, 2, 1, 20, 30, 1e8, time.UTC)), + Result: &crpb.Libraries{ + Libraries: []*crpb.Library{ + &crpb.Library{ + Name: proto.String("TESTLIB"), + Version: proto.String("1.0.0"), + ExprDefs: map[string]*crpb.Value{ + "HasDiabetes": &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: true}, + }, + "HasHypertension": &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: true}, + }, + }, + }, + }, + }, + }, + }, + wantValueRows: []string{ + "{\"EvaluationTimestamp\":\"2023-11-01T01:20:30Z\",\"ID\":\"1\",\"Result\":[{\"libName\":\"TESTLIB\",\"libVersion\":\"1.0.0\",\"expressionDefinitions\":{\"HasDiabetes\":{\"@type\":\"System.Boolean\",\"value\":false},\"HasHypertension\":{\"@type\":\"System.Boolean\",\"value\":false}}}]}\n", + "{\"EvaluationTimestamp\":\"2023-12-02T01:20:30Z\",\"ID\":\"2\",\"Result\":[{\"libName\":\"TESTLIB\",\"libVersion\":\"1.0.0\",\"expressionDefinitions\":{\"HasDiabetes\":{\"@type\":\"System.Boolean\",\"value\":true},\"HasHypertension\":{\"@type\":\"System.Boolean\",\"value\":true}}}]}\n", + }, + }, + } + for _, test := range tests { + var gotValueRows []string + var gotErrors []*cbpb.BeamError + emitValue := func(val string) { gotValueRows = append(gotValueRows, val) } + emitError := func(err *cbpb.BeamError) { gotErrors = append(gotErrors, err) } + for _, output := range test.outputs { + NDJSONSink(context.Background(), output, emitValue, emitError) + } + + if diff := cmp.Diff(test.wantValueRows, gotValueRows, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" { + t.Errorf("NDJSONSink() returned value diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(test.wantErrors, gotErrors); diff != "" { + t.Errorf("NDJSONSink() returned error diff (-want +got):\n%s", diff) + } + } +} diff --git a/beam/transforms/source.go b/beam/transforms/source.go new file mode 100644 index 0000000..23b6a0a --- /dev/null +++ b/beam/transforms/source.go @@ -0,0 +1,61 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transforms + +import ( + "context" + "fmt" + + cbpb "github.com/google/cql/protos/cql_beam_go_proto" + "github.com/google/fhir/go/fhirversion" + "github.com/google/fhir/go/jsonformat" + bpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + "github.com/apache/beam/sdks/v2/go/pkg/beam/io/fileio" + "google.golang.org/protobuf/proto" +) + +// FileToBundle returns a collection of FHIR R4 bundles and a collection of `ProcessingError` protos +// for files that could not be parsed into a bundle. +func FileToBundle(ctx context.Context, file fileio.ReadableFile, emitBundle func(*bpb.Bundle), emitError func(*cbpb.BeamError)) { + emitErr := func(err error) { + bundleErrorCount.Inc(ctx, 1) + emitError(&cbpb.BeamError{ErrorMessage: proto.String(err.Error())}) + } + + data, err := file.Read(ctx) + if err != nil { + emitErr(err) + return + } + + unmarshaller, err := jsonformat.NewUnmarshallerWithoutValidation("UTC", fhirversion.R4) + if err != nil { + emitErr(err) + return + } + + p, err := unmarshaller.Unmarshal(data) + if err != nil { + emitErr(err) + return + } + + b := p.(*bpb.ContainedResource).GetBundle() + if b != nil { + emitBundle(b) + } else { + emitErr(fmt.Errorf("no bundle found in file: %s", file.Metadata.Path)) + } +} diff --git a/cmd/cli/README.md b/cmd/cli/README.md new file mode 100644 index 0000000..d52aa95 --- /dev/null +++ b/cmd/cli/README.md @@ -0,0 +1,89 @@ +# CQL CLI + +A CLI tool for interacting with the CQL engine to run measures against FHIR +input data to output structured results to disk. + +The CLI is primarily designed for executing single patient or small population +workflows. For larger population sets the scalable beam job is a more +appropriate solution. + +## Running + +To build the program from source run the following from the root of the +repository (note you must have [Go](https://go.dev/dl/) installed): + +```bash +go build cmd/cli/cli.go +``` + +This will build the `cli` binary and write it out in your current directory. You +can then run the binary: + +```bash +./cli \ + -cql_dir="path/to/cql/dir/" \ + -fhir_bundle_dir="path/to/bundle/dir/" \ + -fhir_terminology_dir="path/to/terminology/dir/" \ + -json_output_dir="path/to/output/" +``` + +## Flags + +**--cql_dir** -- Required. The path to a directory containing one or more CQL +files. The engine only reads files ending in a `.cql` suffix. ELM inputs are +not currently supported. + +**--execution_timestamp_override** -- Optional. When set overrides the default +evaluation timestamp for the engine. This can be used to run CQL at a given +point in time. The value should be formatted as a CQL DateTime. If not provided +the engine will use the current system DateTime instead. + +Example: + +```bash +--execution_timestamp_override="@2018-02-02T15:02:03.000-04:00" +``` + +**--fhir_bundle_dir** -- Optional. The path containing one or more FHIR bundles. +Each of those bundles will cause one evaluation of the input CQL libraries +results of which will each directly map to outputs. + +Note: Each file in the bundle directory is expected to be one bundle per file. + +**--fhir_terminology_dir** -- Optional. The path to a directory containing json +definitions of FHIR ValueSets. + +**--fhir_parameters_file** -- Optional. A file path to a JSON file containing +FHIR Parameters which will be used as inputs to the CQL execution environment. + +**--parameters** -- Optional. A comma separated list of parameters which will be +used as inputs to the CQL execution environment. These values are passed as raw +golang strings to the parsings stage. This provides some limitations for more +complicated input value types. For such cases it is recommended to use the flag +`--fhir_parameters_file` instead. + +Example: + +```bash +--parameters=”aString='string value',integerValue=2,a id with spaces='value'” +``` + +Note: If both `--parameters` and `--fhir_parameters_file` are provided. FHIR +Parameters will be loaded first. Any parameters in the `--parameters` flag +will override FHIR Parameters with the same name value. + +**--json_output_dir** -- Optional. A directory for outputting structured json +results. Each successful run of an input bundle file will result in one output +result file. If no input bundles are supplied the result will be a single +`result.json` file. + +Note: The output json structure is currently a custom format and is subject to +change. + +**--return_private_defs** -- Optional. When set will have the CQL engine return +both private and public definitions in the CQL results. By default only public +definitions are emitted. + +**-V** -- Optional. Outputs the engine version as well as the CQL version to the +terminal. This flag overrides all other behaviors, so no CQL execution will take +place. diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go new file mode 100644 index 0000000..82d85fb --- /dev/null +++ b/cmd/cli/cli.go @@ -0,0 +1,384 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A CLI for interacting with the CQL engine. +package main + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/google/cql" + "github.com/google/cql/internal/datehelpers" + "github.com/google/cql/internal/iohelpers" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/google/cql/terminology" + "github.com/google/fhir/go/fhirversion" + "github.com/google/fhir/go/jsonformat" + "github.com/google/bulk_fhir_tools/gcs" +) + +type cliConfig struct { + CQLDir string + ExecutionTimestampOverride string + FHIRBundleDir string + FHIRTerminologyDir string + FHIRParametersFile string + GCPProject string + Parameters string + ReturnPrivateDefs bool + JSONOutputDir string + Version bool + + // Should not be set directly by a flag. + gcsEndpoint string +} + +func (cfg *cliConfig) RegisterFlags(fs *flag.FlagSet) { + fs.StringVar(&cfg.CQLDir, "cql_dir", "", "(Required) Directory holding 1 or more CQL files.") + fs.StringVar( + &cfg.ExecutionTimestampOverride, + "execution_timestamp_override", + "", + "(Optional) A DateTime to use for overriding the default execution timestamp of the CQL engine. The value of should match the format of a CQL DateTime. If the value provided doesn't contain a timezone utc the default will be UTC. If not supplied the engine will use the current DateTime. Example: @2024-01-01T00:00:00Z", + ) + fs.StringVar(&cfg.FHIRBundleDir, "fhir_bundle_dir", "", "(Optional) Directory holding FHIR Bundle JSON files.") + fs.StringVar(&cfg.FHIRTerminologyDir, "fhir_terminology_dir", "", "(Optional) Directory holding FHIR Valueset JSONs.") + fs.StringVar(&cfg.FHIRParametersFile, "fhir_parameters_file", "", "(Optional) A JSON file holding FHIR Parameters to use during CQL execution. Currently only supports R4.") + fs.StringVar(&cfg.Parameters, "parameters", "", "(Optional) A comma separated list of parameters to pass to the CQL execution. Example: --parameters=\"aString='string value',integerValue=2\"") + fs.StringVar(&cfg.GCPProject, "gcp_project", "", "(Optional) The GCP project to use when reading from or writing to GCS.") + + // Output flags. + fs.BoolVar(&cfg.ReturnPrivateDefs, "return_private_defs", false, "(Optional) If true, will include the output of all private CQL expression definitions. By default only public definitions are outputted. This should only be used for debugging purposes.") + fs.StringVar(&cfg.JSONOutputDir, "json_output_dir", "", "(Optional) Directory in which to output each evaluation result as a JSON file. If not supplied will output in the current directory.") + + // See: https://cql.hl7.org/history.html for CQL versions. + fs.BoolVar(&cfg.Version, "V", false, "(Optional) Prints the current version of the CQL engine and CQL version.") + + // Set the default gcs endpoint to the public endpoint, only override if running in a test + // environment. + cfg.gcsEndpoint = gcs.DefaultCloudStorageEndpoint +} + +const usageMessage = "The CLI for the golang CQL engine." + +var errMissingFlag = errors.New("missing required flag") + +// The config which is populated by the CLI input flags. +var config cliConfig + +func init() { + config.RegisterFlags(flag.CommandLine) + defaultUsage := flag.Usage + flag.Usage = func() { + fmt.Fprintln(os.Stderr, usageMessage) + defaultUsage() + } +} + +func main() { + flag.Parse() + ctx := context.Background() + if err := mainWrapper(ctx, config); err != nil { + log.Fatalf("CQL CLI failed with an error: %v", err) + } +} + +func validateConfig(ctx context.Context, cfg *cliConfig) error { + if cfg.CQLDir == "" { + return fmt.Errorf("%w --cql_dir", errMissingFlag) + } + err := validatePath(ctx, cfg.CQLDir, cfg.GCPProject, cfg.gcsEndpoint, "cql_dir") + if err != nil { + return err + } + if cfg.FHIRBundleDir != "" { + err := validatePath(ctx, cfg.FHIRBundleDir, cfg.GCPProject, cfg.gcsEndpoint, "fhir_bundle_dir") + if err != nil { + return err + } + } + if cfg.FHIRTerminologyDir != "" { + err := validatePath(ctx, cfg.FHIRTerminologyDir, cfg.GCPProject, cfg.gcsEndpoint, "fhir_terminology_dir") + if err != nil { + return err + } + } + if cfg.JSONOutputDir != "" { + err := validatePath(ctx, cfg.JSONOutputDir, cfg.GCPProject, cfg.gcsEndpoint, "json_output_dir") + if err != nil { + return err + } + } + return nil +} + +// validatePath validates that the path is a valid GCS path or a local file path. +// flagName is the name of the flag that was used to pass in the path that is being validated. +func validatePath(ctx context.Context, validationPath, gcpProject, gcsEndpoint, flagName string) error { + if strings.HasPrefix(validationPath, "gs://") { + bucket, _, err := gcs.PathComponents(validationPath) + if err != nil { + return err + } + if err := validateGCSBucketInProject(ctx, bucket, gcpProject, gcsEndpoint); err != nil { + return err + } + } else if _, err := os.Stat(validationPath); err != nil { + return fmt.Errorf("--%s: %w", flagName, err) + } + return nil +} + +func validateGCSBucketInProject(ctx context.Context, bucket, project, endpoint string) error { + // Only allow writing to and reading from GCS buckets in the same project. + // We do this to prevent cases where a user could mis-type a bucket name and end up writing PHI + // data to a bucket in a different project. + if project == "" { + return fmt.Errorf("--gcp_project must be set if you are using a GCS file IO") + } + c, err := gcs.NewClient(ctx, bucket, endpoint) + if err != nil { + return err + } + isInProject, err := c.IsBucketInProject(ctx, project) + if err != nil { + return err + } + if !isInProject { + return fmt.Errorf("could not find GCS Bucket %s in the GCP project %s", bucket, project) + } + return nil +} + +// TODO: b/340361303 - Consider using something like the logger interface in medical claims tools before open sourcing. +func mainWrapper(ctx context.Context, cfg cliConfig) error { + if cfg.Version { + fmt.Println("CQL Engine Version: (Beta) 0.0.1, CQL Version: 1.5.2") + return nil + } + if err := validateConfig(ctx, &cfg); err != nil { + return err + } + cqlLibs, err := readCQLLibs(ctx, cfg.CQLDir, &cfg) + if err != nil { + return fmt.Errorf("failed to read CQL libraries: %w", err) + } + fhirDM, err := cql.FHIRDataModel("4.0.1") + if err != nil { + return fmt.Errorf("failed to create FHIR data model: %w", err) + } + config := cql.ParseConfig{DataModels: [][]byte{fhirDM}} + if cfg.FHIRParametersFile != "" { + parametersText, err := iohelpers.ReadFile(ctx, cfg.FHIRParametersFile, &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) + if err != nil { + return fmt.Errorf("failed to read FHIR parameters file %s: %w", cfg.FHIRParametersFile, err) + } + params, err := parseFHIRParameters(parametersText) + if err != nil { + return fmt.Errorf("failed to parse FHIR parameters file %s: %w", cfg.FHIRParametersFile, err) + } + config.Parameters = params + } + if cfg.Parameters != "" { + for _, param := range strings.Split(cfg.Parameters, ",") { + parts := strings.Split(param, "=") + if len(parts) != 2 { + return fmt.Errorf("--parameters was passed an invalid input string: %s", param) + } + config.Parameters[result.DefKey{Name: parts[0]}] = parts[1] + } + } + elm, err := cql.Parse(ctx, cqlLibs, config) + if err != nil { + return fmt.Errorf("failed to parse CQL: %w", err) + } + tp, err := maybeGetTerminologyProvider(ctx, cfg.FHIRTerminologyDir, &cfg) + if err != nil { + return fmt.Errorf("failed to get terminology: %w", err) + } + + evalConfig := cql.EvalConfig{ + ReturnPrivateDefs: cfg.ReturnPrivateDefs, + Terminology: tp, + } + if cfg.ExecutionTimestampOverride != "" { + t, _, err := datehelpers.ParseDateTime(cfg.ExecutionTimestampOverride, time.UTC) + if err != nil { + return fmt.Errorf("failed to parse execution timestamp override to a valid DateTime value: %w", err) + } + evalConfig.EvaluationTimestamp = t + } + if err = runCQLWithBundleDir(ctx, elm, cfg.FHIRBundleDir, cfg.JSONOutputDir, evalConfig, &cfg); err != nil { + return fmt.Errorf("failed to run CQL: %w", err) + } + return nil +} + +type cqlResult struct { + BundleSource string `json:"bundleSource,omitempty"` + EvalResults result.Libraries `json:"evalResults"` +} + +func runCQLWithBundleDir(ctx context.Context, elm *cql.ELM, fhirBundleDir string, outputDir string, evalConfig cql.EvalConfig, cfg *cliConfig) error { + // If fhirBundleDir is empty run one eval with empty bundle retriever. + if fhirBundleDir == "" { + r, err := elm.Eval(ctx, &local.Retriever{}, evalConfig) + if err != nil { + return err + } + return outputCQLResults(ctx, outputDir, "results.json", cqlResult{EvalResults: r}, cfg) + } + + bundleFilePaths, err := iohelpers.FilesWithSuffix(ctx, fhirBundleDir, ".json", &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) + if err != nil { + return err + } + if len(bundleFilePaths) == 0 { + fmt.Printf("no files found in FHIR bundle directory %s, exiting", fhirBundleDir) + return nil + } + + // TODO(b/301659936): implement a concurrent version of this. + for _, filePath := range bundleFilePaths { + fhirData, err := iohelpers.ReadFile(ctx, filePath, &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) + if err != nil { + return err + } + ret, err := local.NewRetrieverFromR4Bundle(fhirData) + if err != nil { + return err + } + r, err := elm.Eval(ctx, ret, evalConfig) + if err != nil { + return err + } + _, fileName := filepath.Split(filePath) + if err := outputCQLResults(ctx, outputDir, fileName, cqlResult{BundleSource: filePath, EvalResults: r}, cfg); err != nil { + return err + } + } + return nil +} + +// maybeGetTerminologyProvider constructs a ValueSet terminology provider if provided with a valid directory. +func maybeGetTerminologyProvider(ctx context.Context, terminologyDir string, cfg *cliConfig) (terminology.Provider, error) { + if terminologyDir == "" { + return nil, nil + } + filePaths, err := iohelpers.FilesWithSuffix(ctx, terminologyDir, ".json", &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) + if err != nil { + return nil, err + } + + var jsonTerminologyData []string + for _, filePath := range filePaths { + b, err := iohelpers.ReadFile(ctx, filePath, &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) + if err != nil { + return nil, err + } + jsonTerminologyData = append(jsonTerminologyData, string(b)) + } + return terminology.NewInMemoryFHIRProvider(jsonTerminologyData) +} + +// readCQLLibs reads all CQL (files containing the .cql suffix) files from a directory. +func readCQLLibs(ctx context.Context, dir string, cfg *cliConfig) ([]string, error) { + filePaths, err := iohelpers.FilesWithSuffix(ctx, dir, ".cql", &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) + if err != nil { + return nil, err + } + + cqlLibs := make([]string, 0, len(filePaths)) + for _, filePath := range filePaths { + var cqlData []byte + cqlData, err = iohelpers.ReadFile(ctx, filePath, &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) + if err != nil { + return nil, fmt.Errorf("failed to read CQL file %s: %w", filePath, err) + } + cqlLibs = append(cqlLibs, string(cqlData)) + } + return cqlLibs, nil +} + +// parseFHIRParameters takes in JSON bytes containing FHIR parameters and converts them to parameter +// name to value mappings. +// Like other parts of the engine currently only supports FHIR R4. +func parseFHIRParameters(parametersBytes []byte) (map[result.DefKey]string, error) { + unmarshaller, err := jsonformat.NewUnmarshallerWithoutValidation("UTC", fhirversion.R4) + if err != nil { + return nil, err + } + containedResource, err := unmarshaller.UnmarshalR4(parametersBytes) + if err != nil { + return nil, err + } + params := containedResource.GetParameters() + if params == nil { + return nil, fmt.Errorf("failed to parse FHIR parameters with text: %s", string(parametersBytes)) + } + + // For now we are doing a simple conversion for a subset of the types. + // We take those values and convert them to strings which can be parsed by the parser into CQL + // values. Later we will need to create a parser for FHIR Parameters to/from CQL values. + // TODO b/337956267 - Create a parser for FHIR Parameters to/from CQL values. + r := map[result.DefKey]string{} + for _, param := range params.GetParameter() { + choice := param.GetValue() + name := param.GetName().GetValue() + choice.GetQuantity() + + var val string + if c := choice.GetStringValue(); c != nil { + // Since we need to be able to parse these out into a CQL literal, internally we wrap the + // raw value of FHIR Strings in single quotes. + val = fmt.Sprintf("'%s'", c.GetValue()) + } else if c := choice.GetBoolean(); c != nil { + val = strconv.FormatBool(c.GetValue()) + } else if c := choice.GetCode(); c != nil { + val = c.GetValue() + } else if c := choice.GetDecimal(); c != nil { + val = c.GetValue() + } else if c := choice.GetInteger(); c != nil { + val = strconv.FormatInt(int64(c.GetValue()), 10) + } else if c := choice.GetPositiveInt(); c != nil { + val = strconv.FormatInt(int64(c.GetValue()), 10) + } else if c := choice.GetUnsignedInt(); c != nil { + val = strconv.FormatInt(int64(c.GetValue()), 10) + } else { + return nil, fmt.Errorf("unsupported FHIR Parameter %s was not of type (string, boolean, code, decimal, integer, positiveInt, or unsignedInt)", name) + } + r[result.DefKey{Name: name}] = val + } + return r, nil +} + +func outputCQLResults(ctx context.Context, path, fileName string, results cqlResult, cfg *cliConfig) error { + // need to update this for different output types + jsonResults, err := json.MarshalIndent(results, "", " ") + if err != nil { + return err + } + return iohelpers.WriteFile(ctx, path, fileName, jsonResults, &iohelpers.IOConfig{GCSEndpoint: cfg.gcsEndpoint}) +} diff --git a/cmd/cli/cli_test.go b/cmd/cli/cli_test.go new file mode 100644 index 0000000..1d6bba0 --- /dev/null +++ b/cmd/cli/cli_test.go @@ -0,0 +1,560 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "io/fs" + "os" + "path" + "testing" + + "github.com/google/cql/result" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/bulk_fhir_tools/testhelpers" +) + +const testBucketName = "bucketName" + +func TestCLI(t *testing.T) { + tests := []struct { + name string + cql string + fhirBundle string + fhirTerminology string + fhirParameters string + returnPrivateDefs bool + executionTimestampOverride string + wantTestResult string + }{ + { + name: "Simple CLI call with most flags set", + cql: ` + library TESTLIB + define TESTRESULT: true`, + fhirBundle: `{"resourceType": "Bundle", "id": "example", "entry": []}`, + fhirTerminology: `{"resourceType": "ValueSet", "id": "https://test/emptyVS", "url": "https://test/emptyVS"}`, + fhirParameters: `{"resourceType": "Parameters", "id": "example", "parameter": []}`, + wantTestResult: `{"@type": "System.Boolean", "value": true}`, + }, + { + name: "ReturnPrivateDefs is set and returned", + cql: ` + library TESTLIB + define private TESTRESULT: true`, + fhirBundle: `{"resourceType": "Bundle", "id": "example", "entry": []}`, + returnPrivateDefs: true, + wantTestResult: `{"@type": "System.Boolean", "value": true}`, + }, + { + name: "Can override execution timestamp", + cql: ` + library TESTLIB + define TESTRESULT: Now()`, + fhirBundle: `{"resourceType": "Bundle", "id": "example", "entry": []}`, + executionTimestampOverride: "@2018-02-02T15:02:03.000-04:00", + wantTestResult: `{"@type": "System.DateTime","value": "@2018-02-02T15:02:03.000-04:00"}`, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Create temp directories for each of the file based flags. + testDirCfg := defaultCLIConfig(t) + bundleFileName := "test_bundle.json" + var bundleFilePath string + if tc.fhirBundle != "" { + bundleFilePath = path.Join(testDirCfg.FHIRBundleDir, bundleFileName) + } + // Fill test directories with test file content. + writeLocalFileWithContent(t, path.Join(testDirCfg.CQLDir, "test_code.cql"), tc.cql) + writeLocalFileWithContent(t, path.Join(testDirCfg.FHIRBundleDir, bundleFileName), tc.fhirBundle) + writeLocalFileWithContent(t, path.Join(testDirCfg.FHIRTerminologyDir, "terminology.json"), tc.fhirTerminology) + // need to not always create this file + writeLocalFileWithContent(t, testDirCfg.FHIRParametersFile, tc.fhirParameters) + + // Setup the CLI config. + cfg := cliConfig{ + CQLDir: testDirCfg.CQLDir, + JSONOutputDir: testDirCfg.JSONOutputDir, + ReturnPrivateDefs: tc.returnPrivateDefs, + ExecutionTimestampOverride: tc.executionTimestampOverride, + } + if tc.fhirTerminology != "" { + cfg.FHIRTerminologyDir = testDirCfg.FHIRTerminologyDir + } + if tc.fhirBundle != "" { + cfg.FHIRBundleDir = testDirCfg.FHIRBundleDir + } + if tc.fhirParameters != "" { + cfg.FHIRParametersFile = testDirCfg.FHIRParametersFile + } + + if err := mainWrapper(context.Background(), cfg); err != nil { + t.Errorf("mainWrapper() returned an unexpected error: %v", err) + } + + // Read and validate the output file. + entries, err := os.ReadDir(testDirCfg.JSONOutputDir) + if err != nil { + t.Errorf("os.ReadDir() returned an unexpected error: %v", err) + } + if len(entries) != 1 { + t.Errorf("os.ReadDir() expected %v entries got: %v", 1, len(entries)) + } + resultBytes, err := os.ReadFile(path.Join(testDirCfg.JSONOutputDir, entries[0].Name())) + if err != nil { + t.Errorf("os.ReadFile() returned an unexpected error: %v", err) + } + gotResult := string(normalizeJSON(t, resultBytes)) + // all tests should have a bundle source and a tc.want which will be populated here. + wantResult := string(normalizeJSON(t, []byte(fmt.Sprintf(`{ + "bundleSource": "%s", + "evalResults": [ + { + "expressionDefinitions": { + "TESTRESULT": %s + }, + "libName": "TESTLIB", + "libVersion": "" + } + ] + }`, bundleFilePath, tc.wantTestResult)))) + if diff := cmp.Diff(wantResult, gotResult); diff != "" { + t.Errorf("mainWrapper() returned an unexpected diff (-want +got): %v", diff) + } + }) + } +} + +func TestCLIWithGCS(t *testing.T) { + cql := ` + library TESTLIB + define TESTRESULT: true` + parametersFile := "fhir_parameters/parameters.json" + cfg, gcsServer := defaultGCSConfig(t) + cfg.FHIRParametersFile = gcsPath(t, parametersFile) + gcsServer.AddObject(testBucketName, "cql/test_code.cql", gcsObject(t, cql)) + gcsServer.AddObject(testBucketName, "fhir_bundle/test_bundle.json", gcsObject(t, `{"resourceType": "Bundle", "id": "example", "entry": []}`)) + gcsServer.AddObject(testBucketName, "fhir_terminology/terminology.json", gcsObject(t, `{"resourceType": "ValueSet", "id": "https://test/emptyVS", "url": "https://test/emptyVS"}`)) + gcsServer.AddObject(testBucketName, parametersFile, gcsObject(t, `{"resourceType": "Parameters", "id": "example", "parameter": []}`)) + + if err := mainWrapper(context.Background(), cfg); err != nil { + t.Errorf("mainWrapper() returned an unexpected error: %v", err) + } + + entry, found := gcsServer.GetObject(testBucketName, "json_output/test_bundle.json") + if !found { + t.Errorf("mainWrapper() did not write the expected output file") + } + gotResult := string(normalizeJSON(t, entry.Data)) + wantResult := string(normalizeJSON(t, []byte(`{ + "bundleSource": "gs://bucketName/fhir_bundle/test_bundle.json", + "evalResults": [ + { + "expressionDefinitions": { + "TESTRESULT": { + "@type": "System.Boolean", + "value": true + } + }, + "libName": "TESTLIB", + "libVersion": "" + } + ] + }`))) + if diff := cmp.Diff(wantResult, gotResult); diff != "" { + t.Errorf("mainWrapper() returned an unexpected diff (-want +got): %v", diff) + } +} + +func TestVersionOverridesCQLExecution(t *testing.T) { + // Create a temp directory for each of the file based flags. + cqlDir := t.TempDir() + jsonOutputDir := t.TempDir() + // Create simple example file content. + simpleCQL := []byte(` + library TESTLIB + define TESTRESULT: true`) + // Write the files to their respective temp directories. + if err := os.WriteFile(path.Join(cqlDir, "test_code.cql"), simpleCQL, 0644); err != nil { + t.Fatalf("os.WriteFile() returned an unexpected error: %v", err) + } + // Setup the CLI config. + cfg := cliConfig{ + CQLDir: cqlDir, + JSONOutputDir: jsonOutputDir, + Version: true, + } + + if err := mainWrapper(context.Background(), cfg); err != nil { + t.Errorf("mainWrapper() returned an unexpected error: %v", err) + } + entries, err := os.ReadDir(jsonOutputDir) + if err != nil { + t.Errorf("os.ReadDir() returned an unexpected error: %v", err) + } + // CQL engine should not have written any output. + if len(entries) != 0 { + t.Errorf("os.ReadDir() expected no entries got: %v", len(entries)) + } +} + +func TestParseFHIRParameters(t *testing.T) { + tests := []struct { + name string + parametersText string + want map[result.DefKey]string + }{ + { + name: "empty", + parametersText: `{ + "resourceType": "Parameters", + "id": "example", + "parameter": [] + }`, + want: map[result.DefKey]string{}, + }, + { + name: "boolean", + parametersText: `{ + "resourceType": "Parameters", + "id": "example", + "parameter": [ + { + "name": "boolean value", + "valueBoolean": true + } + ] + }`, + want: map[result.DefKey]string{ + result.DefKey{Name: "boolean value"}: "true", + }, + }, + { + name: "decimal", + parametersText: `{ + "resourceType": "Parameters", + "id": "example", + "parameter": [ + { + "name": "decimal value", + "valueDecimal": 1.1 + } + ] + }`, + want: map[result.DefKey]string{ + result.DefKey{Name: "decimal value"}: "1.1", + }, + }, + { + name: "integer", + parametersText: `{ + "resourceType": "Parameters", + "id": "example", + "parameter": [ + { + "name": "integer value", + "valueInteger": 42 + } + ] + }`, + want: map[result.DefKey]string{ + result.DefKey{Name: "integer value"}: "42", + }, + }, + { + name: "positive integer", + parametersText: `{ + "resourceType": "Parameters", + "id": "example", + "parameter": [ + { + "name": "positive integer value", + "valuePositiveInt": 42 + } + ] + }`, + want: map[result.DefKey]string{ + result.DefKey{Name: "positive integer value"}: "42", + }, + }, + { + name: "unsigned integer", + parametersText: `{ + "resourceType": "Parameters", + "id": "example", + "parameter": [ + { + "name": "unsigned integer value", + "valueUnsignedInt": 43 + } + ] + }`, + want: map[result.DefKey]string{ + result.DefKey{Name: "unsigned integer value"}: "43", + }, + }, + // This currently isn't particularly useful but we will need to support it properly in the future. + { + name: "code", + parametersText: `{ + "resourceType": "Parameters", + "id": "example", + "parameter": [ + { + "name": "code value", + "valueCode": "value: 'code_value'" + } + ] + }`, + want: map[result.DefKey]string{ + result.DefKey{Name: "code value"}: "value: 'code_value'", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := parseFHIRParameters([]byte(tc.parametersText)) + if err != nil { + t.Errorf("parseFHIRParameters() with text %s returned an unexpected error: %v", string(tc.parametersText), err) + } + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("parseFHIRParameters() with text %s returned an unexpected diff (-want +got): %v", string(tc.parametersText), diff) + } + }) + } +} + +func TestValidateConfig(t *testing.T) { + testDirs := defaultCLIConfig(t) + + tests := []struct { + name string + args []string + cfg cliConfig + }{ + { + name: "Simple config with all flags set", + cfg: cliConfig{ + Parameters: "aString='string value'", + CQLDir: testDirs.CQLDir, + FHIRBundleDir: testDirs.FHIRBundleDir, + FHIRTerminologyDir: testDirs.FHIRTerminologyDir, + FHIRParametersFile: testDirs.FHIRParametersFile, + JSONOutputDir: testDirs.JSONOutputDir, + }, + }, + { + name: "Only valid cqlDir is required", + cfg: cliConfig{ + CQLDir: testDirs.CQLDir, + }, + }, + { + name: "valid terminologyDir", + cfg: cliConfig{ + CQLDir: testDirs.CQLDir, + FHIRTerminologyDir: testDirs.FHIRTerminologyDir, + }, + }, + { + name: "valid jsonOutputDir", + cfg: cliConfig{ + CQLDir: testDirs.CQLDir, + JSONOutputDir: testDirs.JSONOutputDir, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if err := validateConfig(context.Background(), &tc.cfg); err != nil { + t.Errorf("validateFlags() with %v returned an unexpected error %v", tc.cfg, err) + } + }) + } +} + +func TestValidateConfigError(t *testing.T) { + tests := []struct { + name string + cfg cliConfig + wantErr error + }{ + { + name: "cqlDir is required", + cfg: cliConfig{}, + wantErr: errMissingFlag, + }, + { + name: "bundleDir invalid path", + cfg: cliConfig{ + CQLDir: t.TempDir(), + FHIRBundleDir: "/bad/path", + }, + wantErr: fs.ErrNotExist, + }, + { + name: "terminologyDir invalid path", + cfg: cliConfig{ + CQLDir: t.TempDir(), + FHIRTerminologyDir: "/bad/path", + }, + wantErr: fs.ErrNotExist, + }, + { + name: "jsonOutputDir invalid path", + cfg: cliConfig{ + CQLDir: t.TempDir(), + JSONOutputDir: "/bad/path", + }, + wantErr: fs.ErrNotExist, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if err := validateConfig(context.Background(), &tc.cfg); !errors.Is(err, tc.wantErr) { + t.Errorf("validateFlags() with %v returned unexpected error wanted %s, got %s", tc.cfg, tc.wantErr.Error(), err.Error()) + } + }) + } +} + +func TestParseFlags(t *testing.T) { + testDirs := defaultCLIConfig(t) + + tests := []struct { + name string + args []string + want cliConfig + }{ + { + name: "Simple config with all flags set", + args: []string{ + `--parameters=aString='string value'`, + "--cql_dir=" + testDirs.CQLDir, + "--fhir_bundle_dir=" + testDirs.FHIRBundleDir, + "--fhir_terminology_dir=" + testDirs.FHIRTerminologyDir, + "--fhir_parameters_file=" + testDirs.FHIRParametersFile, + "--json_output_dir=" + testDirs.JSONOutputDir, + }, + want: cliConfig{ + Parameters: "aString='string value'", + CQLDir: testDirs.CQLDir, + FHIRBundleDir: testDirs.FHIRBundleDir, + FHIRTerminologyDir: testDirs.FHIRTerminologyDir, + FHIRParametersFile: testDirs.FHIRParametersFile, + JSONOutputDir: testDirs.JSONOutputDir, + gcsEndpoint: "https://storage.googleapis.com/", + }, + }, + { + name: "No flags set", + args: []string{}, + want: cliConfig{ + gcsEndpoint: "https://storage.googleapis.com/", + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fs := flag.NewFlagSet("test_flagset", flag.PanicOnError) + var cfg cliConfig + cfg.RegisterFlags(fs) + + if err := fs.Parse(tc.args); err != nil { + t.Errorf("fs.Parse(%v) returned an unexpected error: %v", tc.args, err) + } + if diff := cmp.Diff(tc.want, cfg, cmpopts.IgnoreFields(cliConfig{}, "gcsEndpoint")); diff != "" { + t.Errorf("After fs.Parse(%v) got an unexpected diff (-want +got): %v", tc.args, diff) + } + }) + } +} + +func defaultCLIConfig(t *testing.T) cliConfig { + t.Helper() + return cliConfig{ + CQLDir: t.TempDir(), + FHIRBundleDir: t.TempDir(), + FHIRTerminologyDir: t.TempDir(), + FHIRParametersFile: path.Join(t.TempDir(), "parameters.json"), + JSONOutputDir: t.TempDir(), + } +} + +func defaultGCSConfig(t *testing.T) (cliConfig, *testhelpers.GCSServer) { + t.Helper() + gcsServer := testhelpers.NewGCSServer(t) + return cliConfig{ + CQLDir: gcsPath(t, "cql"), + FHIRBundleDir: gcsPath(t, "fhir_bundle"), + FHIRTerminologyDir: gcsPath(t, "fhir_terminology"), + GCPProject: "test-project", + JSONOutputDir: gcsPath(t, "json_output"), + gcsEndpoint: gcsServer.URL(), + }, gcsServer +} + +func gcsPath(t *testing.T, suffixPath string) string { + t.Helper() + return "gs://" + path.Join(testBucketName, suffixPath) +} + +func gcsObject(t *testing.T, content string) testhelpers.GCSObjectEntry { + t.Helper() + return testhelpers.GCSObjectEntry{ + Data: []byte(content), + } +} + +func normalizeJSON(t *testing.T, b []byte) []byte { + t.Helper() + var v any + if err := json.Unmarshal(b, &v); err != nil { + t.Fatalf("json.Unmarshal() returned an unexpected error: %v", err) + } + outBytes, err := json.MarshalIndent(v, "", " ") + if err != nil { + t.Fatalf("json.Marshal() returned an unexpected error: %v", err) + } + return outBytes +} + +// Only write to a local file if content is not empty. +func writeLocalFileWithContent(t *testing.T, filePath, content string) { + t.Helper() + if content == "" { + return + } + if err := os.WriteFile(filePath, []byte(content), 0644); err != nil { + t.Fatalf("os.WriteFile() returned an unexpected error: %v", err) + } +} + +// newOrFatal returns a new result.Value or calls fatal on error. +func newOrFatal(t testing.TB, a any) result.Value { + t.Helper() + o, err := result.New(a) + if err != nil { + t.Fatalf("New(%v) returned unexpected error: %v", a, err) + } + return o +} diff --git a/cmd/repl/README.md b/cmd/repl/README.md new file mode 100644 index 0000000..0f270e6 --- /dev/null +++ b/cmd/repl/README.md @@ -0,0 +1,72 @@ +# CQL REPL + +A REPL CLI for interacting with the CQL engine. This can be a useful tool for quickly iterating while authoring CQL measures. + +## Running + +To build the program from source run the following from the root of the +repository (note you must have [Go](https://go.dev/dl/) installed): + +```bash +go build cmd/repl/repl.go +``` + +This will build the `repl` binary and write it out in your current directory. +You can then run the binary: + +```bash +./repl +``` + +* If an invalid statement is input, inputting an empty line clears the current +cache +* Typing in `exit` will quit the program. +* To continue a statement across multiple lines type a backslash (\) character +before submitting that line + +ex: +``` +define multiline_example: \ + @2014 + 1 year +``` + +Note: While writing multiple statements this way is technically supported +the output ordering of the results is currently undefined. + +You may also pass in additional flags to `repl` in order to load some FHIR data +or load an external input CQL library, for example: + +```bash +./repl \ + -cql_file="path/to/my/file.cql" \ + -bundle_file="path/to/my/patient_or_resource.json" +``` + +See the Flags section below for more. + +## Flags + +**bundle_file** -- Optional + +The path to a single bundle FHIR resource (often a patient). This is the +resource file the cql will evaluate against for all executions in the +REPL insance. + +**cql_file** -- Optional + +The path to a CQL file. The contents of this file are seeded into the current +context and outputs are displayed before the execution of the first REPL loop. + +**valuesets_dir** -- Optional + +The path to a directory containing json definitions of FHIR valuesets. + +## Future Enhancements + +* Right now the repl can't tell if an expression is unfinished or incorrect. +`define test:` is an unfinished statement that would be nice if we could +handle separately from invalid statements. +* Ability to run against multiple bundle files. This would require some work +to ensure the output for this were readable. +* Ability to clear the current context and load a different bundle resource +to evaluate against. diff --git a/cmd/repl/repl.go b/cmd/repl/repl.go new file mode 100644 index 0000000..202c433 --- /dev/null +++ b/cmd/repl/repl.go @@ -0,0 +1,264 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The repl is a tool that can be used to interact with the CQL engine iteratively. +package main + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "log" + "os" + "strings" + + "flag" + "github.com/google/cql" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/google/cql/terminology" +) + +var ( + // In the future we could add an optional alternative flag taking in a directory. + // This would enable running against multiple resources but may need some work to have + // a readable output. + bundleFile = flag.String("bundle_file", "", + "Path to a single bundle file to seed into the REPL.") + valuesetsDir = flag.String("valuesets_dir", "", + "Directory containing JSON versions of FHIR valuesets.") + cqlFile = flag.String("cql_file", "", + "Path to a single CQL file to seed into the REPL.") +) + +const usageMessage = `This program runs a simple CQL interface for experimenting with the CQL +language in a live environment. + +Users can specify an input CQL file, a bundle file resource and a directory +containing FHIR datasets all as inputs to seed data into the REPL ecosystem. + +The REPL will then proceed to execute any CQL input text input into the terminal and print +any results. If a line fails to properly parse or execute an error is returned and any inputs are +cleared from the current context. + +The '\' character can be used during execution to denote the continuation of an expression +to the next line. + +Inputting an empty line is another way of clearing any active context. + +Typing 'exit' will exit the program.` + +func init() { + defaultUsage := flag.Usage + flag.Usage = func() { + fmt.Fprintln(os.Stderr, usageMessage) + defaultUsage() + } +} + +// validateFlags checks to ensure that the input flags are valid. +func validateFlags(cqlFile, bundleFile, valuesetsDir string) error { + // Validate the required bundle file. + if bundleFile != "" && !strings.HasSuffix(bundleFile, ".json") { + return fmt.Errorf("--bundle_file when specified, is required to be a valid json file path, check that the input path is valid") + } + + // Validate the optional CQL input file + if cqlFile != "" && !strings.HasSuffix(cqlFile, ".cql") { + return fmt.Errorf("--cql_file flag is required to be a valid .cql file path if provided, check that the input path is valid") + } + + // Validate the optional valueset directory is valid. + if valuesetsDir != "" { + if _, err := os.ReadDir(valuesetsDir); err != nil { + return err + } + } + + return nil +} + +// runREPL executes the REPL loop. +// When reading user input `exit` will break from the loop, empty lines +// will clear any current cql context, and lines ending with `\` will be +// treated as continued lines and the current context will be continued +// on through the next line. +func runREPL(seedCQLLibs []string, bundleFileRetriever *local.Retriever, tp terminology.Provider, prevResults result.Libraries) { + in := bufio.NewReader(os.Stdin) + var allText, currExpr string + + for { + if currExpr == "" { + fmt.Print("> ") + } else { + fmt.Print(">> ") + } + + input, err := in.ReadString('\n') + if err != nil { + fmt.Printf("Failed to read input: %v\n", err) + continue + } + input = strings.TrimSpace(input) + if input == "exit" { + fmt.Println("Exiting...") + return + } + if input == "list_defs" { + var defsList []string + for libKey, libResult := range prevResults { + for defKey := range libResult { + defsList = append(defsList, libKey.Name+"."+defKey) + } + } + fmt.Println(strings.Join(defsList, "\n")) + continue + } + if input == "" { + // If the current expression cache still has data but an empty string + // was entered, clear the cache and try again. + if currExpr != "" { + fmt.Printf("Failed to parse expression text: \n%s\n", currExpr) + currExpr = "" + } + continue + } + if strings.HasSuffix(input, `\`) { + input = strings.TrimSpace(input[:len(input)-1]) + currExpr += input + continue + } + + currExpr += input + currCQL := append(seedCQLLibs, allText+currExpr) + evalResults, err := runCQLEngine(currCQL, bundleFileRetriever, tp) + if err != nil { + fmt.Println(err) + currExpr = "" + continue + } + resultsText, err := evalResultsDelta(evalResults, prevResults) + if err != nil { + fmt.Print(err) + continue + } + // Previous expression evaluated, reset vars and output results. + prevResults = evalResults + allText += currExpr + "\n" + currExpr = "" + fmt.Println(resultsText) + } +} + +// runCQLEngine runs the CQL engine and returns the results of that execution or error. +func runCQLEngine(cqlLibs []string, bundleFileRetriever *local.Retriever, tp terminology.Provider) (result.Libraries, error) { + fhirDM, err := cql.FHIRDataModel("4.0.1") + if err != nil { + log.Fatal(err) + } + elm, err := cql.Parse(context.Background(), cqlLibs, cql.ParseConfig{DataModels: [][]byte{fhirDM}}) + if err != nil { + // Failed to parse. This case returns error for already defined identifiers + // and all other parsing related errors. + return nil, err + } + + results, err := elm.Eval(context.Background(), bundleFileRetriever, cql.EvalConfig{ReturnPrivateDefs: true, Terminology: tp}) + if err != nil { + // Failed during execution. + return nil, err + } + return results, nil +} + +// evalResultsDelta returns the delta between the previous and current results. +// +// Parse the results and return the json representation of the evaluation results. +// For now naively use the previous results as way to parse out which evaluation results +// do not need to be emitted again. +// In the future we may look into ways to iteratively evaluate cql and only return the +// resulting new results rather than re-evaluating everything. +// Currently there is no way to order the outputs based on the declaration order of input statements. +func evalResultsDelta(evalResults, previousResults result.Libraries) (string, error) { + var resultDelta []string + for libKey, libResult := range evalResults { + for defKey, defObj := range libResult { + // We previously output the result of this definition. + if _, prevHasDefKey := previousResults[libKey][defKey]; prevHasDefKey { + continue + } + + jr, err := json.Marshal(defObj) + if err != nil { + return "", err + } + resultDelta = append(resultDelta, string(jr)) + } + } + return strings.Join(resultDelta, "\n"), nil +} + +func main() { + flag.Parse() + if err := validateFlags(*cqlFile, *bundleFile, *valuesetsDir); err != nil { + log.Fatal(err) + } + fmt.Println("Welcome to the CQL REPL! Type exit to leave.") + + retriever := &local.Retriever{} + if *bundleFile != "" { + fileData, err := os.ReadFile(*bundleFile) + if err != nil { + log.Printf("Error: failed to read input bundle file with error, %v", err) + } + retriever, err = local.NewRetrieverFromR4Bundle(fileData) + if err != nil { + log.Printf("Error: failed to initialize the bundle retriever with error, %v", err) + } + } + + var tp *terminology.LocalFHIRProvider + if *valuesetsDir != "" { + var err error + if tp, err = terminology.NewLocalFHIRProvider(*valuesetsDir); err != nil { + log.Fatal(err) + } + } + + var cqlInput []string + evalResults := result.Libraries{} + if *cqlFile != "" { + bytes, err := os.ReadFile(*cqlFile) + if err != nil { + log.Printf("Error: failed to read input CQL file with error, %v", err) + } + cqlText := string(bytes) + if cqlText != "" { + cqlInput = append(cqlInput, cqlText) + } + evalResults, err = runCQLEngine(cqlInput, retriever, tp) + if err != nil { + fmt.Println("Ran into error while evaluating input CQL quitting.") + log.Fatal(err) + } + resultsText, err := evalResultsDelta(evalResults, result.Libraries{}) + if err != nil { + fmt.Print(err) + } + fmt.Println(resultsText) + } + + runREPL(cqlInput, retriever, tp, evalResults) +} diff --git a/cmd/repl/repl_test.go b/cmd/repl/repl_test.go new file mode 100644 index 0000000..414a2be --- /dev/null +++ b/cmd/repl/repl_test.go @@ -0,0 +1,100 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "strings" + "testing" +) + +func TestValidateFlags(t *testing.T) { + tests := []struct { + name string + cqlInputText string + bundleFileText string + valuesetDirText string + }{ + { + name: "No input flags is valid", + cqlInputText: "", + bundleFileText: "", + valuesetDirText: "", + }, + { + name: "cql file suffix is valid", + cqlInputText: "/tmp/cql.cql", + bundleFileText: "", + valuesetDirText: "", + }, + { + name: "bundle json suffix is valid", + cqlInputText: "", + bundleFileText: "/tmp/bundle.json", + valuesetDirText: "", + }, + { + name: "valid directory is valid", + cqlInputText: "", + bundleFileText: "", + valuesetDirText: t.TempDir(), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if err := validateFlags(tc.cqlInputText, tc.bundleFileText, tc.valuesetDirText); err != nil { + t.Errorf("validateFlags() returned unexpected error: %v", err) + } + }) + } +} + +func TestValidateFlagsError(t *testing.T) { + tests := []struct { + name string + cqlInputText string + bundleFileText string + valuesetDirText string + wantErr string + }{ + { + name: "cql file without cql suffix returns error", + cqlInputText: "/tmp/cql.txt", + bundleFileText: "", + valuesetDirText: "", + wantErr: "--cql_file flag is required to be a valid .cql", + }, + { + name: "bundle json file without json suffix returns error", + cqlInputText: "", + bundleFileText: "/tmp/bundle.txt", + valuesetDirText: "", + wantErr: "--bundle_file when specified, is required to be a valid json file", + }, + { + name: "invalid directory returns error", + cqlInputText: "", + bundleFileText: "", + valuesetDirText: "/my/fake/dir", + wantErr: "no such file or directory", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if err := validateFlags(tc.cqlInputText, tc.bundleFileText, tc.valuesetDirText); !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("validateFlags() returned unexpected error (-want +got):\n%s, %s", tc.wantErr, err.Error()) + } + }) + } +} diff --git a/cql.go b/cql.go new file mode 100644 index 0000000..eb2987d --- /dev/null +++ b/cql.go @@ -0,0 +1,156 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cql provides tools for parsing and evaluating CQL. +package cql + +import ( + "context" + "fmt" + "time" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/retriever" + "github.com/google/cql/terminology" +) + +// ParseConfig configures the parsing of CQL to our internal ELM like data structure. +type ParseConfig struct { + // DataModels are the xml model info files of the data models that will be used by the parser and + // interpreter. The system model info is included by default. DataModels are optional and could be + // nil in which case the CQL can only use the system data model. + DataModels [][]byte + + // Parameters map between the parameters DefKey and a CQL literal. The DefKey specifies the + // library and the parameters name. The CQL Literal cannot be an expression definition, valueset + // or other CQL construct. It cannot reference other definitions or call functions. It is parsed + // at Term in the CQL grammar. Examples of parameters could be 100, 'string', + // Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) or {1, 2}. Parameters are optional and + // could be nil. + Parameters map[result.DefKey]string +} + +// Parse parses CQL libraries into our internal ELM like data structure, which can then be +// evaluated. +// Errors returned by Parse will always be a result.EngineError. +func Parse(ctx context.Context, libs []string, config ParseConfig) (*ELM, error) { + p, err := parser.New(ctx, config.DataModels) + if err != nil { + return nil, err + } + parsedLibs, err := p.Libraries(ctx, libs, parser.Config{}) + if err != nil { + return nil, err + } + parsedParams, err := p.Parameters(ctx, config.Parameters, parser.Config{}) + if err != nil { + return nil, err + } + + return &ELM{ + dataModels: p.DataModel(), + parsedParams: parsedParams, + parsedLibs: parsedLibs, + }, nil +} + +// EvalConfig configures the interpreter to evaluate ELM to final CQL Results. +type EvalConfig struct { + // Terminology is the interface through which the interpreter connects to terminology servers. If + // the CQL being evaluated does not require a terminology server this can be left nil. To connect + // to a terminology server you will need to implement the terminology.Provider interface, or use + // one of the included terminology providers. See the terminology package for more details. + Terminology terminology.Provider + + // EvaluationTimestamp is the time at which the eval request will be executed. The timestamp is + // used by CQL system operators like Today() and Now(). If not provided EvaluationTimestamp will + // default to time.Now() called at the start of the eval request. + EvaluationTimestamp time.Time + + // ReturnPrivateDefs if true will return all private definitions in result.Libraries. By default + // only public definitions are returned. + ReturnPrivateDefs bool +} + +// Eval executes the parsed CQL against the retriever. The retriever is the interface through which +// the interpreter retrieves external data. So if for example executing the parsed CQL against a +// list of patients, Eval can be called once for each patient with a retriever initialized to +// retrieve data for that patient. To connect to a particular data source you will need to implement +// the retriever.Retriever interface, or use one of the included retrievers. See the retriever +// package for more details. The retriever can be nil if the CQL does not fetch external data. Eval +// should not be called from multiple goroutines on a single *ELM. +// Errors returned by Eval will always be a result.EngineError. +func (e *ELM) Eval(ctx context.Context, retriever retriever.Retriever, config EvalConfig) (result.Libraries, error) { + c := interpreter.Config{ + DataModels: e.dataModels, + Parameters: e.parsedParams, + Retriever: retriever, + Terminology: config.Terminology, + EvaluationTimestamp: config.EvaluationTimestamp, + ReturnPrivateDefs: config.ReturnPrivateDefs, + } + + return interpreter.Eval(ctx, e.parsedLibs, c) +} + +// ELM is the parsed CQL, ready to be evaluated. +type ELM struct { + dataModels *modelinfo.ModelInfos + parsedParams map[result.DefKey]model.IExpression + parsedLibs []*model.Library +} + +// FHIRDataModelAndHelpersLib returns the model info xml file for a FHIR data model and the +// FHIRHelpers CQL library. Currently only version 4.0.1 is supported. +func FHIRDataModelAndHelpersLib(version string) (fhirDM []byte, fhirHelpers string, err error) { + fhirDM, err = FHIRDataModel(version) + if err != nil { + return + } + fhirHelpers, err = FHIRHelpersLib(version) + if err != nil { + return + } + return +} + +// FHIRHelpersLib returns the FHIRHelpers CQL library. Currently only version 4.0.1 is supported. +func FHIRHelpersLib(version string) (string, error) { + if version != "4.0.1" { + return "", fmt.Errorf("FHIRHelpersLib only supports version 4.0.1, got: %v", version) + } + fhirHelpers, err := embeddata.FHIRHelpers.ReadFile("third_party/cqframework/FHIRHelpers-4.0.1.cql") + if err != nil { + return "", fmt.Errorf("internal error - could not read FHIRHelpers-4.0.1.cql: %w", err) + } + return string(fhirHelpers), nil +} + +// FHIRDataModel returns the model info xml file for a FHIR data model. Currently only version 4.0.1 +// is supported. +func FHIRDataModel(version string) ([]byte, error) { + if version != "4.0.1" { + return nil, fmt.Errorf("FHIRDataModel only supports version 4.0.1, got: %v", version) + } + fhirMI, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + return nil, fmt.Errorf("internal error - could not read fhir-modelinfo-4.0.1.xml: %w", err) + } + return fhirMI, nil +} diff --git a/cql_test.go b/cql_test.go new file mode 100644 index 0000000..7973bf7 --- /dev/null +++ b/cql_test.go @@ -0,0 +1,369 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cql_test + +import ( + "context" + "errors" + "testing" + + "github.com/google/cql" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/retriever" + "github.com/google/cql/tests/enginetests" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +// CQL Engine tests are for testing the top level CQL Engine API. For detailed testing see the tests +// folder. + +func TestCQL(t *testing.T) { + tests := []struct { + name string + cql []string + retriever retriever.Retriever + parserConfig cql.ParseConfig + evalConfig cql.EvalConfig + wantResult result.Value + wantSourceExpression model.IExpression + wantSourceValues []result.Value + }{ + { + name: "Simple Query with Retriever", + cql: []string{dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' + context Patient + define TESTRESULT: [Encounter] E`), + fhirHelpers(t), + }, + parserConfig: cql.ParseConfig{ + DataModels: [][]byte{fhirDataModel(t)}, + }, + retriever: enginetests.BuildRetriever(t), + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, result.Named{Value: enginetests.RetrieveFHIRResource(t, "Encounter", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}, + }), + wantSourceExpression: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "E", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Encounter", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Encounter", + CodeProperty: "type", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + }, + }, + }, + wantSourceValues: []result.Value{ + newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, result.Named{Value: enginetests.RetrieveFHIRResource(t, "Encounter", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}, + }), + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + elm, err := cql.Parse(context.Background(), tc.cql, tc.parserConfig) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + + results, err := elm.Eval(context.Background(), tc.retriever, tc.evalConfig) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + + gotResult := getTESTRESULTWithSources(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceExpression, gotResult.SourceExpression(), protocmp.Transform()); tc.wantSourceExpression != nil && diff != "" { + t.Errorf("Eval SourceExpression diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceValues, gotResult.SourceValues(), protocmp.Transform()); tc.wantSourceValues != nil && diff != "" { + t.Errorf("Eval SourceValues diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestCQL_ParseErrors(t *testing.T) { + tests := []struct { + name string + cql []string + retriever retriever.Retriever + parserConfig cql.ParseConfig + wantErr error + }{ + { + name: "Invalid named library", + cql: []string{dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + context Patient + define TESTRESULT: [Encounter] E`)}, + retriever: enginetests.BuildRetriever(t), + wantErr: &parser.LibraryErrors{ + LibKey: result.LibKey{Name: "TESTLIB", IsUnnamed: false /* Version is ignored*/}, + Errors: []*parser.ParsingError{ + {Message: "FHIR 4.0.1 data model not found", Line: 3, Column: 0}, + {Message: "using declaration has not been set", Line: 4, Column: 0}, + {Message: "using declaration has not been set", Line: 5, Column: 20}, + {Message: "retrieves cannot be performed on type System.Any", Line: 5, Column: 19}, + }, + }, + }, + { + name: "Invalid unnamed library", + cql: []string{"define Foo: 1 + 'a'"}, + retriever: enginetests.BuildRetriever(t), + wantErr: &parser.LibraryErrors{ + LibKey: result.LibKey{Name: "Unnamed Library", IsUnnamed: true /* Version is ignored*/}, + Errors: []*parser.ParsingError{ + { + Message: "could not resolve Add(System.Integer, System.String): no matching overloads", + Line: 1, + Column: 12, + }, + }, + }, + }, + { + name: "Invalid parameter", + cql: []string{dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + context Patient + define TESTRESULT: [Encounter] E`), + fhirHelpers(t), + }, + retriever: enginetests.BuildRetriever(t), + parserConfig: cql.ParseConfig{ + Parameters: map[result.DefKey]string{ + result.DefKey{Name: "param name", Library: result.LibKey{Name: "TESTLIB", Version: "1.0.0"}}: "invalid value", + }, + DataModels: [][]byte{fhirDataModel(t)}, + }, + wantErr: &parser.ParameterErrors{ + DefKey: result.DefKey{ + Name: "param name", + Library: result.LibKey{Name: "TESTLIB", Version: "1.0.0"}, + }, + Errors: []*parser.ParsingError{{Message: "must be a single literal"}}, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := cql.Parse(context.Background(), tc.cql, tc.parserConfig) + if err == nil { + t.Fatalf("Parse succeeded, expected error") + } + if diff := cmp.Diff(tc.wantErr, err, cmpopts.IgnoreFields(parser.LibraryErrors{}, "LibKey.Version")); diff != "" { + t.Errorf("Parse returned err: %v, want %v (-want, +got): %v", err, tc.wantErr, diff) + } + }) + } +} + +func TestCQL_EvalErrors(t *testing.T) { + tests := []struct { + name string + cql []string + retriever retriever.Retriever + parserConfig cql.ParseConfig + evalConfig cql.EvalConfig + }{ + { + name: "Retriever nil error", + cql: []string{dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' + context Patient + define TESTRESULT: [Encounter] E`), + fhirHelpers(t), + }, + parserConfig: cql.ParseConfig{ + DataModels: [][]byte{fhirDataModel(t)}, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + elm, err := cql.Parse(context.Background(), tc.cql, tc.parserConfig) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + + _, err = elm.Eval(context.Background(), tc.retriever, tc.evalConfig) + if err == nil { + t.Fatalf("Eval succeeded, expected error") + } + engErr, ok := err.(result.EngineError) + if !ok { + t.Fatalf("Returned error (%s) was not a result.EngineError", err) + } + if !errors.Is(engErr.ErrType, result.ErrEvaluationError) { + t.Errorf("Returned error (%s) was not a result.ErrEvaluationError error", err) + } + }) + } +} + +func TestCQL_MultipleEvals(t *testing.T) { + tests := []struct { + name string + cql []string + retriever retriever.Retriever + parserConfig cql.ParseConfig + evalConfig cql.EvalConfig + wantResult result.Value + wantSourceExpression model.IExpression + wantSourceValues []result.Value + }{ + { + name: "Simple Query with Retriever", + cql: []string{dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' + context Patient + define TESTRESULT: [Encounter] E`), + fhirHelpers(t), + }, + parserConfig: cql.ParseConfig{ + DataModels: [][]byte{fhirDataModel(t)}, + }, + retriever: enginetests.BuildRetriever(t), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: enginetests.RetrieveFHIRResource(t, "Encounter", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}, + }), + wantSourceExpression: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "E", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Encounter", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Encounter", + CodeProperty: "type", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + }, + }, + }, + wantSourceValues: []result.Value{ + newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: enginetests.RetrieveFHIRResource(t, "Encounter", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}, + }), + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + elm, err := cql.Parse(context.Background(), tc.cql, tc.parserConfig) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + + for i := 0; i < 3; i++ { + results, err := elm.Eval(context.Background(), tc.retriever, tc.evalConfig) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + + gotResult := getTESTRESULTWithSources(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceExpression, gotResult.SourceExpression(), protocmp.Transform()); tc.wantSourceExpression != nil && diff != "" { + t.Errorf("Eval SourceExpression diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceValues, gotResult.SourceValues(), protocmp.Transform()); tc.wantSourceValues != nil && diff != "" { + t.Errorf("Eval SourceValues diff (-want +got)\n%v", diff) + } + } + }) + } +} + +func fhirDataModel(t testing.TB) []byte { + t.Helper() + fdm, err := cql.FHIRDataModel("4.0.1") + if err != nil { + t.Fatalf("FHIRDataModel returned unexpected error: %v", err) + } + return fdm +} + +func fhirHelpers(t testing.TB) string { + t.Helper() + fh, err := cql.FHIRHelpersLib("4.0.1") + if err != nil { + t.Fatalf("FHIRHelepersLib returned unexpected error: %v", err) + } + return fh +} + +// getTESTRESULTWithSources finds the first TESTRESULT definition in any library and returns the +// result with sources. +func getTESTRESULTWithSources(t testing.TB, resultLibs result.Libraries) result.Value { + t.Helper() + + for _, resultLib := range resultLibs { + for name, res := range resultLib { + if name == "TESTRESULT" { + return res + } + } + } + + t.Fatalf("Could not find TESTRESULT expression definition") + return result.Value{} +} + +// newOrFatal returns a new result.Value or calls fatal on error. +func newOrFatal(t testing.TB, a any) result.Value { + t.Helper() + o, err := result.New(a) + if err != nil { + t.Fatalf("New(%v) returned unexpected error: %v", a, err) + } + return o +} diff --git a/docs/implementation.md b/docs/implementation.md new file mode 100644 index 0000000..f877e1e --- /dev/null +++ b/docs/implementation.md @@ -0,0 +1,180 @@ +# Overview + +This document provides an overview of the CQL Engine codebase. If you just want to use the engine, check out the [getting started](../README.md) section. + +**[parser](../parser)** - The parser validates and converts CQL strings into an Abstract Syntax Tree called Expression Model Language [ELM](https://cql.hl7.org/elm.html).\ +**[interpreter](../interpreter)** - The interpreter evaluates the ELM using a retriever and terminology provider and returns the results.\ +**[model](../model)** - Model is our internal representation of ELM. Model is produced by the parser and evaluated by the interpreter.\ +**[retriever](../retriever)** - The retriever is an interface called by the interpreter to retrieve data from external databases. There are implementations to connect to a local FHIR bundle or a FHIR bundle in GCP's GCS. Developers can implement their own retrievers to connect to other databases.\ +**[terminology](../terminology)** - Terminology provider is an interface called by the interpreter to fetch value sets from terminology servers. There are implementations to connect to local FHIR value sets. Developers can provide their own implementation to connect to other terminology servers.\ +**[result](../result)** - Result is our internal representation of CQL values. They are evaluated and returned by the interpreter.\ +**[types](../types)** - Types holds a representation of the CQL type system.\ +**[internal/modelinfo](../internal/modelinfo)** - Modelinfo parses model info XML files and provides high level functions like `IsSubType(child, base types.IType)` to interact with the data models.\ +**[internal/reference](../internal/reference)** - The reference resolver is shared by both the parser and the interpreter. It is responsible for storing and resolving references to expression definitions, aliases, function signatures, system operators, parameters... and more both within and across CQL libraries.\ +**[internal/convert](../internal/convert)** - Convert handles overload matching with implicit conversions. + +# Parser + +The [Parser package](../parser), parses CQL strings into Expression Model Language [ELM](https://cql.hl7.org/elm.html) that the [Interpreter package](../interpreter) can then evaluate. The [Model package](../model) holds our ELM like data structure. The Model package is almost completely one to one with ELM, with a few exceptions to make it work better in Golang. For example, in ELM [Binary Expression](https://cql.hl7.org/04-logicalspecification.html#binaryexpression) inherits from Operator Expression which inherits from Expression. Golang is not an object oriented language. Deep hierarchies are not useful so we dropped Operator Expressions. As much as possible we try to keep the Model package one to one with ELM so in the future we can easily import and export ELM directly. + +The parser uses ANTLR to implement a visitor pattern over the [CQL grammar](../internal/embeddata/cqframework/Cql.g4). Each VisitXXX is responsible for taking the ANTLR context and outputting a piece of the model.go tree. The CQL grammar is made up of both the Cql.g4 and fhirpath.g4 files. + +The parser is responsible for all validation. ANTLR and the grammar do some validation work, for example ANTLR would error when parsing `@20155-01-30` since it does not meet grammar's DATEFORMAT. But much of the work of validation needs to be implemented. For example, `@2015-01-99` meets the grammar but should fail since there are not 99 days in January. Errors that occur are not immediately returned. Instead we return a placeholder model.go (usually via v.badExpression()) and continue parsing. In the end a list of all errors as ParsingErrors are returned via the top level API. + +The parser is also responsible for overload matching and implicit conversions. Take for example, `1 + 4.5`. The [Add system operator](https://cql.hl7.org/09-b-cqlreference.html#add) defines the `+(left Decimal, right Decimal) Decimal` overload among others. The Parser uses the [conversion precedence](https://cql.hl7.org/03-developersguide.html#conversion-precedence) to score each of the Add overloads. If there is no matching overload an error is returned. If two overloads tie for the minimum score an ambiguous error is returned. Otherwise, the minimum scoring overload is returned with any necessary implicit conversions inserted. In this case a `model.ToDecimal` would be inserted to convert the 1 to 1.0. The Parser inserts all necessary model.ToXXX, model.As and FHIRHelpers.ToXXX from the conversion info in the model info. + +CQL supports parameters which can override the parameters in a Library. Parameters have restrictions like not referencing expression definitions and being computable at "compile-time". To support parameters the parser takes parameters as strings and parses them starting at Term in the CQL grammar. Starting at Term in the grammar restricts the parameters to only being CQL Literals or selectors like `Interval[@2013-01-01, @2014-01-01)`. The parser does not support parameters like `1 + 2` which according to the CQL spec should be allowed. + +In the parser, we initialize the reference resolver with `reference.Resolver[func() model.IExpression, func() model.IExpression]` This is because the reference resolver should return copies of the resolved model structs. The easiest way to accomplish this in Golang was to return a function that can generate a new struct. + +## Example - Parsing System Operator ToDate +CQL System Operators are any unary, binary, ternary or nary expression that inherit from [operator expression](https://cql.hl7.org/04-logicalspecification.html#operatorexpression). This includes operators like `+` and system functions like `ToDate()`. System Operators do not include things like Query or Parameters. System operators are described in detail in the [CQL reference](https://cql.hl7.org/09-b-cqlreference.html). System Operators have overloads that need to be supported. Taking [Less()](https://cql.hl7.org/09-b-cqlreference.html#less) as an example: + +``` +<(left Integer, right Integer) Boolean +<(left Long, right Long) Boolean +<(left Decimal, right Decimal) Boolean +<(left Quantity, right Quantity) Boolean +<(left Date, right Date) Boolean +<(left DateTime, right DateTime) Boolean +<(left Time, right Time) Boolean +<(left String, right String) Boolean +``` + +To add support for parsing a system operator add a struct to the `loadSystemOperators()` function in [operators.go](../parser/operators.go) file. + +```go +{ + name: "Less", + operands: [][]types.IType{ + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Long, types.Long}, + []types.IType{types.Decimal, types.Decimal}, + []types.IType{types.Quantity, types.Quantity}, + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + []types.IType{types.String, types.String}, + }, + model: func() model.IExpression { + return &model.Less{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, +}, +``` + +`loadSystemOperators()` adds `Less(Date)` and other overloads to the [reference resolver](../internal/reference/reference.go). When we call `ResolveLocalFunc("Less", operands...)` the reference resolver calls [convert.OverloadMatch()](../internal/convert/convert.go) with all the Less overloads that have been loaded. `convert.OverloadMatch` returns the least converting match by summing the score of each of the operands according to the [conversion precedence](https://cql.hl7.org/03-developersguide.html#conversion-precedence). + +Let's take a look at what would happen if we called `Less()` with an operand of `Named`. Many conversions are hard coded, but some are defined in the data model. The [modelinfo package](../internal/modelinfo) parses model info xml files saving all named types, their properties and their implicit conversions. When we call `modelinfo.IsImplicitlyConvertible(from, to types.IType)` it returns `FHIRHelpers.ToDate` which converts the data model's `Named` to a `System.Date`, matching our overload. `convert.OverloadMatch` finds this is the least converting match with a score of 5 (Implicit Conversion To Simple Type) and returns the Operands wrapped in a `model.FunctionRef`. + +```go +&model.FunctionRef{ + Expression: model.ResultType(types.Date), + Name: "ToDate", + LibraryName: "FHIRHelpers", + Operands: []model.IExpression{...Original Operand...}, +}, +``` + +System operators like less can be called in CQL as a function `Less(1, 2)` or non functional syntax `1 < 2`. The functional syntax is defined in the [translation semantics](https://cql.hl7.org/06-translationsemantics.html#functions). In cases like these ensure that less is added to the `loadSystemOperators()`. Then implement a visitor to handle the non function call grammar. The visitor should call `v.parseFunction()` which validates that the correct type was passed to the system operator. + +```go +func (v *visitor) VisitInequalityExpression(ctx *cql.InequalityExpressionContext) model.IExpression { + name := ctx.GetChild(1).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch name { + case "<": + m, err = v.parseFunction("", "Less", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case ">": + m, err = v.parseFunction("", "Greater", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case "<=": + m, err = v.parseFunction("", "LessOrEqual", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case ">=": + m, err = v.parseFunction("", "GreaterOrEqual", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + default: + return v.badExpression("internal error - grammar should not allow this InequalityExpression", ctx) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + return m +} +``` + +Finally, a small quirk, for system operators that can be called in multiple ways, such as exists, the function call syntax often flows through `VisitParenthesizedTerm` and not `VisitFunction` as you may expect. + +# Interpreter + +The interpreter traverses the ELM like tree represented by the [Model package](../model) to evaluate the final results. The [Result package](../result) holds our representation of CQL values (ex CQL Integer or CQL Tuple) and helper functions to convert between CQL Values and Golang Values (ex ToInt32 which converts a CQL Integer to a Golang int32). + +For performance reasons the interpreter does very little validation and assumes that it is passed the correct model. For example, in the parser the reference package validates that the function signatures are unique. If you define `Foo(a Integer)` twice you will get an error. In the interpreter we assume that the model.go is correct and do not check that function signatures are unique. The interpreter also does not do any implicit conversions. The parser should have inserted any necessary operators or calls to FHIRHelper conversion functions such that types of the CQL Values exactly match the overloads of the system operators. ELM that may be valid according to the ELM spec, if translated directly to our internal model would not necessarily meet the validation requirements of our interpreter. In the future if we support ELM, the ELM to model.go mapping will need to do additional validation ensure it meets the assumptions of our interpreter. + +For each resulting CQL Value our interpreter builds a tree that can be used for debugging or explainability. Each CQL Value stores a Source Expression holding the expression that was used to calculate the Value and Source Values with the Values used by the Source Expression. For example, if we return a CQL Value of 9 resulting from `4 + 5`, then the Source Expression would be `model.Add` and the Source Values would be CQL Value 4 and 5. Since Source Values can also have their own Source Expression and Source Values a tree showing what went into calculating each CQL result is built. + +## Example - Evaluating Add + +The Operator Dispatcher is the core framework in the Interpreter that handles matching the correct overload of a CQL System Operator. Taking the [Add operator](https://cql.hl7.org/09-b-cqlreference.html#add) as an example. There are many overloads that need to be supported. + +``` ++(left Integer, right Integer) Integer ++(left Long, right Long) Long ++(left Decimal, right Decimal) Decimal ++(left Quantity, right Quantity) Quantity ++(left Date, right Quantity) Date ++(left DateTime, right Quantity) DateTime ++(left Time, right Quantity) Time +``` + +To add support for `+(left Decimal, right Decimal) Decimal` we first register the overload in [operator_dispatcher.go](../interpreter/operator_dispatcher.go). The Operator Dispatcher will now call `evalArithmeticDecimal` when we receive a model.Add with operands of type Decimal. The parser will insert all of the conversions necessary to exactly match one of the overloads. + +``` go +case *model.Add, *model.Subtract, *model.Multiply, *model.TruncatedDivide, *model.Modulo: + return []types.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: evalArithmeticInteger, + }, + { + Operands: []types.IType{types.Long, types.Long}, + Result: evalArithmeticLong, + }, + { + Operands: []types.IType{types.Decimal, types.Decimal}, + Result: evalArithmeticDecimal, + }, + }, true, nil +``` + +Now that the overload is registered we need to implement `evalArithmeticDecimal`. The golang files are organized following the layout of the CQL Reference so we add the implementation to [operator_arithmetic.go](../internal/operator_arithmetic.go). Every overload implementation starts with handling the null cases. We can then get the golang floats by calling `result.ToFloat64()`. + +``` go +func evalArithmeticDecimal(m model.IBinaryExpression, lVal result.Value, rVal result.Value) (result.Value, error) { + if result.IsNull(lVal) || result.IsNull(rVal) { + return result.New(nil) + } + l, err := result.ToFloat64(lVal) + if err != nil { + return nil, err + } + r, err := result.ToFloat64(rVal) + if err != nil { + return nil, err + } + + return arithmetic(m, l, r) +} +``` + +Code sharing between overloads should generally happen in helper functions that take golang values like `arithmetic[n float64 | int64 | int32](m model.IBinaryExpression, l, r n) (result.Value, error)`. There should be explicit overloads `evalArithmeticInteger`, `evalArithmeticLong`, `evalArithmeticDecimal`... that handle the logic specific to that overload before calling the shared helper. In some special cases like `Last(List) T` there can be one generic overload. + +# Testing + +- The Parser is tested independently with unit tests. Every *.go file in the Parser package is accompanied with a *_test.go file. +- Most of the "unit tests" for the interpreter are actually integration tests located in [tests/enginetests](../tests/enginetests). This was chosen over traditional unit tests, since an update to the parser meant all the model.go inputs to the interpreter unit tests would also need to be updated. Over time this led to the interpreter unit tests becoming out of date with what the parser produced. Enginetests start with CQL strings instead of model.go, but are otherwise treated as unit tests. +- [Largetests](../tests/largetests) are end-to-end tests, designed for complex CQL and complex input data. LargeTests also holds our benchmarks. +- [Spectests](../tests/spectests) are external CQL tests imported from https://github.com/cqframework/cql-tests. diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..8c0854b --- /dev/null +++ b/example_test.go @@ -0,0 +1,172 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cql_test + +import ( + "context" + "fmt" + "time" + + log "github.com/golang/glog" + "github.com/google/cql" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/lithammer/dedent" +) + +// This example demonstrates the CQL API by finding Observations that were effective during a +// measurement period. +func Example() { + // CQL can run on different data models such as FHIR or QDM. The data model defines the CQL Named + // types available in retrieves, their properties, subtyping and more. The parser always includes + // the system data model, but the model info files of other data models can be provided. In this + // example we use the FHIR data model so we can retrieve FHIR Observations and access their + // effective and id properties. We currently only support FHIR version 4.0.1 data model. + // + // FHIR Helpers is a CQL Library with helper functions to covert between + fhirDataModel, fhirHelpers, err := cql.FHIRDataModelAndHelpersLib("4.0.1") + if err != nil { + log.Fatal(err) + } + + // In this example we are returning a list of the ID's of Observations that were effective during + // the measurement period. + libs := []string{ + dedent.Dedent(` + library Example version '1.2.3' + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' + parameter MeasurementPeriod Interval + context Patient + + define EffectiveObservations: [Observation] O where O.effective in MeasurementPeriod return O.id.value + define FirstObservation: First(EffectiveObservations) + `), + fhirHelpers, + } + + // TODO(b/335206660): Golang contexts are not yet properly supported by our engine. + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + // Parameters override the values of the parameters defined in the CQL library. Parameters are a + // map from the library/parameter name to a string CQL literal. Any valid CQL literal syntax will + // be accepted, such as 400 or List>{1, 'stringParam'}. In this example we + // override the MeasurementPeriod parameter to an interval between 2017 and 2019. + parameters := map[result.DefKey]string{ + result.DefKey{ + Library: result.LibKey{Name: "Example", Version: "1.2.3"}, + Name: "MeasurementPeriod", + }: "Interval[@2017, @2019]"} + + // Parse will validate the data models and parse the libraries and parameters. ELM (which stands + // for Expression Logical Model) holds the parsed CQL, ready to be evaluated. Anything in the + // ParseConfig is optional. + elm, err := cql.Parse(ctx, libs, cql.ParseConfig{DataModels: [][]byte{fhirDataModel}, Parameters: parameters}) + if err != nil { + log.Fatalf("Failed to parse: %v", err) + } + + for _, id := range []string{"PatientID1", "PatientID2"} { + // The retriever is used by the interpreter to fetch FHIR resources on each CQL + // retrieve. In this case we are in the `context patient` and call `[Observation]` so the + // retriever will fetch all Observations for the particular Patient. + retriever, err := NewRetriever(id) + if err != nil { + log.Fatalf("Failed to build retriever: %v", err) + } + + // Eval executes the ELM (aka parsed CQL) against this particular instantiation of the + // retriever. Anything in EvalConfig is optional. + results, err := elm.Eval(ctx, retriever, cql.EvalConfig{}) + if err != nil { + log.Fatalf("Failed to evaluate: %v", err) + } + + // The results are stored in maps, and can be accessed via [result.LibKey][Definition]. The CQL + // string, list, integers... are stored in result.Value and can be converted to a golang value + // via GolangValue() or by passing the result.Value to a helper like result.ToString. Another + // option is to use MarshalJSON() to convert the result.Value to json, see the results package + // for more details. + cqlObservationID := results[result.LibKey{Name: "Example", Version: "1.2.3"}]["FirstObservation"] + + if result.IsNull(cqlObservationID) { + fmt.Printf("ID %v: null\n", id) + } else { + golangStr, err := result.ToString(cqlObservationID) + if err != nil { + log.Fatalf("Failed to get golang string: %v", err) + } + fmt.Printf("ID %v: %v\n", id, golangStr) + } + } + + // Output: + // ID PatientID1: null + // ID PatientID2: Observation2 +} + +func NewRetriever(patientID string) (*local.Retriever, error) { + Patient1Bundle := `{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "PatientID1", + "name": [{"given":["John", "Smith"], "family":"Doe"}]} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "Observation1", + "effectiveDateTime": "2012-04-02T10:30:10+01:00" + } + } + ] + }` + Patient2Bundle := `{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "PatientID2", + "name": [{"given":["Jane", "Smith"], "family":"Doe"}]} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "Observation2", + "effectiveDateTime": "2018-04-02T10:30:10+01:00" + } + } + ] + }` + switch patientID { + case "PatientID1": + return local.NewRetrieverFromR4Bundle([]byte(Patient1Bundle)) + case "PatientID2": + return local.NewRetrieverFromR4Bundle([]byte(Patient2Bundle)) + default: + return nil, fmt.Errorf("invalid patient id %v", patientID) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d317ac5 --- /dev/null +++ b/go.mod @@ -0,0 +1,82 @@ +module github.com/google/cql + +go 1.23 + +require ( + github.com/antlr4-go/antlr/v4 v4.13.0 + github.com/apache/beam/sdks/v2 v2.56.0 + github.com/golang/glog v1.2.1 + github.com/google/bulk_fhir_tools v0.1.7 + github.com/google/fhir/go v0.7.4 + github.com/google/fhir/go/protopath v0.7.4 + github.com/google/go-cmp v0.6.0 + github.com/kylelemons/godebug v1.1.0 + github.com/lithammer/dedent v1.1.0 + github.com/pborman/uuid v1.2.1 + google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 + google.golang.org/protobuf v1.34.1 + gopkg.in/gyuho/goraph.v2 v2.0.0-20160328020532-d460590d53a9 +) + +require ( + bitbucket.org/creachadair/stringset v0.0.14 // indirect + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute v1.25.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.7 // indirect + cloud.google.com/go/logging v1.9.0 // indirect + cloud.google.com/go/longrunning v0.5.6 // indirect + cloud.google.com/go/profiler v0.4.0 // indirect + cloud.google.com/go/storage v1.39.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/docker v25.0.5+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/gyuho/goraph v0.0.0-20220410190906-ad625acf7ae3 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nxadm/tail v1.4.11 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.18.0 // indirect + google.golang.org/api v0.171.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/grpc v1.64.0 // indirect + gopkg.in/retry.v1 v1.0.3 // indirect + gotest.tools/v3 v3.5.1 // indirect +) \ No newline at end of file diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dc72c63 --- /dev/null +++ b/go.sum @@ -0,0 +1,919 @@ +bitbucket.org/creachadair/stringset v0.0.9/go.mod h1:t+4WcQ4+PXTa8aQdNKe40ZP6iwesoMFWAxPGd3UGjyY= +bitbucket.org/creachadair/stringset v0.0.14 h1:t1ejQyf8utS4GZV/4fM+1gvYucggZkfhb+tMobDxYOE= +bitbucket.org/creachadair/stringset v0.0.14/go.mod h1:Ej8fsr6rQvmeMDf6CCWMWGb14H9mz8kmDgPPTdiVT0w= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= +cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= +cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= +cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE= +cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= +cloud.google.com/go/profiler v0.4.0 h1:ZeRDZbsOBDyRG0OiK0Op1/XWZ3xeLwJc9zjkzczUxyY= +cloud.google.com/go/profiler v0.4.0/go.mod h1:RvPlm4dilIr3oJtAOeFQU9Lrt5RoySHSDj4pTd6TWeU= +cloud.google.com/go/pubsub v1.37.0 h1:0uEEfaB1VIJzabPpwpZf44zWAKAme3zwKKxHk7vJQxQ= +cloud.google.com/go/pubsub v1.37.0/go.mod h1:YQOQr1uiUM092EXwKs56OPT650nwnawc+8/IjoUeGzQ= +cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/TylerBrock/colorjson v0.0.0-20180527164720-95ec53f28296/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/apache/beam/sdks/v2 v2.56.0 h1:dbtSGGbxTR9myE6JxRk5iAfEVVwnmtWVSu8lt8Pfz3s= +github.com/apache/beam/sdks/v2 v2.56.0/go.mod h1:Xjsof4TTctXyMT78woX0K3JF1efJR6NwL1VQUPls5/0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.28.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/bazelbuild/rules_go v0.24.5 h1:8S5qilf+Il5/TPMZQIOfzQDAZtkhB4jALiAnwRuisDM= +github.com/bazelbuild/rules_go v0.24.5/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/buger/jsonparser v0.0.0-20200322175846-f7e751efca13/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creachadair/staticfile v0.1.3/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= +github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= +github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsouza/fake-gcs-server v1.47.7 h1:56/U4rKY081TaNbq0gHWi7/71UxC2KROqcnrD9BRJhs= +github.com/fsouza/fake-gcs-server v1.47.7/go.mod h1:4vPUynN8/zZlxk5Jpy6LvvTTxItdTAObK4DYnp89Jys= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/bulk_fhir_tools v0.1.7 h1:WiYCgna9Rqrk0l9ChrnwV3tsFgdbYNCl0j3Be5PM0rs= +github.com/google/bulk_fhir_tools v0.1.7/go.mod h1:F7i3MKb/J7GnqF6k7mJoziNgD5Z78Dzr9+ScXQs1GZE= +github.com/google/fhir/go v0.7.4 h1:DZv5LOcX8JIO1hdWESHNm3CD0PiWREuxjYDYE6gmzn0= +github.com/google/fhir/go v0.7.4/go.mod h1:WF6g9QjYPqcQed319oPaRT5IcYWIRz610X3mxIt5TgU= +github.com/google/fhir/go/protopath v0.7.4 h1:UnSxLhWaj0S2LwDmwEaoEkgtHIrRSwE2LiezXPbVuxI= +github.com/google/fhir/go/protopath v0.7.4/go.mod h1:HbcIpajWRTTeeSSi7maEzVBX9Wiq+CpUm1P3/dUXIUg= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/gyuho/goraph v0.0.0-20220410190906-ad625acf7ae3 h1:sqdhbHgf04uwTLE03/FdSoaQbSy2z/hmimOAR/3OmcM= +github.com/gyuho/goraph v0.0.0-20220410190906-ad625acf7ae3/go.mod h1:NtSxZCD+s3sZFwbW6WceOcUD83HM9XD5OE2r4c0P8eg= +github.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4= +github.com/hashicorp/consul/sdk v0.5.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= +github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/krishicks/yaml-patch v0.0.10/go.mod h1:Sm5TchwZS6sm7RJoyg87tzxm2ZcKzdRE4Q7TjNhPrME= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1/go.mod h1:vuvdOZLJuf5HmJAJrKV64MmozrSsk+or0PB5dzdfspg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5-0.20200416053754-163badb3bac6/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= +github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pires/go-proxyproto v0.0.0-20191211124218-517ecdf5bb2b/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= +github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9Se4YQIRkDTCw1EJls9xTUCaCeRM= +github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE= +github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tchap/go-patricia v0.0.0-20160729071656-dd168db6051b/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tinylib/msgp v1.1.1/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/z-division/go-zookeeper v0.0.0-20190128072838-6d7457066b9b/go.mod h1:JNALoWa+nCXR8SmgLluHcBNVJgyejzpKPZk9pX2yXXE= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191219041853-979b82bfef62/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190926190326-7ee9db18f195/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/DataDog/dd-trace-go.v1 v1.17.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gyuho/goraph.v2 v2.0.0-20160328020532-d460590d53a9 h1:e+cCz00LJpQ3gCxq5njRASsDJp/HxRf5oevNlqhwXd8= +gopkg.in/gyuho/goraph.v2 v2.0.0-20160328020532-d460590d53a9/go.mod h1:1Z4GXqa9GSE8TEGrYn/d0l3V5xF/4xmkK09FZjBHtnM= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ldap.v2 v2.5.0/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= +gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0= +k8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY= +k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY= +k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ= +k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ= +k8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +vitess.io/vitess v0.7.0/go.mod h1:MjQFT3yaDsYxY+fwUwxqD0d7MRx7c8+wx0nMeXC9U/s= \ No newline at end of file diff --git a/internal/convert/convert.go b/internal/convert/convert.go new file mode 100644 index 0000000..169c424 --- /dev/null +++ b/internal/convert/convert.go @@ -0,0 +1,593 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package convert is responsible for all things related to implicit conversions. This includes +// inserting necessary implicit conversions into the model at parse time and finding the exact match +// overload at run time in the interpreter. +package convert + +import ( + "errors" + "fmt" + "math" + "strings" + + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/model" + "github.com/google/cql/types" +) + +// ErrAmbiguousMatch is returned when two or more overloads were matched with the same score. +var ErrAmbiguousMatch = errors.New("ambiguous match") + +// ErrNoMatch is returned when no overloads were matched. +var ErrNoMatch = errors.New("no matching overloads") + +// Overload holds the the declared operands and the result returned if those operands are matched by +// an invocation. +type Overload[F any] struct { + Operands []types.IType + // Result is what is returned by OverloadMatch. + Result F +} + +// MatchedOverload returns the result of the OverloadMatch function. +type MatchedOverload[F any] struct { + // Result is the result of the overload that was matched. + Result F + // WrappedOperands are the operands wrapped in all necessary system operators and function refs to + // convert them to the matched overload. + WrappedOperands []model.IExpression +} + +// OverloadMatch returns MatchedOverload on a match, and an error if there is no match or if the +// match is ambiguous. OverloadMatch returns the least converting match by summing the conversion +// score of each of the arguments. Ambiguous is returned if the least converting match has the same +// conversion score. Example of an ambiguous match: +// +// Declared: Foo(Long), Foo(Decimal) +// Invocation: Foo(Integer) +// +// Name is the function name and is only used for error messages. +// https://cql.hl7.org/03-developersguide.html#function-resolution +// https://cql.hl7.org/03-developersguide.html#conversion-precedence +func OverloadMatch[F any](invoked []model.IExpression, overloads []Overload[F], modelinfo *modelinfo.ModelInfos, name string) (MatchedOverload[F], error) { + if len(overloads) == 0 { + return MatchedOverload[F]{}, fmt.Errorf("could not resolve %v(%v): %w", name, OperandsToString(invoked), ErrNoMatch) + } + + // To create concreteOverloads we go through the overloads, find any that have generics and + // replace the generics with concrete types. + concreteOverloads := make([]Overload[F], 0, len(overloads)) + for _, overload := range overloads { + if isGeneric(overload.Operands) { + concreteOverload, matched, err := convertGeneric(invoked, overload, modelinfo) + if err != nil { + return MatchedOverload[F]{}, fmt.Errorf("%v(%v): %w", name, OperandsToString(invoked), err) + } + if matched { + concreteOverloads = append(concreteOverloads, concreteOverload) + } + } else { + concreteOverloads = append(concreteOverloads, overload) + } + } + + ambiguous := false + minScore := math.MaxInt + matched := MatchedOverload[F]{WrappedOperands: make([]model.IExpression, len(invoked))} + for _, overload := range concreteOverloads { + res, err := operandsImplicitConverter(OperandsToTypes(invoked), overload.Operands, invoked, modelinfo) + if err != nil { + return MatchedOverload[F]{}, fmt.Errorf("%v(%v): %w", name, OperandsToString(invoked), err) + } + if res.Matched && res.Score == minScore { + // The least converting match is now ambiguous + ambiguous = true + continue + } + + if res.Matched && res.Score < minScore { + // A new least converting match + ambiguous = false + minScore = res.Score + matched.Result = overload.Result + // Beware of the shallow copy + matched.WrappedOperands = res.WrappedOperands + } + } + if ambiguous { + return matched, fmt.Errorf("%v(%v) %w", name, OperandsToString(invoked), ErrAmbiguousMatch) + } + + if minScore != math.MaxInt { + // Matched with conversion to a single overloaded function. + return matched, nil + } + return MatchedOverload[F]{}, fmt.Errorf("could not resolve %v(%v): %w", name, OperandsToString(invoked), ErrNoMatch) +} + +type convertedOperands struct { + // Matched is true if the invoked types can be converted to the declared types. + Matched bool + // Score is the least converting score of the conversion based on the CQL conversion precedence. + // The score of each operand are added together. Multiple conversions on the same operand are not + // taken into account. https://cql.hl7.org/03-developersguide.html#conversion-precedence + Score int + // WrappedOperands are the operands wrapped in all necessary system operators and function refs to + // convert them. + WrappedOperands []model.IExpression +} + +// operandsImplicitConverter may be called with nil opsToWrap if the caller only cares about the +// score. +func operandsImplicitConverter(invokedTypes []types.IType, declaredTypes []types.IType, opsToWrap []model.IExpression, tHelper *modelinfo.ModelInfos) (convertedOperands, error) { + if len(invokedTypes) != len(declaredTypes) { + return convertedOperands{Matched: false}, nil + } + + if opsToWrap == nil { + // A slice of nil used as placeholder model.IExpressions. + opsToWrap = make([]model.IExpression, len(invokedTypes)) + } + + results := convertedOperands{Matched: true, Score: 0, WrappedOperands: make([]model.IExpression, len(invokedTypes))} + for i := range invokedTypes { + result, err := OperandImplicitConverter(invokedTypes[i], declaredTypes[i], opsToWrap[i], tHelper) + if err != nil { + return convertedOperands{}, err + } + if !result.Matched { + return convertedOperands{Matched: false}, nil + } + results.Score += result.Score + results.WrappedOperands[i] = result.WrappedOperand + } + return results, nil +} + +// ConvertedOperand is the result of OperandImplicitConverter. +type ConvertedOperand struct { + // Matched is true if the invoked type can be converted to the declared type. + Matched bool + // Score is the least converting score of the conversion based on the CQL conversion precedence. + // The score does not take into account multiple conversions. + // https://cql.hl7.org/03-developersguide.html#conversion-precedence + Score int + // WrappedOperand is the operand wrapped in all necessary system operators and function refs to + // convert it. + WrappedOperand model.IExpression +} + +// OperandImplicitConverter wraps the operand in any system operators or FHIRHelper function +// references needed to convert the operand from invokedType to declaredType. For example, if going +// from Integer --> Decimal the operand will be wrapped in ToDecimal(operand). +// operandImplicitConverter may apply multiple conversions, and always returns the least converting +// path. +// +// OperandImplicitConverter may be called with a nil opToWrap if the caller only cares about the +// score. +// +// Implementation note, invokedType may not be the same as opToWrap.GetResultType().The two +// diverge on some recursive calls. opToWrap.GetResultType() should not be used in the implementation +// of operandImplicitConverter. +func OperandImplicitConverter(invokedType types.IType, declaredType types.IType, opToWrap model.IExpression, mi *modelinfo.ModelInfos) (ConvertedOperand, error) { + // As we try different conversion paths minConverted will keep track of the lowest scoring + // conversion. + minConverted := ConvertedOperand{Score: math.MaxInt} + + if invokedType == types.Unset { + return ConvertedOperand{Matched: false, Score: 0, WrappedOperand: opToWrap}, fmt.Errorf("internal error - invokedType is %v", invokedType) + } + if declaredType == types.Unset { + return ConvertedOperand{Matched: false, Score: 0, WrappedOperand: opToWrap}, fmt.Errorf("internal error - declaredType is %v", declaredType) + } + + // EXACT MATCH + if invokedType.Equal(declaredType) { + return ConvertedOperand{Matched: true, Score: 0, WrappedOperand: opToWrap}, nil + } + + // SUBTYPE + isSub, err := mi.IsSubType(invokedType, declaredType) + if err != nil { + return ConvertedOperand{}, err + } + if isSub { + // No wrapper is needed, the interpreter will handle subtypes. + minConverted = ConvertedOperand{Matched: true, Score: 1, WrappedOperand: opToWrap} + } + + // All types can be converted from invoked --> Any --> declared. However that leads to incorrect + // conversions such as String --> Any --> Integer, so BaseTypes does not return Any. + baseTypes, err := mi.BaseTypes(invokedType) + if err != nil { + return ConvertedOperand{}, err + } + for _, baseType := range baseTypes { + r, err := OperandImplicitConverter(baseType, declaredType, opToWrap, mi) + if err != nil { + return ConvertedOperand{}, err + } + if r.Matched { + // Increment score by one since we applied a subtype before recursively calling + // OperandImplicitConverter. + r.Score++ + if r.Score < minConverted.Score { + minConverted = r + } + } + } + + // COMPATIBLE/NULL + // Ex Any --> Decimal As(operand, Decimal) + if invokedType.Equal(types.Any) { + // This is not described well in the CQL spec but, you can pass null literals to any function. + // Null has type Any. + wrapped := &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: opToWrap, + Expression: model.ResultType(declaredType), + }, + AsTypeSpecifier: declaredType, + Strict: false, + } + if 2 < minConverted.Score { + minConverted = ConvertedOperand{Matched: true, Score: 2, WrappedOperand: wrapped} + } + } + + // CAST - invokedType is a Choice type + // Ex Choice --> Decimal ToDecimal(As(operand, Integer)) + if invokedChoice, ok := invokedType.(*types.Choice); ok { + for _, choiceType := range invokedChoice.ChoiceTypes { + choiceWrapped := &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: opToWrap, + Expression: model.ResultType(choiceType), + }, + AsTypeSpecifier: choiceType, + Strict: false, + } + r, err := OperandImplicitConverter(choiceType, declaredType, choiceWrapped, mi) + if err != nil { + return ConvertedOperand{}, err + } + if r.Matched { + r.Score += 3 + if r.Score < minConverted.Score { + minConverted = r + } + } + } + } + + // CAST - declaredType is a Choice type + // Ex Integer --> Choice As(ToDecimal(operand), Choice) + if declaredChoice, ok := declaredType.(*types.Choice); ok { + for _, choiceType := range declaredChoice.ChoiceTypes { + r, err := OperandImplicitConverter(invokedType, choiceType, opToWrap, mi) + if err != nil { + return ConvertedOperand{}, err + } + if r.Matched { + wrapped := &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: r.WrappedOperand, + Expression: model.ResultType(declaredType), + }, + AsTypeSpecifier: declaredType, + Strict: false, + } + if 3 < minConverted.Score { + minConverted = ConvertedOperand{Matched: true, Score: 3, WrappedOperand: wrapped} + } + } + } + } + + // IMPLICIT CONVERSION + res, err := mi.IsImplicitlyConvertible(invokedType, declaredType) + if err != nil { + return ConvertedOperand{}, err + } + + _, invokedIsSystem := invokedType.(types.System) + if res.IsConvertible && invokedIsSystem { + // Ex Integer --> Decimal ToDecimal(operand) + wrapped, err := wrapSystemImplicitConversion(res.Library, res.Function, opToWrap) + if err != nil { + return ConvertedOperand{}, err + } + + score := implicitConversionScore(declaredType) + if score < minConverted.Score { + minConverted = ConvertedOperand{Matched: true, Score: score, WrappedOperand: wrapped} + } + } + + if res.IsConvertible { + // IMPLICIT CONVERSION TO CLASS TYPE + // EX FHIR.date --> System.Date FHIRHelpers.ToDate(operand) + wrapped := &model.FunctionRef{ + LibraryName: res.Library, + Name: res.Function, + Operands: []model.IExpression{opToWrap}, + Expression: model.ResultType(declaredType), + } + + score := implicitConversionScore(declaredType) + if score < minConverted.Score { + minConverted = ConvertedOperand{Matched: true, Score: score, WrappedOperand: wrapped} + } + } + + // IMPLICIT CONVERSION TO CLASS TYPE - Intervals and Lists + switch i := invokedType.(type) { + // Ex Interval --> Interval Interval[ToDecimal(operand.Low), ToDecimal(operand.High), operand.lowClosed, operand.highClosed] + case *types.Interval: + d, ok := declaredType.(*types.Interval) + if !ok { + break + } + low := &model.Property{Source: opToWrap, Path: "low", Expression: model.ResultType(i.PointType)} + high := &model.Property{Source: opToWrap, Path: "high", Expression: model.ResultType(i.PointType)} + rLow, err := OperandImplicitConverter(i.PointType, d.PointType, low, mi) + if err != nil { + return ConvertedOperand{}, err + } + if !rLow.Matched { + break + } + rHigh, err := OperandImplicitConverter(i.PointType, d.PointType, high, mi) + if err != nil { + return ConvertedOperand{}, err + } + if !rHigh.Matched { + break + } + wrapped := &model.Interval{ + Low: rLow.WrappedOperand, + High: rHigh.WrappedOperand, + // Since operand could be any CQL expression that resolves to an interval we use the lowClosed + // and highClosed properties to forwards the bounds of the interval. + LowClosedExpression: &model.Property{Source: opToWrap, Path: "lowClosed", Expression: model.ResultType(types.Boolean)}, + HighClosedExpression: &model.Property{Source: opToWrap, Path: "highClosed", Expression: model.ResultType(types.Boolean)}, + Expression: model.ResultType(d), + } + if 5 < minConverted.Score { + minConverted = ConvertedOperand{Matched: true, Score: 5, WrappedOperand: wrapped} + } + + // Ex List --> List [operand] X return ToDecimal(X) + case *types.List: + d, ok := declaredType.(*types.List) + if !ok { + break + } + ref := &model.AliasRef{Name: "X", Expression: model.ResultType(i.ElementType)} + r, err := OperandImplicitConverter(i.ElementType, d.ElementType, ref, mi) + if err != nil { + return ConvertedOperand{}, err + } + if !r.Matched { + break + } + wrapped := &model.Query{ + Source: []*model.AliasedSource{&model.AliasedSource{ + Alias: "X", + Source: opToWrap, + Expression: model.ResultType(i), + }}, + Return: &model.ReturnClause{ + Expression: r.WrappedOperand, + Distinct: false, + Element: &model.Element{ResultType: d.ElementType}}, + Expression: model.ResultType(declaredType), + } + if 5 < minConverted.Score { + minConverted = ConvertedOperand{Matched: true, Score: 5, WrappedOperand: wrapped} + } + } + + if minConverted.Matched { + return minConverted, nil + } + return ConvertedOperand{Matched: false}, nil +} + +func wrapSystemImplicitConversion(library string, function string, operand model.IExpression) (model.IExpression, error) { + if library != "SYSTEM" { + return nil, fmt.Errorf("internal error - could not find wrapper for %v %v", library, function) + } + switch function { + case "ToDecimal": + return &model.ToDecimal{UnaryExpression: &model.UnaryExpression{Operand: operand, Expression: model.ResultType(types.Decimal)}}, nil + case "ToLong": + return &model.ToLong{UnaryExpression: &model.UnaryExpression{Operand: operand, Expression: model.ResultType(types.Long)}}, nil + case "ToDateTime": + return &model.ToDateTime{UnaryExpression: &model.UnaryExpression{Operand: operand, Expression: model.ResultType(types.DateTime)}}, nil + case "ToQuantity": + return &model.ToQuantity{UnaryExpression: &model.UnaryExpression{Operand: operand, Expression: model.ResultType(types.Quantity)}}, nil + case "ToConcept": + return &model.ToConcept{UnaryExpression: &model.UnaryExpression{Operand: operand, Expression: model.ResultType(types.Concept)}}, nil + } + return nil, fmt.Errorf("internal error - could not find wrapper for %v %v", library, function) +} + +// OperandsToString returns a print friendly representation of the operands. +func OperandsToString(operands []model.IExpression) string { + var stringOperands strings.Builder + for i, operand := range operands { + if i > 0 { + stringOperands.WriteString(", ") + } + if operand == nil || operand.GetResultType() == nil { + stringOperands.WriteString("nil") + } else { + stringOperands.WriteString(operand.GetResultType().String()) + } + } + return stringOperands.String() +} + +// OperandsToTypes returns the types of the ResultType of the operands. +func OperandsToTypes(operands []model.IExpression) []types.IType { + var types []types.IType + for _, operand := range operands { + types = append(types, operand.GetResultType()) + } + return types +} + +func implicitConversionScore(t types.IType) int { + switch t { + case types.String, types.Integer, types.Long, types.Decimal, types.Boolean, types.Date, types.DateTime, types.Time: + // Simple types. + return 4 + default: + // Everything else is a class type. + return 5 + } +} + +// Generic types are used to define generic overloads, the overloads with T in +// https://cql.hl7.org/09-b-cqlreference.html. The only place generics are used is to define system +// operators in the Parser. The ResultType in model should never be a Generic type. The interpreter +// should never deal with generic types. +type Generic string + +const ( + // GenericType represents a generic CQL type, shown as T in the CQL reference. Never nest a + // GenericType in a real type (ex List). Instead use the GenericList below. + GenericType Generic = "GenericType" + // GenericInterval represents a generic interval type, shown as Interval in the CQL reference. + GenericInterval Generic = "GenericInterval" + // GenericList represents a generic list type, shown as List in the CQL reference. + GenericList Generic = "GenericList" +) + +// Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. +func (s Generic) Equal(a types.IType) bool { + aBase, ok := a.(Generic) + if !ok { + return false + } + return s == aBase +} + +// String returns the model info based name for the type, and implements fmt.Stringer for easy +// printing. +func (s Generic) String() string { + return fmt.Sprintf("Generic.%v", string(s)) +} + +// ModelInfoName should never be called for Generics. +func (s Generic) ModelInfoName() (string, error) { + return "", errors.New("Generic type does not have a model info name") +} + +// MarshalJSON should never be called for Generics. +func (s Generic) MarshalJSON() ([]byte, error) { + return nil, errors.New("Generics should not be marshalled") +} + +// convertGeneric takes the invoked operands ex (Integer, String, Decimal), a generic overload (T, +// String, T) and returns the least converting concrete overload that still satisfies the the +// generic constraints, in this case (Decimal, String, Decimal). If there is no concrete +// instantiation of this generic overload that will work false is returned. +func convertGeneric[F any](invoked []model.IExpression, genericDeclared Overload[F], mi *modelinfo.ModelInfos) (Overload[F], bool, error) { + if len(invoked) != len(genericDeclared.Operands) { + // There is no concrete instantiation of this generic overload that will work, so return false. + return Overload[F]{}, false, nil + } + + // genericInvokedTypes holds the types of the invoked operands that need to match with a generic. + // For example for invoked (Integer, String, Decimal), and genericDeclared (T, String, T) then + // genericInvokedTypes is (Integer, Decimal). We use inferMixedType to find the least converting T + // for genericInvokedTypes. + genericInvokedTypes := make([]types.IType, 0) + for i := range invoked { + switch genericDeclared.Operands[i] { + case GenericType: + genericInvokedTypes = append(genericInvokedTypes, invoked[i].GetResultType()) + case GenericInterval: + if interval, ok := invoked[i].GetResultType().(*types.Interval); ok { + genericInvokedTypes = append(genericInvokedTypes, interval.PointType) + } else { + // If we are matching Interval and invoked[i] is not an interval, we will either need to + // apply interval promotion or check that there's an implicit conversion to Interval. + // For now, only the interval promotion case is supported, so we add invoked[i] as is to + // find the least converting path to the T in Interval. + // TODO(b/333923412): add in full support for the cases in which there's an implicit + // conversion to an Interval. + genericInvokedTypes = append(genericInvokedTypes, invoked[i].GetResultType()) + } + case GenericList: + if list, ok := invoked[i].GetResultType().(*types.List); ok { + genericInvokedTypes = append(genericInvokedTypes, list.ElementType) + } else { + genericInvokedTypes = append(genericInvokedTypes, invoked[i].GetResultType()) + } + } + } + + // TODO(b/301606416): We should convert the generic overload into all least converting concrete + // overloads, but if there is a tie inferMixedType just randomly picks one. + inferred, err := inferMixedType(genericInvokedTypes, nil, mi) + if err != nil { + return Overload[F]{}, false, err + } + + if inferred.PuntedToChoice { + // There is no concrete instantiation of this generic overload that will work, so return false. + return Overload[F]{}, false, nil + } + + // concreteOverload is the genericDeclared overload with T replaced by the inferred.UniformType. + concreteOverload := make([]types.IType, len(genericDeclared.Operands)) + for i := range genericDeclared.Operands { + switch genericDeclared.Operands[i] { + case GenericType: + concreteOverload[i] = inferred.UniformType + case GenericInterval: + if _, ok := inferred.UniformType.(*types.Interval); !ok { + // Since we sometimes use the PointType above we need to rewrap in an Interval. + concreteOverload[i] = &types.Interval{PointType: inferred.UniformType} + } else { + concreteOverload[i] = inferred.UniformType + } + case GenericList: + if _, ok := inferred.UniformType.(*types.List); !ok { + concreteOverload[i] = &types.List{ElementType: inferred.UniformType} + } else { + concreteOverload[i] = inferred.UniformType + } + default: + concreteOverload[i] = genericDeclared.Operands[i] + } + } + + genericDeclared.Operands = concreteOverload + return genericDeclared, true, nil +} + +func isGeneric(operands []types.IType) bool { + for _, operand := range operands { + if operand.Equal(GenericType) || operand.Equal(GenericInterval) || operand.Equal(GenericList) { + return true + } + } + return false +} diff --git a/internal/convert/convert_test.go b/internal/convert/convert_test.go new file mode 100644 index 0000000..a86cd43 --- /dev/null +++ b/internal/convert/convert_test.go @@ -0,0 +1,965 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "errors" + "strings" + "testing" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" +) + +func TestOverloadMatch(t *testing.T) { + tests := []struct { + name string + invoked []model.IExpression + overloads []Overload[string] + wantRes MatchedOverload[string] + }{ + { + name: "Multiple Operands", + invoked: []model.IExpression{ + model.NewLiteral("String", types.String), + model.NewInclusiveInterval("@2020-03-04", "@2020-03-05", types.Date), + model.NewList([]string{"4", "5"}, types.Integer), + }, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{types.String, &types.Interval{PointType: types.Date}}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{ + types.String, + &types.Interval{PointType: types.Date}, + &types.List{ElementType: types.Integer}, + &types.Named{TypeName: "Patient"}, + }, + }, + Overload[string]{ + Result: "Just Right", + Operands: []types.IType{ + types.String, + &types.Interval{PointType: types.Date}, + &types.List{ElementType: types.Integer}, + }, + }, + }, + wantRes: MatchedOverload[string]{ + Result: "Just Right", + WrappedOperands: []model.IExpression{ + model.NewLiteral("String", types.String), + model.NewInclusiveInterval("@2020-03-04", "@2020-03-05", types.Date), + model.NewList([]string{"4", "5"}, types.Integer), + }, + }, + }, + { + name: "Single Operand", + invoked: []model.IExpression{ + model.NewLiteral("String", types.String), + }, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Just Right", + Operands: []types.IType{types.String}, + }, + Overload[string]{ + Result: "Wrong Type", + Operands: []types.IType{types.Integer}, + }, + }, + wantRes: MatchedOverload[string]{ + Result: "Just Right", + WrappedOperands: []model.IExpression{ + model.NewLiteral("String", types.String), + }, + }, + }, + { + name: "No Operands", + invoked: []model.IExpression{}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{types.Integer}, + }, + Overload[string]{ + Result: "Just Right", + Operands: []types.IType{}, + }, + }, + wantRes: MatchedOverload[string]{ + Result: "Just Right", + WrappedOperands: []model.IExpression{}, + }, + }, + { + name: "Multiple Conversions", + invoked: []model.IExpression{model.NewLiteral("4", types.Integer), model.NewLiteral("@2020-03-05", types.Date)}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "One Simple Conversion", + Operands: []types.IType{types.Decimal, types.Date}, + }, + Overload[string]{ + Result: "Two Simple Conversion", + Operands: []types.IType{types.Decimal, types.DateTime}, + }, + }, + wantRes: MatchedOverload[string]{ + Result: "One Simple Conversion", + WrappedOperands: []model.IExpression{ + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + model.NewLiteral("@2020-03-05", types.Date), + }, + }, + }, + { + name: "Ambiguous Follwed By Exact Match", + invoked: []model.IExpression{model.NewLiteral("4", types.Integer), model.NewLiteral("@2020-03-05", types.Date)}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Ambiguous 1", + Operands: []types.IType{types.Long, types.DateTime}, + }, + Overload[string]{ + Result: "Ambiguous 2", + Operands: []types.IType{types.Decimal, types.DateTime}, + }, + Overload[string]{ + Result: "Exact Match", + Operands: []types.IType{types.Integer, types.Date}, + }, + }, + wantRes: MatchedOverload[string]{ + Result: "Exact Match", + WrappedOperands: []model.IExpression{ + model.NewLiteral("4", types.Integer), + model.NewLiteral("@2020-03-05", types.Date), + }, + }, + }, + { + name: "Generics", + invoked: []model.IExpression{ + model.NewLiteral("@2020-03-05", types.Date), + model.NewInclusiveInterval("@2020-03-05", "@2020-03-05", types.DateTime), + model.NewList([]string{"@2020-03-05", "@2020-03-05"}, types.DateTime), + model.NewLiteral("Apples", types.String), + }, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{GenericType, GenericInterval}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{ + GenericType, + GenericInterval, + GenericList, + types.String, + types.String, + }, + }, + Overload[string]{ + Result: "Wrong Type", + Operands: []types.IType{ + GenericType, + GenericInterval, + GenericList, + &types.Named{TypeName: "Patient"}, + }, + }, + Overload[string]{ + Result: "Just Right", + Operands: []types.IType{ + GenericType, + GenericInterval, + GenericList, + types.String, + }, + }, + }, + wantRes: MatchedOverload[string]{ + Result: "Just Right", + WrappedOperands: []model.IExpression{ + &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("@2020-03-05", types.Date), + Expression: model.ResultType(types.DateTime), + }, + }, + model.NewInclusiveInterval("@2020-03-05", "@2020-03-05", types.DateTime), + model.NewList([]string{"@2020-03-05", "@2020-03-05"}, types.DateTime), + model.NewLiteral("Apples", types.String), + }, + }, + }, + // TODO(b/312172420): Add tests where generics need a list promotion and interval promotion once + // supported in the conversion precedence. + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + gotRes, err := OverloadMatch(tc.invoked, tc.overloads, modelinfo, "Name") + if err != nil { + t.Fatalf("overloadMatch() unexpected err: %v", err) + } + if diff := cmp.Diff(tc.wantRes, gotRes); diff != "" { + t.Errorf("overloadMatch() diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestOverloadMatch_Error(t *testing.T) { + tests := []struct { + name string + invoked []model.IExpression + overloads []Overload[string] + errContains string + errIs error // optional + }{ + { + name: "Single No Match", + invoked: []model.IExpression{ + model.NewLiteral("String", types.String), + }, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Wrong Type", + Operands: []types.IType{types.Integer}, + }, + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{types.String, types.String}, + }, + }, + errContains: "could not resolve", + errIs: ErrNoMatch, + }, + { + name: "Multiple No Match", + invoked: []model.IExpression{ + model.NewLiteral("String", types.String), + model.NewInclusiveInterval("@2020-03-04", "@2020-03-05", types.Date), + model.NewList([]string{"4", "5"}, types.Integer), + }, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{types.String, &types.Interval{PointType: types.DateTime}}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{ + types.String, + &types.Interval{PointType: types.DateTime}, + &types.List{ElementType: types.Integer}, + &types.Named{TypeName: "Patient"}, + }, + }, + Overload[string]{ + Result: "Wrong Type", + Operands: []types.IType{ + types.Integer, + &types.Interval{PointType: types.DateTime}, + &types.List{ElementType: types.Integer}, + }, + }, + }, + errContains: "could not resolve", + errIs: ErrNoMatch, + }, + { + name: "Ambiguous Match", + invoked: []model.IExpression{model.NewLiteral("String", types.String), model.NewLiteral("String", types.String)}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Ambiguous 1", + Operands: []types.IType{types.Any, types.String}, + }, + Overload[string]{ + Result: "Ambiguous 2", + Operands: []types.IType{types.Any, types.String}, + }, + }, + errContains: "ambiguous", + errIs: ErrAmbiguousMatch, + }, + { + name: "Generic No Match", + invoked: []model.IExpression{ + model.NewLiteral("String", types.String), + model.NewInclusiveInterval("@2020-03-04", "@2020-03-05", types.Date), + model.NewList([]string{"4", "5"}, types.Integer), + }, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{GenericType, GenericInterval}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{ + GenericType, + GenericInterval, + GenericList, + &types.Named{TypeName: "Patient"}, + }, + }, + Overload[string]{ + Result: "No Uniform Type", + Operands: []types.IType{ + GenericType, + GenericInterval, + GenericList, + }, + }, + }, + errContains: "could not resolve", + errIs: ErrNoMatch, + }, + { + name: "Ambiguous Generic", + invoked: []model.IExpression{model.NewLiteral("String", types.String), model.NewLiteral("String", types.String)}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Ambiguous 1", + Operands: []types.IType{GenericType, types.String}, + }, + Overload[string]{ + Result: "Ambiguous 2", + Operands: []types.IType{types.String, GenericType}, + }, + }, + errContains: "ambiguous", + }, + { + name: "Nil ResultType", + invoked: []model.IExpression{&model.Literal{Value: "Apple"}}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Overload", + Operands: []types.IType{types.String}, + }, + }, + errContains: "internal error - invokedType is", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + _, err := OverloadMatch(tc.invoked, tc.overloads, modelinfo, "Name") + if err == nil { + t.Fatalf("overloadMatch() did not return an error") + } + if !strings.Contains(err.Error(), tc.errContains) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err.Error(), tc.errContains) + } + if tc.errIs != nil && !errors.Is(err, tc.errIs) { + t.Errorf("returned error %v that is not errors.Is() the expected err: %v", err, tc.errIs) + } + }) + } +} + +func TestOperandImplicitConverter(t *testing.T) { + tests := []struct { + name string + invokedType types.IType + declaredType types.IType + want ConvertedOperand + }{ + { + name: "Exact Match", + invokedType: &types.Interval{PointType: types.DateTime}, + declaredType: &types.Interval{PointType: types.DateTime}, + want: ConvertedOperand{Matched: true, Score: 0, WrappedOperand: model.NewLiteral("operand", types.String)}, + }, + { + name: "SubType", + invokedType: &types.Interval{PointType: types.DateTime}, + declaredType: types.Any, + want: ConvertedOperand{Matched: true, Score: 1, WrappedOperand: model.NewLiteral("operand", types.String)}, + }, + { + name: "Tuple SubType", + invokedType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet, "bar": types.String}}, + declaredType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Vocabulary, "bar": types.String}}, + want: ConvertedOperand{Matched: true, Score: 1, WrappedOperand: model.NewLiteral("operand", types.String)}, + }, + { + name: "List SubType", + invokedType: &types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet, "bar": types.String}}}, + declaredType: &types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Vocabulary, "bar": types.String}}}, + want: ConvertedOperand{Matched: true, Score: 1, WrappedOperand: model.NewLiteral("operand", types.String)}, + }, + { + name: "Compatible aka Null", + invokedType: types.Any, + declaredType: types.Decimal, + want: ConvertedOperand{ + Matched: true, + Score: 2, + WrappedOperand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(types.Decimal), + }, + AsTypeSpecifier: types.Decimal, + Strict: false, + }, + }, + }, + { + name: "Choice Cast to Concrete Type", + invokedType: &types.Choice{ + ChoiceTypes: []types.IType{ + &types.Interval{PointType: types.DateTime}, + &types.Interval{PointType: types.Date}, + }, + }, + declaredType: &types.Interval{PointType: types.DateTime}, + want: ConvertedOperand{ + Matched: true, + Score: 3, + WrappedOperand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + }, + AsTypeSpecifier: &types.Interval{PointType: types.DateTime}, + Strict: false, + }, + }, + }, + { + name: "Concrete Type Cast to Choice", + invokedType: &types.Interval{PointType: types.DateTime}, + declaredType: &types.Choice{ + ChoiceTypes: []types.IType{ + &types.Interval{PointType: types.DateTime}, + &types.Interval{PointType: types.Date}, + }, + }, + want: ConvertedOperand{ + Matched: true, + Score: 3, + WrappedOperand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.Choice{ + ChoiceTypes: []types.IType{ + &types.Interval{PointType: types.DateTime}, + &types.Interval{PointType: types.Date}, + }, + }), + }, + AsTypeSpecifier: &types.Choice{ + ChoiceTypes: []types.IType{ + &types.Interval{PointType: types.DateTime}, + &types.Interval{PointType: types.Date}, + }, + }, + Strict: false, + }, + }, + }, + { + name: "Choice to Choice", + invokedType: &types.Choice{ + ChoiceTypes: []types.IType{ + types.String, + types.Integer, + }, + }, + declaredType: &types.Choice{ + ChoiceTypes: []types.IType{ + &types.Interval{PointType: types.Integer}, + types.Decimal, + }, + }, + want: ConvertedOperand{ + Matched: true, + Score: 3, + WrappedOperand: &model.As{ + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{&types.Interval{PointType: types.Integer}, types.Decimal}}, + Strict: false, + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{&types.Interval{PointType: types.Integer}, types.Decimal}}), + Operand: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Decimal), + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + Strict: false, + }, + }, + }, + }, + }, + }, + }, + { + name: "FHIR ModelInfo Converison to Simple", + invokedType: &types.Named{TypeName: "FHIR.date"}, + declaredType: types.Date, + want: ConvertedOperand{ + Matched: true, + Score: 4, + WrappedOperand: &model.FunctionRef{ + LibraryName: "FHIRHelpers", + Name: "ToDate", + Expression: model.ResultType(types.Date), + Operands: []model.IExpression{model.NewLiteral("operand", types.String)}, + }, + }, + }, + { + name: "FHIR ModelInfo Conversion to Class", + invokedType: &types.Named{TypeName: "FHIR.Period"}, + declaredType: &types.Interval{PointType: types.DateTime}, + want: ConvertedOperand{ + Matched: true, + Score: 5, + WrappedOperand: &model.FunctionRef{ + LibraryName: "FHIRHelpers", + Name: "ToInterval", + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + Operands: []model.IExpression{model.NewLiteral("operand", types.String)}, + }, + }, + }, + { + name: "System ModelInfo Conversion to Class", + invokedType: types.Integer, + declaredType: types.Quantity, + want: ConvertedOperand{ + Matched: true, + Score: 5, + WrappedOperand: &model.ToQuantity{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(types.Quantity), + }, + }, + }, + }, + { + name: "Simple Conversion", + invokedType: types.Date, + declaredType: types.DateTime, + want: ConvertedOperand{ + Matched: true, + Score: 4, + WrappedOperand: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(types.DateTime), + }, + }, + }, + }, + { + name: "Interval Conversion", + invokedType: &types.Interval{PointType: types.Decimal}, + declaredType: &types.Interval{PointType: types.Quantity}, + want: ConvertedOperand{ + Matched: true, + Score: 5, + WrappedOperand: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Quantity}), + LowClosedExpression: &model.Property{Source: model.NewLiteral("operand", types.String), Path: "lowClosed", Expression: model.ResultType(types.Boolean)}, + HighClosedExpression: &model.Property{Source: model.NewLiteral("operand", types.String), Path: "highClosed", Expression: model.ResultType(types.Boolean)}, + Low: &model.ToQuantity{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Quantity), + Operand: &model.Property{ + Path: "low", + Expression: model.ResultType(types.Decimal), + Source: model.NewLiteral("operand", types.String), + }, + }, + }, + High: &model.ToQuantity{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Quantity), + Operand: &model.Property{ + Path: "high", + Expression: model.ResultType(types.Decimal), + Source: model.NewLiteral("operand", types.String), + }, + }, + }, + }, + }, + }, + { + name: "List Conversion", + invokedType: &types.List{ElementType: types.Integer}, + declaredType: &types.List{ElementType: types.Long}, + want: ConvertedOperand{ + Matched: true, + Score: 5, + WrappedOperand: &model.Query{ + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "X", + Source: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Return: &model.ReturnClause{ + Element: &model.Element{ResultType: types.Long}, + Expression: &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Long), + Operand: &model.AliasRef{Name: "X", Expression: model.ResultType(types.Integer)}, + }, + }, + Distinct: false, + }, + Expression: model.ResultType(&types.List{ElementType: types.Long}), + }, + }, + }, + { + name: "Multiple Conversions - Subtype and FHIR ModelInfo Converison to Simple", + invokedType: &types.Named{TypeName: "FHIR.id"}, + declaredType: types.String, + want: ConvertedOperand{ + Matched: true, + Score: 5, + WrappedOperand: &model.FunctionRef{ + Expression: model.ResultType(types.String), + Name: "ToString", + LibraryName: "FHIRHelpers", + Operands: []model.IExpression{model.NewLiteral("operand", types.String)}, + }}, + }, + { + name: "Multiple Conversions - FHIR Cast and Implicit Conversion to Simple", + invokedType: &types.Choice{ + ChoiceTypes: []types.IType{ + &types.Named{TypeName: "FHIR.Quantity"}, + &types.Named{TypeName: "FHIR.CodeableConcept"}, + &types.Named{TypeName: "FHIR.string"}, + &types.Named{TypeName: "FHIR.boolean"}, + &types.Named{TypeName: "FHIR.integer"}, + &types.Named{TypeName: "FHIR.Range"}, + &types.Named{TypeName: "FHIR.Ratio"}, + &types.Named{TypeName: "FHIR.SampledData"}, + &types.Named{TypeName: "FHIR.time"}, + &types.Named{TypeName: "FHIR.dateTime"}, + &types.Named{TypeName: "FHIR.Period"}, + }, + }, + declaredType: types.Boolean, + want: ConvertedOperand{ + Matched: true, + Score: 7, + WrappedOperand: &model.FunctionRef{ + LibraryName: "FHIRHelpers", + Name: "ToBoolean", + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.Named{TypeName: "FHIR.boolean"}), + }, + AsTypeSpecifier: &types.Named{TypeName: "FHIR.boolean"}, + Strict: false, + }, + }, + }, + }, + }, + { + name: "Multiple Conversions - Cast and Implicit Conversion to Class", + invokedType: &types.Choice{ + ChoiceTypes: []types.IType{ + &types.Interval{PointType: types.Date}, + }, + }, + declaredType: &types.Interval{PointType: types.DateTime}, + want: ConvertedOperand{ + Matched: true, + Score: 8, + WrappedOperand: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowClosedExpression: &model.Property{ + Source: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + }, + AsTypeSpecifier: &types.Interval{PointType: types.Date}, + Strict: false, + }, + Path: "lowClosed", + Expression: model.ResultType(types.Boolean), + }, + HighClosedExpression: &model.Property{ + Source: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + }, + AsTypeSpecifier: &types.Interval{PointType: types.Date}, + Strict: false, + }, + Path: "highClosed", + Expression: model.ResultType(types.Boolean), + }, + Low: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: &model.Property{ + Path: "low", + Expression: model.ResultType(types.Date), + Source: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + }, + AsTypeSpecifier: &types.Interval{PointType: types.Date}, + Strict: false, + }, + }, + }, + }, + High: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: &model.Property{ + Path: "high", + Expression: model.ResultType(types.Date), + Source: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("operand", types.String), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + }, + AsTypeSpecifier: &types.Interval{PointType: types.Date}, + Strict: false, + }, + }, + }, + }, + }, + }, + }, + { + name: "Invalid Simple Conversion", + invokedType: types.Integer, + declaredType: types.DateTime, + want: ConvertedOperand{Matched: false}, + }, + { + name: "Invalid Choice to Concrete", + invokedType: &types.Choice{ChoiceTypes: []types.IType{types.Date, types.String}}, + declaredType: types.Integer, + want: ConvertedOperand{Matched: false}, + }, + { + name: "Invalid Concrete to Choice", + invokedType: &types.Interval{PointType: types.Date}, + declaredType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + want: ConvertedOperand{Matched: false}, + }, + { + name: "Invalid Choice to Choice", + invokedType: &types.Choice{ + ChoiceTypes: []types.IType{ + types.Date, + types.String, + }, + }, + declaredType: &types.Choice{ + ChoiceTypes: []types.IType{ + types.Integer, + &types.Interval{PointType: types.String}, + }, + }, + want: ConvertedOperand{Matched: false}, + }, + { + name: "Invalid FHIR ModelInfo Conversion", + invokedType: &types.Named{TypeName: "FHIR.date"}, + declaredType: types.Integer, + want: ConvertedOperand{Matched: false}, + }, + { + name: "Invalid Interval Conversion", + invokedType: &types.Interval{PointType: types.String}, + declaredType: &types.Interval{PointType: types.Integer}, + want: ConvertedOperand{Matched: false}, + }, + { + name: "Invalid List Conversion", + invokedType: &types.List{ElementType: &types.Interval{PointType: types.String}}, + declaredType: &types.List{ElementType: types.String}, + want: ConvertedOperand{Matched: false}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + got, err := OperandImplicitConverter(tc.invokedType, tc.declaredType, model.NewLiteral("operand", types.String), modelinfo) + if err != nil { + t.Fatalf("operandImplicitConverter() unexpected error: %v", err) + } + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("operandImplicitConverter() diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestOperandImplicitConverter_NilOperands(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + got, err := OperandImplicitConverter(types.Date, types.DateTime, nil, modelinfo) + if err != nil { + t.Fatalf("operandImplicitConverter() unexpected error: %v", err) + } + + want := ConvertedOperand{ + Matched: true, + Score: 4, + WrappedOperand: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + // This is nil which is ok because the caller does not care about the wrapped operand only + // the score. + Operand: nil, + Expression: model.ResultType(types.DateTime), + }, + }, + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("operandImplicitConverter() diff (-want +got):\n%s", diff) + } +} + +func TestOperandImplicitConverter_Error(t *testing.T) { + tests := []struct { + name string + invokedType types.IType + declaredType types.IType + errContains string + }{ + { + name: "invokedType Unsupported", + invokedType: types.Unset, + declaredType: types.Integer, + errContains: "internal error - invokedType is", + }, + { + name: "declaredType Unsupported", + invokedType: types.Integer, + declaredType: types.Unset, + errContains: "internal error - declaredType is", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + _, err := OperandImplicitConverter(tc.invokedType, tc.declaredType, model.NewLiteral("operand", types.String), modelinfo) + if err == nil { + t.Fatalf("OperandImplicitConverter() did not return an error") + } + if !strings.Contains(err.Error(), tc.errContains) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err.Error(), tc.errContains) + } + }) + } +} + +func TestOperandsToString(t *testing.T) { + tests := []struct { + name string + operands []model.IExpression + want string + }{ + { + name: "Multiple", + operands: []model.IExpression{ + model.NewLiteral("String", types.String), + model.NewInclusiveInterval("@2020-03-04", "@2020-03-05", types.Date), + model.NewList([]string{"4", "5"}, types.Integer), + }, + want: "System.String, Interval, List", + }, + { + name: "Single", + operands: []model.IExpression{model.NewLiteral("String", types.String)}, + want: "System.String", + }, + { + name: "Empty", + operands: []model.IExpression{}, + want: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := OperandsToString(tc.operands) + if got != tc.want { + t.Errorf("OperandsToString() = %v want: %v", got, tc.want) + } + }) + } +} + +func newFHIRModelInfo(t *testing.T) *modelinfo.ModelInfos { + t.Helper() + rawFHIRMI, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + t.Fatalf("Reading embedded file %s failed unexpectedly: %v", "third_party/cqframework/fhir-modelinfo-4.0.1.xml", err) + } + + mi, err := modelinfo.New([][]byte{rawFHIRMI}) + if err != nil { + t.Fatalf("modelinfo.New() unexpected error: %v", err) + } + mi.SetUsing(modelinfo.Key{Name: "FHIR", Version: "4.0.1"}) + return mi +} diff --git a/internal/convert/exact.go b/internal/convert/exact.go new file mode 100644 index 0000000..f6dbc90 --- /dev/null +++ b/internal/convert/exact.go @@ -0,0 +1,105 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "fmt" + "math" + + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/types" +) + +// ExactOverloadMatch returns F on a match, and an error if there is no match or if the match is +// ambiguous. Matches must be exact meaning the invoked operands are equal or a subtype of the +// matched overload. If there are two exact matches an ambiguous error is returned. +func ExactOverloadMatch[F any](invoked []types.IType, overloads []Overload[F], modelinfo *modelinfo.ModelInfos, name string) (F, error) { + if len(overloads) == 0 { + return zero[F](), fmt.Errorf("could not resolve %v(%v): %w", name, types.ToStrings(invoked), ErrNoMatch) + } + + foundMatch := false + minScore := math.MaxInt + ambiguous := false + var matched F + for _, overload := range overloads { + match, score, err := operandsExactOrSubtypeMatch(invoked, overload.Operands, modelinfo) + if err != nil { + return zero[F](), fmt.Errorf("%v(%v): %w", name, types.ToStrings(invoked), err) + } + + if match && score == minScore { + // Least converting match is now ambiguous + ambiguous = true + continue + } + + if match && score < minScore { + // New least converting match + foundMatch = true + ambiguous = false + minScore = score + matched = overload.Result + } + } + + if foundMatch && ambiguous { + return zero[F](), fmt.Errorf("%v(%v) ambiguous match", name, types.ToStrings(invoked)) + } + if foundMatch { + return matched, nil + } + + return zero[F](), fmt.Errorf("could not resolve %v(%v): %w", name, types.ToStrings(invoked), ErrNoMatch) +} + +func operandsExactOrSubtypeMatch(invoked []types.IType, declared []types.IType, modelinfo *modelinfo.ModelInfos) (bool, int, error) { + if len(invoked) != len(declared) { + return false, 0, nil + } + + score := 0 + for i := range invoked { + if invoked[i] == types.Unset { + return false, score, fmt.Errorf("internal error - invokedType is types.Unsupported") + } + + if declared[i] == types.Unset { + return false, score, fmt.Errorf("internal error - declaredType is types.Unsupported") + } + // EXACT MATCH + if invoked[i].Equal(declared[i]) { + continue + } + + // SUBTYPE + isSub, err := modelinfo.IsSubType(invoked[i], declared[i]) + if err != nil { + return false, score, err + } + if !isSub { + return false, score, nil + } + score++ // is a subtype match, so we increment + } + + return true, score, nil +} + +// zero is a helper function to return the Zero value of a generic type T. +func zero[T any]() T { + var zero T + return zero +} diff --git a/internal/convert/exact_test.go b/internal/convert/exact_test.go new file mode 100644 index 0000000..936028f --- /dev/null +++ b/internal/convert/exact_test.go @@ -0,0 +1,267 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "strings" + "testing" + + "github.com/google/cql/types" +) + +func TestExactOverloadMatch(t *testing.T) { + tests := []struct { + name string + invoked []types.IType + overloads []Overload[string] + want string + }{ + { + name: "Multiple Operands", + invoked: []types.IType{types.String, &types.Interval{PointType: types.Date}, &types.List{ElementType: types.Integer}}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{types.String, &types.Interval{PointType: types.Date}}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{ + types.String, + &types.Interval{PointType: types.Date}, + &types.List{ElementType: types.Integer}, + &types.Named{TypeName: "Patient"}, + }, + }, + Overload[string]{ + Result: "Just Right", + Operands: []types.IType{ + types.String, + &types.Interval{PointType: types.Date}, + &types.List{ElementType: types.Integer}, + }, + }, + }, + want: "Just Right", + }, + { + name: "Single Operand", + invoked: []types.IType{types.String}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Just Right", + Operands: []types.IType{types.String}, + }, + Overload[string]{ + Result: "Wrong Type", + Operands: []types.IType{types.Integer}, + }, + }, + want: "Just Right", + }, + { + name: "No Operands", + invoked: []types.IType{}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{types.Integer}, + }, + Overload[string]{ + Result: "Just Right", + Operands: []types.IType{}, + }, + }, + want: "Just Right", + }, + { + name: "SubType Is Exact Match", + invoked: []types.IType{types.Integer, types.Date}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "SubType", + Operands: []types.IType{types.Any, types.Date}, + }, + Overload[string]{ + Result: "One Simple Conversion", + Operands: []types.IType{types.Decimal, types.Date}, + }, + }, + want: "SubType", + }, + { + name: "Conversions Followed By Exact Match", + invoked: []types.IType{types.Integer, types.Date}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Conversion 1", + Operands: []types.IType{types.Long, types.DateTime}, + }, + Overload[string]{ + Result: "Conversion 2", + Operands: []types.IType{types.Decimal, types.DateTime}, + }, + Overload[string]{ + Result: "Exact Match", + Operands: []types.IType{types.Integer, types.Date}, + }, + }, + want: "Exact Match", + }, + { + name: "Exact Match beats SubType match", + invoked: []types.IType{types.CodeSystem, types.CodeSystem}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Exact Match", + Operands: []types.IType{types.CodeSystem, types.CodeSystem}, + }, + Overload[string]{ + Result: "SubType", + Operands: []types.IType{types.Any, types.Any}, + }, + }, + want: "Exact Match", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + modelInfo := newFHIRModelInfo(t) + got, err := ExactOverloadMatch(tc.invoked, tc.overloads, modelInfo, "Name") + if err != nil { + t.Fatalf("ExactOverloadMatch() unexpected err: %v", err) + } + if got != tc.want { + t.Errorf("ExactOverloadMatch() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestExactOverloadMatch_Error(t *testing.T) { + tests := []struct { + name string + invoked []types.IType + overloads []Overload[string] + errContains string + }{ + { + name: "Conversions No Match", + invoked: []types.IType{types.Integer, types.Date}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "One Simple Conversion", + Operands: []types.IType{types.Decimal, types.Date}, + }, + Overload[string]{ + Result: "Two Simple Conversion", + Operands: []types.IType{types.Decimal, types.DateTime}, + }, + }, + errContains: "could not resolve", + }, + { + name: "Single No Match", + invoked: []types.IType{types.String}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Wrong Type", + Operands: []types.IType{types.Integer}, + }, + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{types.String, types.String}, + }, + }, + errContains: "could not resolve", + }, + { + name: "Multiple No Match", + invoked: []types.IType{ + types.String, + &types.Interval{PointType: types.Date}, + &types.List{ElementType: types.Integer}, + }, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Too Short", + Operands: []types.IType{types.String, &types.Interval{PointType: types.DateTime}}, + }, + Overload[string]{ + Result: "Too Long", + Operands: []types.IType{ + types.String, + &types.Interval{PointType: types.DateTime}, + &types.List{ElementType: types.Integer}, + &types.Named{TypeName: "Patient"}, + }, + }, + Overload[string]{ + Result: "Wrong Type", + Operands: []types.IType{ + types.Integer, + &types.Interval{PointType: types.DateTime}, + &types.List{ElementType: types.Integer}, + }, + }, + }, + errContains: "could not resolve", + }, + { + name: "Ambiguous Match", + invoked: []types.IType{types.String, types.String}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Ambiguous 1", + Operands: []types.IType{types.Any, types.String}, + }, + Overload[string]{ + Result: "Ambiguous 2", + Operands: []types.IType{types.Any, types.String}, + }, + }, + errContains: "ambiguous", + }, + { + name: "Unsupported ResultType", + invoked: []types.IType{types.Unset}, + overloads: []Overload[string]{ + Overload[string]{ + Result: "Overload", + Operands: []types.IType{types.String}, + }, + }, + errContains: "internal error - invokedType", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + _, err := ExactOverloadMatch(tc.invoked, tc.overloads, modelinfo, "Name") + if err == nil { + t.Fatalf("ExactOverloadMatch() did not return an error") + } + if !strings.Contains(err.Error(), tc.errContains) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err.Error(), tc.errContains) + } + }) + } +} diff --git a/internal/convert/mixed.go b/internal/convert/mixed.go new file mode 100644 index 0000000..aa20330 --- /dev/null +++ b/internal/convert/mixed.go @@ -0,0 +1,235 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "errors" + "fmt" + "math" + + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/model" + "github.com/google/cql/types" +) + +// Infered is the result of the InferMixedType function. +type Infered struct { + // PuntedToChoice is true if all types could not be implicitly converted to a uniform type and we + // instead converted to a Choice type. + PuntedToChoice bool + // UniformType is the type that all invoked types were converted to. + UniformType types.IType + // WrappedOperands are the invoked operands wrapped in all necessary system operators and function + // refs to convert them. + WrappedOperands []model.IExpression +} + +// InferMixed wraps the invoked operands in all necessary system operators and function refs to +// convert them to the same type. As a last resort, InferMixed will convert all operands to a +// Choice types consisting of the set of types of the operands. The least converting path is chosen +// and on ambiguous paths any of the least converting paths is chosen. This function is used to +// infer the type of mixed lists and case expressions in the parser. +// +// [4, 4.5] --> [ToDecimal(4), 4.5] +// ['str', 4] --> [As{'str', Choice}, As{4, Choice}] +func InferMixed(invoked []model.IExpression, modelinfo *modelinfo.ModelInfos) (Infered, error) { + return inferMixedType(OperandsToTypes(invoked), invoked, modelinfo) +} + +// inferMixed may be called with nil opsToWrap if the caller only cares about the uniform type. +func inferMixedType(invokedTypes []types.IType, opsToWrap []model.IExpression, modelinfo *modelinfo.ModelInfos) (Infered, error) { + if len(invokedTypes) == 0 { + return Infered{PuntedToChoice: false, UniformType: types.Any, WrappedOperands: []model.IExpression{}}, nil + } + + if opsToWrap == nil { + // A slice of nil used as placeholder model.IExpressions. + opsToWrap = make([]model.IExpression, len(invokedTypes)) + } + + allTypesAny := true + for _, t := range invokedTypes { + if !t.Equal(types.Any) { + allTypesAny = false + break + } + } + if allTypesAny { + // This special case is necessary because we skip trying the uniform type of Any below. If we + // are passed all nulls, then the desired behaviour is to return uniform type of Any. + return Infered{PuntedToChoice: false, UniformType: types.Any, WrappedOperands: opsToWrap}, nil + } + + minScore := math.MaxInt + matched := []model.IExpression{} + var matchedType types.IType + for _, t := range invokedTypes { + if t.Equal(types.Any) { + // All types are a subtype of Any, and can be converted to Any. However, when inferring a + // uniform type we prefer to use compatible (score 3) over subtype (score 2) and cast the null + // of type Any to a concrete type. So skip the Any overload. + continue + } + // TODO(b/301606416): Right now we just check the types in invoked as possible UniformTypes, but + // there may be a UniformType that requires converting all invoked types. For example, if A and + // B are subtypes of C then invoked (A, B) should return UniformType C. This does create a + // problem because every type can be converted to Any which would override the intended + // behaviour of punting to a Choice type below. + possibleType := make([]types.IType, len(invokedTypes)) + for i := range possibleType { + possibleType[i] = t + } + + res, err := operandsImplicitConverter(invokedTypes, possibleType, opsToWrap, modelinfo) + if err != nil { + return Infered{}, fmt.Errorf("while inferring mixed type: %w", err) + } + + if res.Matched && res.Score < minScore { + // A new least converting match + minScore = res.Score + + // Beware of the shallow copy + matched = res.WrappedOperands + matchedType = t + } + } + + // I am not sure if this is the correct behaviour, but if ambiguous we just take the last chosen + // conversion. I cannot think of a case that would result in ambiguous other than when all types + // are equal. + if minScore != math.MaxInt { + // Matched with conversion to a single type. + return Infered{PuntedToChoice: false, UniformType: matchedType, WrappedOperands: matched}, nil + } + + // All types in invoked could not be converted to a uniform type. Convert them to a uniform choice + // type instead. + choiceType, err := DeDuplicate(invokedTypes) + if err != nil { + return Infered{}, err + } + + for _, o := range opsToWrap { + wrapped := &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: o, + Expression: model.ResultType(choiceType), + }, + AsTypeSpecifier: choiceType, + Strict: false, + } + matched = append(matched, wrapped) + } + + return Infered{PuntedToChoice: true, UniformType: choiceType, WrappedOperands: matched}, nil +} + +// DeDuplicate finds a minimal choice type given a list of types. Duplicates are removed and +// Choice types are recursively flattened. No implicit conversions are applied. Ex: +// [Integer, String, Choice, String] will return Choice +func DeDuplicate(ts []types.IType) (types.IType, error) { + if ts == nil || len(ts) == 0 { + return nil, errors.New("internal error - empty or nil list of types passed to DeDuplicate") + } + + var flatTypes []types.IType + for _, t := range ts { + ts, err := flattenChoices(t, 0) + if err != nil { + return nil, err + } + flatTypes = append(flatTypes, ts...) + } + + choiceType := &types.Choice{ChoiceTypes: []types.IType{}} + for _, t := range flatTypes { + if !containsType(choiceType.ChoiceTypes, t) { + choiceType.ChoiceTypes = append(choiceType.ChoiceTypes, t) + } + } + + if len(choiceType.ChoiceTypes) == 1 { + // Choice type is not needed. + return choiceType.ChoiceTypes[0], nil + } + + return choiceType, nil +} + +// Intersect finds the intersection of two types. Choice types are flattened and no implicit +// conversions are applied. Ex Integer, Choice will return Integer. +func Intersect(left types.IType, right types.IType) (types.IType, error) { + if left.Equal(right) { + return left, nil + } + + flatTypesLeft, err := flattenChoices(left, 0) + if err != nil { + return nil, err + } + flatTypesRight, err := flattenChoices(right, 0) + if err != nil { + return nil, err + } + + choiceType := &types.Choice{ChoiceTypes: []types.IType{}} + for _, t := range flatTypesLeft { + if containsType(flatTypesRight, t) && !containsType(choiceType.ChoiceTypes, t) { + choiceType.ChoiceTypes = append(choiceType.ChoiceTypes, t) + } + } + + if len(choiceType.ChoiceTypes) == 1 { + // Choice type is not needed. + return choiceType.ChoiceTypes[0], nil + } + + if len(choiceType.ChoiceTypes) == 0 { + return nil, fmt.Errorf("no common types between %v and %v", left, right) + } + + return choiceType, nil +} + +func flattenChoices(t types.IType, recursion int) ([]types.IType, error) { + if recursion > 100000 { + return nil, fmt.Errorf("internal error - nested choice recursion limit exceeded") + } + choiceT, ok := t.(*types.Choice) + if !ok { + // Not a choice type so end recursion. + return []types.IType{t}, nil + } + + var flatTypes []types.IType + for _, t := range choiceT.ChoiceTypes { + ts, err := flattenChoices(t, recursion+1) + if err != nil { + return nil, err + } + flatTypes = append(flatTypes, ts...) + } + return flatTypes, nil +} + +func containsType(types []types.IType, arg types.IType) bool { + for _, t := range types { + if t.Equal(arg) { + return true + } + } + return false +} diff --git a/internal/convert/mixed_test.go b/internal/convert/mixed_test.go new file mode 100644 index 0000000..5ea9779 --- /dev/null +++ b/internal/convert/mixed_test.go @@ -0,0 +1,290 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" +) + +func TestInferMixedType(t *testing.T) { + tests := []struct { + name string + invoked []model.IExpression + want Infered + }{ + { + name: "Multiple Operands Compatible", + invoked: []model.IExpression{ + model.NewLiteral("4", types.Integer), + model.NewLiteral("4.4", types.Decimal), + model.NewLiteral("5", types.Long)}, + want: Infered{ + PuntedToChoice: false, + UniformType: types.Decimal, + WrappedOperands: []model.IExpression{ + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + model.NewLiteral("4.4", types.Decimal), + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("5", types.Long), + Expression: model.ResultType(types.Decimal), + }, + }, + }, + }, + }, + { + name: "Multiple Operands Compatible Same Type", + invoked: []model.IExpression{ + model.NewLiteral("4", types.Integer), + model.NewLiteral("5", types.Integer)}, + want: Infered{ + PuntedToChoice: false, + UniformType: types.Integer, + WrappedOperands: []model.IExpression{ + model.NewLiteral("4", types.Integer), + model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "Any triggers Compatible not Subtype", + invoked: []model.IExpression{ + model.NewLiteral("4", types.Integer), + model.NewLiteral("null", types.Any)}, + want: Infered{ + PuntedToChoice: false, + UniformType: types.Integer, + WrappedOperands: []model.IExpression{ + model.NewLiteral("4", types.Integer), + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + Strict: false, + }, + }, + }, + }, + { + name: "All Any", + invoked: []model.IExpression{ + model.NewLiteral("null", types.Any), + model.NewLiteral("null", types.Any), + }, + want: Infered{ + PuntedToChoice: false, + UniformType: types.Any, + WrappedOperands: []model.IExpression{ + model.NewLiteral("null", types.Any), + model.NewLiteral("null", types.Any), + }, + }, + }, + { + name: "Single Operand Compatible", + invoked: []model.IExpression{ + model.NewLiteral("string", types.String), + }, + want: Infered{ + PuntedToChoice: false, + UniformType: types.String, + WrappedOperands: []model.IExpression{ + model.NewLiteral("string", types.String), + }, + }, + }, + { + name: "No Operands", + invoked: []model.IExpression{}, + want: Infered{ + PuntedToChoice: false, + UniformType: types.Any, + WrappedOperands: []model.IExpression{}, + }, + }, + { + name: "Multiple Operands Incompatible", + invoked: []model.IExpression{ + model.NewLiteral("String", types.String), + // This tests that it is Choice> not Choice> + model.NewLiteral("String2", types.String), + model.NewList([]string{"4", "5"}, types.Integer), + }, + want: Infered{ + PuntedToChoice: true, + UniformType: &types.Choice{ChoiceTypes: []types.IType{types.String, &types.List{ElementType: types.Integer}}}, + WrappedOperands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("String", types.String), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.String, &types.List{ElementType: types.Integer}}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.String, &types.List{ElementType: types.Integer}}}, + Strict: false, + }, + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("String2", types.String), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.String, &types.List{ElementType: types.Integer}}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.String, &types.List{ElementType: types.Integer}}}, + Strict: false, + }, + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewList([]string{"4", "5"}, types.Integer), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.String, &types.List{ElementType: types.Integer}}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.String, &types.List{ElementType: types.Integer}}}, + Strict: false, + }, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + got, err := InferMixed(tc.invoked, modelinfo) + if err != nil { + t.Fatalf("InferMixed() unexpected err: %v", err) + } + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("InferMixed() diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestDeDuplicate(t *testing.T) { + tests := []struct { + name string + input []types.IType + want types.IType + }{ + { + name: "Duplicates removed", + input: []types.IType{types.Integer, types.String, types.Integer}, + want: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + }, + { + name: "Choice flattened with duplicates removed", + input: []types.IType{types.Integer, types.String, &types.Choice{ChoiceTypes: []types.IType{types.Integer, &types.List{ElementType: types.Integer}, &types.Choice{ChoiceTypes: []types.IType{types.Quantity}}}}}, + want: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String, &types.List{ElementType: types.Integer}, types.Quantity}}, + }, + { + name: "Choice is not needed", + input: []types.IType{types.Integer, types.Integer}, + want: types.Integer, + }, + { + name: "No implicit conversions applied", + input: []types.IType{types.Decimal, types.Integer}, + want: &types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.Integer}}, + }, + } + + for _, tc := range tests { + got, err := DeDuplicate(tc.input) + if err != nil { + t.Errorf("DeDuplicate(%v) returned an unexpected error: %v", tc.input, err) + continue + } + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("DeDuplicate(%v) returned an unexpected diff (-want +got): %v", tc.input, diff) + } + } +} + +func TestIntersect(t *testing.T) { + tests := []struct { + name string + left types.IType + right types.IType + want types.IType + }{ + { + name: "Equal types", + left: types.Integer, + right: types.Integer, + want: types.Integer, + }, + { + name: "Choice flattened", + left: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String, &types.List{ElementType: types.Integer}, types.Quantity}}, + right: &types.Choice{ChoiceTypes: []types.IType{types.Integer, &types.List{ElementType: types.Integer}, &types.Choice{ChoiceTypes: []types.IType{types.Quantity}}}}, + want: &types.Choice{ChoiceTypes: []types.IType{types.Integer, &types.List{ElementType: types.Integer}, types.Quantity}}, + }, + { + name: "Choice is not needed", + left: &types.Choice{ChoiceTypes: []types.IType{types.Integer}}, + right: types.Integer, + want: types.Integer, + }, + } + + for _, tc := range tests { + got, err := Intersect(tc.left, tc.right) + if err != nil { + t.Errorf("Intersect(%v, %v) returned an unexpected error: %v", tc.left, tc.right, err) + continue + } + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("Intersect(%v, %v) returned an unexpected diff (-want +got): %v", tc.left, tc.right, diff) + } + } +} + +func TestIntersect_Errors(t *testing.T) { + tests := []struct { + name string + left types.IType + right types.IType + wantErr string + }{ + { + name: "No common types", + left: types.Integer, + right: types.String, + wantErr: "no common types between", + }, + } + + for _, tc := range tests { + _, gotErr := Intersect(tc.left, tc.right) + if gotErr == nil { + t.Fatalf("Intersect succeeded, but expected an error") + } + if !strings.Contains(gotErr.Error(), tc.wantErr) { + t.Errorf("Unexpected evaluation error contents. got (%v) want (%v)", gotErr.Error(), tc.wantErr) + } + } +} diff --git a/internal/datehelpers/parse.go b/internal/datehelpers/parse.go new file mode 100644 index 0000000..3676606 --- /dev/null +++ b/internal/datehelpers/parse.go @@ -0,0 +1,349 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package datehelpers provides functions for parsing CQL date, datetime and time strings. +package datehelpers + +import ( + "errors" + "fmt" + regex "regexp" + "strconv" + "strings" + "time" + + "github.com/google/cql/model" + "github.com/google/cql/types" + d4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" +) + +// Constants for parsing CQL date, datetime and time strings. +var ( + // Date layout constants. + dateYear = "2006" + dateMonth = "2006-01" + dateDay = "2006-01-02" + + // DateTime layout constants. + dateTimeYear = "2006T" + dateTimeMonth = "2006-01T" + dateTimeDay = "2006-01-02T" + dateTimeHour = "2006-01-02T15" + dateTimeMinute = "2006-01-02T15:04" + dateTimeSecond = "2006-01-02T15:04:05" + dateTimeOneMillisecond = "2006-01-02T15:04:05.0" + dateTimeTwoMillisecond = "2006-01-02T15:04:05.00" + dateTimeThreeMillisecond = "2006-01-02T15:04:05.000" + + // Time layout constants. + timeHour = "T15" + timeMinute = "T15:04" + timeSecond = "T15:04:05" + timeOneMillisecond = "T15:04:05.0" + timeTwoMillisecond = "T15:04:05.00" + timeThreeMillisecond = "T15:04:05.000" + + // Timezone constants. + zuluTZ = "Z" + tz = "-07:00" +) + +// ErrUnsupportedPrecision is returned when a precision is not supported. +var ErrUnsupportedPrecision = errors.New("unsupported precision") + +// ParseDate parses a CQL Date string into a golang time. CQL Dates start with @ and follow a subset +// of ISO-8601. +// +// CQL Dates do not have timezone offsets, but when converting a Date to a DateTime the offset of +// the evaluation timestamp is used. Since all golang times require a location we set all Date +// offset to the offset of the evaluation timestamp. +func ParseDate(rawStr string, evaluationLoc *time.Location) (time.Time, model.DateTimePrecision, error) { + if evaluationLoc == nil { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error - evaluationLoc must be set when calling ParseDate") + } + + if len(rawStr) == 0 || rawStr[0] != '@' { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error - datetime string %v, must start with @", rawStr) + } + str := rawStr[1:] + + dates := []struct { + layout string + precision model.DateTimePrecision + }{ + {layout: dateYear, precision: model.YEAR}, + {layout: dateMonth, precision: model.MONTH}, + {layout: dateDay, precision: model.DAY}, + } + + var err error + var parsedTime time.Time + for _, d := range dates { + parsedTime, err = time.ParseInLocation(d.layout, str, evaluationLoc) + if err == nil { + return parsedTime, d.precision, nil + } + } + + if parseErr, ok := err.(*time.ParseError); ok { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmtParsingErr(rawStr, types.Date, "@YYYY-MM-DD", parseErr) + } + return time.Time{}, model.UNSETDATETIMEPRECISION, err +} + +// ParseDateTime parses a CQL DateTime string into a golang time. CQL Dates start with @ and follow +// a subset of ISO-8601. If rawStr does not include an offset then evaluationLoc will be used. +// Otherwise, the offset in rawStr is used. +func ParseDateTime(rawStr string, evaluationLoc *time.Location) (time.Time, model.DateTimePrecision, error) { + if evaluationLoc == nil { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error - evaluationLoc must be set when calling ParseDateTime") + } + + if len(rawStr) == 0 || rawStr[0] != '@' { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error - datetime string %v, must start with @", rawStr) + } + str := rawStr[1:] + + // Since time.ParseInLocation allows any number of fractional seconds no matter the layout, we + // must manually check. + re := regex.MustCompile(`\.\d{4}`) + if re.MatchString(rawStr) { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("%v %v can have at most 3 digits of milliseconds precision, want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm)", types.DateTime, rawStr) + } + + datetimes := []struct { + layout string + precision model.DateTimePrecision + }{ + {layout: dateTimeYear, precision: model.YEAR}, + {layout: dateTimeMonth, precision: model.MONTH}, + {layout: dateTimeDay, precision: model.DAY}, + {layout: dateTimeHour, precision: model.HOUR}, + {layout: dateTimeMinute, precision: model.MINUTE}, + // For ParseInLocation, the input may contain a fractional second field immediately after the + // seconds field, even if the layout does not signify its presence. So, we have to do things in + // this order. + {layout: dateTimeOneMillisecond, precision: model.MILLISECOND}, + {layout: dateTimeTwoMillisecond, precision: model.MILLISECOND}, + {layout: dateTimeThreeMillisecond, precision: model.MILLISECOND}, + {layout: dateTimeSecond, precision: model.SECOND}, + } + + var err error + var parsedTime time.Time + for _, dt := range datetimes { + for _, timezone := range []string{zuluTZ, tz, ""} { + loc := evaluationLoc + if timezone == zuluTZ { + loc = time.UTC + } + parsedTime, err = time.ParseInLocation(fmt.Sprintf("%v%v", dt.layout, timezone), str, loc) + if err == nil { + return parsedTime, dt.precision, nil + } + } + } + + if parseErr, ok := err.(*time.ParseError); ok { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmtParsingErr(rawStr, types.DateTime, "@YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm)", parseErr) + } + return time.Time{}, model.UNSETDATETIMEPRECISION, err +} + +// ParseTime parses a CQL Time string into a golang time. CQL Time start with @ and roughly follow +// ISO-8601. +func ParseTime(rawStr string, evaluationLoc *time.Location) (time.Time, model.DateTimePrecision, error) { + if len(rawStr) == 0 || rawStr[0] != '@' { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error - datetime string %v, must start with @", rawStr) + } + str := rawStr[1:] + + // Since time.ParseInLocation allows any number of fractional seconds no matter the layout, we + // must manually check. + re := regex.MustCompile(`\.\d{4}`) + if re.MatchString(rawStr) { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("%v %v can have at most 3 digits of milliseconds precision, want a layout like @Thh:mm:ss.fff", types.Time, rawStr) + } + + times := []struct { + layout string + precision model.DateTimePrecision + }{ + {layout: timeHour, precision: model.HOUR}, + {layout: timeMinute, precision: model.MINUTE}, + // For ParseInLocation, the input may contain a fractional second field immediately after the + // seconds field, even if the layout does not signify its presence. So, we have to do things in + // this order. + {layout: timeOneMillisecond, precision: model.MILLISECOND}, + {layout: timeTwoMillisecond, precision: model.MILLISECOND}, + {layout: timeThreeMillisecond, precision: model.MILLISECOND}, + {layout: timeSecond, precision: model.SECOND}, + } + + var err error + var parsedTime time.Time + for _, t := range times { + parsedTime, err = time.ParseInLocation(t.layout, str, evaluationLoc) + if err == nil { + return parsedTime, t.precision, nil + } + } + + if parseErr, ok := err.(*time.ParseError); ok { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmtParsingErr(rawStr, types.Time, "@Thh:mm:ss.fff", parseErr) + } + return time.Time{}, model.UNSETDATETIMEPRECISION, err +} + +// TODO: b/341120071 - if we refactor the FHIR proto library to expose the date/dateTime helpers, +// we can consider using them here to help. Specifically we can consider using SerializeDate to +// transform back to a FHIR string which might be easier to process, and include the FHIR proto +// roundtrip logic. + +// ParseFHIRDate parses a FHIR Date proto into a golang time. Similar to other helpers in +// this package, if the proto does not have a timezone set then evaluationLoc will be used. +// +// To match ParseDate, we take the year, month, and day values from the FHIR proto (in the FHIR +// proto's timezone) and create a new time.Time at 0:00:00 in the evaluationLoc timezone, which is +// the default timezone we attach to all CQL Dates in our codebase. +func ParseFHIRDate(d *d4pb.Date, evaluationLoc *time.Location) (time.Time, model.DateTimePrecision, error) { + secs := d.GetValueUs() / 1e6 // Integer division + // time.Unix parses the Date into the local timezone, which is not what we want. What we want is + // to get the Date (year, month, day) values in the original timezone of the proto, and then + // attach the evaluationLoc timezone to it at 0:00:00. + t := time.Unix(secs, 0) + // if tz is not set then use the evaluationLoc. It's unclear if it's even possible for this to + // be unset. + loc := evaluationLoc + if tz := d.GetTimezone(); tz != "" { + var err error + loc, err = getLocation(tz) + if err != nil { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("error loading timezone from FHIR %w", err) + } + } + t = t.In(loc) + // t is now in its original timezone. We're going to grab the day, month, and year and create + // a new time in the evaluation request timezone. + t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, evaluationLoc) + + return t, datePrecisionFromProto(d.GetPrecision()), nil +} + +// ParseFHIRDateTime parses a FHIR DateTime proto into a golang time. Similar to other helpers in +// this package, if the proto does not have a timezone set then evaluationLoc will be used. +func ParseFHIRDateTime(d *d4pb.DateTime, evaluationLoc *time.Location) (time.Time, model.DateTimePrecision, error) { + if evaluationLoc == nil { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error - evaluationLoc must be set when calling ParseFHIRDateTime") + } + secs := d.GetValueUs() / 1e6 // Integer division + usecRemainder := d.GetValueUs() % 1e6 + nsRemainder := usecRemainder * 1e3 + t := time.Unix(secs, nsRemainder) + + // If the proto has a timezone set then we use it, otherwise we use the evaluationLoc. It's + // unclear if it's even possible for the timezone to be unset. + loc := evaluationLoc + var err error + if tz := d.GetTimezone(); tz != "" { + loc, err = getLocation(tz) + if err != nil { + return time.Time{}, model.UNSETDATETIMEPRECISION, fmt.Errorf("error loading timezone from FHIR %w", err) + } + } + + return t.In(loc), dateTimePrecisionFromProto(d.GetPrecision()), nil +} + +func datePrecisionFromProto(p d4pb.Date_Precision) model.DateTimePrecision { + switch p { + case d4pb.Date_YEAR: + return model.YEAR + case d4pb.Date_MONTH: + return model.MONTH + case d4pb.Date_DAY: + return model.DAY + } + return model.UNSETDATETIMEPRECISION +} + +func dateTimePrecisionFromProto(p d4pb.DateTime_Precision) model.DateTimePrecision { + switch p { + case d4pb.DateTime_YEAR: + return model.YEAR + case d4pb.DateTime_MONTH: + return model.MONTH + case d4pb.DateTime_DAY: + return model.DAY + case d4pb.DateTime_SECOND: + return model.SECOND + case d4pb.DateTime_MILLISECOND: + return model.MILLISECOND + // FHIR datetimes can have microsecond precision, since CQL doesn't support this we map it to millisecond. + case d4pb.DateTime_MICROSECOND: + return model.MILLISECOND + } + return model.UNSETDATETIMEPRECISION +} + +// getLocation and offsetToSeconds are copied from FHIR Proto: +// https://github.com/google/fhir/blob/5ae1b8d319bce275c16457c1f3c321804c202488/go/jsonformat/internal/jsonpbhelper/fhirutil.go#L500 +// TODO: b/341120071 - we should refactor FHIR proto so we can depend on their time helpers +// directly. + +// getLocation parses tz as an IANA location or a UTC offset. +func getLocation(tz string) (*time.Location, error) { + if tz == "UTC" { + return time.UTC, nil + } + l, err := time.LoadLocation(tz) + if err != nil { + offset, err := offsetToSeconds(tz) + if err != nil { + return nil, err + } + return time.FixedZone(tz, offset), nil + } + return l, nil +} + +func offsetToSeconds(offset string) (int, error) { + if offset == "" || offset == "UTC" { + return 0, nil + } + sign := offset[0] + if sign != '+' && sign != '-' { + return 0, fmt.Errorf("invalid timezone offset: %v", offset) + } + arr := strings.Split(offset[1:], ":") + if len(arr) != 2 { + return 0, fmt.Errorf("invalid timezone offset: %v", offset) + } + hour, err := strconv.Atoi(arr[0]) + if err != nil { + return 0, fmt.Errorf("invalid hour in timezone offset %v: %v", offset, err) + } + minute, err := strconv.Atoi(arr[1]) + if err != nil { + return 0, fmt.Errorf("invalid minute in timezone offset %v: %v", offset, err) + } + if sign == '-' { + return -hour*3600 - minute*60, nil + } + return hour*3600 + minute*60, nil +} + +func fmtParsingErr(rawStr string, t types.IType, layout string, e *time.ParseError) error { + return fmt.Errorf("got %v %v but want a layout like %v%v", t, rawStr, layout, e.Message) +} diff --git a/internal/datehelpers/parse_test.go b/internal/datehelpers/parse_test.go new file mode 100644 index 0000000..88e168c --- /dev/null +++ b/internal/datehelpers/parse_test.go @@ -0,0 +1,640 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datehelpers + +import ( + "strings" + "testing" + "time" + + "github.com/google/cql/model" + d4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" +) + +func TestParseDate(t *testing.T) { + evaluationLoc := time.FixedZone("Fixed", 4*60*60) + tests := []struct { + name string + str string + wantTime time.Time + wantPrecision model.DateTimePrecision + }{ + { + name: "Year", + str: "@2018", + wantTime: time.Date(2018, 1, 1, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.YEAR, + }, + { + name: "Month", + str: "@2018-02", + wantTime: time.Date(2018, 2, 1, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.MONTH, + }, + { + name: "Day", + str: "@2018-02-02", + wantTime: time.Date(2018, 2, 2, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.DAY, + }, + { + name: "Max date", + str: "@9999-12-31", + wantTime: time.Date(9999, 12, 31, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.DAY, + }, + { + name: "Min date", + str: "@0001-01-01", + wantTime: time.Date(1, 1, 1, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.DAY, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotTime, gotPrecision, err := ParseDate(tc.str, evaluationLoc) + if err != nil { + t.Errorf("ParseDate returned unexpected error: %v", err) + } + if !gotTime.Equal(tc.wantTime) { + t.Errorf("ParseDate returned unexpected time: got %v, want %v", gotTime, tc.wantTime) + } + if gotPrecision != tc.wantPrecision { + t.Errorf("ParseDate returned unexpected precision: got %v, want %v", gotPrecision, tc.wantPrecision) + } + }) + } +} + +func TestParseDate_Error(t *testing.T) { + evaluationLoc := time.FixedZone("Fixed", 4*60*60) + tests := []struct { + name string + str string + wantError string + }{ + { + name: "Missing @", + str: "2018-01-01", + wantError: "must start with @", + }, + { + name: "Month out of range", + str: "@2018-13", + wantError: `got System.Date @2018-13 but want a layout like @YYYY-MM-DD: month out of range`, + }, + { + name: "Ends with T", + str: "@2018-01-01T", + wantError: `got System.Date @2018-01-01T but want a layout like @YYYY-MM-DD: extra text: "T"`, + }, + { + name: "Does not match format", + str: "@2018-01-01T15", + wantError: `got System.Date @2018-01-01T15 but want a layout like @YYYY-MM-DD: extra text: "T15"`, + }, + { + name: "Also does not match format", + str: "@01-01", + wantError: `got System.Date @01-01 but want a layout like @YYYY-MM-DD`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, _, err := ParseDate(tc.str, evaluationLoc) + if err == nil { + t.Fatal("ParseDate returned did not return an error") + } + if !strings.Contains(err.Error(), tc.wantError) { + t.Errorf("ParseDate returned unexpected error: got (%v) want (%v)", err, tc.wantError) + } + }) + } +} + +func TestParseDateTime(t *testing.T) { + evaluationLoc := time.FixedZone("Fixed", 9*60*60) + tests := []struct { + name string + str string + wantTime time.Time + wantPrecision model.DateTimePrecision + }{ + { + name: "Year", + str: "@2018T", + wantTime: time.Date(2018, 1, 1, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.YEAR, + }, + { + name: "Month", + str: "@2018-02T", + wantTime: time.Date(2018, 2, 1, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.MONTH, + }, + { + name: "Day", + str: "@2018-02-02T", + wantTime: time.Date(2018, 2, 2, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.DAY, + }, + { + name: "Hour", + str: "@2018-02-02T15", + wantTime: time.Date(2018, 2, 2, 15, 0, 0, 0, evaluationLoc), + wantPrecision: model.HOUR, + }, + { + name: "Minute", + str: "@2018-02-02T15:02", + wantTime: time.Date(2018, 2, 2, 15, 2, 0, 0, evaluationLoc), + wantPrecision: model.MINUTE, + }, + { + name: "Second", + str: "@2018-02-02T15:02:03", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 0, evaluationLoc), + wantPrecision: model.SECOND, + }, + { + name: "One Digit Millisecond", + str: "@2018-02-02T15:02:03.1", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 100000000, evaluationLoc), + wantPrecision: model.MILLISECOND, + }, + { + name: "Two Digit Millisecond", + str: "@2018-02-02T15:02:03.12", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 120000000, evaluationLoc), + wantPrecision: model.MILLISECOND, + }, + { + name: "Three Digit Millisecond", + str: "@2018-02-02T15:02:03.123", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 123000000, evaluationLoc), + wantPrecision: model.MILLISECOND, + }, + { + name: "Year with timezone", + str: "@2018T-04:00", + wantTime: time.Date(2018, 1, 1, 0, 0, 0, 0, time.FixedZone("-04:00", -4*60*60)), + wantPrecision: model.YEAR, + }, + { + name: "Month with timezone", + str: "@2018-02T-04:00", + wantTime: time.Date(2018, 2, 1, 0, 0, 0, 0, time.FixedZone("-04:00", -4*60*60)), + wantPrecision: model.MONTH, + }, + { + name: "Day with timezone", + str: "@2018-02-02T-04:00", + wantTime: time.Date(2018, 2, 2, 0, 0, 0, 0, time.FixedZone("-04:00", -4*60*60)), + wantPrecision: model.DAY, + }, + { + name: "Hour with timezone", + str: "@2018-02-02T15-04:00", + wantTime: time.Date(2018, 2, 2, 15, 0, 0, 0, time.FixedZone("-04:00", -4*60*60)), + wantPrecision: model.HOUR, + }, + { + name: "Minute with timezone", + str: "@2018-02-02T15:02-04:00", + wantTime: time.Date(2018, 2, 2, 15, 2, 0, 0, time.FixedZone("-04:00", -4*60*60)), + wantPrecision: model.MINUTE, + }, + + { + name: "Second with timezone", + str: "@2018-02-02T15:02:03-04:00", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 0, time.FixedZone("-04:00", -4*60*60)), + wantPrecision: model.SECOND, + }, + { + name: "Millisecond with timezone", + str: "@2018-02-02T15:02:03.004-04:00", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 4000000, time.FixedZone("-04:00", -4*60*60)), + wantPrecision: model.MILLISECOND, + }, + { + name: "Year with zulu timezone", + str: "@2018TZ", + wantTime: time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), + wantPrecision: model.YEAR, + }, + { + name: "Month with zulu timezone", + str: "@2018-02TZ", + wantTime: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC), + wantPrecision: model.MONTH, + }, + { + name: "Day with zulu timezone", + str: "@2018-02-02TZ", + wantTime: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC), + wantPrecision: model.DAY, + }, + { + name: "Hour with zulu timezone", + str: "@2018-02-02T15Z", + wantTime: time.Date(2018, 2, 2, 15, 0, 0, 0, time.UTC), + wantPrecision: model.HOUR, + }, + { + name: "Minute with zulu timezone", + str: "@2018-02-02T15:02Z", + wantTime: time.Date(2018, 2, 2, 15, 2, 0, 0, time.UTC), + wantPrecision: model.MINUTE, + }, + { + name: "Second with zulu timezone", + str: "@2018-02-02T15:02:03Z", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 0, time.UTC), + wantPrecision: model.SECOND, + }, + { + name: "Millisecond with zulu timezone", + str: "@2018-02-02T15:02:03.004Z", + wantTime: time.Date(2018, 2, 2, 15, 2, 3, 4000000, time.UTC), + wantPrecision: model.MILLISECOND, + }, + { + name: "Max datetime", + str: "@9999-12-31T23:59:59.999", + wantTime: time.Date(9999, 12, 31, 23, 59, 59, 999000000, evaluationLoc), + wantPrecision: model.MILLISECOND, + }, + { + name: "Min datetime", + str: "@0001-01-01T00:00:00.0", + wantTime: time.Date(1, 1, 1, 0, 0, 0, 0, evaluationLoc), + wantPrecision: model.MILLISECOND, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotTime, gotPrecision, err := ParseDateTime(tc.str, evaluationLoc) + if err != nil { + t.Errorf("ParseDateTime returned unexpected error: %v", err) + } + if !gotTime.Equal(tc.wantTime) { + t.Errorf("ParseDateTime returned unexpected time: got %v, want %v", gotTime, tc.wantTime) + } + if gotPrecision != tc.wantPrecision { + t.Errorf("ParseDateTime returned unexpected precision: got %v, want %v", gotPrecision, tc.wantPrecision) + } + }) + } +} + +func TestParseDateTime_Error(t *testing.T) { + evaluationLoc := time.FixedZone("Fixed", 4*60*60) + tests := []struct { + name string + str string + wantError string + }{ + { + name: "Missing @", + str: "2018-02-02T15", + wantError: "must start with @", + }, + { + name: "Hour out of range", + str: "@2018-02-02T29", + wantError: `got System.DateTime @2018-02-02T29 but want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm): hour out of range`, + }, + { + name: "Time component without day", + str: "@2018-01T15", + wantError: `got System.DateTime @2018-01T15 but want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm)`, + }, + { + name: "Too precise", + str: "@2018-02-02T15:02:03.004523Z", + wantError: `System.DateTime @2018-02-02T15:02:03.004523Z can have at most 3 digits of milliseconds precision, want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm)`, + }, + { + name: "Does not match format", + str: "@01-01", + wantError: `got System.DateTime @01-01 but want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm)`, + }, + { + name: "Invalid timezone offset", + str: "@2018-02-02T15:02:03.004-34:00", + wantError: `got System.DateTime @2018-02-02T15:02:03.004-34:00 but want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm): extra text: "-34:00"`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, _, err := ParseDateTime(tc.str, evaluationLoc) + if err == nil { + // TODO: This test cases parses with Go 1.22 + // but fails (correctly) with Go 1.23. + // Until switching to Go 1.23, + // ignore this test if it parses. + // This can be removed after we can assume + // that all users are at Go 1.23. + if tc.str == "@2018-02-02T15:02:03.004-34:00" { + t.Skip("skipping will not start failing until Go 1.23") + } + t.Fatal("ParseDateTime returned did not return an error") + } + if !strings.Contains(err.Error(), tc.wantError) { + t.Errorf("ParseDateTime returned unexpected error: got (%v) want (%v)", err, tc.wantError) + } + }) + } +} + +func TestParseTime(t *testing.T) { + tests := []struct { + name string + str string + wantTime time.Time + wantPrecision model.DateTimePrecision + }{ + { + name: "Hour", + str: "@T15", + wantTime: time.Date(0, 1, 1, 15, 0, 0, 0, time.UTC), + wantPrecision: model.HOUR, + }, + { + name: "Minute", + str: "@T15:02", + wantTime: time.Date(0, 1, 1, 15, 2, 0, 0, time.UTC), + wantPrecision: model.MINUTE, + }, + { + name: "Second", + str: "@T15:02:03", + wantTime: time.Date(0, 1, 1, 15, 2, 3, 0, time.UTC), + wantPrecision: model.SECOND, + }, + { + name: "Millisecond", + str: "@T15:02:03.004", + wantTime: time.Date(0, 1, 1, 15, 2, 3, 4000000, time.UTC), + wantPrecision: model.MILLISECOND, + }, + { + name: "Max time", + str: "@T23:59:59.999", + wantTime: time.Date(0, 1, 1, 23, 59, 59, 999000000, time.UTC), + wantPrecision: model.MILLISECOND, + }, + { + name: "Min time", + str: "@T00:00:00.0", + wantTime: time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), + wantPrecision: model.MILLISECOND, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotTime, gotPrecision, err := ParseTime(tc.str, time.UTC) + if err != nil { + t.Errorf("ParseTime returned unexpected error: %v", err) + } + if !gotTime.Equal(tc.wantTime) { + t.Errorf("ParseTime returned unexpected time: got %v, want %v", gotTime, tc.wantTime) + } + if gotPrecision != tc.wantPrecision { + t.Errorf("ParseTime returned unexpected precision: got %v, want %v", gotPrecision, tc.wantPrecision) + } + }) + } +} + +func TestParseTime_Error(t *testing.T) { + tests := []struct { + name string + str string + wantError string + }{ + { + name: "Missing @", + str: "T15", + wantError: "must start with @", + }, + { + name: "Hour out of range", + str: "@T29", + wantError: `got System.Time @T29 but want a layout like @Thh:mm:ss.fff: hour out of range`, + }, + { + name: "Too precise", + str: "@T15:02:03.0045", + wantError: `System.Time @T15:02:03.0045 can have at most 3 digits of milliseconds precision, want a layout like @Thh:mm:ss.fff`, + }, + { + name: "Has a timezone", + str: "@T15:02:03.004Z", + wantError: `got System.Time @T15:02:03.004Z but want a layout like @Thh:mm:ss.fff: extra text: "Z"`, + }, + { + name: "Does not match format", + str: "@01-01", + wantError: `got System.Time @01-01 but want a layout like @Thh:mm:ss.fff`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, _, err := ParseTime(tc.str, time.UTC) + if err == nil { + t.Fatal("ParseTime returned did not return an error") + } + if !strings.Contains(err.Error(), tc.wantError) { + t.Errorf("ParseTime returned unexpected error: got (%v) want (%v)", err, tc.wantError) + } + }) + } +} + +func TestParseFHIRDateTime(t *testing.T) { + tests := []struct { + name string + dateTime *d4pb.DateTime + evaluationLoc *time.Location + wantTime time.Time + wantPrecision model.DateTimePrecision + }{ + { + name: "DateTime in UTC", + dateTime: &d4pb.DateTime{ValueUs: 1711936984000000, Precision: d4pb.DateTime_SECOND, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 2, 3, 4, 0, time.UTC), + wantPrecision: model.SECOND, + }, + { + name: "DateTime in America/Los_Angeles", + dateTime: &d4pb.DateTime{ValueUs: 1711936984000000, Precision: d4pb.DateTime_SECOND, Timezone: "America/Los_Angeles"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.March, 31, 19, 3, 4, 0, time.FixedZone("America/Los_Angeles", -7*60*60)), + wantPrecision: model.SECOND, + }, + { + name: "DateTime using -07:00 offset", + dateTime: &d4pb.DateTime{ValueUs: 1711936984000000, Precision: d4pb.DateTime_SECOND, Timezone: "-07:00"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.March, 31, 19, 3, 4, 0, time.FixedZone("-07:00", -7*60*60)), + wantPrecision: model.SECOND, + }, + { + name: "DateTime using +07:00 offset", + dateTime: &d4pb.DateTime{ValueUs: 1711936984000000, Precision: d4pb.DateTime_SECOND, Timezone: "+07:00"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 9, 3, 4, 0, time.FixedZone("+07:00", +7*60*60)), + wantPrecision: model.SECOND, + }, + { + name: "DateTime fallback to evaluation location", + dateTime: &d4pb.DateTime{ValueUs: 1711936984000000, Precision: d4pb.DateTime_SECOND, Timezone: ""}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.March, 31, 19, 3, 4, 0, time.FixedZone("America/Los_Angeles", -7*60*60)), + wantPrecision: model.SECOND, + }, + { + name: "DateTime Millisecond precision", + dateTime: &d4pb.DateTime{ValueUs: 1711936984500000, Precision: d4pb.DateTime_MILLISECOND, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 2, 3, 4, 5e8, time.UTC), + wantPrecision: model.MILLISECOND, + }, + { + name: "DateTime Microsecond maps to Millisecond precision", + dateTime: &d4pb.DateTime{ValueUs: 1711936984500000, Precision: d4pb.DateTime_MICROSECOND, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 2, 3, 4, 5e8, time.UTC), + wantPrecision: model.MILLISECOND, + }, + { + name: "DateTime Day precision", + dateTime: &d4pb.DateTime{ValueUs: 1711936984500000, Precision: d4pb.DateTime_DAY, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 2, 3, 4, 5e8, time.UTC), + wantPrecision: model.DAY, + }, + { + name: "DateTime Month precision", + dateTime: &d4pb.DateTime{ValueUs: 1711936984500000, Precision: d4pb.DateTime_MONTH, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 2, 3, 4, 5e8, time.UTC), + wantPrecision: model.MONTH, + }, + { + name: "DateTime Year precision", + dateTime: &d4pb.DateTime{ValueUs: 1711936984500000, Precision: d4pb.DateTime_YEAR, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 2, 3, 4, 5e8, time.UTC), + wantPrecision: model.YEAR, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotTime, gotPrecision, err := ParseFHIRDateTime(tc.dateTime, tc.evaluationLoc) + if err != nil { + t.Errorf("ParseFHIRDateTime returned unexpected error: %v", err) + } + if !gotTime.Equal(tc.wantTime) { + t.Errorf("ParseFHIRDateTime returned unexpected time: got %v, want %v", gotTime, tc.wantTime) + } + if gotPrecision != tc.wantPrecision { + t.Errorf("ParseFHIRDateTime returned unexpected precision: got %v, want %v", gotPrecision, tc.wantPrecision) + } + }) + } +} + +func TestParseFHIRDateTime_NoEvalTimestampError(t *testing.T) { + _, _, err := ParseFHIRDateTime(&d4pb.DateTime{ValueUs: 1711929600000000, Precision: d4pb.DateTime_SECOND, Timezone: ""}, nil) + if err == nil { + t.Fatal("ParseFHIRDateTime returned did not return an error") + } + if !strings.Contains(err.Error(), "internal error - evaluationLoc must be set when calling ParseFHIRDateTime") { + t.Errorf("ParseFHIRDateTime returned unexpected error: got (%v) want (%v)", err, "evaluation location") + } +} + +func TestParseFHIRDate(t *testing.T) { + tests := []struct { + name string + date *d4pb.Date + evaluationLoc *time.Location + wantTime time.Time + wantPrecision model.DateTimePrecision + }{ + { + name: "Date with Day precision", + date: &d4pb.Date{ValueUs: 1711929600000000, Precision: d4pb.Date_DAY, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 0, 0, 0, 0, time.FixedZone("America/Los_Angeles", -7*60*60)), + wantPrecision: model.DAY, + }, + { + name: "Date with offset based timzeone in the proto", + date: &d4pb.Date{ValueUs: 1711929600000000, Precision: d4pb.Date_DAY, Timezone: "-07:00"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + // The date corresponding to ValueUS in -7:00, is actually March 31, 2024. In the previous + // test case, the date in UTC for the same ValueUS timestamp is April 1 already. + wantTime: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.FixedZone("America/Los_Angeles", -7*60*60)), + wantPrecision: model.DAY, + }, + { + name: "Date with missing timezone in proto falls back to evaluationLoc", + date: &d4pb.Date{ValueUs: 1711929600000000, Precision: d4pb.Date_DAY, Timezone: ""}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.FixedZone("America/Los_Angeles", -7*60*60)), + wantPrecision: model.DAY, + }, + { + name: "Date with Month precision", + date: &d4pb.Date{ValueUs: 1711929600000000, Precision: d4pb.Date_MONTH, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 0, 0, 0, 0, time.FixedZone("America/Los_Angeles", -7*60*60)), + wantPrecision: model.MONTH, + }, + { + name: "Date with Year precision", + date: &d4pb.Date{ValueUs: 1711929600000000, Precision: d4pb.Date_YEAR, Timezone: "UTC"}, + evaluationLoc: time.FixedZone("America/Los_Angeles", -7*60*60), + wantTime: time.Date(2024, time.April, 1, 0, 0, 0, 0, time.FixedZone("America/Los_Angeles", -7*60*60)), + wantPrecision: model.YEAR, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotTime, gotPrecision, err := ParseFHIRDate(tc.date, tc.evaluationLoc) + if err != nil { + t.Errorf("ParseFHIRDate returned unexpected error: %v", err) + } + if !gotTime.Equal(tc.wantTime) { + t.Errorf("ParseFHIRDate returned unexpected time: got %v, want %v", gotTime, tc.wantTime) + } + if gotPrecision != tc.wantPrecision { + t.Errorf("ParseFHIRDate returned unexpected precision: got %v, want %v", gotPrecision, tc.wantPrecision) + } + }) + } +} diff --git a/internal/datehelpers/string.go b/internal/datehelpers/string.go new file mode 100644 index 0000000..e8fb881 --- /dev/null +++ b/internal/datehelpers/string.go @@ -0,0 +1,94 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datehelpers + +import ( + "fmt" + "time" + + "github.com/google/cql/model" +) + +// DateString returns a CQL Date string representation of a Date. +func DateString(d time.Time, precision model.DateTimePrecision) (string, error) { + var s string + switch precision { + case model.YEAR: + s = d.Format(dateYear) + case model.MONTH: + s = d.Format(dateMonth) + case model.DAY: + s = d.Format(dateDay) + default: + return "", fmt.Errorf("unsupported precision in Date with value %v %w", precision, ErrUnsupportedPrecision) + } + return "@" + s, nil +} + +// DateTimeString returns a CQL DateTime string representation of a DateTime. +func DateTimeString(d time.Time, precision model.DateTimePrecision) (string, error) { + var dtFormat string + switch precision { + case model.YEAR: + dtFormat = dateTimeYear + case model.MONTH: + dtFormat = dateTimeMonth + case model.DAY: + dtFormat = dateTimeDay + case model.HOUR: + dtFormat = dateTimeHour + case model.MINUTE: + dtFormat = dateTimeMinute + case model.SECOND: + dtFormat = dateTimeSecond + case model.MILLISECOND: + dtFormat = dateTimeThreeMillisecond + default: + return "", fmt.Errorf("unsupported precision in Date with value %v %w", precision, ErrUnsupportedPrecision) + } + timeZone, err := locationToTimeZoneString(d.Location()) + if err != nil { + return "", err + } + return "@" + d.Format(dtFormat+timeZone), nil +} + +// TimeString returns a CQL Time string representation of a Time. +func TimeString(d time.Time, precision model.DateTimePrecision) (string, error) { + var tFormat string + switch precision { + case model.HOUR: + tFormat = timeHour + case model.MINUTE: + tFormat = timeMinute + case model.SECOND: + tFormat = timeSecond + case model.MILLISECOND: + tFormat = timeThreeMillisecond + default: + return "", fmt.Errorf("unsupported precision in Date with value %v %w", precision, ErrUnsupportedPrecision) + } + return d.Format(tFormat), nil +} + +func locationToTimeZoneString(loc *time.Location) (string, error) { + if loc == nil { + return "", fmt.Errorf("internal error - loc must be set when calling locationToTimeZoneString") + } + if loc == time.UTC { + return zuluTZ, nil + } + return tz, nil +} diff --git a/internal/datehelpers/string_test.go b/internal/datehelpers/string_test.go new file mode 100644 index 0000000..91fb6f1 --- /dev/null +++ b/internal/datehelpers/string_test.go @@ -0,0 +1,157 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datehelpers + +import ( + "testing" + "time" + + "github.com/google/cql/model" +) + +func TestDateString(t *testing.T) { + tests := []struct { + name string + d time.Time + precision model.DateTimePrecision + want string + }{ + { + name: "year precision", + d: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC), + precision: model.YEAR, + want: "@2024", + }, + { + name: "month precision", + d: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC), + precision: model.MONTH, + want: "@2024-03", + }, + { + name: "day precision", + d: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC), + precision: model.DAY, + want: "@2024-03-31", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := DateString(tc.d, tc.precision) + if err != nil { + t.Errorf("DateString(%v) returned unexpected error: %v", tc.d, err) + } + if got != tc.want { + t.Errorf("DateString(%v) = %v, want %v", tc.d, got, tc.want) + } + }) + } +} + +func TestDateTimeString(t *testing.T) { + tests := []struct { + name string + dt time.Time + precision model.DateTimePrecision + want string + }{ + { + name: "hour precision", + dt: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + precision: model.HOUR, + want: "@2024-03-31T01Z", + }, + { + name: "minute precision", + dt: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + precision: model.MINUTE, + want: "@2024-03-31T01:20Z", + }, + { + name: "second precision", + dt: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + precision: model.SECOND, + want: "@2024-03-31T01:20:30Z", + }, + { + name: "millisecond precision", + dt: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + precision: model.MILLISECOND, + want: "@2024-03-31T01:20:30.100Z", + }, + { + name: "non-utc timezone", + dt: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.FixedZone("Fixed", 4*60*60)), + precision: model.MILLISECOND, + want: "@2024-03-31T01:20:30.100+04:00", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := DateTimeString(tc.dt, tc.precision) + if err != nil { + t.Errorf("DateTimeString(%v) returned unexpected error: %v", tc.dt, err) + } + if got != tc.want { + t.Errorf("DateTimeString(%v) = %v, want %v", tc.dt, got, tc.want) + } + }) + } +} + +func TestTimeString(t *testing.T) { + tests := []struct { + name string + t time.Time + precision model.DateTimePrecision + want string + }{ + { + name: "hour precision", + t: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC), + precision: model.HOUR, + want: "T01", + }, + { + name: "minute precision", + t: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC), + precision: model.MINUTE, + want: "T01:20", + }, + { + name: "second precision", + t: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC), + precision: model.SECOND, + want: "T01:20:30", + }, + { + name: "millisecond precision", + t: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC), + precision: model.MILLISECOND, + want: "T01:20:30.100", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := TimeString(tc.t, tc.precision) + if err != nil { + t.Errorf("TimeString(%v) returned unexpected error: %v", tc.t, err) + } + if got != tc.want { + t.Errorf("TimeString(%v) = %v, want %v", tc.t, got, tc.want) + } + }) + } +} diff --git a/internal/embeddata/embeddata.go b/internal/embeddata/embeddata.go new file mode 100644 index 0000000..aca5c32 --- /dev/null +++ b/internal/embeddata/embeddata.go @@ -0,0 +1,30 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package embeddata holds embedded data required by the production CQL engine. Test data embeds +// can be found in the testdata package. +package embeddata + +import "embed" + +// ModelInfos contain embedded model info files, specifically fhir-modelinfo-4.0.1.xml and +// system-modelinfo.xml. +// +//go:embed third_party/cqframework/fhir-modelinfo-4.0.1.xml third_party/cqframework/system-modelinfo.xml +var ModelInfos embed.FS + +// FHIRHelpers contains the embedded FHIRHelpers-4.0.1.cql file. +// +//go:embed third_party/cqframework/FHIRHelpers-4.0.1.cql +var FHIRHelpers embed.FS diff --git a/internal/embeddata/third_party/cqframework/Cql.g4 b/internal/embeddata/third_party/cqframework/Cql.g4 new file mode 100644 index 0000000..c497ec3 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/Cql.g4 @@ -0,0 +1,963 @@ +// CQL grammar, sourced from https://raw.githubusercontent.com/cqframework/clinical_quality_language/v1.5.2 +// Capitalized to work with both ANTLR conventions and Go visibility rules. +grammar Cql; + +/* + * Clinical Quality Language Grammar Specification + * Version 1.5 - Mixed Normative/Trial-Use + */ + +import fhirpath; + +/* + * Parser Rules + */ + +definition + : usingDefinition + | includeDefinition + | codesystemDefinition + | valuesetDefinition + | codeDefinition + | conceptDefinition + | parameterDefinition + ; + +library + : + libraryDefinition? + definition* + statement* + EOF + ; + +/* + * Definitions + */ + +libraryDefinition + : 'library' qualifiedIdentifier ('version' versionSpecifier)? + ; + +usingDefinition + : 'using' qualifiedIdentifier ('version' versionSpecifier)? ('called' localIdentifier)? + ; + +includeDefinition + : 'include' qualifiedIdentifier ('version' versionSpecifier)? ('called' localIdentifier)? + ; + +localIdentifier + : identifier + ; + +accessModifier + : 'public' + | 'private' + ; + +parameterDefinition + : accessModifier? 'parameter' identifier (typeSpecifier)? ('default' expression)? + ; + +codesystemDefinition + : accessModifier? 'codesystem' identifier ':' codesystemId ('version' versionSpecifier)? + ; + +valuesetDefinition + : accessModifier? 'valueset' identifier ':' valuesetId ('version' versionSpecifier)? codesystems? + ; + +codesystems + : 'codesystems' '{' codesystemIdentifier (',' codesystemIdentifier)* '}' + ; + +codesystemIdentifier + : (libraryIdentifier '.')? identifier + ; + +libraryIdentifier + : identifier + ; + +codeDefinition + : accessModifier? 'code' identifier ':' codeId 'from' codesystemIdentifier displayClause? + ; + +conceptDefinition + : accessModifier? 'concept' identifier ':' '{' codeIdentifier (',' codeIdentifier)* '}' displayClause? + ; + +codeIdentifier + : (libraryIdentifier '.')? identifier + ; + +codesystemId + : STRING + ; + +valuesetId + : STRING + ; + +versionSpecifier + : STRING + ; + +codeId + : STRING + ; + +/* + * Type Specifiers + */ + +typeSpecifier + : namedTypeSpecifier + | listTypeSpecifier + | intervalTypeSpecifier + | tupleTypeSpecifier + | choiceTypeSpecifier + ; + +namedTypeSpecifier + : (qualifier '.')* referentialOrTypeNameIdentifier + ; + +modelIdentifier + : identifier + ; + +listTypeSpecifier + : 'List' '<' typeSpecifier '>' + ; + +intervalTypeSpecifier + : 'Interval' '<' typeSpecifier '>' + ; + +tupleTypeSpecifier + : 'Tuple' '{' tupleElementDefinition (',' tupleElementDefinition)* '}' + ; + +tupleElementDefinition + : referentialIdentifier typeSpecifier + ; + +choiceTypeSpecifier + : 'Choice' '<' typeSpecifier (',' typeSpecifier)* '>' + ; + +/* + * Statements + */ + +statement + : expressionDefinition + | contextDefinition + | functionDefinition + ; + +expressionDefinition + : 'define' accessModifier? identifier ':' expression + ; + +contextDefinition + : 'context' (modelIdentifier '.')? identifier + ; + +fluentModifier + : 'fluent' + ; + +functionDefinition + : 'define' accessModifier? fluentModifier? 'function' identifierOrFunctionIdentifier '(' (operandDefinition (',' operandDefinition)*)? ')' + ('returns' typeSpecifier)? + ':' (functionBody | 'external') + ; + +operandDefinition + : referentialIdentifier typeSpecifier + ; + +functionBody + : expression + ; + +/* + * Expressions + */ + +querySource + : retrieve + | qualifiedIdentifierExpression + | '(' expression ')' + ; + +aliasedQuerySource + : querySource alias + ; + +alias + : identifier + ; + +queryInclusionClause + : withClause + | withoutClause + ; + +withClause + : 'with' aliasedQuerySource 'such that' expression + ; + +withoutClause + : 'without' aliasedQuerySource 'such that' expression + ; + +retrieve + : '[' (contextIdentifier '->')? namedTypeSpecifier (':' (codePath codeComparator)? terminology)? ']' + ; + +contextIdentifier + : qualifiedIdentifierExpression + ; + +codePath + : simplePath + ; + +codeComparator + : 'in' + | '=' + | '~' + ; + +terminology + : qualifiedIdentifierExpression + | expression + ; + +qualifier + : identifier + ; + +query + : sourceClause letClause? queryInclusionClause* whereClause? (aggregateClause | returnClause)? sortClause? + ; + +sourceClause + : 'from'? aliasedQuerySource (',' aliasedQuerySource)* + ; + +letClause + : 'let' letClauseItem (',' letClauseItem)* + ; + +letClauseItem + : identifier ':' expression + ; + +whereClause + : 'where' expression + ; + +returnClause + : 'return' ('all' | 'distinct')? expression + ; + +aggregateClause + : 'aggregate' ('all' | 'distinct')? identifier startingClause? ':' expression + ; + +startingClause + : 'starting' (simpleLiteral | quantity | ('(' expression ')')) + ; + +sortClause + : 'sort' ( sortDirection | ('by' sortByItem (',' sortByItem)*) ) + ; + +sortDirection + : 'asc' | 'ascending' + | 'desc' | 'descending' + ; + +sortByItem + : expressionTerm sortDirection? + ; + +qualifiedIdentifier + : (qualifier '.')* identifier + ; + +qualifiedIdentifierExpression + : (qualifierExpression '.')* referentialIdentifier + ; + +qualifierExpression + : referentialIdentifier + ; + +simplePath + : referentialIdentifier #simplePathReferentialIdentifier + | simplePath '.' referentialIdentifier #simplePathQualifiedIdentifier + | simplePath '[' simpleLiteral ']' #simplePathIndexer + ; + +simpleLiteral + : STRING #simpleStringLiteral + | NUMBER #simpleNumberLiteral + ; + +expression + : expressionTerm #termExpression + | retrieve #retrieveExpression + | query #queryExpression + | expression 'is' 'not'? ('null' | 'true' | 'false') #booleanExpression + | expression ('is' | 'as') typeSpecifier #typeExpression + | 'cast' expression 'as' typeSpecifier #castExpression + | 'not' expression #notExpression + | 'exists' expression #existenceExpression + | expression 'properly'? 'between' expressionTerm 'and' expressionTerm #betweenExpression + | ('duration' 'in')? pluralDateTimePrecision 'between' expressionTerm 'and' expressionTerm #durationBetweenExpression + | 'difference' 'in' pluralDateTimePrecision 'between' expressionTerm 'and' expressionTerm #differenceBetweenExpression + | expression ('<=' | '<' | '>' | '>=') expression #inequalityExpression + | expression intervalOperatorPhrase expression #timingExpression + | expression ('=' | '!=' | '~' | '!~') expression #equalityExpression + | expression ('in' | 'contains') dateTimePrecisionSpecifier? expression #membershipExpression + | expression 'and' expression #andExpression + | expression ('or' | 'xor') expression #orExpression + | expression 'implies' expression #impliesExpression + | expression ('|' | 'union' | 'intersect' | 'except') expression #inFixSetExpression + ; + +dateTimePrecision + : 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond' + ; + +dateTimeComponent + : dateTimePrecision + | 'date' + | 'time' + | 'timezone' // NOTE: 1.3 compatibility level only + | 'timezoneoffset' + ; + +pluralDateTimePrecision + : 'years' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds' + ; + +expressionTerm + : term #termExpressionTerm + | expressionTerm '.' qualifiedInvocation #invocationExpressionTerm + | expressionTerm '[' expression ']' #indexedExpressionTerm + | 'convert' expression 'to' (typeSpecifier | unit) #conversionExpressionTerm + | ('+' | '-') expressionTerm #polarityExpressionTerm + | ('start' | 'end') 'of' expressionTerm #timeBoundaryExpressionTerm + | dateTimeComponent 'from' expressionTerm #timeUnitExpressionTerm + | 'duration' 'in' pluralDateTimePrecision 'of' expressionTerm #durationExpressionTerm + | 'difference' 'in' pluralDateTimePrecision 'of' expressionTerm #differenceExpressionTerm + | 'width' 'of' expressionTerm #widthExpressionTerm + | 'successor' 'of' expressionTerm #successorExpressionTerm + | 'predecessor' 'of' expressionTerm #predecessorExpressionTerm + | 'singleton' 'from' expressionTerm #elementExtractorExpressionTerm + | 'point' 'from' expressionTerm #pointExtractorExpressionTerm + | ('minimum' | 'maximum') namedTypeSpecifier #typeExtentExpressionTerm + | expressionTerm '^' expressionTerm #powerExpressionTerm + | expressionTerm ('*' | '/' | 'div' | 'mod') expressionTerm #multiplicationExpressionTerm + | expressionTerm ('+' | '-' | '&') expressionTerm #additionExpressionTerm + | 'if' expression 'then' expression 'else' expression #ifThenElseExpressionTerm + | 'case' expression? caseExpressionItem+ 'else' expression 'end' #caseExpressionTerm + | ('distinct' | 'flatten') expression #aggregateExpressionTerm + | ('expand' | 'collapse') expression ('per' (dateTimePrecision | expression))? #setAggregateExpressionTerm + ; + +caseExpressionItem + : 'when' expression 'then' expression + ; + +dateTimePrecisionSpecifier + : dateTimePrecision 'of' + ; + +relativeQualifier + : 'or before' + | 'or after' + ; + +offsetRelativeQualifier + : 'or more' + | 'or less' + ; + +exclusiveRelativeQualifier + : 'less than' + | 'more than' + ; + +quantityOffset + : (quantity offsetRelativeQualifier?) + | (exclusiveRelativeQualifier quantity) + ; + +temporalRelationship + : ('on or'? ('before' | 'after')) + | (('before' | 'after') 'or on'?) + ; + +intervalOperatorPhrase + : ('starts' | 'ends' | 'occurs')? 'same' dateTimePrecision? (relativeQualifier | 'as') ('start' | 'end')? #concurrentWithIntervalOperatorPhrase + | 'properly'? 'includes' dateTimePrecisionSpecifier? ('start' | 'end')? #includesIntervalOperatorPhrase + | ('starts' | 'ends' | 'occurs')? 'properly'? ('during' | 'included in') dateTimePrecisionSpecifier? #includedInIntervalOperatorPhrase + | ('starts' | 'ends' | 'occurs')? quantityOffset? temporalRelationship dateTimePrecisionSpecifier? ('start' | 'end')? #beforeOrAfterIntervalOperatorPhrase + | ('starts' | 'ends' | 'occurs')? 'properly'? 'within' quantity 'of' ('start' | 'end')? #withinIntervalOperatorPhrase + | 'meets' ('before' | 'after')? dateTimePrecisionSpecifier? #meetsIntervalOperatorPhrase + | 'overlaps' ('before' | 'after')? dateTimePrecisionSpecifier? #overlapsIntervalOperatorPhrase + | 'starts' dateTimePrecisionSpecifier? #startsIntervalOperatorPhrase + | 'ends' dateTimePrecisionSpecifier? #endsIntervalOperatorPhrase + ; + +term + : invocation #invocationTerm + | literal #literalTerm + | externalConstant #externalConstantTerm + | intervalSelector #intervalSelectorTerm + | tupleSelector #tupleSelectorTerm + | instanceSelector #instanceSelectorTerm + | listSelector #listSelectorTerm + | codeSelector #codeSelectorTerm + | conceptSelector #conceptSelectorTerm + | '(' expression ')' #parenthesizedTerm + ; + +qualifiedInvocation // Terms that can be used after the function/member invocation '.' + : referentialIdentifier #qualifiedMemberInvocation + | qualifiedFunction #qualifiedFunctionInvocation + ; + +qualifiedFunction + : identifierOrFunctionIdentifier '(' paramList? ')' + ; + +invocation + : referentialIdentifier #memberInvocation + | function #functionInvocation + | '$this' #thisInvocation + | '$index' #indexInvocation + | '$total' #totalInvocation + ; + +function + : referentialIdentifier '(' paramList? ')' + ; + +ratio + : quantity ':' quantity + ; + +literal + : ('true' | 'false') #booleanLiteral + | 'null' #nullLiteral + | STRING #stringLiteral + | NUMBER #numberLiteral + | LONGNUMBER #longNumberLiteral + | DATETIME #dateTimeLiteral + | DATE #dateLiteral + | TIME #timeLiteral + | quantity #quantityLiteral + | ratio #ratioLiteral + ; + +intervalSelector + : // TODO: Consider this as an alternative syntax for intervals... (would need to be moved up to expression to make it work) + //expression ( '..' | '*.' | '.*' | '**' ) expression; + 'Interval' ('['|'(') expression ',' expression (']'|')') + ; + +tupleSelector + : 'Tuple'? '{' (':' | (tupleElementSelector (',' tupleElementSelector)*)) '}' + ; + +tupleElementSelector + : referentialIdentifier ':' expression + ; + +instanceSelector + : namedTypeSpecifier '{' (':' | (instanceElementSelector (',' instanceElementSelector)*)) '}' + ; + +instanceElementSelector + : referentialIdentifier ':' expression + ; + +listSelector + : ('List' ('<' typeSpecifier '>')?)? '{' (expression (',' expression)*)? '}' + ; + +displayClause + : 'display' STRING + ; + +codeSelector + : 'Code' STRING 'from' codesystemIdentifier displayClause? + ; + +conceptSelector + : 'Concept' '{' codeSelector (',' codeSelector)* '}' displayClause? + ; + +keyword + : 'after' + | 'aggregate' + | 'all' + | 'and' + | 'as' + | 'asc' + | 'ascending' + | 'before' + | 'between' + | 'by' + | 'called' + | 'case' + | 'cast' + | 'code' + | 'Code' + | 'codesystem' + | 'codesystems' + | 'collapse' + | 'concept' + | 'Concept' + | 'contains' + | 'context' + | 'convert' + | 'date' + | 'day' + | 'days' + | 'default' + | 'define' + | 'desc' + | 'descending' + | 'difference' + | 'display' + | 'distinct' + | 'div' + | 'duration' + | 'during' + | 'else' + | 'end' + | 'ends' + | 'except' + | 'exists' + | 'expand' + | 'false' + | 'flatten' + | 'fluent' + | 'from' + | 'function' + | 'hour' + | 'hours' + | 'if' + | 'implies' + | 'in' + | 'include' + | 'includes' + | 'included in' + | 'intersect' + | 'Interval' + | 'is' + | 'let' + | 'library' + | 'List' + | 'maximum' + | 'meets' + | 'millisecond' + | 'milliseconds' + | 'minimum' + | 'minute' + | 'minutes' + | 'mod' + | 'month' + | 'months' + | 'not' + | 'null' + | 'occurs' + | 'of' + | 'on or' + | 'or' + | 'or after' + | 'or before' + | 'or less' + | 'or more' + | 'or on' + | 'overlaps' + | 'parameter' + | 'per' + | 'point' + | 'predecessor' + | 'private' + | 'properly' + | 'public' + | 'return' + | 'same' + | 'second' + | 'seconds' + | 'singleton' + | 'start' + | 'starting' + | 'starts' + | 'sort' + | 'successor' + | 'such that' + | 'then' + | 'time' + | 'timezone' // NOTE: 1.3 Compatibility level only + | 'timezoneoffset' + | 'to' + | 'true' + | 'Tuple' + | 'union' + | 'using' + | 'valueset' + | 'version' + | 'week' + | 'weeks' + | 'where' + | 'when' + | 'width' + | 'with' + | 'within' + | 'without' + | 'xor' + | 'year' + | 'years' + ; + +// NOTE: Not used, this is the set of reserved words that may not appear as identifiers in ambiguous contexts +reservedWord + : 'aggregate' + | 'all' + | 'and' + | 'as' + | 'after' + | 'before' + | 'between' + | 'case' + | 'cast' + | 'Code' + | 'collapse' + | 'Concept' + | 'convert' + | 'day' + | 'days' + | 'difference' + | 'distinct' + | 'duration' + | 'during' + | 'else' + | 'exists' + | 'expand' + | 'false' + | 'flatten' + | 'from' + | 'if' + | 'in' + | 'included in' + | 'is' + | 'hour' + | 'hours' + | 'Interval' + | 'let' + | 'List' + | 'maximum' + | 'millisecond' + | 'milliseconds' + | 'minimum' + | 'minute' + | 'minutes' + | 'month' + | 'months' + | 'not' + | 'null' + | 'occurs' + | 'of' + | 'on or' + | 'or' + | 'or on' + | 'per' + | 'point' + | 'properly' + | 'return' + | 'same' + | 'second' + | 'seconds' + | 'singleton' + | 'sort' + | 'such that' + | 'then' + | 'to' + | 'true' + | 'Tuple' + | 'week' + | 'weeks' + | 'when' + | 'with' + | 'within' + | 'without' + | 'year' + | 'years' + ; + +// Keyword identifiers are keywords that may be used as identifiers in a referential context +// Effectively, keyword except reservedWord +keywordIdentifier + : 'asc' + | 'ascending' + | 'by' + | 'called' + | 'code' + | 'codesystem' + | 'codesystems' + | 'concept' + | 'contains' + | 'context' + | 'date' + | 'default' + | 'define' + | 'desc' + | 'descending' + | 'display' + | 'div' + | 'end' + | 'ends' + | 'except' + | 'fluent' + | 'function' + | 'implies' + | 'include' + | 'includes' + | 'intersect' + | 'library' + | 'meets' + | 'mod' + | 'or after' + | 'or before' + | 'or less' + | 'or more' + | 'overlaps' + | 'parameter' + | 'predecessor' + | 'private' + | 'public' + | 'start' + | 'starting' + | 'starts' + | 'successor' + | 'time' + | 'timezone' // NOTE: 1.3 Compatibility Level only + | 'timezoneoffset' + | 'union' + | 'using' + | 'valueset' + | 'version' + | 'where' + | 'width' + | 'xor' + ; + +// Obsolete identifiers are keywords that could be used as identifiers in CQL 1.3 +// NOTE: Not currently used, this is the set of keywords that were defined as allowed identifiers as part of 1.3 +// NOTE: Several keywords were commented out in this list (notably exists) because of an issue with the ANTLR tooling. +// In 4.5, having these keywords as identifiers results in unacceptable parsing performance. In 4.6+, having them as +// identifiers resulted in incorrect parsing. See Github issue [#343](https://github.com/cqframework/clinical_quality_language/issues/343) for more detail +// This should no longer be an issue with 1.4 due to the introduction of reserved words +obsoleteIdentifier + : 'all' + | 'Code' + | 'code' + | 'Concept' + | 'concept' + | 'contains' + | 'date' + | 'display' + | 'distinct' + | 'end' + | 'exists' + | 'not' + | 'start' + | 'time' + | 'timezone' // NOTE: 1.3 Compatibility level only + | 'timezoneoffset' + | 'version' + | 'where' + ; + +// Function identifiers are keywords that may be used as identifiers for functions +functionIdentifier + : 'after' + | 'aggregate' + | 'all' + | 'and' + | 'as' + | 'asc' + | 'ascending' + | 'before' + | 'between' + | 'by' + | 'called' + | 'case' + | 'cast' + | 'code' + | 'Code' + | 'codesystem' + | 'codesystems' + | 'collapse' + | 'concept' + | 'Concept' + | 'contains' + | 'context' + | 'convert' + | 'date' + | 'day' + | 'days' + | 'default' + | 'define' + | 'desc' + | 'descending' + | 'difference' + | 'display' + | 'distinct' + | 'div' + | 'duration' + | 'during' + | 'else' + | 'end' + | 'ends' + | 'except' + | 'exists' + | 'expand' + | 'false' + | 'flatten' + | 'fluent' + | 'from' + | 'function' + | 'hour' + | 'hours' + | 'if' + | 'implies' + | 'in' + | 'include' + | 'includes' + | 'included in' + | 'intersect' + | 'Interval' + | 'is' + | 'let' + | 'library' + | 'List' + | 'maximum' + | 'meets' + | 'millisecond' + | 'milliseconds' + | 'minimum' + | 'minute' + | 'minutes' + | 'mod' + | 'month' + | 'months' + | 'not' + | 'null' + | 'occurs' + | 'of' + | 'or' + | 'or after' + | 'or before' + | 'or less' + | 'or more' + | 'overlaps' + | 'parameter' + | 'per' + | 'point' + | 'predecessor' + | 'private' + | 'properly' + | 'public' + | 'return' + | 'same' + | 'singleton' + | 'second' + | 'seconds' + | 'start' + | 'starting' + | 'starts' + | 'sort' + | 'successor' + | 'such that' + | 'then' + | 'time' + | 'timezone' // NOTE: 1.3 Compatibility level only + | 'timezoneoffset' + | 'to' + | 'true' + | 'Tuple' + | 'union' + | 'using' + | 'valueset' + | 'version' + | 'week' + | 'weeks' + | 'where' + | 'when' + | 'width' + | 'with' + | 'within' + | 'without' + | 'xor' + | 'year' + | 'years' + ; + +// Reserved words that are also type names +typeNameIdentifier + : 'Code' + | 'Concept' + | 'date' + | 'time' + ; + +referentialIdentifier + : identifier + | keywordIdentifier + ; + +referentialOrTypeNameIdentifier + : referentialIdentifier + | typeNameIdentifier + ; + +identifierOrFunctionIdentifier + : identifier + | functionIdentifier + ; + +identifier + : IDENTIFIER + | DELIMITEDIDENTIFIER + | QUOTEDIDENTIFIER + ; + +QUOTEDIDENTIFIER + : '"' (ESC | .)*? '"' + ; + +DATETIME + : '@' DATEFORMAT 'T' TIMEFORMAT? TIMEZONEOFFSETFORMAT? + ; + +LONGNUMBER + : [0-9]+'L' + ; + +fragment ESC + : '\\' ([`'"\\/fnrt] | UNICODE) // allow \`, \', \", \\, \/, \f, etc. and \uXXX + ; diff --git a/internal/embeddata/third_party/cqframework/Cvlt.g4 b/internal/embeddata/third_party/cqframework/Cvlt.g4 new file mode 100644 index 0000000..259da36 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/Cvlt.g4 @@ -0,0 +1,218 @@ +grammar Cvlt; + +/* +This is a literal grammar for CQL that supports all literals from CQL, including type specifiers +Based on FHIRPath and CQL grammars for CQL v1.5.2 + */ + +/* + * Type Specifiers + */ + +typeSpecifier + : namedTypeSpecifier + | listTypeSpecifier + | intervalTypeSpecifier + | tupleTypeSpecifier + | choiceTypeSpecifier + ; + +namedTypeSpecifier + : (identifier '.')* identifier + ; + +modelIdentifier + : identifier + ; + +listTypeSpecifier + : 'List' '<' typeSpecifier '>' + ; + +intervalTypeSpecifier + : 'Interval' '<' typeSpecifier '>' + ; + +tupleTypeSpecifier + : 'Tuple' '{' tupleElementDefinition (',' tupleElementDefinition)* '}' + ; + +tupleElementDefinition + : identifier typeSpecifier + ; + +choiceTypeSpecifier + : 'Choice' '<' typeSpecifier (',' typeSpecifier)* '>' + ; + +term + : literal #literalTerm + | intervalSelector #intervalSelectorTerm + | tupleSelector #tupleSelectorTerm + | instanceSelector #instanceSelectorTerm + | listSelector #listSelectorTerm + | codeSelector #codeSelectorTerm + | conceptSelector #conceptSelectorTerm + ; + +ratio + : quantity ':' quantity + ; + +literal + : ('true' | 'false') #booleanLiteral + | 'null' #nullLiteral + | STRING #stringLiteral + | NUMBER #numberLiteral + | LONGNUMBER #longNumberLiteral + | DATETIME #dateTimeLiteral + | DATE #dateLiteral + | TIME #timeLiteral + | quantity #quantityLiteral + | ratio #ratioLiteral + ; + +intervalSelector + : 'Interval' ('['|'(') literal ',' literal (']'|')') + ; + +tupleSelector + : 'Tuple'? '{' (':' | (tupleElementSelector (',' tupleElementSelector)*)) '}' + ; + +tupleElementSelector + : identifier ':' term + ; + +instanceSelector + : identifier '{' (':' | (instanceElementSelector (',' instanceElementSelector)*)) '}' + ; + +instanceElementSelector + : identifier ':' term + ; + +listSelector + : ('List' ('<' typeSpecifier '>')?)? '{' (term (',' term)*)? '}' + ; + +displayClause + : 'display' STRING + ; + +codeSelector + : 'Code' STRING 'from' identifier displayClause? + ; + +conceptSelector + : 'Concept' '{' codeSelector (',' codeSelector)* '}' displayClause? + ; + +identifier + : IDENTIFIER + | DELIMITEDIDENTIFIER + | QUOTEDIDENTIFIER + ; + +quantity + : NUMBER unit? + ; + +unit + : dateTimePrecision + | pluralDateTimePrecision + | STRING // UCUM syntax for units of measure + ; + +dateTimePrecision + : 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond' + ; + +pluralDateTimePrecision + : 'years' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds' + ; + + +/**************************************************************** + Lexical rules +*****************************************************************/ + +/* +NOTE: The goal of these rules in the grammar is to provide a date +token to the parser. As such it is not attempting to validate that +the date is a correct date, that task is for the parser or interpreter. +*/ + +DATE + : '@' DATEFORMAT + ; + +DATETIME + : '@' DATEFORMAT 'T' TIMEFORMAT? TIMEZONEOFFSETFORMAT? + ; + +TIME + : '@' 'T' TIMEFORMAT + ; + +fragment DATEFORMAT + : [0-9][0-9][0-9][0-9] ('-'[0-9][0-9] ('-'[0-9][0-9])?)? + ; + +fragment TIMEFORMAT + : [0-9][0-9] (':'[0-9][0-9] (':'[0-9][0-9] ('.'[0-9]+)?)?)? + ; + +fragment TIMEZONEOFFSETFORMAT + : ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9]) + ; + +IDENTIFIER + : ([A-Za-z] | '_')([A-Za-z0-9] | '_')* + ; + +DELIMITEDIDENTIFIER + : '`' (ESC | .)*? '`' + ; + +QUOTEDIDENTIFIER + : '"' (ESC | .)*? '"' + ; + +STRING + : '\'' (ESC | .)*? '\'' + ; + +// Also allows leading zeroes now (just like CQL and XSD) +NUMBER + : ('+' | '-')?[0-9]+('.' [0-9]+)? + ; + +LONGNUMBER + : ('+' | '-')?[0-9]+'L' + ; + +// Pipe whitespace to the HIDDEN channel to support retrieving source text through the parser. +WS + : [ \r\n\t]+ -> channel(HIDDEN) + ; + +COMMENT + : '/*' .*? '*/' -> channel(HIDDEN) + ; + +LINE_COMMENT + : '//' ~[\r\n]* -> channel(HIDDEN) + ; + +fragment ESC + : '\\' ([`'"\\/fnrt] | UNICODE) // allow \`, \', \", \\, \/, \f, etc. and \uXXX + ; + +fragment UNICODE + : 'u' HEX HEX HEX HEX + ; + +fragment HEX + : [0-9a-fA-F] + ; \ No newline at end of file diff --git a/internal/embeddata/third_party/cqframework/FHIRHelpers-4.0.1.cql b/internal/embeddata/third_party/cqframework/FHIRHelpers-4.0.1.cql new file mode 100644 index 0000000..4fc791f --- /dev/null +++ b/internal/embeddata/third_party/cqframework/FHIRHelpers-4.0.1.cql @@ -0,0 +1,407 @@ +/* +@author: Bryn Rhodes +@description: This library defines functions to convert between FHIR + data types and CQL system-defined types, as well as functions to support + FHIRPath implementation. For more information, the FHIRHelpers wiki page: + https://github.com/cqframework/clinical_quality_language/wiki/FHIRHelpers +@allowFluent: true +*/ +library FHIRHelpers version '4.0.1' + +using FHIR version '4.0.1' + +define function ToInterval(period FHIR.Period): + if period is null then + null + else + if period."start" is null then + Interval(period."start".value, period."end".value] + else + Interval[period."start".value, period."end".value] + +define function ToCalendarUnit(unit System.String): + case unit + when 'ms' then 'millisecond' + when 's' then 'second' + when 'min' then 'minute' + when 'h' then 'hour' + when 'd' then 'day' + when 'wk' then 'week' + when 'mo' then 'month' + when 'a' then 'year' + else unit + end + +define function ToQuantity(quantity FHIR.Quantity): + case + when quantity is null then null + when quantity.value is null then null + when quantity.comparator is not null then + Message(null, true, 'FHIRHelpers.ToQuantity.ComparatorQuantityNotSupported', 'Error', 'FHIR Quantity value has a comparator and cannot be converted to a System.Quantity value.') + when quantity.system is null or quantity.system.value = 'http://unitsofmeasure.org' + or quantity.system.value = 'http://hl7.org/fhirpath/CodeSystem/calendar-units' then + System.Quantity { value: quantity.value.value, unit: ToCalendarUnit(Coalesce(quantity.code.value, quantity.unit.value, '1')) } + else + Message(null, true, 'FHIRHelpers.ToQuantity.InvalidFHIRQuantity', 'Error', 'Invalid FHIR Quantity code: ' & quantity.unit.value & ' (' & quantity.system.value & '|' & quantity.code.value & ')') + end + +define function ToQuantityIgnoringComparator(quantity FHIR.Quantity): + case + when quantity is null then null + when quantity.value is null then null + when quantity.system is null or quantity.system.value = 'http://unitsofmeasure.org' + or quantity.system.value = 'http://hl7.org/fhirpath/CodeSystem/calendar-units' then + System.Quantity { value: quantity.value.value, unit: ToCalendarUnit(Coalesce(quantity.code.value, quantity.unit.value, '1')) } + else + Message(null, true, 'FHIRHelpers.ToQuantity.InvalidFHIRQuantity', 'Error', 'Invalid FHIR Quantity code: ' & quantity.unit.value & ' (' & quantity.system.value & '|' & quantity.code.value & ')') + end + +define function ToInterval(quantity FHIR.Quantity): + if quantity is null then null else + case quantity.comparator.value + when '<' then + Interval[ + null, + ToQuantityIgnoringComparator(quantity) + ) + when '<=' then + Interval[ + null, + ToQuantityIgnoringComparator(quantity) + ] + when '>=' then + Interval[ + ToQuantityIgnoringComparator(quantity), + null + ] + when '>' then + Interval( + ToQuantityIgnoringComparator(quantity), + null + ] + else + Interval[ToQuantity(quantity), ToQuantity(quantity)] + end + +define function ToRatio(ratio FHIR.Ratio): + if ratio is null then + null + else + System.Ratio { numerator: ToQuantity(ratio.numerator), denominator: ToQuantity(ratio.denominator) } + +define function ToInterval(range FHIR.Range): + if range is null then + null + else + Interval[ToQuantity(range.low), ToQuantity(range.high)] + +define function ToCode(coding FHIR.Coding): + if coding is null then + null + else + System.Code { + code: coding.code.value, + system: coding.system.value, + version: coding.version.value, + display: coding.display.value + } + +define function ToConcept(concept FHIR.CodeableConcept): + if concept is null then + null + else + System.Concept { + codes: concept.coding C return ToCode(C), + display: concept.text.value + } + +define function reference(reference String): + if reference is null then + null + else + Reference { reference: string { value: reference } } + +define function resolve(reference String) returns Resource: external +define function resolve(reference Reference) returns Resource: external +define function reference(resource Resource) returns Reference: external +define function extension(element Element, url String) returns List: external +define function extension(resource Resource, url String) returns List: external +define function hasValue(element Element) returns Boolean: external +define function getValue(element Element) returns Any: external +define function ofType(identifier String) returns List: external +define function is(identifier String) returns Boolean: external +define function as(identifier String) returns Any: external +define function elementDefinition(element Element) returns ElementDefinition: external +define function slice(element Element, url String, name String) returns List: external +define function checkModifiers(resource Resource) returns Resource: external +define function checkModifiers(resource Resource, modifier String) returns Resource: external +define function checkModifiers(element Element) returns Element: external +define function checkModifiers(element Element, modifier String) returns Element: external +define function conformsTo(resource Resource, structure String) returns Boolean: external +define function memberOf(code code, valueSet String) returns Boolean: external +define function memberOf(coding Coding, valueSet String) returns Boolean: external +define function memberOf(concept CodeableConcept, valueSet String) returns Boolean: external +define function subsumes(coding Coding, subsumedCoding Coding) returns Boolean: external +define function subsumes(concept CodeableConcept, subsumedConcept CodeableConcept) returns Boolean: external +define function subsumedBy(coding Coding, subsumingCoding Coding) returns Boolean: external +define function subsumedBy(concept CodeableConcept, subsumingConcept CodeableConcept) returns Boolean: external +define function htmlChecks(element Element) returns Boolean: external + +define function ToString(value AccountStatus): value.value +define function ToString(value ActionCardinalityBehavior): value.value +define function ToString(value ActionConditionKind): value.value +define function ToString(value ActionGroupingBehavior): value.value +define function ToString(value ActionParticipantType): value.value +define function ToString(value ActionPrecheckBehavior): value.value +define function ToString(value ActionRelationshipType): value.value +define function ToString(value ActionRequiredBehavior): value.value +define function ToString(value ActionSelectionBehavior): value.value +define function ToString(value ActivityDefinitionKind): value.value +define function ToString(value ActivityParticipantType): value.value +define function ToString(value AddressType): value.value +define function ToString(value AddressUse): value.value +define function ToString(value AdministrativeGender): value.value +define function ToString(value AdverseEventActuality): value.value +define function ToString(value AggregationMode): value.value +define function ToString(value AllergyIntoleranceCategory): value.value +define function ToString(value AllergyIntoleranceCriticality): value.value +define function ToString(value AllergyIntoleranceSeverity): value.value +define function ToString(value AllergyIntoleranceType): value.value +define function ToString(value AppointmentStatus): value.value +define function ToString(value AssertionDirectionType): value.value +define function ToString(value AssertionOperatorType): value.value +define function ToString(value AssertionResponseTypes): value.value +define function ToString(value AuditEventAction): value.value +define function ToString(value AuditEventAgentNetworkType): value.value +define function ToString(value AuditEventOutcome): value.value +define function ToString(value BindingStrength): value.value +define function ToString(value BiologicallyDerivedProductCategory): value.value +define function ToString(value BiologicallyDerivedProductStatus): value.value +define function ToString(value BiologicallyDerivedProductStorageScale): value.value +define function ToString(value BundleType): value.value +define function ToString(value CapabilityStatementKind): value.value +define function ToString(value CarePlanActivityKind): value.value +define function ToString(value CarePlanActivityStatus): value.value +define function ToString(value CarePlanIntent): value.value +define function ToString(value CarePlanStatus): value.value +define function ToString(value CareTeamStatus): value.value +define function ToString(value CatalogEntryRelationType): value.value +define function ToString(value ChargeItemDefinitionPriceComponentType): value.value +define function ToString(value ChargeItemStatus): value.value +define function ToString(value ClaimResponseStatus): value.value +define function ToString(value ClaimStatus): value.value +define function ToString(value ClinicalImpressionStatus): value.value +define function ToString(value CodeSearchSupport): value.value +define function ToString(value CodeSystemContentMode): value.value +define function ToString(value CodeSystemHierarchyMeaning): value.value +define function ToString(value CommunicationPriority): value.value +define function ToString(value CommunicationRequestStatus): value.value +define function ToString(value CommunicationStatus): value.value +define function ToString(value CompartmentCode): value.value +define function ToString(value CompartmentType): value.value +define function ToString(value CompositionAttestationMode): value.value +define function ToString(value CompositionStatus): value.value +define function ToString(value ConceptMapEquivalence): value.value +define function ToString(value ConceptMapGroupUnmappedMode): value.value +define function ToString(value ConditionalDeleteStatus): value.value +define function ToString(value ConditionalReadStatus): value.value +define function ToString(value ConsentDataMeaning): value.value +define function ToString(value ConsentProvisionType): value.value +define function ToString(value ConsentState): value.value +define function ToString(value ConstraintSeverity): value.value +define function ToString(value ContactPointSystem): value.value +define function ToString(value ContactPointUse): value.value +define function ToString(value ContractPublicationStatus): value.value +define function ToString(value ContractStatus): value.value +define function ToString(value ContributorType): value.value +define function ToString(value CoverageStatus): value.value +define function ToString(value CurrencyCode): value.value +define function ToString(value DayOfWeek): value.value +define function ToString(value DaysOfWeek): value.value +define function ToString(value DetectedIssueSeverity): value.value +define function ToString(value DetectedIssueStatus): value.value +define function ToString(value DeviceMetricCalibrationState): value.value +define function ToString(value DeviceMetricCalibrationType): value.value +define function ToString(value DeviceMetricCategory): value.value +define function ToString(value DeviceMetricColor): value.value +define function ToString(value DeviceMetricOperationalStatus): value.value +define function ToString(value DeviceNameType): value.value +define function ToString(value DeviceRequestStatus): value.value +define function ToString(value DeviceUseStatementStatus): value.value +define function ToString(value DiagnosticReportStatus): value.value +define function ToString(value DiscriminatorType): value.value +define function ToString(value DocumentConfidentiality): value.value +define function ToString(value DocumentMode): value.value +define function ToString(value DocumentReferenceStatus): value.value +define function ToString(value DocumentRelationshipType): value.value +define function ToString(value EligibilityRequestPurpose): value.value +define function ToString(value EligibilityRequestStatus): value.value +define function ToString(value EligibilityResponsePurpose): value.value +define function ToString(value EligibilityResponseStatus): value.value +define function ToString(value EnableWhenBehavior): value.value +define function ToString(value EncounterLocationStatus): value.value +define function ToString(value EncounterStatus): value.value +define function ToString(value EndpointStatus): value.value +define function ToString(value EnrollmentRequestStatus): value.value +define function ToString(value EnrollmentResponseStatus): value.value +define function ToString(value EpisodeOfCareStatus): value.value +define function ToString(value EventCapabilityMode): value.value +define function ToString(value EventTiming): value.value +define function ToString(value EvidenceVariableType): value.value +define function ToString(value ExampleScenarioActorType): value.value +define function ToString(value ExplanationOfBenefitStatus): value.value +define function ToString(value ExposureState): value.value +define function ToString(value ExtensionContextType): value.value +define function ToString(value FHIRAllTypes): value.value +define function ToString(value FHIRDefinedType): value.value +define function ToString(value FHIRDeviceStatus): value.value +define function ToString(value FHIRResourceType): value.value +define function ToString(value FHIRSubstanceStatus): value.value +define function ToString(value FHIRVersion): value.value +define function ToString(value FamilyHistoryStatus): value.value +define function ToString(value FilterOperator): value.value +define function ToString(value FlagStatus): value.value +define function ToString(value GoalLifecycleStatus): value.value +define function ToString(value GraphCompartmentRule): value.value +define function ToString(value GraphCompartmentUse): value.value +define function ToString(value GroupMeasure): value.value +define function ToString(value GroupType): value.value +define function ToString(value GuidanceResponseStatus): value.value +define function ToString(value GuidePageGeneration): value.value +define function ToString(value GuideParameterCode): value.value +define function ToString(value HTTPVerb): value.value +define function ToString(value IdentifierUse): value.value +define function ToString(value IdentityAssuranceLevel): value.value +define function ToString(value ImagingStudyStatus): value.value +define function ToString(value ImmunizationEvaluationStatus): value.value +define function ToString(value ImmunizationStatus): value.value +define function ToString(value InvoicePriceComponentType): value.value +define function ToString(value InvoiceStatus): value.value +define function ToString(value IssueSeverity): value.value +define function ToString(value IssueType): value.value +define function ToString(value LinkType): value.value +define function ToString(value LinkageType): value.value +define function ToString(value ListMode): value.value +define function ToString(value ListStatus): value.value +define function ToString(value LocationMode): value.value +define function ToString(value LocationStatus): value.value +define function ToString(value MeasureReportStatus): value.value +define function ToString(value MeasureReportType): value.value +define function ToString(value MediaStatus): value.value +define function ToString(value MedicationAdministrationStatus): value.value +define function ToString(value MedicationDispenseStatus): value.value +define function ToString(value MedicationKnowledgeStatus): value.value +define function ToString(value MedicationRequestIntent): value.value +define function ToString(value MedicationRequestPriority): value.value +define function ToString(value MedicationRequestStatus): value.value +define function ToString(value MedicationStatementStatus): value.value +define function ToString(value MedicationStatus): value.value +define function ToString(value MessageSignificanceCategory): value.value +define function ToString(value Messageheader_Response_Request): value.value +define function ToString(value MimeType): value.value +define function ToString(value NameUse): value.value +define function ToString(value NamingSystemIdentifierType): value.value +define function ToString(value NamingSystemType): value.value +define function ToString(value NarrativeStatus): value.value +define function ToString(value NoteType): value.value +define function ToString(value NutritiionOrderIntent): value.value +define function ToString(value NutritionOrderStatus): value.value +define function ToString(value ObservationDataType): value.value +define function ToString(value ObservationRangeCategory): value.value +define function ToString(value ObservationStatus): value.value +define function ToString(value OperationKind): value.value +define function ToString(value OperationParameterUse): value.value +define function ToString(value OrientationType): value.value +define function ToString(value ParameterUse): value.value +define function ToString(value ParticipantRequired): value.value +define function ToString(value ParticipantStatus): value.value +define function ToString(value ParticipationStatus): value.value +define function ToString(value PaymentNoticeStatus): value.value +define function ToString(value PaymentReconciliationStatus): value.value +define function ToString(value ProcedureStatus): value.value +define function ToString(value PropertyRepresentation): value.value +define function ToString(value PropertyType): value.value +define function ToString(value ProvenanceEntityRole): value.value +define function ToString(value PublicationStatus): value.value +define function ToString(value QualityType): value.value +define function ToString(value QuantityComparator): value.value +define function ToString(value QuestionnaireItemOperator): value.value +define function ToString(value QuestionnaireItemType): value.value +define function ToString(value QuestionnaireResponseStatus): value.value +define function ToString(value ReferenceHandlingPolicy): value.value +define function ToString(value ReferenceVersionRules): value.value +define function ToString(value ReferredDocumentStatus): value.value +define function ToString(value RelatedArtifactType): value.value +define function ToString(value RemittanceOutcome): value.value +define function ToString(value RepositoryType): value.value +define function ToString(value RequestIntent): value.value +define function ToString(value RequestPriority): value.value +define function ToString(value RequestStatus): value.value +define function ToString(value ResearchElementType): value.value +define function ToString(value ResearchStudyStatus): value.value +define function ToString(value ResearchSubjectStatus): value.value +define function ToString(value ResourceType): value.value +define function ToString(value ResourceVersionPolicy): value.value +define function ToString(value ResponseType): value.value +define function ToString(value RestfulCapabilityMode): value.value +define function ToString(value RiskAssessmentStatus): value.value +define function ToString(value SPDXLicense): value.value +define function ToString(value SearchComparator): value.value +define function ToString(value SearchEntryMode): value.value +define function ToString(value SearchModifierCode): value.value +define function ToString(value SearchParamType): value.value +define function ToString(value SectionMode): value.value +define function ToString(value SequenceType): value.value +define function ToString(value ServiceRequestIntent): value.value +define function ToString(value ServiceRequestPriority): value.value +define function ToString(value ServiceRequestStatus): value.value +define function ToString(value SlicingRules): value.value +define function ToString(value SlotStatus): value.value +define function ToString(value SortDirection): value.value +define function ToString(value SpecimenContainedPreference): value.value +define function ToString(value SpecimenStatus): value.value +define function ToString(value Status): value.value +define function ToString(value StrandType): value.value +define function ToString(value StructureDefinitionKind): value.value +define function ToString(value StructureMapContextType): value.value +define function ToString(value StructureMapGroupTypeMode): value.value +define function ToString(value StructureMapInputMode): value.value +define function ToString(value StructureMapModelMode): value.value +define function ToString(value StructureMapSourceListMode): value.value +define function ToString(value StructureMapTargetListMode): value.value +define function ToString(value StructureMapTransform): value.value +define function ToString(value SubscriptionChannelType): value.value +define function ToString(value SubscriptionStatus): value.value +define function ToString(value SupplyDeliveryStatus): value.value +define function ToString(value SupplyRequestStatus): value.value +define function ToString(value SystemRestfulInteraction): value.value +define function ToString(value TaskIntent): value.value +define function ToString(value TaskPriority): value.value +define function ToString(value TaskStatus): value.value +define function ToString(value TestReportActionResult): value.value +define function ToString(value TestReportParticipantType): value.value +define function ToString(value TestReportResult): value.value +define function ToString(value TestReportStatus): value.value +define function ToString(value TestScriptRequestMethodCode): value.value +define function ToString(value TriggerType): value.value +define function ToString(value TypeDerivationRule): value.value +define function ToString(value TypeRestfulInteraction): value.value +define function ToString(value UDIEntryType): value.value +define function ToString(value UnitsOfTime): value.value +define function ToString(value Use): value.value +define function ToString(value VariableType): value.value +define function ToString(value VisionBase): value.value +define function ToString(value VisionEyes): value.value +define function ToString(value VisionStatus): value.value +define function ToString(value XPathUsageType): value.value +define function ToString(value base64Binary): value.value +define function ToBoolean(value boolean): value.value +define function ToDate(value date): value.value +define function ToDateTime(value dateTime): value.value +define function ToDecimal(value decimal): value.value +define function ToDateTime(value instant): value.value +define function ToInteger(value integer): value.value +define function ToString(value string): value.value +define function ToTime(value time): value.value +define function ToString(value uri): value.value +define function ToString(value xhtml): value.value diff --git a/internal/embeddata/third_party/cqframework/LICENSE b/internal/embeddata/third_party/cqframework/LICENSE new file mode 100644 index 0000000..5c304d1 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/internal/embeddata/third_party/cqframework/README.md b/internal/embeddata/third_party/cqframework/README.md new file mode 100644 index 0000000..f18475f --- /dev/null +++ b/internal/embeddata/third_party/cqframework/README.md @@ -0,0 +1,61 @@ +# CQL grammar + +This folder includes the Antlr 4.0 grammar, FHIRHelpers CQL library and ModelInfo files for the CQL language. + +## Source +The files are sourced from the [reference implementation repository](https://github.com/cqframework/clinical_quality_language), in the [v1.5.2 tag](https://github.com/cqframework/clinical_quality_language/tree/v1.5.2/Src), which is sourced under [Apache License 2.0](https://github.com/cqframework/clinical_quality_language/blob/v1.5.2/LICENSE). + +Note that v1.5.2 tag in this repository does _not_ correspond to the [HL7 CQL +spec v1.5.2](https://cql.hl7.org/history.html). More details in b/325656978, and +we should consider using a later version of the tag. + +## Downloading the files +``` +# Download files from the v1.5.2 release: +REPOROOT=https://raw.githubusercontent.com/cqframework/clinical_quality_language/v1.5.2 +# Download the fhirpath grammar dependency: +curl $REPOROOT/Src/grammar/fhirpath.g4 > fhirpath.g4 +# Download the cql grammar and capitalize its name: +rm Cql.g4 +echo "// CQL grammar, sourced from $REPOROOT" > Cql.g4 +echo '// Capitalized to work with both ANTLR conventions and Go visibility rules.' >> Cql.g4 +curl $REPOROOT/Src/grammar/cql.g4 | sed '1 s/^grammar cql;$/grammar Cql;/' >> Cql.g4 +# Download modelinfo +curl $REPOROOT/Src/java/quick/src/main/resources/org/hl7/fhir/fhir-modelinfo-4.0.1.xml > fhir-modelinfo-4.0.1.xml +curl $REPOROOT/Src/java/model/src/main/resources/org/hl7/elm/r1/system-modelinfo.xml > system-modelinfo.xml +# Download FHIRHelpers +curl $REPOROOT/Src/java/quick/src/main/resources/org/hl7/fhir/FHIRHelpers-4.0.1.cql > FHIRHelpers-4.0.1.cql +# Download the license file: +curl $REPOROOT/LICENSE > LICENSE +# Download the CVLT grammar dependency: +curl https://https://raw.githubusercontent.com/cqframework/cql-tests-runner/main/cvl/.grammar/cvlt.g4 >> Cvlt.g4 +``` + +## Changes from the source +In order for generated code to work with both ANTLR conventions and Go visibility rules we are capitalizing the cql grammar's name from `cql` to `Cql`. + +The system-modelinfo.xml is missing conversion functions. Add the following conversion functions in the system-modelinfo.xml so implicit conversions are supported. + +``` xml + + + + + + + +``` + +Also update ValueSet and Vocabulary to the following, to ensure all properties are supported. +```xml + + + + + + + + + + +``` diff --git a/internal/embeddata/third_party/cqframework/cql/cql_base_visitor.go b/internal/embeddata/third_party/cqframework/cql/cql_base_visitor.go new file mode 100755 index 0000000..55fad33 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cql/cql_base_visitor.go @@ -0,0 +1,725 @@ +// Code generated from Cql.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cql // Cql +import "github.com/antlr4-go/antlr/v4" + + +type BaseCqlVisitor struct { + *antlr.BaseParseTreeVisitor +} + +func (v *BaseCqlVisitor) VisitDefinition(ctx *DefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLibrary(ctx *LibraryContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLibraryDefinition(ctx *LibraryDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitUsingDefinition(ctx *UsingDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIncludeDefinition(ctx *IncludeDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLocalIdentifier(ctx *LocalIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitAccessModifier(ctx *AccessModifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitParameterDefinition(ctx *ParameterDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodesystemDefinition(ctx *CodesystemDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitValuesetDefinition(ctx *ValuesetDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodesystems(ctx *CodesystemsContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodesystemIdentifier(ctx *CodesystemIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLibraryIdentifier(ctx *LibraryIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodeDefinition(ctx *CodeDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitConceptDefinition(ctx *ConceptDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodeIdentifier(ctx *CodeIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodesystemId(ctx *CodesystemIdContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitValuesetId(ctx *ValuesetIdContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitVersionSpecifier(ctx *VersionSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodeId(ctx *CodeIdContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTypeSpecifier(ctx *TypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitNamedTypeSpecifier(ctx *NamedTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitModelIdentifier(ctx *ModelIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitListTypeSpecifier(ctx *ListTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIntervalTypeSpecifier(ctx *IntervalTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTupleTypeSpecifier(ctx *TupleTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTupleElementDefinition(ctx *TupleElementDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitChoiceTypeSpecifier(ctx *ChoiceTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitStatement(ctx *StatementContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitExpressionDefinition(ctx *ExpressionDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitContextDefinition(ctx *ContextDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitFluentModifier(ctx *FluentModifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitFunctionDefinition(ctx *FunctionDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitOperandDefinition(ctx *OperandDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitFunctionBody(ctx *FunctionBodyContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQuerySource(ctx *QuerySourceContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitAliasedQuerySource(ctx *AliasedQuerySourceContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitAlias(ctx *AliasContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQueryInclusionClause(ctx *QueryInclusionClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitWithClause(ctx *WithClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitWithoutClause(ctx *WithoutClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitRetrieve(ctx *RetrieveContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitContextIdentifier(ctx *ContextIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodePath(ctx *CodePathContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodeComparator(ctx *CodeComparatorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTerminology(ctx *TerminologyContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQualifier(ctx *QualifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQuery(ctx *QueryContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSourceClause(ctx *SourceClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLetClause(ctx *LetClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLetClauseItem(ctx *LetClauseItemContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitWhereClause(ctx *WhereClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitReturnClause(ctx *ReturnClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitAggregateClause(ctx *AggregateClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitStartingClause(ctx *StartingClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSortClause(ctx *SortClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSortDirection(ctx *SortDirectionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSortByItem(ctx *SortByItemContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQualifiedIdentifier(ctx *QualifiedIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQualifiedIdentifierExpression(ctx *QualifiedIdentifierExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQualifierExpression(ctx *QualifierExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSimplePathIndexer(ctx *SimplePathIndexerContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSimplePathQualifiedIdentifier(ctx *SimplePathQualifiedIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSimplePathReferentialIdentifier(ctx *SimplePathReferentialIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSimpleStringLiteral(ctx *SimpleStringLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSimpleNumberLiteral(ctx *SimpleNumberLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDurationBetweenExpression(ctx *DurationBetweenExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitInFixSetExpression(ctx *InFixSetExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitRetrieveExpression(ctx *RetrieveExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTimingExpression(ctx *TimingExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQueryExpression(ctx *QueryExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitNotExpression(ctx *NotExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitBooleanExpression(ctx *BooleanExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitOrExpression(ctx *OrExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCastExpression(ctx *CastExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitAndExpression(ctx *AndExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitBetweenExpression(ctx *BetweenExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitMembershipExpression(ctx *MembershipExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDifferenceBetweenExpression(ctx *DifferenceBetweenExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitInequalityExpression(ctx *InequalityExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitEqualityExpression(ctx *EqualityExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitExistenceExpression(ctx *ExistenceExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitImpliesExpression(ctx *ImpliesExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTermExpression(ctx *TermExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTypeExpression(ctx *TypeExpressionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDateTimePrecision(ctx *DateTimePrecisionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDateTimeComponent(ctx *DateTimeComponentContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitPluralDateTimePrecision(ctx *PluralDateTimePrecisionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitAdditionExpressionTerm(ctx *AdditionExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIndexedExpressionTerm(ctx *IndexedExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitWidthExpressionTerm(ctx *WidthExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSetAggregateExpressionTerm(ctx *SetAggregateExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTimeUnitExpressionTerm(ctx *TimeUnitExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIfThenElseExpressionTerm(ctx *IfThenElseExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTimeBoundaryExpressionTerm(ctx *TimeBoundaryExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitElementExtractorExpressionTerm(ctx *ElementExtractorExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitConversionExpressionTerm(ctx *ConversionExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTypeExtentExpressionTerm(ctx *TypeExtentExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitPredecessorExpressionTerm(ctx *PredecessorExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitPointExtractorExpressionTerm(ctx *PointExtractorExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitMultiplicationExpressionTerm(ctx *MultiplicationExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitAggregateExpressionTerm(ctx *AggregateExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDurationExpressionTerm(ctx *DurationExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDifferenceExpressionTerm(ctx *DifferenceExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCaseExpressionTerm(ctx *CaseExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitPowerExpressionTerm(ctx *PowerExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitSuccessorExpressionTerm(ctx *SuccessorExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitPolarityExpressionTerm(ctx *PolarityExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTermExpressionTerm(ctx *TermExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitInvocationExpressionTerm(ctx *InvocationExpressionTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCaseExpressionItem(ctx *CaseExpressionItemContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDateTimePrecisionSpecifier(ctx *DateTimePrecisionSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitRelativeQualifier(ctx *RelativeQualifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitOffsetRelativeQualifier(ctx *OffsetRelativeQualifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitExclusiveRelativeQualifier(ctx *ExclusiveRelativeQualifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQuantityOffset(ctx *QuantityOffsetContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTemporalRelationship(ctx *TemporalRelationshipContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitConcurrentWithIntervalOperatorPhrase(ctx *ConcurrentWithIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIncludesIntervalOperatorPhrase(ctx *IncludesIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIncludedInIntervalOperatorPhrase(ctx *IncludedInIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitBeforeOrAfterIntervalOperatorPhrase(ctx *BeforeOrAfterIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitWithinIntervalOperatorPhrase(ctx *WithinIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitMeetsIntervalOperatorPhrase(ctx *MeetsIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitOverlapsIntervalOperatorPhrase(ctx *OverlapsIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitStartsIntervalOperatorPhrase(ctx *StartsIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitEndsIntervalOperatorPhrase(ctx *EndsIntervalOperatorPhraseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitInvocationTerm(ctx *InvocationTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLiteralTerm(ctx *LiteralTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitExternalConstantTerm(ctx *ExternalConstantTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIntervalSelectorTerm(ctx *IntervalSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTupleSelectorTerm(ctx *TupleSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitInstanceSelectorTerm(ctx *InstanceSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitListSelectorTerm(ctx *ListSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodeSelectorTerm(ctx *CodeSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitConceptSelectorTerm(ctx *ConceptSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitParenthesizedTerm(ctx *ParenthesizedTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQualifiedMemberInvocation(ctx *QualifiedMemberInvocationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQualifiedFunctionInvocation(ctx *QualifiedFunctionInvocationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQualifiedFunction(ctx *QualifiedFunctionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitMemberInvocation(ctx *MemberInvocationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitFunctionInvocation(ctx *FunctionInvocationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitThisInvocation(ctx *ThisInvocationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIndexInvocation(ctx *IndexInvocationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTotalInvocation(ctx *TotalInvocationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitFunction(ctx *FunctionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitRatio(ctx *RatioContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitBooleanLiteral(ctx *BooleanLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitNullLiteral(ctx *NullLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitStringLiteral(ctx *StringLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitNumberLiteral(ctx *NumberLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitLongNumberLiteral(ctx *LongNumberLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDateTimeLiteral(ctx *DateTimeLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDateLiteral(ctx *DateLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTimeLiteral(ctx *TimeLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQuantityLiteral(ctx *QuantityLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitRatioLiteral(ctx *RatioLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIntervalSelector(ctx *IntervalSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTupleSelector(ctx *TupleSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTupleElementSelector(ctx *TupleElementSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitInstanceSelector(ctx *InstanceSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitInstanceElementSelector(ctx *InstanceElementSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitListSelector(ctx *ListSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitDisplayClause(ctx *DisplayClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitCodeSelector(ctx *CodeSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitConceptSelector(ctx *ConceptSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitKeyword(ctx *KeywordContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitReservedWord(ctx *ReservedWordContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitKeywordIdentifier(ctx *KeywordIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitObsoleteIdentifier(ctx *ObsoleteIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitFunctionIdentifier(ctx *FunctionIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitTypeNameIdentifier(ctx *TypeNameIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitReferentialIdentifier(ctx *ReferentialIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitReferentialOrTypeNameIdentifier(ctx *ReferentialOrTypeNameIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIdentifierOrFunctionIdentifier(ctx *IdentifierOrFunctionIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitIdentifier(ctx *IdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitExternalConstant(ctx *ExternalConstantContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitParamList(ctx *ParamListContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitQuantity(ctx *QuantityContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCqlVisitor) VisitUnit(ctx *UnitContext) interface{} { + return v.VisitChildren(ctx) +} diff --git a/internal/embeddata/third_party/cqframework/cql/cql_lexer.go b/internal/embeddata/third_party/cqframework/cql/cql_lexer.go new file mode 100755 index 0000000..e7dad36 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cql/cql_lexer.go @@ -0,0 +1,991 @@ +// Code generated from Cql.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cql +import ( + "fmt" + "sync" + "unicode" + "github.com/antlr4-go/antlr/v4" +) +// Suppress unused import error +var _ = fmt.Printf +var _ = sync.Once{} +var _ = unicode.IsLetter + + +type CqlLexer struct { + *antlr.BaseLexer + channelNames []string + modeNames []string + // TODO: EOF string +} + +var CqlLexerLexerStaticData struct { + once sync.Once + serializedATN []int32 + ChannelNames []string + ModeNames []string + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func cqllexerLexerInit() { + staticData := &CqlLexerLexerStaticData + staticData.ChannelNames = []string{ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + } + staticData.ModeNames = []string{ + "DEFAULT_MODE", + } + staticData.LiteralNames = []string{ + "", "'library'", "'version'", "'using'", "'called'", "'include'", "'public'", + "'private'", "'parameter'", "'default'", "'codesystem'", "':'", "'valueset'", + "'codesystems'", "'{'", "','", "'}'", "'.'", "'code'", "'from'", "'concept'", + "'List'", "'<'", "'>'", "'Interval'", "'Tuple'", "'Choice'", "'define'", + "'context'", "'fluent'", "'function'", "'('", "')'", "'returns'", "'external'", + "'with'", "'such that'", "'without'", "'['", "'->'", "']'", "'in'", + "'='", "'~'", "'let'", "'where'", "'return'", "'all'", "'distinct'", + "'aggregate'", "'starting'", "'sort'", "'by'", "'asc'", "'ascending'", + "'desc'", "'descending'", "'is'", "'not'", "'null'", "'true'", "'false'", + "'as'", "'cast'", "'exists'", "'properly'", "'between'", "'and'", "'duration'", + "'difference'", "'<='", "'>='", "'!='", "'!~'", "'contains'", "'or'", + "'xor'", "'implies'", "'|'", "'union'", "'intersect'", "'except'", "'year'", + "'month'", "'week'", "'day'", "'hour'", "'minute'", "'second'", "'millisecond'", + "'date'", "'time'", "'timezone'", "'timezoneoffset'", "'years'", "'months'", + "'weeks'", "'days'", "'hours'", "'minutes'", "'seconds'", "'milliseconds'", + "'convert'", "'to'", "'+'", "'-'", "'start'", "'end'", "'of'", "'width'", + "'successor'", "'predecessor'", "'singleton'", "'point'", "'minimum'", + "'maximum'", "'^'", "'*'", "'/'", "'div'", "'mod'", "'&'", "'if'", "'then'", + "'else'", "'case'", "'flatten'", "'expand'", "'collapse'", "'per'", + "'when'", "'or before'", "'or after'", "'or more'", "'or less'", "'less than'", + "'more than'", "'on or'", "'before'", "'after'", "'or on'", "'starts'", + "'ends'", "'occurs'", "'same'", "'includes'", "'during'", "'included in'", + "'within'", "'meets'", "'overlaps'", "'$this'", "'$index'", "'$total'", + "'display'", "'Code'", "'Concept'", "'%'", + } + staticData.SymbolicNames = []string{ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "QUOTEDIDENTIFIER", "DATETIME", "LONGNUMBER", "DATE", + "TIME", "IDENTIFIER", "DELIMITEDIDENTIFIER", "STRING", "NUMBER", "WS", + "COMMENT", "LINE_COMMENT", + } + staticData.RuleNames = []string{ + "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8", + "T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16", + "T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "T__23", "T__24", + "T__25", "T__26", "T__27", "T__28", "T__29", "T__30", "T__31", "T__32", + "T__33", "T__34", "T__35", "T__36", "T__37", "T__38", "T__39", "T__40", + "T__41", "T__42", "T__43", "T__44", "T__45", "T__46", "T__47", "T__48", + "T__49", "T__50", "T__51", "T__52", "T__53", "T__54", "T__55", "T__56", + "T__57", "T__58", "T__59", "T__60", "T__61", "T__62", "T__63", "T__64", + "T__65", "T__66", "T__67", "T__68", "T__69", "T__70", "T__71", "T__72", + "T__73", "T__74", "T__75", "T__76", "T__77", "T__78", "T__79", "T__80", + "T__81", "T__82", "T__83", "T__84", "T__85", "T__86", "T__87", "T__88", + "T__89", "T__90", "T__91", "T__92", "T__93", "T__94", "T__95", "T__96", + "T__97", "T__98", "T__99", "T__100", "T__101", "T__102", "T__103", "T__104", + "T__105", "T__106", "T__107", "T__108", "T__109", "T__110", "T__111", + "T__112", "T__113", "T__114", "T__115", "T__116", "T__117", "T__118", + "T__119", "T__120", "T__121", "T__122", "T__123", "T__124", "T__125", + "T__126", "T__127", "T__128", "T__129", "T__130", "T__131", "T__132", + "T__133", "T__134", "T__135", "T__136", "T__137", "T__138", "T__139", + "T__140", "T__141", "T__142", "T__143", "T__144", "T__145", "T__146", + "T__147", "T__148", "T__149", "T__150", "T__151", "T__152", "T__153", + "T__154", "T__155", "T__156", "QUOTEDIDENTIFIER", "DATETIME", "LONGNUMBER", + "ESC", "DATE", "TIME", "DATEFORMAT", "TIMEFORMAT", "TIMEZONEOFFSETFORMAT", + "IDENTIFIER", "DELIMITEDIDENTIFIER", "STRING", "NUMBER", "WS", "COMMENT", + "LINE_COMMENT", "UNICODE", "HEX", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 0, 169, 1487, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, + 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, + 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, + 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, + 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, + 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, + 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, + 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, + 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, + 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, + 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, + 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, + 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, + 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, + 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, + 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, + 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, + 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, + 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, + 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, + 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, + 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, + 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, + 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, + 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, + 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, + 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, + 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, + 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, + 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, + 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, + 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, + 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, + 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, + 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, + 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, + 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, + 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, + 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, + 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, + 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, + 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, + 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, + 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, + 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, + 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, + 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, + 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, + 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, + 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, + 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, + 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, + 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, + 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, + 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, + 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, + 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, + 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, + 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, + 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, + 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, + 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, + 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, + 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, + 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, + 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, + 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, + 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, + 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, + 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, + 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, + 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, + 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, + 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, + 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, + 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, + 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, + 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, + 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, + 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, + 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, + 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, + 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, + 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, + 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, + 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, + 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, + 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, + 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, + 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, + 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, + 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, + 101, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 104, 1, 104, 1, 105, 1, + 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, + 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, + 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, + 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, + 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, + 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, + 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, + 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, + 115, 1, 115, 1, 116, 1, 116, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, + 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 121, 1, 121, 1, + 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, + 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, + 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, + 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, + 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, + 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, + 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, + 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, + 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, + 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, + 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, + 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, + 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, + 138, 1, 138, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, + 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 140, 1, 140, 1, + 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, + 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 143, 1, + 144, 1, 144, 1, 144, 1, 144, 1, 144, 1, 144, 1, 144, 1, 144, 1, 144, 1, + 145, 1, 145, 1, 145, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, + 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, + 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, + 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, + 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, + 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, + 152, 1, 152, 1, 152, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, + 153, 1, 153, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, + 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, + 155, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 5, 157, 1328, 8, 157, 10, + 157, 12, 157, 1331, 9, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, + 158, 3, 158, 1339, 8, 158, 1, 158, 3, 158, 1342, 8, 158, 1, 159, 4, 159, + 1345, 8, 159, 11, 159, 12, 159, 1346, 1, 159, 1, 159, 1, 160, 1, 160, 1, + 160, 3, 160, 1354, 8, 160, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, + 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, + 1, 163, 1, 163, 3, 163, 1373, 8, 163, 3, 163, 1375, 8, 163, 1, 164, 1, + 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 4, + 164, 1387, 8, 164, 11, 164, 12, 164, 1388, 3, 164, 1391, 8, 164, 3, 164, + 1393, 8, 164, 3, 164, 1395, 8, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, + 165, 1, 165, 1, 165, 3, 165, 1404, 8, 165, 1, 166, 3, 166, 1407, 8, 166, + 1, 166, 5, 166, 1410, 8, 166, 10, 166, 12, 166, 1413, 9, 166, 1, 167, 1, + 167, 1, 167, 5, 167, 1418, 8, 167, 10, 167, 12, 167, 1421, 9, 167, 1, 167, + 1, 167, 1, 168, 1, 168, 1, 168, 5, 168, 1428, 8, 168, 10, 168, 12, 168, + 1431, 9, 168, 1, 168, 1, 168, 1, 169, 4, 169, 1436, 8, 169, 11, 169, 12, + 169, 1437, 1, 169, 1, 169, 4, 169, 1442, 8, 169, 11, 169, 12, 169, 1443, + 3, 169, 1446, 8, 169, 1, 170, 4, 170, 1449, 8, 170, 11, 170, 12, 170, 1450, + 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 5, 171, 1459, 8, 171, 10, + 171, 12, 171, 1462, 9, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, + 172, 1, 172, 1, 172, 1, 172, 5, 172, 1473, 8, 172, 10, 172, 12, 172, 1476, + 9, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 173, 1, 173, + 1, 174, 1, 174, 4, 1329, 1419, 1429, 1460, 0, 175, 1, 1, 3, 2, 5, 3, 7, + 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, + 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, + 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, + 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, + 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 95, 48, 97, 49, 99, + 50, 101, 51, 103, 52, 105, 53, 107, 54, 109, 55, 111, 56, 113, 57, 115, + 58, 117, 59, 119, 60, 121, 61, 123, 62, 125, 63, 127, 64, 129, 65, 131, + 66, 133, 67, 135, 68, 137, 69, 139, 70, 141, 71, 143, 72, 145, 73, 147, + 74, 149, 75, 151, 76, 153, 77, 155, 78, 157, 79, 159, 80, 161, 81, 163, + 82, 165, 83, 167, 84, 169, 85, 171, 86, 173, 87, 175, 88, 177, 89, 179, + 90, 181, 91, 183, 92, 185, 93, 187, 94, 189, 95, 191, 96, 193, 97, 195, + 98, 197, 99, 199, 100, 201, 101, 203, 102, 205, 103, 207, 104, 209, 105, + 211, 106, 213, 107, 215, 108, 217, 109, 219, 110, 221, 111, 223, 112, 225, + 113, 227, 114, 229, 115, 231, 116, 233, 117, 235, 118, 237, 119, 239, 120, + 241, 121, 243, 122, 245, 123, 247, 124, 249, 125, 251, 126, 253, 127, 255, + 128, 257, 129, 259, 130, 261, 131, 263, 132, 265, 133, 267, 134, 269, 135, + 271, 136, 273, 137, 275, 138, 277, 139, 279, 140, 281, 141, 283, 142, 285, + 143, 287, 144, 289, 145, 291, 146, 293, 147, 295, 148, 297, 149, 299, 150, + 301, 151, 303, 152, 305, 153, 307, 154, 309, 155, 311, 156, 313, 157, 315, + 158, 317, 159, 319, 160, 321, 0, 323, 161, 325, 162, 327, 0, 329, 0, 331, + 0, 333, 163, 335, 164, 337, 165, 339, 166, 341, 167, 343, 168, 345, 169, + 347, 0, 349, 0, 1, 0, 8, 1, 0, 48, 57, 9, 0, 34, 34, 39, 39, 47, 47, 92, + 92, 96, 96, 102, 102, 110, 110, 114, 114, 116, 116, 2, 0, 43, 43, 45, 45, + 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 3, + 0, 9, 10, 13, 13, 32, 32, 2, 0, 10, 10, 13, 13, 3, 0, 48, 57, 65, 70, 97, + 102, 1504, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, + 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, + 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, + 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, + 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, + 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, + 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, + 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, + 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, + 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, + 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, + 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, + 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, + 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, + 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, 1, 0, 0, 0, 0, 113, 1, + 0, 0, 0, 0, 115, 1, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 119, 1, 0, 0, 0, 0, + 121, 1, 0, 0, 0, 0, 123, 1, 0, 0, 0, 0, 125, 1, 0, 0, 0, 0, 127, 1, 0, + 0, 0, 0, 129, 1, 0, 0, 0, 0, 131, 1, 0, 0, 0, 0, 133, 1, 0, 0, 0, 0, 135, + 1, 0, 0, 0, 0, 137, 1, 0, 0, 0, 0, 139, 1, 0, 0, 0, 0, 141, 1, 0, 0, 0, + 0, 143, 1, 0, 0, 0, 0, 145, 1, 0, 0, 0, 0, 147, 1, 0, 0, 0, 0, 149, 1, + 0, 0, 0, 0, 151, 1, 0, 0, 0, 0, 153, 1, 0, 0, 0, 0, 155, 1, 0, 0, 0, 0, + 157, 1, 0, 0, 0, 0, 159, 1, 0, 0, 0, 0, 161, 1, 0, 0, 0, 0, 163, 1, 0, + 0, 0, 0, 165, 1, 0, 0, 0, 0, 167, 1, 0, 0, 0, 0, 169, 1, 0, 0, 0, 0, 171, + 1, 0, 0, 0, 0, 173, 1, 0, 0, 0, 0, 175, 1, 0, 0, 0, 0, 177, 1, 0, 0, 0, + 0, 179, 1, 0, 0, 0, 0, 181, 1, 0, 0, 0, 0, 183, 1, 0, 0, 0, 0, 185, 1, + 0, 0, 0, 0, 187, 1, 0, 0, 0, 0, 189, 1, 0, 0, 0, 0, 191, 1, 0, 0, 0, 0, + 193, 1, 0, 0, 0, 0, 195, 1, 0, 0, 0, 0, 197, 1, 0, 0, 0, 0, 199, 1, 0, + 0, 0, 0, 201, 1, 0, 0, 0, 0, 203, 1, 0, 0, 0, 0, 205, 1, 0, 0, 0, 0, 207, + 1, 0, 0, 0, 0, 209, 1, 0, 0, 0, 0, 211, 1, 0, 0, 0, 0, 213, 1, 0, 0, 0, + 0, 215, 1, 0, 0, 0, 0, 217, 1, 0, 0, 0, 0, 219, 1, 0, 0, 0, 0, 221, 1, + 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 225, 1, 0, 0, 0, 0, 227, 1, 0, 0, 0, 0, + 229, 1, 0, 0, 0, 0, 231, 1, 0, 0, 0, 0, 233, 1, 0, 0, 0, 0, 235, 1, 0, + 0, 0, 0, 237, 1, 0, 0, 0, 0, 239, 1, 0, 0, 0, 0, 241, 1, 0, 0, 0, 0, 243, + 1, 0, 0, 0, 0, 245, 1, 0, 0, 0, 0, 247, 1, 0, 0, 0, 0, 249, 1, 0, 0, 0, + 0, 251, 1, 0, 0, 0, 0, 253, 1, 0, 0, 0, 0, 255, 1, 0, 0, 0, 0, 257, 1, + 0, 0, 0, 0, 259, 1, 0, 0, 0, 0, 261, 1, 0, 0, 0, 0, 263, 1, 0, 0, 0, 0, + 265, 1, 0, 0, 0, 0, 267, 1, 0, 0, 0, 0, 269, 1, 0, 0, 0, 0, 271, 1, 0, + 0, 0, 0, 273, 1, 0, 0, 0, 0, 275, 1, 0, 0, 0, 0, 277, 1, 0, 0, 0, 0, 279, + 1, 0, 0, 0, 0, 281, 1, 0, 0, 0, 0, 283, 1, 0, 0, 0, 0, 285, 1, 0, 0, 0, + 0, 287, 1, 0, 0, 0, 0, 289, 1, 0, 0, 0, 0, 291, 1, 0, 0, 0, 0, 293, 1, + 0, 0, 0, 0, 295, 1, 0, 0, 0, 0, 297, 1, 0, 0, 0, 0, 299, 1, 0, 0, 0, 0, + 301, 1, 0, 0, 0, 0, 303, 1, 0, 0, 0, 0, 305, 1, 0, 0, 0, 0, 307, 1, 0, + 0, 0, 0, 309, 1, 0, 0, 0, 0, 311, 1, 0, 0, 0, 0, 313, 1, 0, 0, 0, 0, 315, + 1, 0, 0, 0, 0, 317, 1, 0, 0, 0, 0, 319, 1, 0, 0, 0, 0, 323, 1, 0, 0, 0, + 0, 325, 1, 0, 0, 0, 0, 333, 1, 0, 0, 0, 0, 335, 1, 0, 0, 0, 0, 337, 1, + 0, 0, 0, 0, 339, 1, 0, 0, 0, 0, 341, 1, 0, 0, 0, 0, 343, 1, 0, 0, 0, 0, + 345, 1, 0, 0, 0, 1, 351, 1, 0, 0, 0, 3, 359, 1, 0, 0, 0, 5, 367, 1, 0, + 0, 0, 7, 373, 1, 0, 0, 0, 9, 380, 1, 0, 0, 0, 11, 388, 1, 0, 0, 0, 13, + 395, 1, 0, 0, 0, 15, 403, 1, 0, 0, 0, 17, 413, 1, 0, 0, 0, 19, 421, 1, + 0, 0, 0, 21, 432, 1, 0, 0, 0, 23, 434, 1, 0, 0, 0, 25, 443, 1, 0, 0, 0, + 27, 455, 1, 0, 0, 0, 29, 457, 1, 0, 0, 0, 31, 459, 1, 0, 0, 0, 33, 461, + 1, 0, 0, 0, 35, 463, 1, 0, 0, 0, 37, 468, 1, 0, 0, 0, 39, 473, 1, 0, 0, + 0, 41, 481, 1, 0, 0, 0, 43, 486, 1, 0, 0, 0, 45, 488, 1, 0, 0, 0, 47, 490, + 1, 0, 0, 0, 49, 499, 1, 0, 0, 0, 51, 505, 1, 0, 0, 0, 53, 512, 1, 0, 0, + 0, 55, 519, 1, 0, 0, 0, 57, 527, 1, 0, 0, 0, 59, 534, 1, 0, 0, 0, 61, 543, + 1, 0, 0, 0, 63, 545, 1, 0, 0, 0, 65, 547, 1, 0, 0, 0, 67, 555, 1, 0, 0, + 0, 69, 564, 1, 0, 0, 0, 71, 569, 1, 0, 0, 0, 73, 579, 1, 0, 0, 0, 75, 587, + 1, 0, 0, 0, 77, 589, 1, 0, 0, 0, 79, 592, 1, 0, 0, 0, 81, 594, 1, 0, 0, + 0, 83, 597, 1, 0, 0, 0, 85, 599, 1, 0, 0, 0, 87, 601, 1, 0, 0, 0, 89, 605, + 1, 0, 0, 0, 91, 611, 1, 0, 0, 0, 93, 618, 1, 0, 0, 0, 95, 622, 1, 0, 0, + 0, 97, 631, 1, 0, 0, 0, 99, 641, 1, 0, 0, 0, 101, 650, 1, 0, 0, 0, 103, + 655, 1, 0, 0, 0, 105, 658, 1, 0, 0, 0, 107, 662, 1, 0, 0, 0, 109, 672, + 1, 0, 0, 0, 111, 677, 1, 0, 0, 0, 113, 688, 1, 0, 0, 0, 115, 691, 1, 0, + 0, 0, 117, 695, 1, 0, 0, 0, 119, 700, 1, 0, 0, 0, 121, 705, 1, 0, 0, 0, + 123, 711, 1, 0, 0, 0, 125, 714, 1, 0, 0, 0, 127, 719, 1, 0, 0, 0, 129, + 726, 1, 0, 0, 0, 131, 735, 1, 0, 0, 0, 133, 743, 1, 0, 0, 0, 135, 747, + 1, 0, 0, 0, 137, 756, 1, 0, 0, 0, 139, 767, 1, 0, 0, 0, 141, 770, 1, 0, + 0, 0, 143, 773, 1, 0, 0, 0, 145, 776, 1, 0, 0, 0, 147, 779, 1, 0, 0, 0, + 149, 788, 1, 0, 0, 0, 151, 791, 1, 0, 0, 0, 153, 795, 1, 0, 0, 0, 155, + 803, 1, 0, 0, 0, 157, 805, 1, 0, 0, 0, 159, 811, 1, 0, 0, 0, 161, 821, + 1, 0, 0, 0, 163, 828, 1, 0, 0, 0, 165, 833, 1, 0, 0, 0, 167, 839, 1, 0, + 0, 0, 169, 844, 1, 0, 0, 0, 171, 848, 1, 0, 0, 0, 173, 853, 1, 0, 0, 0, + 175, 860, 1, 0, 0, 0, 177, 867, 1, 0, 0, 0, 179, 879, 1, 0, 0, 0, 181, + 884, 1, 0, 0, 0, 183, 889, 1, 0, 0, 0, 185, 898, 1, 0, 0, 0, 187, 913, + 1, 0, 0, 0, 189, 919, 1, 0, 0, 0, 191, 926, 1, 0, 0, 0, 193, 932, 1, 0, + 0, 0, 195, 937, 1, 0, 0, 0, 197, 943, 1, 0, 0, 0, 199, 951, 1, 0, 0, 0, + 201, 959, 1, 0, 0, 0, 203, 972, 1, 0, 0, 0, 205, 980, 1, 0, 0, 0, 207, + 983, 1, 0, 0, 0, 209, 985, 1, 0, 0, 0, 211, 987, 1, 0, 0, 0, 213, 993, + 1, 0, 0, 0, 215, 997, 1, 0, 0, 0, 217, 1000, 1, 0, 0, 0, 219, 1006, 1, + 0, 0, 0, 221, 1016, 1, 0, 0, 0, 223, 1028, 1, 0, 0, 0, 225, 1038, 1, 0, + 0, 0, 227, 1044, 1, 0, 0, 0, 229, 1052, 1, 0, 0, 0, 231, 1060, 1, 0, 0, + 0, 233, 1062, 1, 0, 0, 0, 235, 1064, 1, 0, 0, 0, 237, 1066, 1, 0, 0, 0, + 239, 1070, 1, 0, 0, 0, 241, 1074, 1, 0, 0, 0, 243, 1076, 1, 0, 0, 0, 245, + 1079, 1, 0, 0, 0, 247, 1084, 1, 0, 0, 0, 249, 1089, 1, 0, 0, 0, 251, 1094, + 1, 0, 0, 0, 253, 1102, 1, 0, 0, 0, 255, 1109, 1, 0, 0, 0, 257, 1118, 1, + 0, 0, 0, 259, 1122, 1, 0, 0, 0, 261, 1127, 1, 0, 0, 0, 263, 1137, 1, 0, + 0, 0, 265, 1146, 1, 0, 0, 0, 267, 1154, 1, 0, 0, 0, 269, 1162, 1, 0, 0, + 0, 271, 1172, 1, 0, 0, 0, 273, 1182, 1, 0, 0, 0, 275, 1188, 1, 0, 0, 0, + 277, 1195, 1, 0, 0, 0, 279, 1201, 1, 0, 0, 0, 281, 1207, 1, 0, 0, 0, 283, + 1214, 1, 0, 0, 0, 285, 1219, 1, 0, 0, 0, 287, 1226, 1, 0, 0, 0, 289, 1231, + 1, 0, 0, 0, 291, 1240, 1, 0, 0, 0, 293, 1247, 1, 0, 0, 0, 295, 1259, 1, + 0, 0, 0, 297, 1266, 1, 0, 0, 0, 299, 1272, 1, 0, 0, 0, 301, 1281, 1, 0, + 0, 0, 303, 1287, 1, 0, 0, 0, 305, 1294, 1, 0, 0, 0, 307, 1301, 1, 0, 0, + 0, 309, 1309, 1, 0, 0, 0, 311, 1314, 1, 0, 0, 0, 313, 1322, 1, 0, 0, 0, + 315, 1324, 1, 0, 0, 0, 317, 1334, 1, 0, 0, 0, 319, 1344, 1, 0, 0, 0, 321, + 1350, 1, 0, 0, 0, 323, 1355, 1, 0, 0, 0, 325, 1358, 1, 0, 0, 0, 327, 1362, + 1, 0, 0, 0, 329, 1376, 1, 0, 0, 0, 331, 1403, 1, 0, 0, 0, 333, 1406, 1, + 0, 0, 0, 335, 1414, 1, 0, 0, 0, 337, 1424, 1, 0, 0, 0, 339, 1435, 1, 0, + 0, 0, 341, 1448, 1, 0, 0, 0, 343, 1454, 1, 0, 0, 0, 345, 1468, 1, 0, 0, + 0, 347, 1479, 1, 0, 0, 0, 349, 1485, 1, 0, 0, 0, 351, 352, 5, 108, 0, 0, + 352, 353, 5, 105, 0, 0, 353, 354, 5, 98, 0, 0, 354, 355, 5, 114, 0, 0, + 355, 356, 5, 97, 0, 0, 356, 357, 5, 114, 0, 0, 357, 358, 5, 121, 0, 0, + 358, 2, 1, 0, 0, 0, 359, 360, 5, 118, 0, 0, 360, 361, 5, 101, 0, 0, 361, + 362, 5, 114, 0, 0, 362, 363, 5, 115, 0, 0, 363, 364, 5, 105, 0, 0, 364, + 365, 5, 111, 0, 0, 365, 366, 5, 110, 0, 0, 366, 4, 1, 0, 0, 0, 367, 368, + 5, 117, 0, 0, 368, 369, 5, 115, 0, 0, 369, 370, 5, 105, 0, 0, 370, 371, + 5, 110, 0, 0, 371, 372, 5, 103, 0, 0, 372, 6, 1, 0, 0, 0, 373, 374, 5, + 99, 0, 0, 374, 375, 5, 97, 0, 0, 375, 376, 5, 108, 0, 0, 376, 377, 5, 108, + 0, 0, 377, 378, 5, 101, 0, 0, 378, 379, 5, 100, 0, 0, 379, 8, 1, 0, 0, + 0, 380, 381, 5, 105, 0, 0, 381, 382, 5, 110, 0, 0, 382, 383, 5, 99, 0, + 0, 383, 384, 5, 108, 0, 0, 384, 385, 5, 117, 0, 0, 385, 386, 5, 100, 0, + 0, 386, 387, 5, 101, 0, 0, 387, 10, 1, 0, 0, 0, 388, 389, 5, 112, 0, 0, + 389, 390, 5, 117, 0, 0, 390, 391, 5, 98, 0, 0, 391, 392, 5, 108, 0, 0, + 392, 393, 5, 105, 0, 0, 393, 394, 5, 99, 0, 0, 394, 12, 1, 0, 0, 0, 395, + 396, 5, 112, 0, 0, 396, 397, 5, 114, 0, 0, 397, 398, 5, 105, 0, 0, 398, + 399, 5, 118, 0, 0, 399, 400, 5, 97, 0, 0, 400, 401, 5, 116, 0, 0, 401, + 402, 5, 101, 0, 0, 402, 14, 1, 0, 0, 0, 403, 404, 5, 112, 0, 0, 404, 405, + 5, 97, 0, 0, 405, 406, 5, 114, 0, 0, 406, 407, 5, 97, 0, 0, 407, 408, 5, + 109, 0, 0, 408, 409, 5, 101, 0, 0, 409, 410, 5, 116, 0, 0, 410, 411, 5, + 101, 0, 0, 411, 412, 5, 114, 0, 0, 412, 16, 1, 0, 0, 0, 413, 414, 5, 100, + 0, 0, 414, 415, 5, 101, 0, 0, 415, 416, 5, 102, 0, 0, 416, 417, 5, 97, + 0, 0, 417, 418, 5, 117, 0, 0, 418, 419, 5, 108, 0, 0, 419, 420, 5, 116, + 0, 0, 420, 18, 1, 0, 0, 0, 421, 422, 5, 99, 0, 0, 422, 423, 5, 111, 0, + 0, 423, 424, 5, 100, 0, 0, 424, 425, 5, 101, 0, 0, 425, 426, 5, 115, 0, + 0, 426, 427, 5, 121, 0, 0, 427, 428, 5, 115, 0, 0, 428, 429, 5, 116, 0, + 0, 429, 430, 5, 101, 0, 0, 430, 431, 5, 109, 0, 0, 431, 20, 1, 0, 0, 0, + 432, 433, 5, 58, 0, 0, 433, 22, 1, 0, 0, 0, 434, 435, 5, 118, 0, 0, 435, + 436, 5, 97, 0, 0, 436, 437, 5, 108, 0, 0, 437, 438, 5, 117, 0, 0, 438, + 439, 5, 101, 0, 0, 439, 440, 5, 115, 0, 0, 440, 441, 5, 101, 0, 0, 441, + 442, 5, 116, 0, 0, 442, 24, 1, 0, 0, 0, 443, 444, 5, 99, 0, 0, 444, 445, + 5, 111, 0, 0, 445, 446, 5, 100, 0, 0, 446, 447, 5, 101, 0, 0, 447, 448, + 5, 115, 0, 0, 448, 449, 5, 121, 0, 0, 449, 450, 5, 115, 0, 0, 450, 451, + 5, 116, 0, 0, 451, 452, 5, 101, 0, 0, 452, 453, 5, 109, 0, 0, 453, 454, + 5, 115, 0, 0, 454, 26, 1, 0, 0, 0, 455, 456, 5, 123, 0, 0, 456, 28, 1, + 0, 0, 0, 457, 458, 5, 44, 0, 0, 458, 30, 1, 0, 0, 0, 459, 460, 5, 125, + 0, 0, 460, 32, 1, 0, 0, 0, 461, 462, 5, 46, 0, 0, 462, 34, 1, 0, 0, 0, + 463, 464, 5, 99, 0, 0, 464, 465, 5, 111, 0, 0, 465, 466, 5, 100, 0, 0, + 466, 467, 5, 101, 0, 0, 467, 36, 1, 0, 0, 0, 468, 469, 5, 102, 0, 0, 469, + 470, 5, 114, 0, 0, 470, 471, 5, 111, 0, 0, 471, 472, 5, 109, 0, 0, 472, + 38, 1, 0, 0, 0, 473, 474, 5, 99, 0, 0, 474, 475, 5, 111, 0, 0, 475, 476, + 5, 110, 0, 0, 476, 477, 5, 99, 0, 0, 477, 478, 5, 101, 0, 0, 478, 479, + 5, 112, 0, 0, 479, 480, 5, 116, 0, 0, 480, 40, 1, 0, 0, 0, 481, 482, 5, + 76, 0, 0, 482, 483, 5, 105, 0, 0, 483, 484, 5, 115, 0, 0, 484, 485, 5, + 116, 0, 0, 485, 42, 1, 0, 0, 0, 486, 487, 5, 60, 0, 0, 487, 44, 1, 0, 0, + 0, 488, 489, 5, 62, 0, 0, 489, 46, 1, 0, 0, 0, 490, 491, 5, 73, 0, 0, 491, + 492, 5, 110, 0, 0, 492, 493, 5, 116, 0, 0, 493, 494, 5, 101, 0, 0, 494, + 495, 5, 114, 0, 0, 495, 496, 5, 118, 0, 0, 496, 497, 5, 97, 0, 0, 497, + 498, 5, 108, 0, 0, 498, 48, 1, 0, 0, 0, 499, 500, 5, 84, 0, 0, 500, 501, + 5, 117, 0, 0, 501, 502, 5, 112, 0, 0, 502, 503, 5, 108, 0, 0, 503, 504, + 5, 101, 0, 0, 504, 50, 1, 0, 0, 0, 505, 506, 5, 67, 0, 0, 506, 507, 5, + 104, 0, 0, 507, 508, 5, 111, 0, 0, 508, 509, 5, 105, 0, 0, 509, 510, 5, + 99, 0, 0, 510, 511, 5, 101, 0, 0, 511, 52, 1, 0, 0, 0, 512, 513, 5, 100, + 0, 0, 513, 514, 5, 101, 0, 0, 514, 515, 5, 102, 0, 0, 515, 516, 5, 105, + 0, 0, 516, 517, 5, 110, 0, 0, 517, 518, 5, 101, 0, 0, 518, 54, 1, 0, 0, + 0, 519, 520, 5, 99, 0, 0, 520, 521, 5, 111, 0, 0, 521, 522, 5, 110, 0, + 0, 522, 523, 5, 116, 0, 0, 523, 524, 5, 101, 0, 0, 524, 525, 5, 120, 0, + 0, 525, 526, 5, 116, 0, 0, 526, 56, 1, 0, 0, 0, 527, 528, 5, 102, 0, 0, + 528, 529, 5, 108, 0, 0, 529, 530, 5, 117, 0, 0, 530, 531, 5, 101, 0, 0, + 531, 532, 5, 110, 0, 0, 532, 533, 5, 116, 0, 0, 533, 58, 1, 0, 0, 0, 534, + 535, 5, 102, 0, 0, 535, 536, 5, 117, 0, 0, 536, 537, 5, 110, 0, 0, 537, + 538, 5, 99, 0, 0, 538, 539, 5, 116, 0, 0, 539, 540, 5, 105, 0, 0, 540, + 541, 5, 111, 0, 0, 541, 542, 5, 110, 0, 0, 542, 60, 1, 0, 0, 0, 543, 544, + 5, 40, 0, 0, 544, 62, 1, 0, 0, 0, 545, 546, 5, 41, 0, 0, 546, 64, 1, 0, + 0, 0, 547, 548, 5, 114, 0, 0, 548, 549, 5, 101, 0, 0, 549, 550, 5, 116, + 0, 0, 550, 551, 5, 117, 0, 0, 551, 552, 5, 114, 0, 0, 552, 553, 5, 110, + 0, 0, 553, 554, 5, 115, 0, 0, 554, 66, 1, 0, 0, 0, 555, 556, 5, 101, 0, + 0, 556, 557, 5, 120, 0, 0, 557, 558, 5, 116, 0, 0, 558, 559, 5, 101, 0, + 0, 559, 560, 5, 114, 0, 0, 560, 561, 5, 110, 0, 0, 561, 562, 5, 97, 0, + 0, 562, 563, 5, 108, 0, 0, 563, 68, 1, 0, 0, 0, 564, 565, 5, 119, 0, 0, + 565, 566, 5, 105, 0, 0, 566, 567, 5, 116, 0, 0, 567, 568, 5, 104, 0, 0, + 568, 70, 1, 0, 0, 0, 569, 570, 5, 115, 0, 0, 570, 571, 5, 117, 0, 0, 571, + 572, 5, 99, 0, 0, 572, 573, 5, 104, 0, 0, 573, 574, 5, 32, 0, 0, 574, 575, + 5, 116, 0, 0, 575, 576, 5, 104, 0, 0, 576, 577, 5, 97, 0, 0, 577, 578, + 5, 116, 0, 0, 578, 72, 1, 0, 0, 0, 579, 580, 5, 119, 0, 0, 580, 581, 5, + 105, 0, 0, 581, 582, 5, 116, 0, 0, 582, 583, 5, 104, 0, 0, 583, 584, 5, + 111, 0, 0, 584, 585, 5, 117, 0, 0, 585, 586, 5, 116, 0, 0, 586, 74, 1, + 0, 0, 0, 587, 588, 5, 91, 0, 0, 588, 76, 1, 0, 0, 0, 589, 590, 5, 45, 0, + 0, 590, 591, 5, 62, 0, 0, 591, 78, 1, 0, 0, 0, 592, 593, 5, 93, 0, 0, 593, + 80, 1, 0, 0, 0, 594, 595, 5, 105, 0, 0, 595, 596, 5, 110, 0, 0, 596, 82, + 1, 0, 0, 0, 597, 598, 5, 61, 0, 0, 598, 84, 1, 0, 0, 0, 599, 600, 5, 126, + 0, 0, 600, 86, 1, 0, 0, 0, 601, 602, 5, 108, 0, 0, 602, 603, 5, 101, 0, + 0, 603, 604, 5, 116, 0, 0, 604, 88, 1, 0, 0, 0, 605, 606, 5, 119, 0, 0, + 606, 607, 5, 104, 0, 0, 607, 608, 5, 101, 0, 0, 608, 609, 5, 114, 0, 0, + 609, 610, 5, 101, 0, 0, 610, 90, 1, 0, 0, 0, 611, 612, 5, 114, 0, 0, 612, + 613, 5, 101, 0, 0, 613, 614, 5, 116, 0, 0, 614, 615, 5, 117, 0, 0, 615, + 616, 5, 114, 0, 0, 616, 617, 5, 110, 0, 0, 617, 92, 1, 0, 0, 0, 618, 619, + 5, 97, 0, 0, 619, 620, 5, 108, 0, 0, 620, 621, 5, 108, 0, 0, 621, 94, 1, + 0, 0, 0, 622, 623, 5, 100, 0, 0, 623, 624, 5, 105, 0, 0, 624, 625, 5, 115, + 0, 0, 625, 626, 5, 116, 0, 0, 626, 627, 5, 105, 0, 0, 627, 628, 5, 110, + 0, 0, 628, 629, 5, 99, 0, 0, 629, 630, 5, 116, 0, 0, 630, 96, 1, 0, 0, + 0, 631, 632, 5, 97, 0, 0, 632, 633, 5, 103, 0, 0, 633, 634, 5, 103, 0, + 0, 634, 635, 5, 114, 0, 0, 635, 636, 5, 101, 0, 0, 636, 637, 5, 103, 0, + 0, 637, 638, 5, 97, 0, 0, 638, 639, 5, 116, 0, 0, 639, 640, 5, 101, 0, + 0, 640, 98, 1, 0, 0, 0, 641, 642, 5, 115, 0, 0, 642, 643, 5, 116, 0, 0, + 643, 644, 5, 97, 0, 0, 644, 645, 5, 114, 0, 0, 645, 646, 5, 116, 0, 0, + 646, 647, 5, 105, 0, 0, 647, 648, 5, 110, 0, 0, 648, 649, 5, 103, 0, 0, + 649, 100, 1, 0, 0, 0, 650, 651, 5, 115, 0, 0, 651, 652, 5, 111, 0, 0, 652, + 653, 5, 114, 0, 0, 653, 654, 5, 116, 0, 0, 654, 102, 1, 0, 0, 0, 655, 656, + 5, 98, 0, 0, 656, 657, 5, 121, 0, 0, 657, 104, 1, 0, 0, 0, 658, 659, 5, + 97, 0, 0, 659, 660, 5, 115, 0, 0, 660, 661, 5, 99, 0, 0, 661, 106, 1, 0, + 0, 0, 662, 663, 5, 97, 0, 0, 663, 664, 5, 115, 0, 0, 664, 665, 5, 99, 0, + 0, 665, 666, 5, 101, 0, 0, 666, 667, 5, 110, 0, 0, 667, 668, 5, 100, 0, + 0, 668, 669, 5, 105, 0, 0, 669, 670, 5, 110, 0, 0, 670, 671, 5, 103, 0, + 0, 671, 108, 1, 0, 0, 0, 672, 673, 5, 100, 0, 0, 673, 674, 5, 101, 0, 0, + 674, 675, 5, 115, 0, 0, 675, 676, 5, 99, 0, 0, 676, 110, 1, 0, 0, 0, 677, + 678, 5, 100, 0, 0, 678, 679, 5, 101, 0, 0, 679, 680, 5, 115, 0, 0, 680, + 681, 5, 99, 0, 0, 681, 682, 5, 101, 0, 0, 682, 683, 5, 110, 0, 0, 683, + 684, 5, 100, 0, 0, 684, 685, 5, 105, 0, 0, 685, 686, 5, 110, 0, 0, 686, + 687, 5, 103, 0, 0, 687, 112, 1, 0, 0, 0, 688, 689, 5, 105, 0, 0, 689, 690, + 5, 115, 0, 0, 690, 114, 1, 0, 0, 0, 691, 692, 5, 110, 0, 0, 692, 693, 5, + 111, 0, 0, 693, 694, 5, 116, 0, 0, 694, 116, 1, 0, 0, 0, 695, 696, 5, 110, + 0, 0, 696, 697, 5, 117, 0, 0, 697, 698, 5, 108, 0, 0, 698, 699, 5, 108, + 0, 0, 699, 118, 1, 0, 0, 0, 700, 701, 5, 116, 0, 0, 701, 702, 5, 114, 0, + 0, 702, 703, 5, 117, 0, 0, 703, 704, 5, 101, 0, 0, 704, 120, 1, 0, 0, 0, + 705, 706, 5, 102, 0, 0, 706, 707, 5, 97, 0, 0, 707, 708, 5, 108, 0, 0, + 708, 709, 5, 115, 0, 0, 709, 710, 5, 101, 0, 0, 710, 122, 1, 0, 0, 0, 711, + 712, 5, 97, 0, 0, 712, 713, 5, 115, 0, 0, 713, 124, 1, 0, 0, 0, 714, 715, + 5, 99, 0, 0, 715, 716, 5, 97, 0, 0, 716, 717, 5, 115, 0, 0, 717, 718, 5, + 116, 0, 0, 718, 126, 1, 0, 0, 0, 719, 720, 5, 101, 0, 0, 720, 721, 5, 120, + 0, 0, 721, 722, 5, 105, 0, 0, 722, 723, 5, 115, 0, 0, 723, 724, 5, 116, + 0, 0, 724, 725, 5, 115, 0, 0, 725, 128, 1, 0, 0, 0, 726, 727, 5, 112, 0, + 0, 727, 728, 5, 114, 0, 0, 728, 729, 5, 111, 0, 0, 729, 730, 5, 112, 0, + 0, 730, 731, 5, 101, 0, 0, 731, 732, 5, 114, 0, 0, 732, 733, 5, 108, 0, + 0, 733, 734, 5, 121, 0, 0, 734, 130, 1, 0, 0, 0, 735, 736, 5, 98, 0, 0, + 736, 737, 5, 101, 0, 0, 737, 738, 5, 116, 0, 0, 738, 739, 5, 119, 0, 0, + 739, 740, 5, 101, 0, 0, 740, 741, 5, 101, 0, 0, 741, 742, 5, 110, 0, 0, + 742, 132, 1, 0, 0, 0, 743, 744, 5, 97, 0, 0, 744, 745, 5, 110, 0, 0, 745, + 746, 5, 100, 0, 0, 746, 134, 1, 0, 0, 0, 747, 748, 5, 100, 0, 0, 748, 749, + 5, 117, 0, 0, 749, 750, 5, 114, 0, 0, 750, 751, 5, 97, 0, 0, 751, 752, + 5, 116, 0, 0, 752, 753, 5, 105, 0, 0, 753, 754, 5, 111, 0, 0, 754, 755, + 5, 110, 0, 0, 755, 136, 1, 0, 0, 0, 756, 757, 5, 100, 0, 0, 757, 758, 5, + 105, 0, 0, 758, 759, 5, 102, 0, 0, 759, 760, 5, 102, 0, 0, 760, 761, 5, + 101, 0, 0, 761, 762, 5, 114, 0, 0, 762, 763, 5, 101, 0, 0, 763, 764, 5, + 110, 0, 0, 764, 765, 5, 99, 0, 0, 765, 766, 5, 101, 0, 0, 766, 138, 1, + 0, 0, 0, 767, 768, 5, 60, 0, 0, 768, 769, 5, 61, 0, 0, 769, 140, 1, 0, + 0, 0, 770, 771, 5, 62, 0, 0, 771, 772, 5, 61, 0, 0, 772, 142, 1, 0, 0, + 0, 773, 774, 5, 33, 0, 0, 774, 775, 5, 61, 0, 0, 775, 144, 1, 0, 0, 0, + 776, 777, 5, 33, 0, 0, 777, 778, 5, 126, 0, 0, 778, 146, 1, 0, 0, 0, 779, + 780, 5, 99, 0, 0, 780, 781, 5, 111, 0, 0, 781, 782, 5, 110, 0, 0, 782, + 783, 5, 116, 0, 0, 783, 784, 5, 97, 0, 0, 784, 785, 5, 105, 0, 0, 785, + 786, 5, 110, 0, 0, 786, 787, 5, 115, 0, 0, 787, 148, 1, 0, 0, 0, 788, 789, + 5, 111, 0, 0, 789, 790, 5, 114, 0, 0, 790, 150, 1, 0, 0, 0, 791, 792, 5, + 120, 0, 0, 792, 793, 5, 111, 0, 0, 793, 794, 5, 114, 0, 0, 794, 152, 1, + 0, 0, 0, 795, 796, 5, 105, 0, 0, 796, 797, 5, 109, 0, 0, 797, 798, 5, 112, + 0, 0, 798, 799, 5, 108, 0, 0, 799, 800, 5, 105, 0, 0, 800, 801, 5, 101, + 0, 0, 801, 802, 5, 115, 0, 0, 802, 154, 1, 0, 0, 0, 803, 804, 5, 124, 0, + 0, 804, 156, 1, 0, 0, 0, 805, 806, 5, 117, 0, 0, 806, 807, 5, 110, 0, 0, + 807, 808, 5, 105, 0, 0, 808, 809, 5, 111, 0, 0, 809, 810, 5, 110, 0, 0, + 810, 158, 1, 0, 0, 0, 811, 812, 5, 105, 0, 0, 812, 813, 5, 110, 0, 0, 813, + 814, 5, 116, 0, 0, 814, 815, 5, 101, 0, 0, 815, 816, 5, 114, 0, 0, 816, + 817, 5, 115, 0, 0, 817, 818, 5, 101, 0, 0, 818, 819, 5, 99, 0, 0, 819, + 820, 5, 116, 0, 0, 820, 160, 1, 0, 0, 0, 821, 822, 5, 101, 0, 0, 822, 823, + 5, 120, 0, 0, 823, 824, 5, 99, 0, 0, 824, 825, 5, 101, 0, 0, 825, 826, + 5, 112, 0, 0, 826, 827, 5, 116, 0, 0, 827, 162, 1, 0, 0, 0, 828, 829, 5, + 121, 0, 0, 829, 830, 5, 101, 0, 0, 830, 831, 5, 97, 0, 0, 831, 832, 5, + 114, 0, 0, 832, 164, 1, 0, 0, 0, 833, 834, 5, 109, 0, 0, 834, 835, 5, 111, + 0, 0, 835, 836, 5, 110, 0, 0, 836, 837, 5, 116, 0, 0, 837, 838, 5, 104, + 0, 0, 838, 166, 1, 0, 0, 0, 839, 840, 5, 119, 0, 0, 840, 841, 5, 101, 0, + 0, 841, 842, 5, 101, 0, 0, 842, 843, 5, 107, 0, 0, 843, 168, 1, 0, 0, 0, + 844, 845, 5, 100, 0, 0, 845, 846, 5, 97, 0, 0, 846, 847, 5, 121, 0, 0, + 847, 170, 1, 0, 0, 0, 848, 849, 5, 104, 0, 0, 849, 850, 5, 111, 0, 0, 850, + 851, 5, 117, 0, 0, 851, 852, 5, 114, 0, 0, 852, 172, 1, 0, 0, 0, 853, 854, + 5, 109, 0, 0, 854, 855, 5, 105, 0, 0, 855, 856, 5, 110, 0, 0, 856, 857, + 5, 117, 0, 0, 857, 858, 5, 116, 0, 0, 858, 859, 5, 101, 0, 0, 859, 174, + 1, 0, 0, 0, 860, 861, 5, 115, 0, 0, 861, 862, 5, 101, 0, 0, 862, 863, 5, + 99, 0, 0, 863, 864, 5, 111, 0, 0, 864, 865, 5, 110, 0, 0, 865, 866, 5, + 100, 0, 0, 866, 176, 1, 0, 0, 0, 867, 868, 5, 109, 0, 0, 868, 869, 5, 105, + 0, 0, 869, 870, 5, 108, 0, 0, 870, 871, 5, 108, 0, 0, 871, 872, 5, 105, + 0, 0, 872, 873, 5, 115, 0, 0, 873, 874, 5, 101, 0, 0, 874, 875, 5, 99, + 0, 0, 875, 876, 5, 111, 0, 0, 876, 877, 5, 110, 0, 0, 877, 878, 5, 100, + 0, 0, 878, 178, 1, 0, 0, 0, 879, 880, 5, 100, 0, 0, 880, 881, 5, 97, 0, + 0, 881, 882, 5, 116, 0, 0, 882, 883, 5, 101, 0, 0, 883, 180, 1, 0, 0, 0, + 884, 885, 5, 116, 0, 0, 885, 886, 5, 105, 0, 0, 886, 887, 5, 109, 0, 0, + 887, 888, 5, 101, 0, 0, 888, 182, 1, 0, 0, 0, 889, 890, 5, 116, 0, 0, 890, + 891, 5, 105, 0, 0, 891, 892, 5, 109, 0, 0, 892, 893, 5, 101, 0, 0, 893, + 894, 5, 122, 0, 0, 894, 895, 5, 111, 0, 0, 895, 896, 5, 110, 0, 0, 896, + 897, 5, 101, 0, 0, 897, 184, 1, 0, 0, 0, 898, 899, 5, 116, 0, 0, 899, 900, + 5, 105, 0, 0, 900, 901, 5, 109, 0, 0, 901, 902, 5, 101, 0, 0, 902, 903, + 5, 122, 0, 0, 903, 904, 5, 111, 0, 0, 904, 905, 5, 110, 0, 0, 905, 906, + 5, 101, 0, 0, 906, 907, 5, 111, 0, 0, 907, 908, 5, 102, 0, 0, 908, 909, + 5, 102, 0, 0, 909, 910, 5, 115, 0, 0, 910, 911, 5, 101, 0, 0, 911, 912, + 5, 116, 0, 0, 912, 186, 1, 0, 0, 0, 913, 914, 5, 121, 0, 0, 914, 915, 5, + 101, 0, 0, 915, 916, 5, 97, 0, 0, 916, 917, 5, 114, 0, 0, 917, 918, 5, + 115, 0, 0, 918, 188, 1, 0, 0, 0, 919, 920, 5, 109, 0, 0, 920, 921, 5, 111, + 0, 0, 921, 922, 5, 110, 0, 0, 922, 923, 5, 116, 0, 0, 923, 924, 5, 104, + 0, 0, 924, 925, 5, 115, 0, 0, 925, 190, 1, 0, 0, 0, 926, 927, 5, 119, 0, + 0, 927, 928, 5, 101, 0, 0, 928, 929, 5, 101, 0, 0, 929, 930, 5, 107, 0, + 0, 930, 931, 5, 115, 0, 0, 931, 192, 1, 0, 0, 0, 932, 933, 5, 100, 0, 0, + 933, 934, 5, 97, 0, 0, 934, 935, 5, 121, 0, 0, 935, 936, 5, 115, 0, 0, + 936, 194, 1, 0, 0, 0, 937, 938, 5, 104, 0, 0, 938, 939, 5, 111, 0, 0, 939, + 940, 5, 117, 0, 0, 940, 941, 5, 114, 0, 0, 941, 942, 5, 115, 0, 0, 942, + 196, 1, 0, 0, 0, 943, 944, 5, 109, 0, 0, 944, 945, 5, 105, 0, 0, 945, 946, + 5, 110, 0, 0, 946, 947, 5, 117, 0, 0, 947, 948, 5, 116, 0, 0, 948, 949, + 5, 101, 0, 0, 949, 950, 5, 115, 0, 0, 950, 198, 1, 0, 0, 0, 951, 952, 5, + 115, 0, 0, 952, 953, 5, 101, 0, 0, 953, 954, 5, 99, 0, 0, 954, 955, 5, + 111, 0, 0, 955, 956, 5, 110, 0, 0, 956, 957, 5, 100, 0, 0, 957, 958, 5, + 115, 0, 0, 958, 200, 1, 0, 0, 0, 959, 960, 5, 109, 0, 0, 960, 961, 5, 105, + 0, 0, 961, 962, 5, 108, 0, 0, 962, 963, 5, 108, 0, 0, 963, 964, 5, 105, + 0, 0, 964, 965, 5, 115, 0, 0, 965, 966, 5, 101, 0, 0, 966, 967, 5, 99, + 0, 0, 967, 968, 5, 111, 0, 0, 968, 969, 5, 110, 0, 0, 969, 970, 5, 100, + 0, 0, 970, 971, 5, 115, 0, 0, 971, 202, 1, 0, 0, 0, 972, 973, 5, 99, 0, + 0, 973, 974, 5, 111, 0, 0, 974, 975, 5, 110, 0, 0, 975, 976, 5, 118, 0, + 0, 976, 977, 5, 101, 0, 0, 977, 978, 5, 114, 0, 0, 978, 979, 5, 116, 0, + 0, 979, 204, 1, 0, 0, 0, 980, 981, 5, 116, 0, 0, 981, 982, 5, 111, 0, 0, + 982, 206, 1, 0, 0, 0, 983, 984, 5, 43, 0, 0, 984, 208, 1, 0, 0, 0, 985, + 986, 5, 45, 0, 0, 986, 210, 1, 0, 0, 0, 987, 988, 5, 115, 0, 0, 988, 989, + 5, 116, 0, 0, 989, 990, 5, 97, 0, 0, 990, 991, 5, 114, 0, 0, 991, 992, + 5, 116, 0, 0, 992, 212, 1, 0, 0, 0, 993, 994, 5, 101, 0, 0, 994, 995, 5, + 110, 0, 0, 995, 996, 5, 100, 0, 0, 996, 214, 1, 0, 0, 0, 997, 998, 5, 111, + 0, 0, 998, 999, 5, 102, 0, 0, 999, 216, 1, 0, 0, 0, 1000, 1001, 5, 119, + 0, 0, 1001, 1002, 5, 105, 0, 0, 1002, 1003, 5, 100, 0, 0, 1003, 1004, 5, + 116, 0, 0, 1004, 1005, 5, 104, 0, 0, 1005, 218, 1, 0, 0, 0, 1006, 1007, + 5, 115, 0, 0, 1007, 1008, 5, 117, 0, 0, 1008, 1009, 5, 99, 0, 0, 1009, + 1010, 5, 99, 0, 0, 1010, 1011, 5, 101, 0, 0, 1011, 1012, 5, 115, 0, 0, + 1012, 1013, 5, 115, 0, 0, 1013, 1014, 5, 111, 0, 0, 1014, 1015, 5, 114, + 0, 0, 1015, 220, 1, 0, 0, 0, 1016, 1017, 5, 112, 0, 0, 1017, 1018, 5, 114, + 0, 0, 1018, 1019, 5, 101, 0, 0, 1019, 1020, 5, 100, 0, 0, 1020, 1021, 5, + 101, 0, 0, 1021, 1022, 5, 99, 0, 0, 1022, 1023, 5, 101, 0, 0, 1023, 1024, + 5, 115, 0, 0, 1024, 1025, 5, 115, 0, 0, 1025, 1026, 5, 111, 0, 0, 1026, + 1027, 5, 114, 0, 0, 1027, 222, 1, 0, 0, 0, 1028, 1029, 5, 115, 0, 0, 1029, + 1030, 5, 105, 0, 0, 1030, 1031, 5, 110, 0, 0, 1031, 1032, 5, 103, 0, 0, + 1032, 1033, 5, 108, 0, 0, 1033, 1034, 5, 101, 0, 0, 1034, 1035, 5, 116, + 0, 0, 1035, 1036, 5, 111, 0, 0, 1036, 1037, 5, 110, 0, 0, 1037, 224, 1, + 0, 0, 0, 1038, 1039, 5, 112, 0, 0, 1039, 1040, 5, 111, 0, 0, 1040, 1041, + 5, 105, 0, 0, 1041, 1042, 5, 110, 0, 0, 1042, 1043, 5, 116, 0, 0, 1043, + 226, 1, 0, 0, 0, 1044, 1045, 5, 109, 0, 0, 1045, 1046, 5, 105, 0, 0, 1046, + 1047, 5, 110, 0, 0, 1047, 1048, 5, 105, 0, 0, 1048, 1049, 5, 109, 0, 0, + 1049, 1050, 5, 117, 0, 0, 1050, 1051, 5, 109, 0, 0, 1051, 228, 1, 0, 0, + 0, 1052, 1053, 5, 109, 0, 0, 1053, 1054, 5, 97, 0, 0, 1054, 1055, 5, 120, + 0, 0, 1055, 1056, 5, 105, 0, 0, 1056, 1057, 5, 109, 0, 0, 1057, 1058, 5, + 117, 0, 0, 1058, 1059, 5, 109, 0, 0, 1059, 230, 1, 0, 0, 0, 1060, 1061, + 5, 94, 0, 0, 1061, 232, 1, 0, 0, 0, 1062, 1063, 5, 42, 0, 0, 1063, 234, + 1, 0, 0, 0, 1064, 1065, 5, 47, 0, 0, 1065, 236, 1, 0, 0, 0, 1066, 1067, + 5, 100, 0, 0, 1067, 1068, 5, 105, 0, 0, 1068, 1069, 5, 118, 0, 0, 1069, + 238, 1, 0, 0, 0, 1070, 1071, 5, 109, 0, 0, 1071, 1072, 5, 111, 0, 0, 1072, + 1073, 5, 100, 0, 0, 1073, 240, 1, 0, 0, 0, 1074, 1075, 5, 38, 0, 0, 1075, + 242, 1, 0, 0, 0, 1076, 1077, 5, 105, 0, 0, 1077, 1078, 5, 102, 0, 0, 1078, + 244, 1, 0, 0, 0, 1079, 1080, 5, 116, 0, 0, 1080, 1081, 5, 104, 0, 0, 1081, + 1082, 5, 101, 0, 0, 1082, 1083, 5, 110, 0, 0, 1083, 246, 1, 0, 0, 0, 1084, + 1085, 5, 101, 0, 0, 1085, 1086, 5, 108, 0, 0, 1086, 1087, 5, 115, 0, 0, + 1087, 1088, 5, 101, 0, 0, 1088, 248, 1, 0, 0, 0, 1089, 1090, 5, 99, 0, + 0, 1090, 1091, 5, 97, 0, 0, 1091, 1092, 5, 115, 0, 0, 1092, 1093, 5, 101, + 0, 0, 1093, 250, 1, 0, 0, 0, 1094, 1095, 5, 102, 0, 0, 1095, 1096, 5, 108, + 0, 0, 1096, 1097, 5, 97, 0, 0, 1097, 1098, 5, 116, 0, 0, 1098, 1099, 5, + 116, 0, 0, 1099, 1100, 5, 101, 0, 0, 1100, 1101, 5, 110, 0, 0, 1101, 252, + 1, 0, 0, 0, 1102, 1103, 5, 101, 0, 0, 1103, 1104, 5, 120, 0, 0, 1104, 1105, + 5, 112, 0, 0, 1105, 1106, 5, 97, 0, 0, 1106, 1107, 5, 110, 0, 0, 1107, + 1108, 5, 100, 0, 0, 1108, 254, 1, 0, 0, 0, 1109, 1110, 5, 99, 0, 0, 1110, + 1111, 5, 111, 0, 0, 1111, 1112, 5, 108, 0, 0, 1112, 1113, 5, 108, 0, 0, + 1113, 1114, 5, 97, 0, 0, 1114, 1115, 5, 112, 0, 0, 1115, 1116, 5, 115, + 0, 0, 1116, 1117, 5, 101, 0, 0, 1117, 256, 1, 0, 0, 0, 1118, 1119, 5, 112, + 0, 0, 1119, 1120, 5, 101, 0, 0, 1120, 1121, 5, 114, 0, 0, 1121, 258, 1, + 0, 0, 0, 1122, 1123, 5, 119, 0, 0, 1123, 1124, 5, 104, 0, 0, 1124, 1125, + 5, 101, 0, 0, 1125, 1126, 5, 110, 0, 0, 1126, 260, 1, 0, 0, 0, 1127, 1128, + 5, 111, 0, 0, 1128, 1129, 5, 114, 0, 0, 1129, 1130, 5, 32, 0, 0, 1130, + 1131, 5, 98, 0, 0, 1131, 1132, 5, 101, 0, 0, 1132, 1133, 5, 102, 0, 0, + 1133, 1134, 5, 111, 0, 0, 1134, 1135, 5, 114, 0, 0, 1135, 1136, 5, 101, + 0, 0, 1136, 262, 1, 0, 0, 0, 1137, 1138, 5, 111, 0, 0, 1138, 1139, 5, 114, + 0, 0, 1139, 1140, 5, 32, 0, 0, 1140, 1141, 5, 97, 0, 0, 1141, 1142, 5, + 102, 0, 0, 1142, 1143, 5, 116, 0, 0, 1143, 1144, 5, 101, 0, 0, 1144, 1145, + 5, 114, 0, 0, 1145, 264, 1, 0, 0, 0, 1146, 1147, 5, 111, 0, 0, 1147, 1148, + 5, 114, 0, 0, 1148, 1149, 5, 32, 0, 0, 1149, 1150, 5, 109, 0, 0, 1150, + 1151, 5, 111, 0, 0, 1151, 1152, 5, 114, 0, 0, 1152, 1153, 5, 101, 0, 0, + 1153, 266, 1, 0, 0, 0, 1154, 1155, 5, 111, 0, 0, 1155, 1156, 5, 114, 0, + 0, 1156, 1157, 5, 32, 0, 0, 1157, 1158, 5, 108, 0, 0, 1158, 1159, 5, 101, + 0, 0, 1159, 1160, 5, 115, 0, 0, 1160, 1161, 5, 115, 0, 0, 1161, 268, 1, + 0, 0, 0, 1162, 1163, 5, 108, 0, 0, 1163, 1164, 5, 101, 0, 0, 1164, 1165, + 5, 115, 0, 0, 1165, 1166, 5, 115, 0, 0, 1166, 1167, 5, 32, 0, 0, 1167, + 1168, 5, 116, 0, 0, 1168, 1169, 5, 104, 0, 0, 1169, 1170, 5, 97, 0, 0, + 1170, 1171, 5, 110, 0, 0, 1171, 270, 1, 0, 0, 0, 1172, 1173, 5, 109, 0, + 0, 1173, 1174, 5, 111, 0, 0, 1174, 1175, 5, 114, 0, 0, 1175, 1176, 5, 101, + 0, 0, 1176, 1177, 5, 32, 0, 0, 1177, 1178, 5, 116, 0, 0, 1178, 1179, 5, + 104, 0, 0, 1179, 1180, 5, 97, 0, 0, 1180, 1181, 5, 110, 0, 0, 1181, 272, + 1, 0, 0, 0, 1182, 1183, 5, 111, 0, 0, 1183, 1184, 5, 110, 0, 0, 1184, 1185, + 5, 32, 0, 0, 1185, 1186, 5, 111, 0, 0, 1186, 1187, 5, 114, 0, 0, 1187, + 274, 1, 0, 0, 0, 1188, 1189, 5, 98, 0, 0, 1189, 1190, 5, 101, 0, 0, 1190, + 1191, 5, 102, 0, 0, 1191, 1192, 5, 111, 0, 0, 1192, 1193, 5, 114, 0, 0, + 1193, 1194, 5, 101, 0, 0, 1194, 276, 1, 0, 0, 0, 1195, 1196, 5, 97, 0, + 0, 1196, 1197, 5, 102, 0, 0, 1197, 1198, 5, 116, 0, 0, 1198, 1199, 5, 101, + 0, 0, 1199, 1200, 5, 114, 0, 0, 1200, 278, 1, 0, 0, 0, 1201, 1202, 5, 111, + 0, 0, 1202, 1203, 5, 114, 0, 0, 1203, 1204, 5, 32, 0, 0, 1204, 1205, 5, + 111, 0, 0, 1205, 1206, 5, 110, 0, 0, 1206, 280, 1, 0, 0, 0, 1207, 1208, + 5, 115, 0, 0, 1208, 1209, 5, 116, 0, 0, 1209, 1210, 5, 97, 0, 0, 1210, + 1211, 5, 114, 0, 0, 1211, 1212, 5, 116, 0, 0, 1212, 1213, 5, 115, 0, 0, + 1213, 282, 1, 0, 0, 0, 1214, 1215, 5, 101, 0, 0, 1215, 1216, 5, 110, 0, + 0, 1216, 1217, 5, 100, 0, 0, 1217, 1218, 5, 115, 0, 0, 1218, 284, 1, 0, + 0, 0, 1219, 1220, 5, 111, 0, 0, 1220, 1221, 5, 99, 0, 0, 1221, 1222, 5, + 99, 0, 0, 1222, 1223, 5, 117, 0, 0, 1223, 1224, 5, 114, 0, 0, 1224, 1225, + 5, 115, 0, 0, 1225, 286, 1, 0, 0, 0, 1226, 1227, 5, 115, 0, 0, 1227, 1228, + 5, 97, 0, 0, 1228, 1229, 5, 109, 0, 0, 1229, 1230, 5, 101, 0, 0, 1230, + 288, 1, 0, 0, 0, 1231, 1232, 5, 105, 0, 0, 1232, 1233, 5, 110, 0, 0, 1233, + 1234, 5, 99, 0, 0, 1234, 1235, 5, 108, 0, 0, 1235, 1236, 5, 117, 0, 0, + 1236, 1237, 5, 100, 0, 0, 1237, 1238, 5, 101, 0, 0, 1238, 1239, 5, 115, + 0, 0, 1239, 290, 1, 0, 0, 0, 1240, 1241, 5, 100, 0, 0, 1241, 1242, 5, 117, + 0, 0, 1242, 1243, 5, 114, 0, 0, 1243, 1244, 5, 105, 0, 0, 1244, 1245, 5, + 110, 0, 0, 1245, 1246, 5, 103, 0, 0, 1246, 292, 1, 0, 0, 0, 1247, 1248, + 5, 105, 0, 0, 1248, 1249, 5, 110, 0, 0, 1249, 1250, 5, 99, 0, 0, 1250, + 1251, 5, 108, 0, 0, 1251, 1252, 5, 117, 0, 0, 1252, 1253, 5, 100, 0, 0, + 1253, 1254, 5, 101, 0, 0, 1254, 1255, 5, 100, 0, 0, 1255, 1256, 5, 32, + 0, 0, 1256, 1257, 5, 105, 0, 0, 1257, 1258, 5, 110, 0, 0, 1258, 294, 1, + 0, 0, 0, 1259, 1260, 5, 119, 0, 0, 1260, 1261, 5, 105, 0, 0, 1261, 1262, + 5, 116, 0, 0, 1262, 1263, 5, 104, 0, 0, 1263, 1264, 5, 105, 0, 0, 1264, + 1265, 5, 110, 0, 0, 1265, 296, 1, 0, 0, 0, 1266, 1267, 5, 109, 0, 0, 1267, + 1268, 5, 101, 0, 0, 1268, 1269, 5, 101, 0, 0, 1269, 1270, 5, 116, 0, 0, + 1270, 1271, 5, 115, 0, 0, 1271, 298, 1, 0, 0, 0, 1272, 1273, 5, 111, 0, + 0, 1273, 1274, 5, 118, 0, 0, 1274, 1275, 5, 101, 0, 0, 1275, 1276, 5, 114, + 0, 0, 1276, 1277, 5, 108, 0, 0, 1277, 1278, 5, 97, 0, 0, 1278, 1279, 5, + 112, 0, 0, 1279, 1280, 5, 115, 0, 0, 1280, 300, 1, 0, 0, 0, 1281, 1282, + 5, 36, 0, 0, 1282, 1283, 5, 116, 0, 0, 1283, 1284, 5, 104, 0, 0, 1284, + 1285, 5, 105, 0, 0, 1285, 1286, 5, 115, 0, 0, 1286, 302, 1, 0, 0, 0, 1287, + 1288, 5, 36, 0, 0, 1288, 1289, 5, 105, 0, 0, 1289, 1290, 5, 110, 0, 0, + 1290, 1291, 5, 100, 0, 0, 1291, 1292, 5, 101, 0, 0, 1292, 1293, 5, 120, + 0, 0, 1293, 304, 1, 0, 0, 0, 1294, 1295, 5, 36, 0, 0, 1295, 1296, 5, 116, + 0, 0, 1296, 1297, 5, 111, 0, 0, 1297, 1298, 5, 116, 0, 0, 1298, 1299, 5, + 97, 0, 0, 1299, 1300, 5, 108, 0, 0, 1300, 306, 1, 0, 0, 0, 1301, 1302, + 5, 100, 0, 0, 1302, 1303, 5, 105, 0, 0, 1303, 1304, 5, 115, 0, 0, 1304, + 1305, 5, 112, 0, 0, 1305, 1306, 5, 108, 0, 0, 1306, 1307, 5, 97, 0, 0, + 1307, 1308, 5, 121, 0, 0, 1308, 308, 1, 0, 0, 0, 1309, 1310, 5, 67, 0, + 0, 1310, 1311, 5, 111, 0, 0, 1311, 1312, 5, 100, 0, 0, 1312, 1313, 5, 101, + 0, 0, 1313, 310, 1, 0, 0, 0, 1314, 1315, 5, 67, 0, 0, 1315, 1316, 5, 111, + 0, 0, 1316, 1317, 5, 110, 0, 0, 1317, 1318, 5, 99, 0, 0, 1318, 1319, 5, + 101, 0, 0, 1319, 1320, 5, 112, 0, 0, 1320, 1321, 5, 116, 0, 0, 1321, 312, + 1, 0, 0, 0, 1322, 1323, 5, 37, 0, 0, 1323, 314, 1, 0, 0, 0, 1324, 1329, + 5, 34, 0, 0, 1325, 1328, 3, 321, 160, 0, 1326, 1328, 9, 0, 0, 0, 1327, + 1325, 1, 0, 0, 0, 1327, 1326, 1, 0, 0, 0, 1328, 1331, 1, 0, 0, 0, 1329, + 1330, 1, 0, 0, 0, 1329, 1327, 1, 0, 0, 0, 1330, 1332, 1, 0, 0, 0, 1331, + 1329, 1, 0, 0, 0, 1332, 1333, 5, 34, 0, 0, 1333, 316, 1, 0, 0, 0, 1334, + 1335, 5, 64, 0, 0, 1335, 1336, 3, 327, 163, 0, 1336, 1338, 5, 84, 0, 0, + 1337, 1339, 3, 329, 164, 0, 1338, 1337, 1, 0, 0, 0, 1338, 1339, 1, 0, 0, + 0, 1339, 1341, 1, 0, 0, 0, 1340, 1342, 3, 331, 165, 0, 1341, 1340, 1, 0, + 0, 0, 1341, 1342, 1, 0, 0, 0, 1342, 318, 1, 0, 0, 0, 1343, 1345, 7, 0, + 0, 0, 1344, 1343, 1, 0, 0, 0, 1345, 1346, 1, 0, 0, 0, 1346, 1344, 1, 0, + 0, 0, 1346, 1347, 1, 0, 0, 0, 1347, 1348, 1, 0, 0, 0, 1348, 1349, 5, 76, + 0, 0, 1349, 320, 1, 0, 0, 0, 1350, 1353, 5, 92, 0, 0, 1351, 1354, 7, 1, + 0, 0, 1352, 1354, 3, 347, 173, 0, 1353, 1351, 1, 0, 0, 0, 1353, 1352, 1, + 0, 0, 0, 1354, 322, 1, 0, 0, 0, 1355, 1356, 5, 64, 0, 0, 1356, 1357, 3, + 327, 163, 0, 1357, 324, 1, 0, 0, 0, 1358, 1359, 5, 64, 0, 0, 1359, 1360, + 5, 84, 0, 0, 1360, 1361, 3, 329, 164, 0, 1361, 326, 1, 0, 0, 0, 1362, 1363, + 7, 0, 0, 0, 1363, 1364, 7, 0, 0, 0, 1364, 1365, 7, 0, 0, 0, 1365, 1374, + 7, 0, 0, 0, 1366, 1367, 5, 45, 0, 0, 1367, 1368, 7, 0, 0, 0, 1368, 1372, + 7, 0, 0, 0, 1369, 1370, 5, 45, 0, 0, 1370, 1371, 7, 0, 0, 0, 1371, 1373, + 7, 0, 0, 0, 1372, 1369, 1, 0, 0, 0, 1372, 1373, 1, 0, 0, 0, 1373, 1375, + 1, 0, 0, 0, 1374, 1366, 1, 0, 0, 0, 1374, 1375, 1, 0, 0, 0, 1375, 328, + 1, 0, 0, 0, 1376, 1377, 7, 0, 0, 0, 1377, 1394, 7, 0, 0, 0, 1378, 1379, + 5, 58, 0, 0, 1379, 1380, 7, 0, 0, 0, 1380, 1392, 7, 0, 0, 0, 1381, 1382, + 5, 58, 0, 0, 1382, 1383, 7, 0, 0, 0, 1383, 1390, 7, 0, 0, 0, 1384, 1386, + 5, 46, 0, 0, 1385, 1387, 7, 0, 0, 0, 1386, 1385, 1, 0, 0, 0, 1387, 1388, + 1, 0, 0, 0, 1388, 1386, 1, 0, 0, 0, 1388, 1389, 1, 0, 0, 0, 1389, 1391, + 1, 0, 0, 0, 1390, 1384, 1, 0, 0, 0, 1390, 1391, 1, 0, 0, 0, 1391, 1393, + 1, 0, 0, 0, 1392, 1381, 1, 0, 0, 0, 1392, 1393, 1, 0, 0, 0, 1393, 1395, + 1, 0, 0, 0, 1394, 1378, 1, 0, 0, 0, 1394, 1395, 1, 0, 0, 0, 1395, 330, + 1, 0, 0, 0, 1396, 1404, 5, 90, 0, 0, 1397, 1398, 7, 2, 0, 0, 1398, 1399, + 7, 0, 0, 0, 1399, 1400, 7, 0, 0, 0, 1400, 1401, 5, 58, 0, 0, 1401, 1402, + 7, 0, 0, 0, 1402, 1404, 7, 0, 0, 0, 1403, 1396, 1, 0, 0, 0, 1403, 1397, + 1, 0, 0, 0, 1404, 332, 1, 0, 0, 0, 1405, 1407, 7, 3, 0, 0, 1406, 1405, + 1, 0, 0, 0, 1407, 1411, 1, 0, 0, 0, 1408, 1410, 7, 4, 0, 0, 1409, 1408, + 1, 0, 0, 0, 1410, 1413, 1, 0, 0, 0, 1411, 1409, 1, 0, 0, 0, 1411, 1412, + 1, 0, 0, 0, 1412, 334, 1, 0, 0, 0, 1413, 1411, 1, 0, 0, 0, 1414, 1419, + 5, 96, 0, 0, 1415, 1418, 3, 321, 160, 0, 1416, 1418, 9, 0, 0, 0, 1417, + 1415, 1, 0, 0, 0, 1417, 1416, 1, 0, 0, 0, 1418, 1421, 1, 0, 0, 0, 1419, + 1420, 1, 0, 0, 0, 1419, 1417, 1, 0, 0, 0, 1420, 1422, 1, 0, 0, 0, 1421, + 1419, 1, 0, 0, 0, 1422, 1423, 5, 96, 0, 0, 1423, 336, 1, 0, 0, 0, 1424, + 1429, 5, 39, 0, 0, 1425, 1428, 3, 321, 160, 0, 1426, 1428, 9, 0, 0, 0, + 1427, 1425, 1, 0, 0, 0, 1427, 1426, 1, 0, 0, 0, 1428, 1431, 1, 0, 0, 0, + 1429, 1430, 1, 0, 0, 0, 1429, 1427, 1, 0, 0, 0, 1430, 1432, 1, 0, 0, 0, + 1431, 1429, 1, 0, 0, 0, 1432, 1433, 5, 39, 0, 0, 1433, 338, 1, 0, 0, 0, + 1434, 1436, 7, 0, 0, 0, 1435, 1434, 1, 0, 0, 0, 1436, 1437, 1, 0, 0, 0, + 1437, 1435, 1, 0, 0, 0, 1437, 1438, 1, 0, 0, 0, 1438, 1445, 1, 0, 0, 0, + 1439, 1441, 5, 46, 0, 0, 1440, 1442, 7, 0, 0, 0, 1441, 1440, 1, 0, 0, 0, + 1442, 1443, 1, 0, 0, 0, 1443, 1441, 1, 0, 0, 0, 1443, 1444, 1, 0, 0, 0, + 1444, 1446, 1, 0, 0, 0, 1445, 1439, 1, 0, 0, 0, 1445, 1446, 1, 0, 0, 0, + 1446, 340, 1, 0, 0, 0, 1447, 1449, 7, 5, 0, 0, 1448, 1447, 1, 0, 0, 0, + 1449, 1450, 1, 0, 0, 0, 1450, 1448, 1, 0, 0, 0, 1450, 1451, 1, 0, 0, 0, + 1451, 1452, 1, 0, 0, 0, 1452, 1453, 6, 170, 0, 0, 1453, 342, 1, 0, 0, 0, + 1454, 1455, 5, 47, 0, 0, 1455, 1456, 5, 42, 0, 0, 1456, 1460, 1, 0, 0, + 0, 1457, 1459, 9, 0, 0, 0, 1458, 1457, 1, 0, 0, 0, 1459, 1462, 1, 0, 0, + 0, 1460, 1461, 1, 0, 0, 0, 1460, 1458, 1, 0, 0, 0, 1461, 1463, 1, 0, 0, + 0, 1462, 1460, 1, 0, 0, 0, 1463, 1464, 5, 42, 0, 0, 1464, 1465, 5, 47, + 0, 0, 1465, 1466, 1, 0, 0, 0, 1466, 1467, 6, 171, 0, 0, 1467, 344, 1, 0, + 0, 0, 1468, 1469, 5, 47, 0, 0, 1469, 1470, 5, 47, 0, 0, 1470, 1474, 1, + 0, 0, 0, 1471, 1473, 8, 6, 0, 0, 1472, 1471, 1, 0, 0, 0, 1473, 1476, 1, + 0, 0, 0, 1474, 1472, 1, 0, 0, 0, 1474, 1475, 1, 0, 0, 0, 1475, 1477, 1, + 0, 0, 0, 1476, 1474, 1, 0, 0, 0, 1477, 1478, 6, 172, 0, 0, 1478, 346, 1, + 0, 0, 0, 1479, 1480, 5, 117, 0, 0, 1480, 1481, 3, 349, 174, 0, 1481, 1482, + 3, 349, 174, 0, 1482, 1483, 3, 349, 174, 0, 1483, 1484, 3, 349, 174, 0, + 1484, 348, 1, 0, 0, 0, 1485, 1486, 7, 7, 0, 0, 1486, 350, 1, 0, 0, 0, 27, + 0, 1327, 1329, 1338, 1341, 1346, 1353, 1372, 1374, 1388, 1390, 1392, 1394, + 1403, 1406, 1409, 1411, 1417, 1419, 1427, 1429, 1437, 1443, 1445, 1450, + 1460, 1474, 1, 0, 1, 0, +} + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// CqlLexerInit initializes any static state used to implement CqlLexer. By default the +// static state used to implement the lexer is lazily initialized during the first call to +// NewCqlLexer(). You can call this function if you wish to initialize the static state ahead +// of time. +func CqlLexerInit() { + staticData := &CqlLexerLexerStaticData + staticData.once.Do(cqllexerLexerInit) +} + +// NewCqlLexer produces a new lexer instance for the optional input antlr.CharStream. +func NewCqlLexer(input antlr.CharStream) *CqlLexer { + CqlLexerInit() + l := new(CqlLexer) + l.BaseLexer = antlr.NewBaseLexer(input) + staticData := &CqlLexerLexerStaticData + l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + l.channelNames = staticData.ChannelNames + l.modeNames = staticData.ModeNames + l.RuleNames = staticData.RuleNames + l.LiteralNames = staticData.LiteralNames + l.SymbolicNames = staticData.SymbolicNames + l.GrammarFileName = "Cql.g4" + // TODO: l.EOF = antlr.TokenEOF + + return l +} + +// CqlLexer tokens. +const ( + CqlLexerT__0 = 1 + CqlLexerT__1 = 2 + CqlLexerT__2 = 3 + CqlLexerT__3 = 4 + CqlLexerT__4 = 5 + CqlLexerT__5 = 6 + CqlLexerT__6 = 7 + CqlLexerT__7 = 8 + CqlLexerT__8 = 9 + CqlLexerT__9 = 10 + CqlLexerT__10 = 11 + CqlLexerT__11 = 12 + CqlLexerT__12 = 13 + CqlLexerT__13 = 14 + CqlLexerT__14 = 15 + CqlLexerT__15 = 16 + CqlLexerT__16 = 17 + CqlLexerT__17 = 18 + CqlLexerT__18 = 19 + CqlLexerT__19 = 20 + CqlLexerT__20 = 21 + CqlLexerT__21 = 22 + CqlLexerT__22 = 23 + CqlLexerT__23 = 24 + CqlLexerT__24 = 25 + CqlLexerT__25 = 26 + CqlLexerT__26 = 27 + CqlLexerT__27 = 28 + CqlLexerT__28 = 29 + CqlLexerT__29 = 30 + CqlLexerT__30 = 31 + CqlLexerT__31 = 32 + CqlLexerT__32 = 33 + CqlLexerT__33 = 34 + CqlLexerT__34 = 35 + CqlLexerT__35 = 36 + CqlLexerT__36 = 37 + CqlLexerT__37 = 38 + CqlLexerT__38 = 39 + CqlLexerT__39 = 40 + CqlLexerT__40 = 41 + CqlLexerT__41 = 42 + CqlLexerT__42 = 43 + CqlLexerT__43 = 44 + CqlLexerT__44 = 45 + CqlLexerT__45 = 46 + CqlLexerT__46 = 47 + CqlLexerT__47 = 48 + CqlLexerT__48 = 49 + CqlLexerT__49 = 50 + CqlLexerT__50 = 51 + CqlLexerT__51 = 52 + CqlLexerT__52 = 53 + CqlLexerT__53 = 54 + CqlLexerT__54 = 55 + CqlLexerT__55 = 56 + CqlLexerT__56 = 57 + CqlLexerT__57 = 58 + CqlLexerT__58 = 59 + CqlLexerT__59 = 60 + CqlLexerT__60 = 61 + CqlLexerT__61 = 62 + CqlLexerT__62 = 63 + CqlLexerT__63 = 64 + CqlLexerT__64 = 65 + CqlLexerT__65 = 66 + CqlLexerT__66 = 67 + CqlLexerT__67 = 68 + CqlLexerT__68 = 69 + CqlLexerT__69 = 70 + CqlLexerT__70 = 71 + CqlLexerT__71 = 72 + CqlLexerT__72 = 73 + CqlLexerT__73 = 74 + CqlLexerT__74 = 75 + CqlLexerT__75 = 76 + CqlLexerT__76 = 77 + CqlLexerT__77 = 78 + CqlLexerT__78 = 79 + CqlLexerT__79 = 80 + CqlLexerT__80 = 81 + CqlLexerT__81 = 82 + CqlLexerT__82 = 83 + CqlLexerT__83 = 84 + CqlLexerT__84 = 85 + CqlLexerT__85 = 86 + CqlLexerT__86 = 87 + CqlLexerT__87 = 88 + CqlLexerT__88 = 89 + CqlLexerT__89 = 90 + CqlLexerT__90 = 91 + CqlLexerT__91 = 92 + CqlLexerT__92 = 93 + CqlLexerT__93 = 94 + CqlLexerT__94 = 95 + CqlLexerT__95 = 96 + CqlLexerT__96 = 97 + CqlLexerT__97 = 98 + CqlLexerT__98 = 99 + CqlLexerT__99 = 100 + CqlLexerT__100 = 101 + CqlLexerT__101 = 102 + CqlLexerT__102 = 103 + CqlLexerT__103 = 104 + CqlLexerT__104 = 105 + CqlLexerT__105 = 106 + CqlLexerT__106 = 107 + CqlLexerT__107 = 108 + CqlLexerT__108 = 109 + CqlLexerT__109 = 110 + CqlLexerT__110 = 111 + CqlLexerT__111 = 112 + CqlLexerT__112 = 113 + CqlLexerT__113 = 114 + CqlLexerT__114 = 115 + CqlLexerT__115 = 116 + CqlLexerT__116 = 117 + CqlLexerT__117 = 118 + CqlLexerT__118 = 119 + CqlLexerT__119 = 120 + CqlLexerT__120 = 121 + CqlLexerT__121 = 122 + CqlLexerT__122 = 123 + CqlLexerT__123 = 124 + CqlLexerT__124 = 125 + CqlLexerT__125 = 126 + CqlLexerT__126 = 127 + CqlLexerT__127 = 128 + CqlLexerT__128 = 129 + CqlLexerT__129 = 130 + CqlLexerT__130 = 131 + CqlLexerT__131 = 132 + CqlLexerT__132 = 133 + CqlLexerT__133 = 134 + CqlLexerT__134 = 135 + CqlLexerT__135 = 136 + CqlLexerT__136 = 137 + CqlLexerT__137 = 138 + CqlLexerT__138 = 139 + CqlLexerT__139 = 140 + CqlLexerT__140 = 141 + CqlLexerT__141 = 142 + CqlLexerT__142 = 143 + CqlLexerT__143 = 144 + CqlLexerT__144 = 145 + CqlLexerT__145 = 146 + CqlLexerT__146 = 147 + CqlLexerT__147 = 148 + CqlLexerT__148 = 149 + CqlLexerT__149 = 150 + CqlLexerT__150 = 151 + CqlLexerT__151 = 152 + CqlLexerT__152 = 153 + CqlLexerT__153 = 154 + CqlLexerT__154 = 155 + CqlLexerT__155 = 156 + CqlLexerT__156 = 157 + CqlLexerQUOTEDIDENTIFIER = 158 + CqlLexerDATETIME = 159 + CqlLexerLONGNUMBER = 160 + CqlLexerDATE = 161 + CqlLexerTIME = 162 + CqlLexerIDENTIFIER = 163 + CqlLexerDELIMITEDIDENTIFIER = 164 + CqlLexerSTRING = 165 + CqlLexerNUMBER = 166 + CqlLexerWS = 167 + CqlLexerCOMMENT = 168 + CqlLexerLINE_COMMENT = 169 +) + diff --git a/internal/embeddata/third_party/cqframework/cql/cql_parser.go b/internal/embeddata/third_party/cqframework/cql/cql_parser.go new file mode 100755 index 0000000..47bb83e --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cql/cql_parser.go @@ -0,0 +1,23921 @@ +// Code generated from Cql.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cql // Cql +import ( + "fmt" + "strconv" + "sync" + + "github.com/antlr4-go/antlr/v4" +) + +// Suppress unused import errors +var _ = fmt.Printf +var _ = strconv.Itoa +var _ = sync.Once{} + + +type CqlParser struct { + *antlr.BaseParser +} + +var CqlParserStaticData struct { + once sync.Once + serializedATN []int32 + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func cqlParserInit() { + staticData := &CqlParserStaticData + staticData.LiteralNames = []string{ + "", "'library'", "'version'", "'using'", "'called'", "'include'", "'public'", + "'private'", "'parameter'", "'default'", "'codesystem'", "':'", "'valueset'", + "'codesystems'", "'{'", "','", "'}'", "'.'", "'code'", "'from'", "'concept'", + "'List'", "'<'", "'>'", "'Interval'", "'Tuple'", "'Choice'", "'define'", + "'context'", "'fluent'", "'function'", "'('", "')'", "'returns'", "'external'", + "'with'", "'such that'", "'without'", "'['", "'->'", "']'", "'in'", + "'='", "'~'", "'let'", "'where'", "'return'", "'all'", "'distinct'", + "'aggregate'", "'starting'", "'sort'", "'by'", "'asc'", "'ascending'", + "'desc'", "'descending'", "'is'", "'not'", "'null'", "'true'", "'false'", + "'as'", "'cast'", "'exists'", "'properly'", "'between'", "'and'", "'duration'", + "'difference'", "'<='", "'>='", "'!='", "'!~'", "'contains'", "'or'", + "'xor'", "'implies'", "'|'", "'union'", "'intersect'", "'except'", "'year'", + "'month'", "'week'", "'day'", "'hour'", "'minute'", "'second'", "'millisecond'", + "'date'", "'time'", "'timezone'", "'timezoneoffset'", "'years'", "'months'", + "'weeks'", "'days'", "'hours'", "'minutes'", "'seconds'", "'milliseconds'", + "'convert'", "'to'", "'+'", "'-'", "'start'", "'end'", "'of'", "'width'", + "'successor'", "'predecessor'", "'singleton'", "'point'", "'minimum'", + "'maximum'", "'^'", "'*'", "'/'", "'div'", "'mod'", "'&'", "'if'", "'then'", + "'else'", "'case'", "'flatten'", "'expand'", "'collapse'", "'per'", + "'when'", "'or before'", "'or after'", "'or more'", "'or less'", "'less than'", + "'more than'", "'on or'", "'before'", "'after'", "'or on'", "'starts'", + "'ends'", "'occurs'", "'same'", "'includes'", "'during'", "'included in'", + "'within'", "'meets'", "'overlaps'", "'$this'", "'$index'", "'$total'", + "'display'", "'Code'", "'Concept'", "'%'", + } + staticData.SymbolicNames = []string{ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "QUOTEDIDENTIFIER", "DATETIME", "LONGNUMBER", "DATE", + "TIME", "IDENTIFIER", "DELIMITEDIDENTIFIER", "STRING", "NUMBER", "WS", + "COMMENT", "LINE_COMMENT", + } + staticData.RuleNames = []string{ + "definition", "library", "libraryDefinition", "usingDefinition", "includeDefinition", + "localIdentifier", "accessModifier", "parameterDefinition", "codesystemDefinition", + "valuesetDefinition", "codesystems", "codesystemIdentifier", "libraryIdentifier", + "codeDefinition", "conceptDefinition", "codeIdentifier", "codesystemId", + "valuesetId", "versionSpecifier", "codeId", "typeSpecifier", "namedTypeSpecifier", + "modelIdentifier", "listTypeSpecifier", "intervalTypeSpecifier", "tupleTypeSpecifier", + "tupleElementDefinition", "choiceTypeSpecifier", "statement", "expressionDefinition", + "contextDefinition", "fluentModifier", "functionDefinition", "operandDefinition", + "functionBody", "querySource", "aliasedQuerySource", "alias", "queryInclusionClause", + "withClause", "withoutClause", "retrieve", "contextIdentifier", "codePath", + "codeComparator", "terminology", "qualifier", "query", "sourceClause", + "letClause", "letClauseItem", "whereClause", "returnClause", "aggregateClause", + "startingClause", "sortClause", "sortDirection", "sortByItem", "qualifiedIdentifier", + "qualifiedIdentifierExpression", "qualifierExpression", "simplePath", + "simpleLiteral", "expression", "dateTimePrecision", "dateTimeComponent", + "pluralDateTimePrecision", "expressionTerm", "caseExpressionItem", "dateTimePrecisionSpecifier", + "relativeQualifier", "offsetRelativeQualifier", "exclusiveRelativeQualifier", + "quantityOffset", "temporalRelationship", "intervalOperatorPhrase", + "term", "qualifiedInvocation", "qualifiedFunction", "invocation", "function", + "ratio", "literal", "intervalSelector", "tupleSelector", "tupleElementSelector", + "instanceSelector", "instanceElementSelector", "listSelector", "displayClause", + "codeSelector", "conceptSelector", "keyword", "reservedWord", "keywordIdentifier", + "obsoleteIdentifier", "functionIdentifier", "typeNameIdentifier", "referentialIdentifier", + "referentialOrTypeNameIdentifier", "identifierOrFunctionIdentifier", + "identifier", "externalConstant", "paramList", "quantity", "unit", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 1, 169, 1188, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, + 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, + 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, + 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, + 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, + 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, + 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, + 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, + 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, + 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, + 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, + 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, + 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, + 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, + 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, + 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, + 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, + 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, + 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, + 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, + 104, 7, 104, 2, 105, 7, 105, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 3, 0, 220, 8, 0, 1, 1, 3, 1, 223, 8, 1, 1, 1, 5, 1, 226, 8, 1, 10, 1, 12, + 1, 229, 9, 1, 1, 1, 5, 1, 232, 8, 1, 10, 1, 12, 1, 235, 9, 1, 1, 1, 1, + 1, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 243, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 3, + 3, 249, 8, 3, 1, 3, 1, 3, 3, 3, 253, 8, 3, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, + 259, 8, 4, 1, 4, 1, 4, 3, 4, 263, 8, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 3, + 7, 270, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 275, 8, 7, 1, 7, 1, 7, 3, 7, 279, + 8, 7, 1, 8, 3, 8, 282, 8, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, + 290, 8, 8, 1, 9, 3, 9, 293, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, + 9, 301, 8, 9, 1, 9, 3, 9, 304, 8, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, + 5, 10, 311, 8, 10, 10, 10, 12, 10, 314, 9, 10, 1, 10, 1, 10, 1, 11, 1, + 11, 1, 11, 3, 11, 321, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 3, 13, + 328, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 3, 13, 337, + 8, 13, 1, 14, 3, 14, 340, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, + 14, 1, 14, 5, 14, 349, 8, 14, 10, 14, 12, 14, 352, 9, 14, 1, 14, 1, 14, + 3, 14, 356, 8, 14, 1, 15, 1, 15, 1, 15, 3, 15, 361, 8, 15, 1, 15, 1, 15, + 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, + 20, 1, 20, 1, 20, 3, 20, 378, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 383, 8, + 21, 10, 21, 12, 21, 386, 9, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, + 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, + 25, 1, 25, 1, 25, 5, 25, 407, 8, 25, 10, 25, 12, 25, 410, 9, 25, 1, 25, + 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 5, 27, 422, + 8, 27, 10, 27, 12, 27, 425, 9, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 3, + 28, 432, 8, 28, 1, 29, 1, 29, 3, 29, 436, 8, 29, 1, 29, 1, 29, 1, 29, 1, + 29, 1, 30, 1, 30, 1, 30, 1, 30, 3, 30, 446, 8, 30, 1, 30, 1, 30, 1, 31, + 1, 31, 1, 32, 1, 32, 3, 32, 454, 8, 32, 1, 32, 3, 32, 457, 8, 32, 1, 32, + 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 465, 8, 32, 10, 32, 12, 32, 468, + 9, 32, 3, 32, 470, 8, 32, 1, 32, 1, 32, 1, 32, 3, 32, 475, 8, 32, 1, 32, + 1, 32, 1, 32, 3, 32, 480, 8, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, + 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 3, 35, 493, 8, 35, 1, 36, 1, 36, + 1, 36, 1, 37, 1, 37, 1, 38, 1, 38, 3, 38, 502, 8, 38, 1, 39, 1, 39, 1, + 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, + 1, 41, 3, 41, 518, 8, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 525, + 8, 41, 1, 41, 3, 41, 528, 8, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, + 43, 1, 44, 1, 44, 1, 45, 1, 45, 3, 45, 540, 8, 45, 1, 46, 1, 46, 1, 47, + 1, 47, 3, 47, 546, 8, 47, 1, 47, 5, 47, 549, 8, 47, 10, 47, 12, 47, 552, + 9, 47, 1, 47, 3, 47, 555, 8, 47, 1, 47, 1, 47, 3, 47, 559, 8, 47, 1, 47, + 3, 47, 562, 8, 47, 1, 48, 3, 48, 565, 8, 48, 1, 48, 1, 48, 1, 48, 5, 48, + 570, 8, 48, 10, 48, 12, 48, 573, 9, 48, 1, 49, 1, 49, 1, 49, 1, 49, 5, + 49, 579, 8, 49, 10, 49, 12, 49, 582, 9, 49, 1, 50, 1, 50, 1, 50, 1, 50, + 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 3, 52, 593, 8, 52, 1, 52, 1, 52, 1, + 53, 1, 53, 3, 53, 599, 8, 53, 1, 53, 1, 53, 3, 53, 603, 8, 53, 1, 53, 1, + 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 3, 54, 615, + 8, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 623, 8, 55, 10, + 55, 12, 55, 626, 9, 55, 3, 55, 628, 8, 55, 1, 56, 1, 56, 1, 57, 1, 57, + 3, 57, 634, 8, 57, 1, 58, 1, 58, 1, 58, 5, 58, 639, 8, 58, 10, 58, 12, + 58, 642, 9, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 5, 59, 649, 8, 59, 10, + 59, 12, 59, 652, 9, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, + 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 669, 8, + 61, 10, 61, 12, 61, 672, 9, 61, 1, 62, 1, 62, 3, 62, 676, 8, 62, 1, 63, + 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, + 63, 1, 63, 1, 63, 1, 63, 3, 63, 693, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, + 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, + 63, 709, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, + 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 724, 8, 63, 1, 63, 1, 63, 1, + 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, + 1, 63, 1, 63, 1, 63, 3, 63, 742, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, + 63, 1, 63, 3, 63, 750, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 5, 63, + 757, 8, 63, 10, 63, 12, 63, 760, 9, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, + 65, 1, 65, 1, 65, 3, 65, 769, 8, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, + 1, 67, 1, 67, 1, 67, 1, 67, 3, 67, 780, 8, 67, 1, 67, 1, 67, 1, 67, 1, + 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, + 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, + 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, + 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, + 67, 1, 67, 3, 67, 829, 8, 67, 1, 67, 4, 67, 832, 8, 67, 11, 67, 12, 67, + 833, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, + 67, 1, 67, 3, 67, 847, 8, 67, 3, 67, 849, 8, 67, 3, 67, 851, 8, 67, 1, + 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, + 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 870, 8, 67, 10, 67, 12, + 67, 873, 9, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, + 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 891, 8, + 73, 1, 73, 1, 73, 1, 73, 3, 73, 896, 8, 73, 1, 74, 3, 74, 899, 8, 74, 1, + 74, 1, 74, 1, 74, 3, 74, 904, 8, 74, 3, 74, 906, 8, 74, 1, 75, 3, 75, 909, + 8, 75, 1, 75, 1, 75, 3, 75, 913, 8, 75, 1, 75, 1, 75, 3, 75, 917, 8, 75, + 1, 75, 3, 75, 920, 8, 75, 1, 75, 3, 75, 923, 8, 75, 1, 75, 1, 75, 3, 75, + 927, 8, 75, 1, 75, 3, 75, 930, 8, 75, 1, 75, 3, 75, 933, 8, 75, 1, 75, + 3, 75, 936, 8, 75, 1, 75, 1, 75, 3, 75, 940, 8, 75, 1, 75, 3, 75, 943, + 8, 75, 1, 75, 3, 75, 946, 8, 75, 1, 75, 1, 75, 3, 75, 950, 8, 75, 1, 75, + 3, 75, 953, 8, 75, 1, 75, 3, 75, 956, 8, 75, 1, 75, 3, 75, 959, 8, 75, + 1, 75, 1, 75, 1, 75, 1, 75, 3, 75, 965, 8, 75, 1, 75, 1, 75, 3, 75, 969, + 8, 75, 1, 75, 3, 75, 972, 8, 75, 1, 75, 1, 75, 3, 75, 976, 8, 75, 1, 75, + 3, 75, 979, 8, 75, 1, 75, 1, 75, 3, 75, 983, 8, 75, 1, 75, 1, 75, 3, 75, + 987, 8, 75, 3, 75, 989, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, + 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 3, 76, 1004, 8, 76, 1, + 77, 1, 77, 3, 77, 1008, 8, 77, 1, 78, 1, 78, 1, 78, 3, 78, 1013, 8, 78, + 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 3, 79, 1022, 8, 79, 1, + 80, 1, 80, 1, 80, 3, 80, 1027, 8, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, + 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, + 82, 3, 82, 1045, 8, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, + 1, 84, 3, 84, 1055, 8, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 5, 84, 1062, + 8, 84, 10, 84, 12, 84, 1065, 9, 84, 3, 84, 1067, 8, 84, 1, 84, 1, 84, 1, + 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 5, 86, + 1081, 8, 86, 10, 86, 12, 86, 1084, 9, 86, 3, 86, 1086, 8, 86, 1, 86, 1, + 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 3, 88, + 1099, 8, 88, 3, 88, 1101, 8, 88, 1, 88, 1, 88, 1, 88, 1, 88, 5, 88, 1107, + 8, 88, 10, 88, 12, 88, 1110, 9, 88, 3, 88, 1112, 8, 88, 1, 88, 1, 88, 1, + 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 3, 90, 1124, 8, 90, + 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 5, 91, 1131, 8, 91, 10, 91, 12, 91, + 1134, 9, 91, 1, 91, 1, 91, 3, 91, 1138, 8, 91, 1, 92, 1, 92, 1, 93, 1, + 93, 1, 94, 1, 94, 1, 95, 1, 95, 1, 96, 1, 96, 1, 97, 1, 97, 1, 98, 1, 98, + 3, 98, 1154, 8, 98, 1, 99, 1, 99, 3, 99, 1158, 8, 99, 1, 100, 1, 100, 3, + 100, 1162, 8, 100, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 3, 102, 1169, + 8, 102, 1, 103, 1, 103, 1, 103, 5, 103, 1174, 8, 103, 10, 103, 12, 103, + 1177, 9, 103, 1, 104, 1, 104, 3, 104, 1181, 8, 104, 1, 105, 1, 105, 1, + 105, 3, 105, 1186, 8, 105, 1, 105, 0, 3, 122, 126, 134, 106, 0, 2, 4, 6, + 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, + 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, + 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, + 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, + 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, + 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, + 204, 206, 208, 210, 0, 36, 1, 0, 6, 7, 1, 0, 41, 43, 1, 0, 47, 48, 1, 0, + 53, 56, 2, 0, 22, 23, 70, 71, 2, 0, 42, 43, 72, 73, 2, 0, 41, 41, 74, 74, + 1, 0, 75, 76, 1, 0, 78, 81, 1, 0, 59, 61, 2, 0, 57, 57, 62, 62, 1, 0, 82, + 89, 1, 0, 94, 101, 1, 0, 104, 105, 1, 0, 106, 107, 1, 0, 114, 115, 2, 0, + 48, 48, 126, 126, 1, 0, 127, 128, 1, 0, 117, 120, 2, 0, 104, 105, 121, + 121, 1, 0, 131, 132, 1, 0, 133, 134, 1, 0, 135, 136, 1, 0, 138, 139, 1, + 0, 141, 143, 1, 0, 146, 147, 1, 0, 60, 61, 2, 0, 31, 31, 38, 38, 2, 0, + 32, 32, 40, 40, 15, 0, 1, 10, 12, 13, 18, 21, 24, 25, 27, 30, 35, 37, 41, + 41, 44, 69, 74, 77, 79, 103, 106, 115, 119, 120, 122, 134, 137, 150, 154, + 156, 19, 0, 19, 19, 21, 21, 24, 25, 35, 37, 41, 41, 44, 44, 46, 49, 51, + 51, 57, 69, 75, 75, 82, 89, 94, 103, 108, 108, 112, 115, 122, 130, 137, + 140, 143, 144, 146, 148, 155, 156, 20, 0, 1, 10, 12, 13, 18, 18, 20, 20, + 27, 30, 45, 45, 50, 50, 52, 56, 74, 74, 76, 77, 79, 81, 90, 93, 106, 107, + 109, 111, 119, 120, 131, 134, 141, 142, 145, 145, 149, 150, 154, 154, 11, + 0, 2, 2, 18, 18, 20, 20, 45, 45, 47, 48, 58, 58, 64, 64, 74, 74, 90, 93, + 106, 107, 154, 156, 16, 0, 1, 10, 12, 13, 18, 21, 24, 25, 27, 30, 35, 37, + 41, 41, 44, 69, 74, 77, 79, 103, 106, 115, 119, 120, 122, 134, 138, 139, + 141, 150, 154, 156, 2, 0, 90, 91, 155, 156, 2, 0, 158, 158, 163, 164, 1290, + 0, 219, 1, 0, 0, 0, 2, 222, 1, 0, 0, 0, 4, 238, 1, 0, 0, 0, 6, 244, 1, + 0, 0, 0, 8, 254, 1, 0, 0, 0, 10, 264, 1, 0, 0, 0, 12, 266, 1, 0, 0, 0, + 14, 269, 1, 0, 0, 0, 16, 281, 1, 0, 0, 0, 18, 292, 1, 0, 0, 0, 20, 305, + 1, 0, 0, 0, 22, 320, 1, 0, 0, 0, 24, 324, 1, 0, 0, 0, 26, 327, 1, 0, 0, + 0, 28, 339, 1, 0, 0, 0, 30, 360, 1, 0, 0, 0, 32, 364, 1, 0, 0, 0, 34, 366, + 1, 0, 0, 0, 36, 368, 1, 0, 0, 0, 38, 370, 1, 0, 0, 0, 40, 377, 1, 0, 0, + 0, 42, 384, 1, 0, 0, 0, 44, 389, 1, 0, 0, 0, 46, 391, 1, 0, 0, 0, 48, 396, + 1, 0, 0, 0, 50, 401, 1, 0, 0, 0, 52, 413, 1, 0, 0, 0, 54, 416, 1, 0, 0, + 0, 56, 431, 1, 0, 0, 0, 58, 433, 1, 0, 0, 0, 60, 441, 1, 0, 0, 0, 62, 449, + 1, 0, 0, 0, 64, 451, 1, 0, 0, 0, 66, 481, 1, 0, 0, 0, 68, 484, 1, 0, 0, + 0, 70, 492, 1, 0, 0, 0, 72, 494, 1, 0, 0, 0, 74, 497, 1, 0, 0, 0, 76, 501, + 1, 0, 0, 0, 78, 503, 1, 0, 0, 0, 80, 508, 1, 0, 0, 0, 82, 513, 1, 0, 0, + 0, 84, 531, 1, 0, 0, 0, 86, 533, 1, 0, 0, 0, 88, 535, 1, 0, 0, 0, 90, 539, + 1, 0, 0, 0, 92, 541, 1, 0, 0, 0, 94, 543, 1, 0, 0, 0, 96, 564, 1, 0, 0, + 0, 98, 574, 1, 0, 0, 0, 100, 583, 1, 0, 0, 0, 102, 587, 1, 0, 0, 0, 104, + 590, 1, 0, 0, 0, 106, 596, 1, 0, 0, 0, 108, 607, 1, 0, 0, 0, 110, 616, + 1, 0, 0, 0, 112, 629, 1, 0, 0, 0, 114, 631, 1, 0, 0, 0, 116, 640, 1, 0, + 0, 0, 118, 650, 1, 0, 0, 0, 120, 655, 1, 0, 0, 0, 122, 657, 1, 0, 0, 0, + 124, 675, 1, 0, 0, 0, 126, 708, 1, 0, 0, 0, 128, 761, 1, 0, 0, 0, 130, + 768, 1, 0, 0, 0, 132, 770, 1, 0, 0, 0, 134, 850, 1, 0, 0, 0, 136, 874, + 1, 0, 0, 0, 138, 879, 1, 0, 0, 0, 140, 882, 1, 0, 0, 0, 142, 884, 1, 0, + 0, 0, 144, 886, 1, 0, 0, 0, 146, 895, 1, 0, 0, 0, 148, 905, 1, 0, 0, 0, + 150, 988, 1, 0, 0, 0, 152, 1003, 1, 0, 0, 0, 154, 1007, 1, 0, 0, 0, 156, + 1009, 1, 0, 0, 0, 158, 1021, 1, 0, 0, 0, 160, 1023, 1, 0, 0, 0, 162, 1030, + 1, 0, 0, 0, 164, 1044, 1, 0, 0, 0, 166, 1046, 1, 0, 0, 0, 168, 1054, 1, + 0, 0, 0, 170, 1070, 1, 0, 0, 0, 172, 1074, 1, 0, 0, 0, 174, 1089, 1, 0, + 0, 0, 176, 1100, 1, 0, 0, 0, 178, 1115, 1, 0, 0, 0, 180, 1118, 1, 0, 0, + 0, 182, 1125, 1, 0, 0, 0, 184, 1139, 1, 0, 0, 0, 186, 1141, 1, 0, 0, 0, + 188, 1143, 1, 0, 0, 0, 190, 1145, 1, 0, 0, 0, 192, 1147, 1, 0, 0, 0, 194, + 1149, 1, 0, 0, 0, 196, 1153, 1, 0, 0, 0, 198, 1157, 1, 0, 0, 0, 200, 1161, + 1, 0, 0, 0, 202, 1163, 1, 0, 0, 0, 204, 1165, 1, 0, 0, 0, 206, 1170, 1, + 0, 0, 0, 208, 1178, 1, 0, 0, 0, 210, 1185, 1, 0, 0, 0, 212, 220, 3, 6, + 3, 0, 213, 220, 3, 8, 4, 0, 214, 220, 3, 16, 8, 0, 215, 220, 3, 18, 9, + 0, 216, 220, 3, 26, 13, 0, 217, 220, 3, 28, 14, 0, 218, 220, 3, 14, 7, + 0, 219, 212, 1, 0, 0, 0, 219, 213, 1, 0, 0, 0, 219, 214, 1, 0, 0, 0, 219, + 215, 1, 0, 0, 0, 219, 216, 1, 0, 0, 0, 219, 217, 1, 0, 0, 0, 219, 218, + 1, 0, 0, 0, 220, 1, 1, 0, 0, 0, 221, 223, 3, 4, 2, 0, 222, 221, 1, 0, 0, + 0, 222, 223, 1, 0, 0, 0, 223, 227, 1, 0, 0, 0, 224, 226, 3, 0, 0, 0, 225, + 224, 1, 0, 0, 0, 226, 229, 1, 0, 0, 0, 227, 225, 1, 0, 0, 0, 227, 228, + 1, 0, 0, 0, 228, 233, 1, 0, 0, 0, 229, 227, 1, 0, 0, 0, 230, 232, 3, 56, + 28, 0, 231, 230, 1, 0, 0, 0, 232, 235, 1, 0, 0, 0, 233, 231, 1, 0, 0, 0, + 233, 234, 1, 0, 0, 0, 234, 236, 1, 0, 0, 0, 235, 233, 1, 0, 0, 0, 236, + 237, 5, 0, 0, 1, 237, 3, 1, 0, 0, 0, 238, 239, 5, 1, 0, 0, 239, 242, 3, + 116, 58, 0, 240, 241, 5, 2, 0, 0, 241, 243, 3, 36, 18, 0, 242, 240, 1, + 0, 0, 0, 242, 243, 1, 0, 0, 0, 243, 5, 1, 0, 0, 0, 244, 245, 5, 3, 0, 0, + 245, 248, 3, 116, 58, 0, 246, 247, 5, 2, 0, 0, 247, 249, 3, 36, 18, 0, + 248, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 252, 1, 0, 0, 0, 250, + 251, 5, 4, 0, 0, 251, 253, 3, 10, 5, 0, 252, 250, 1, 0, 0, 0, 252, 253, + 1, 0, 0, 0, 253, 7, 1, 0, 0, 0, 254, 255, 5, 5, 0, 0, 255, 258, 3, 116, + 58, 0, 256, 257, 5, 2, 0, 0, 257, 259, 3, 36, 18, 0, 258, 256, 1, 0, 0, + 0, 258, 259, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 261, 5, 4, 0, 0, 261, + 263, 3, 10, 5, 0, 262, 260, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 9, 1, + 0, 0, 0, 264, 265, 3, 202, 101, 0, 265, 11, 1, 0, 0, 0, 266, 267, 7, 0, + 0, 0, 267, 13, 1, 0, 0, 0, 268, 270, 3, 12, 6, 0, 269, 268, 1, 0, 0, 0, + 269, 270, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 272, 5, 8, 0, 0, 272, + 274, 3, 202, 101, 0, 273, 275, 3, 40, 20, 0, 274, 273, 1, 0, 0, 0, 274, + 275, 1, 0, 0, 0, 275, 278, 1, 0, 0, 0, 276, 277, 5, 9, 0, 0, 277, 279, + 3, 126, 63, 0, 278, 276, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 15, 1, + 0, 0, 0, 280, 282, 3, 12, 6, 0, 281, 280, 1, 0, 0, 0, 281, 282, 1, 0, 0, + 0, 282, 283, 1, 0, 0, 0, 283, 284, 5, 10, 0, 0, 284, 285, 3, 202, 101, + 0, 285, 286, 5, 11, 0, 0, 286, 289, 3, 32, 16, 0, 287, 288, 5, 2, 0, 0, + 288, 290, 3, 36, 18, 0, 289, 287, 1, 0, 0, 0, 289, 290, 1, 0, 0, 0, 290, + 17, 1, 0, 0, 0, 291, 293, 3, 12, 6, 0, 292, 291, 1, 0, 0, 0, 292, 293, + 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 5, 12, 0, 0, 295, 296, 3, 202, + 101, 0, 296, 297, 5, 11, 0, 0, 297, 300, 3, 34, 17, 0, 298, 299, 5, 2, + 0, 0, 299, 301, 3, 36, 18, 0, 300, 298, 1, 0, 0, 0, 300, 301, 1, 0, 0, + 0, 301, 303, 1, 0, 0, 0, 302, 304, 3, 20, 10, 0, 303, 302, 1, 0, 0, 0, + 303, 304, 1, 0, 0, 0, 304, 19, 1, 0, 0, 0, 305, 306, 5, 13, 0, 0, 306, + 307, 5, 14, 0, 0, 307, 312, 3, 22, 11, 0, 308, 309, 5, 15, 0, 0, 309, 311, + 3, 22, 11, 0, 310, 308, 1, 0, 0, 0, 311, 314, 1, 0, 0, 0, 312, 310, 1, + 0, 0, 0, 312, 313, 1, 0, 0, 0, 313, 315, 1, 0, 0, 0, 314, 312, 1, 0, 0, + 0, 315, 316, 5, 16, 0, 0, 316, 21, 1, 0, 0, 0, 317, 318, 3, 24, 12, 0, + 318, 319, 5, 17, 0, 0, 319, 321, 1, 0, 0, 0, 320, 317, 1, 0, 0, 0, 320, + 321, 1, 0, 0, 0, 321, 322, 1, 0, 0, 0, 322, 323, 3, 202, 101, 0, 323, 23, + 1, 0, 0, 0, 324, 325, 3, 202, 101, 0, 325, 25, 1, 0, 0, 0, 326, 328, 3, + 12, 6, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 329, 1, 0, 0, + 0, 329, 330, 5, 18, 0, 0, 330, 331, 3, 202, 101, 0, 331, 332, 5, 11, 0, + 0, 332, 333, 3, 38, 19, 0, 333, 334, 5, 19, 0, 0, 334, 336, 3, 22, 11, + 0, 335, 337, 3, 178, 89, 0, 336, 335, 1, 0, 0, 0, 336, 337, 1, 0, 0, 0, + 337, 27, 1, 0, 0, 0, 338, 340, 3, 12, 6, 0, 339, 338, 1, 0, 0, 0, 339, + 340, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 342, 5, 20, 0, 0, 342, 343, + 3, 202, 101, 0, 343, 344, 5, 11, 0, 0, 344, 345, 5, 14, 0, 0, 345, 350, + 3, 30, 15, 0, 346, 347, 5, 15, 0, 0, 347, 349, 3, 30, 15, 0, 348, 346, + 1, 0, 0, 0, 349, 352, 1, 0, 0, 0, 350, 348, 1, 0, 0, 0, 350, 351, 1, 0, + 0, 0, 351, 353, 1, 0, 0, 0, 352, 350, 1, 0, 0, 0, 353, 355, 5, 16, 0, 0, + 354, 356, 3, 178, 89, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, + 29, 1, 0, 0, 0, 357, 358, 3, 24, 12, 0, 358, 359, 5, 17, 0, 0, 359, 361, + 1, 0, 0, 0, 360, 357, 1, 0, 0, 0, 360, 361, 1, 0, 0, 0, 361, 362, 1, 0, + 0, 0, 362, 363, 3, 202, 101, 0, 363, 31, 1, 0, 0, 0, 364, 365, 5, 165, + 0, 0, 365, 33, 1, 0, 0, 0, 366, 367, 5, 165, 0, 0, 367, 35, 1, 0, 0, 0, + 368, 369, 5, 165, 0, 0, 369, 37, 1, 0, 0, 0, 370, 371, 5, 165, 0, 0, 371, + 39, 1, 0, 0, 0, 372, 378, 3, 42, 21, 0, 373, 378, 3, 46, 23, 0, 374, 378, + 3, 48, 24, 0, 375, 378, 3, 50, 25, 0, 376, 378, 3, 54, 27, 0, 377, 372, + 1, 0, 0, 0, 377, 373, 1, 0, 0, 0, 377, 374, 1, 0, 0, 0, 377, 375, 1, 0, + 0, 0, 377, 376, 1, 0, 0, 0, 378, 41, 1, 0, 0, 0, 379, 380, 3, 92, 46, 0, + 380, 381, 5, 17, 0, 0, 381, 383, 1, 0, 0, 0, 382, 379, 1, 0, 0, 0, 383, + 386, 1, 0, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 387, + 1, 0, 0, 0, 386, 384, 1, 0, 0, 0, 387, 388, 3, 198, 99, 0, 388, 43, 1, + 0, 0, 0, 389, 390, 3, 202, 101, 0, 390, 45, 1, 0, 0, 0, 391, 392, 5, 21, + 0, 0, 392, 393, 5, 22, 0, 0, 393, 394, 3, 40, 20, 0, 394, 395, 5, 23, 0, + 0, 395, 47, 1, 0, 0, 0, 396, 397, 5, 24, 0, 0, 397, 398, 5, 22, 0, 0, 398, + 399, 3, 40, 20, 0, 399, 400, 5, 23, 0, 0, 400, 49, 1, 0, 0, 0, 401, 402, + 5, 25, 0, 0, 402, 403, 5, 14, 0, 0, 403, 408, 3, 52, 26, 0, 404, 405, 5, + 15, 0, 0, 405, 407, 3, 52, 26, 0, 406, 404, 1, 0, 0, 0, 407, 410, 1, 0, + 0, 0, 408, 406, 1, 0, 0, 0, 408, 409, 1, 0, 0, 0, 409, 411, 1, 0, 0, 0, + 410, 408, 1, 0, 0, 0, 411, 412, 5, 16, 0, 0, 412, 51, 1, 0, 0, 0, 413, + 414, 3, 196, 98, 0, 414, 415, 3, 40, 20, 0, 415, 53, 1, 0, 0, 0, 416, 417, + 5, 26, 0, 0, 417, 418, 5, 22, 0, 0, 418, 423, 3, 40, 20, 0, 419, 420, 5, + 15, 0, 0, 420, 422, 3, 40, 20, 0, 421, 419, 1, 0, 0, 0, 422, 425, 1, 0, + 0, 0, 423, 421, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 426, 1, 0, 0, 0, + 425, 423, 1, 0, 0, 0, 426, 427, 5, 23, 0, 0, 427, 55, 1, 0, 0, 0, 428, + 432, 3, 58, 29, 0, 429, 432, 3, 60, 30, 0, 430, 432, 3, 64, 32, 0, 431, + 428, 1, 0, 0, 0, 431, 429, 1, 0, 0, 0, 431, 430, 1, 0, 0, 0, 432, 57, 1, + 0, 0, 0, 433, 435, 5, 27, 0, 0, 434, 436, 3, 12, 6, 0, 435, 434, 1, 0, + 0, 0, 435, 436, 1, 0, 0, 0, 436, 437, 1, 0, 0, 0, 437, 438, 3, 202, 101, + 0, 438, 439, 5, 11, 0, 0, 439, 440, 3, 126, 63, 0, 440, 59, 1, 0, 0, 0, + 441, 445, 5, 28, 0, 0, 442, 443, 3, 44, 22, 0, 443, 444, 5, 17, 0, 0, 444, + 446, 1, 0, 0, 0, 445, 442, 1, 0, 0, 0, 445, 446, 1, 0, 0, 0, 446, 447, + 1, 0, 0, 0, 447, 448, 3, 202, 101, 0, 448, 61, 1, 0, 0, 0, 449, 450, 5, + 29, 0, 0, 450, 63, 1, 0, 0, 0, 451, 453, 5, 27, 0, 0, 452, 454, 3, 12, + 6, 0, 453, 452, 1, 0, 0, 0, 453, 454, 1, 0, 0, 0, 454, 456, 1, 0, 0, 0, + 455, 457, 3, 62, 31, 0, 456, 455, 1, 0, 0, 0, 456, 457, 1, 0, 0, 0, 457, + 458, 1, 0, 0, 0, 458, 459, 5, 30, 0, 0, 459, 460, 3, 200, 100, 0, 460, + 469, 5, 31, 0, 0, 461, 466, 3, 66, 33, 0, 462, 463, 5, 15, 0, 0, 463, 465, + 3, 66, 33, 0, 464, 462, 1, 0, 0, 0, 465, 468, 1, 0, 0, 0, 466, 464, 1, + 0, 0, 0, 466, 467, 1, 0, 0, 0, 467, 470, 1, 0, 0, 0, 468, 466, 1, 0, 0, + 0, 469, 461, 1, 0, 0, 0, 469, 470, 1, 0, 0, 0, 470, 471, 1, 0, 0, 0, 471, + 474, 5, 32, 0, 0, 472, 473, 5, 33, 0, 0, 473, 475, 3, 40, 20, 0, 474, 472, + 1, 0, 0, 0, 474, 475, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 479, 5, 11, + 0, 0, 477, 480, 3, 68, 34, 0, 478, 480, 5, 34, 0, 0, 479, 477, 1, 0, 0, + 0, 479, 478, 1, 0, 0, 0, 480, 65, 1, 0, 0, 0, 481, 482, 3, 196, 98, 0, + 482, 483, 3, 40, 20, 0, 483, 67, 1, 0, 0, 0, 484, 485, 3, 126, 63, 0, 485, + 69, 1, 0, 0, 0, 486, 493, 3, 82, 41, 0, 487, 493, 3, 118, 59, 0, 488, 489, + 5, 31, 0, 0, 489, 490, 3, 126, 63, 0, 490, 491, 5, 32, 0, 0, 491, 493, + 1, 0, 0, 0, 492, 486, 1, 0, 0, 0, 492, 487, 1, 0, 0, 0, 492, 488, 1, 0, + 0, 0, 493, 71, 1, 0, 0, 0, 494, 495, 3, 70, 35, 0, 495, 496, 3, 74, 37, + 0, 496, 73, 1, 0, 0, 0, 497, 498, 3, 202, 101, 0, 498, 75, 1, 0, 0, 0, + 499, 502, 3, 78, 39, 0, 500, 502, 3, 80, 40, 0, 501, 499, 1, 0, 0, 0, 501, + 500, 1, 0, 0, 0, 502, 77, 1, 0, 0, 0, 503, 504, 5, 35, 0, 0, 504, 505, + 3, 72, 36, 0, 505, 506, 5, 36, 0, 0, 506, 507, 3, 126, 63, 0, 507, 79, + 1, 0, 0, 0, 508, 509, 5, 37, 0, 0, 509, 510, 3, 72, 36, 0, 510, 511, 5, + 36, 0, 0, 511, 512, 3, 126, 63, 0, 512, 81, 1, 0, 0, 0, 513, 517, 5, 38, + 0, 0, 514, 515, 3, 84, 42, 0, 515, 516, 5, 39, 0, 0, 516, 518, 1, 0, 0, + 0, 517, 514, 1, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 519, 1, 0, 0, 0, 519, + 527, 3, 42, 21, 0, 520, 524, 5, 11, 0, 0, 521, 522, 3, 86, 43, 0, 522, + 523, 3, 88, 44, 0, 523, 525, 1, 0, 0, 0, 524, 521, 1, 0, 0, 0, 524, 525, + 1, 0, 0, 0, 525, 526, 1, 0, 0, 0, 526, 528, 3, 90, 45, 0, 527, 520, 1, + 0, 0, 0, 527, 528, 1, 0, 0, 0, 528, 529, 1, 0, 0, 0, 529, 530, 5, 40, 0, + 0, 530, 83, 1, 0, 0, 0, 531, 532, 3, 118, 59, 0, 532, 85, 1, 0, 0, 0, 533, + 534, 3, 122, 61, 0, 534, 87, 1, 0, 0, 0, 535, 536, 7, 1, 0, 0, 536, 89, + 1, 0, 0, 0, 537, 540, 3, 118, 59, 0, 538, 540, 3, 126, 63, 0, 539, 537, + 1, 0, 0, 0, 539, 538, 1, 0, 0, 0, 540, 91, 1, 0, 0, 0, 541, 542, 3, 202, + 101, 0, 542, 93, 1, 0, 0, 0, 543, 545, 3, 96, 48, 0, 544, 546, 3, 98, 49, + 0, 545, 544, 1, 0, 0, 0, 545, 546, 1, 0, 0, 0, 546, 550, 1, 0, 0, 0, 547, + 549, 3, 76, 38, 0, 548, 547, 1, 0, 0, 0, 549, 552, 1, 0, 0, 0, 550, 548, + 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 554, 1, 0, 0, 0, 552, 550, 1, 0, + 0, 0, 553, 555, 3, 102, 51, 0, 554, 553, 1, 0, 0, 0, 554, 555, 1, 0, 0, + 0, 555, 558, 1, 0, 0, 0, 556, 559, 3, 106, 53, 0, 557, 559, 3, 104, 52, + 0, 558, 556, 1, 0, 0, 0, 558, 557, 1, 0, 0, 0, 558, 559, 1, 0, 0, 0, 559, + 561, 1, 0, 0, 0, 560, 562, 3, 110, 55, 0, 561, 560, 1, 0, 0, 0, 561, 562, + 1, 0, 0, 0, 562, 95, 1, 0, 0, 0, 563, 565, 5, 19, 0, 0, 564, 563, 1, 0, + 0, 0, 564, 565, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 571, 3, 72, 36, + 0, 567, 568, 5, 15, 0, 0, 568, 570, 3, 72, 36, 0, 569, 567, 1, 0, 0, 0, + 570, 573, 1, 0, 0, 0, 571, 569, 1, 0, 0, 0, 571, 572, 1, 0, 0, 0, 572, + 97, 1, 0, 0, 0, 573, 571, 1, 0, 0, 0, 574, 575, 5, 44, 0, 0, 575, 580, + 3, 100, 50, 0, 576, 577, 5, 15, 0, 0, 577, 579, 3, 100, 50, 0, 578, 576, + 1, 0, 0, 0, 579, 582, 1, 0, 0, 0, 580, 578, 1, 0, 0, 0, 580, 581, 1, 0, + 0, 0, 581, 99, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 583, 584, 3, 202, 101, + 0, 584, 585, 5, 11, 0, 0, 585, 586, 3, 126, 63, 0, 586, 101, 1, 0, 0, 0, + 587, 588, 5, 45, 0, 0, 588, 589, 3, 126, 63, 0, 589, 103, 1, 0, 0, 0, 590, + 592, 5, 46, 0, 0, 591, 593, 7, 2, 0, 0, 592, 591, 1, 0, 0, 0, 592, 593, + 1, 0, 0, 0, 593, 594, 1, 0, 0, 0, 594, 595, 3, 126, 63, 0, 595, 105, 1, + 0, 0, 0, 596, 598, 5, 49, 0, 0, 597, 599, 7, 2, 0, 0, 598, 597, 1, 0, 0, + 0, 598, 599, 1, 0, 0, 0, 599, 600, 1, 0, 0, 0, 600, 602, 3, 202, 101, 0, + 601, 603, 3, 108, 54, 0, 602, 601, 1, 0, 0, 0, 602, 603, 1, 0, 0, 0, 603, + 604, 1, 0, 0, 0, 604, 605, 5, 11, 0, 0, 605, 606, 3, 126, 63, 0, 606, 107, + 1, 0, 0, 0, 607, 614, 5, 50, 0, 0, 608, 615, 3, 124, 62, 0, 609, 615, 3, + 208, 104, 0, 610, 611, 5, 31, 0, 0, 611, 612, 3, 126, 63, 0, 612, 613, + 5, 32, 0, 0, 613, 615, 1, 0, 0, 0, 614, 608, 1, 0, 0, 0, 614, 609, 1, 0, + 0, 0, 614, 610, 1, 0, 0, 0, 615, 109, 1, 0, 0, 0, 616, 627, 5, 51, 0, 0, + 617, 628, 3, 112, 56, 0, 618, 619, 5, 52, 0, 0, 619, 624, 3, 114, 57, 0, + 620, 621, 5, 15, 0, 0, 621, 623, 3, 114, 57, 0, 622, 620, 1, 0, 0, 0, 623, + 626, 1, 0, 0, 0, 624, 622, 1, 0, 0, 0, 624, 625, 1, 0, 0, 0, 625, 628, + 1, 0, 0, 0, 626, 624, 1, 0, 0, 0, 627, 617, 1, 0, 0, 0, 627, 618, 1, 0, + 0, 0, 628, 111, 1, 0, 0, 0, 629, 630, 7, 3, 0, 0, 630, 113, 1, 0, 0, 0, + 631, 633, 3, 134, 67, 0, 632, 634, 3, 112, 56, 0, 633, 632, 1, 0, 0, 0, + 633, 634, 1, 0, 0, 0, 634, 115, 1, 0, 0, 0, 635, 636, 3, 92, 46, 0, 636, + 637, 5, 17, 0, 0, 637, 639, 1, 0, 0, 0, 638, 635, 1, 0, 0, 0, 639, 642, + 1, 0, 0, 0, 640, 638, 1, 0, 0, 0, 640, 641, 1, 0, 0, 0, 641, 643, 1, 0, + 0, 0, 642, 640, 1, 0, 0, 0, 643, 644, 3, 202, 101, 0, 644, 117, 1, 0, 0, + 0, 645, 646, 3, 120, 60, 0, 646, 647, 5, 17, 0, 0, 647, 649, 1, 0, 0, 0, + 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, + 651, 1, 0, 0, 0, 651, 653, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, + 3, 196, 98, 0, 654, 119, 1, 0, 0, 0, 655, 656, 3, 196, 98, 0, 656, 121, + 1, 0, 0, 0, 657, 658, 6, 61, -1, 0, 658, 659, 3, 196, 98, 0, 659, 670, + 1, 0, 0, 0, 660, 661, 10, 2, 0, 0, 661, 662, 5, 17, 0, 0, 662, 669, 3, + 196, 98, 0, 663, 664, 10, 1, 0, 0, 664, 665, 5, 38, 0, 0, 665, 666, 3, + 124, 62, 0, 666, 667, 5, 40, 0, 0, 667, 669, 1, 0, 0, 0, 668, 660, 1, 0, + 0, 0, 668, 663, 1, 0, 0, 0, 669, 672, 1, 0, 0, 0, 670, 668, 1, 0, 0, 0, + 670, 671, 1, 0, 0, 0, 671, 123, 1, 0, 0, 0, 672, 670, 1, 0, 0, 0, 673, + 676, 5, 165, 0, 0, 674, 676, 5, 166, 0, 0, 675, 673, 1, 0, 0, 0, 675, 674, + 1, 0, 0, 0, 676, 125, 1, 0, 0, 0, 677, 678, 6, 63, -1, 0, 678, 709, 3, + 134, 67, 0, 679, 709, 3, 82, 41, 0, 680, 709, 3, 94, 47, 0, 681, 682, 5, + 63, 0, 0, 682, 683, 3, 126, 63, 0, 683, 684, 5, 62, 0, 0, 684, 685, 3, + 40, 20, 0, 685, 709, 1, 0, 0, 0, 686, 687, 5, 58, 0, 0, 687, 709, 3, 126, + 63, 13, 688, 689, 5, 64, 0, 0, 689, 709, 3, 126, 63, 12, 690, 691, 5, 68, + 0, 0, 691, 693, 5, 41, 0, 0, 692, 690, 1, 0, 0, 0, 692, 693, 1, 0, 0, 0, + 693, 694, 1, 0, 0, 0, 694, 695, 3, 132, 66, 0, 695, 696, 5, 66, 0, 0, 696, + 697, 3, 134, 67, 0, 697, 698, 5, 67, 0, 0, 698, 699, 3, 134, 67, 0, 699, + 709, 1, 0, 0, 0, 700, 701, 5, 69, 0, 0, 701, 702, 5, 41, 0, 0, 702, 703, + 3, 132, 66, 0, 703, 704, 5, 66, 0, 0, 704, 705, 3, 134, 67, 0, 705, 706, + 5, 67, 0, 0, 706, 707, 3, 134, 67, 0, 707, 709, 1, 0, 0, 0, 708, 677, 1, + 0, 0, 0, 708, 679, 1, 0, 0, 0, 708, 680, 1, 0, 0, 0, 708, 681, 1, 0, 0, + 0, 708, 686, 1, 0, 0, 0, 708, 688, 1, 0, 0, 0, 708, 692, 1, 0, 0, 0, 708, + 700, 1, 0, 0, 0, 709, 758, 1, 0, 0, 0, 710, 711, 10, 8, 0, 0, 711, 712, + 7, 4, 0, 0, 712, 757, 3, 126, 63, 9, 713, 714, 10, 7, 0, 0, 714, 715, 3, + 150, 75, 0, 715, 716, 3, 126, 63, 8, 716, 757, 1, 0, 0, 0, 717, 718, 10, + 6, 0, 0, 718, 719, 7, 5, 0, 0, 719, 757, 3, 126, 63, 7, 720, 721, 10, 5, + 0, 0, 721, 723, 7, 6, 0, 0, 722, 724, 3, 138, 69, 0, 723, 722, 1, 0, 0, + 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 757, 3, 126, 63, 6, + 726, 727, 10, 4, 0, 0, 727, 728, 5, 67, 0, 0, 728, 757, 3, 126, 63, 5, + 729, 730, 10, 3, 0, 0, 730, 731, 7, 7, 0, 0, 731, 757, 3, 126, 63, 4, 732, + 733, 10, 2, 0, 0, 733, 734, 5, 77, 0, 0, 734, 757, 3, 126, 63, 3, 735, + 736, 10, 1, 0, 0, 736, 737, 7, 8, 0, 0, 737, 757, 3, 126, 63, 2, 738, 739, + 10, 16, 0, 0, 739, 741, 5, 57, 0, 0, 740, 742, 5, 58, 0, 0, 741, 740, 1, + 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 1, 0, 0, 0, 743, 757, 7, 9, 0, + 0, 744, 745, 10, 15, 0, 0, 745, 746, 7, 10, 0, 0, 746, 757, 3, 40, 20, + 0, 747, 749, 10, 11, 0, 0, 748, 750, 5, 65, 0, 0, 749, 748, 1, 0, 0, 0, + 749, 750, 1, 0, 0, 0, 750, 751, 1, 0, 0, 0, 751, 752, 5, 66, 0, 0, 752, + 753, 3, 134, 67, 0, 753, 754, 5, 67, 0, 0, 754, 755, 3, 134, 67, 0, 755, + 757, 1, 0, 0, 0, 756, 710, 1, 0, 0, 0, 756, 713, 1, 0, 0, 0, 756, 717, + 1, 0, 0, 0, 756, 720, 1, 0, 0, 0, 756, 726, 1, 0, 0, 0, 756, 729, 1, 0, + 0, 0, 756, 732, 1, 0, 0, 0, 756, 735, 1, 0, 0, 0, 756, 738, 1, 0, 0, 0, + 756, 744, 1, 0, 0, 0, 756, 747, 1, 0, 0, 0, 757, 760, 1, 0, 0, 0, 758, + 756, 1, 0, 0, 0, 758, 759, 1, 0, 0, 0, 759, 127, 1, 0, 0, 0, 760, 758, + 1, 0, 0, 0, 761, 762, 7, 11, 0, 0, 762, 129, 1, 0, 0, 0, 763, 769, 3, 128, + 64, 0, 764, 769, 5, 90, 0, 0, 765, 769, 5, 91, 0, 0, 766, 769, 5, 92, 0, + 0, 767, 769, 5, 93, 0, 0, 768, 763, 1, 0, 0, 0, 768, 764, 1, 0, 0, 0, 768, + 765, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 768, 767, 1, 0, 0, 0, 769, 131, + 1, 0, 0, 0, 770, 771, 7, 12, 0, 0, 771, 133, 1, 0, 0, 0, 772, 773, 6, 67, + -1, 0, 773, 851, 3, 152, 76, 0, 774, 775, 5, 102, 0, 0, 775, 776, 3, 126, + 63, 0, 776, 779, 5, 103, 0, 0, 777, 780, 3, 40, 20, 0, 778, 780, 3, 210, + 105, 0, 779, 777, 1, 0, 0, 0, 779, 778, 1, 0, 0, 0, 780, 851, 1, 0, 0, + 0, 781, 782, 7, 13, 0, 0, 782, 851, 3, 134, 67, 18, 783, 784, 7, 14, 0, + 0, 784, 785, 5, 108, 0, 0, 785, 851, 3, 134, 67, 17, 786, 787, 3, 130, + 65, 0, 787, 788, 5, 19, 0, 0, 788, 789, 3, 134, 67, 16, 789, 851, 1, 0, + 0, 0, 790, 791, 5, 68, 0, 0, 791, 792, 5, 41, 0, 0, 792, 793, 3, 132, 66, + 0, 793, 794, 5, 108, 0, 0, 794, 795, 3, 134, 67, 15, 795, 851, 1, 0, 0, + 0, 796, 797, 5, 69, 0, 0, 797, 798, 5, 41, 0, 0, 798, 799, 3, 132, 66, + 0, 799, 800, 5, 108, 0, 0, 800, 801, 3, 134, 67, 14, 801, 851, 1, 0, 0, + 0, 802, 803, 5, 109, 0, 0, 803, 804, 5, 108, 0, 0, 804, 851, 3, 134, 67, + 13, 805, 806, 5, 110, 0, 0, 806, 807, 5, 108, 0, 0, 807, 851, 3, 134, 67, + 12, 808, 809, 5, 111, 0, 0, 809, 810, 5, 108, 0, 0, 810, 851, 3, 134, 67, + 11, 811, 812, 5, 112, 0, 0, 812, 813, 5, 19, 0, 0, 813, 851, 3, 134, 67, + 10, 814, 815, 5, 113, 0, 0, 815, 816, 5, 19, 0, 0, 816, 851, 3, 134, 67, + 9, 817, 818, 7, 15, 0, 0, 818, 851, 3, 42, 21, 0, 819, 820, 5, 122, 0, + 0, 820, 821, 3, 126, 63, 0, 821, 822, 5, 123, 0, 0, 822, 823, 3, 126, 63, + 0, 823, 824, 5, 124, 0, 0, 824, 825, 3, 126, 63, 0, 825, 851, 1, 0, 0, + 0, 826, 828, 5, 125, 0, 0, 827, 829, 3, 126, 63, 0, 828, 827, 1, 0, 0, + 0, 828, 829, 1, 0, 0, 0, 829, 831, 1, 0, 0, 0, 830, 832, 3, 136, 68, 0, + 831, 830, 1, 0, 0, 0, 832, 833, 1, 0, 0, 0, 833, 831, 1, 0, 0, 0, 833, + 834, 1, 0, 0, 0, 834, 835, 1, 0, 0, 0, 835, 836, 5, 124, 0, 0, 836, 837, + 3, 126, 63, 0, 837, 838, 5, 107, 0, 0, 838, 851, 1, 0, 0, 0, 839, 840, + 7, 16, 0, 0, 840, 851, 3, 126, 63, 0, 841, 842, 7, 17, 0, 0, 842, 848, + 3, 126, 63, 0, 843, 846, 5, 129, 0, 0, 844, 847, 3, 128, 64, 0, 845, 847, + 3, 126, 63, 0, 846, 844, 1, 0, 0, 0, 846, 845, 1, 0, 0, 0, 847, 849, 1, + 0, 0, 0, 848, 843, 1, 0, 0, 0, 848, 849, 1, 0, 0, 0, 849, 851, 1, 0, 0, + 0, 850, 772, 1, 0, 0, 0, 850, 774, 1, 0, 0, 0, 850, 781, 1, 0, 0, 0, 850, + 783, 1, 0, 0, 0, 850, 786, 1, 0, 0, 0, 850, 790, 1, 0, 0, 0, 850, 796, + 1, 0, 0, 0, 850, 802, 1, 0, 0, 0, 850, 805, 1, 0, 0, 0, 850, 808, 1, 0, + 0, 0, 850, 811, 1, 0, 0, 0, 850, 814, 1, 0, 0, 0, 850, 817, 1, 0, 0, 0, + 850, 819, 1, 0, 0, 0, 850, 826, 1, 0, 0, 0, 850, 839, 1, 0, 0, 0, 850, + 841, 1, 0, 0, 0, 851, 871, 1, 0, 0, 0, 852, 853, 10, 7, 0, 0, 853, 854, + 5, 116, 0, 0, 854, 870, 3, 134, 67, 8, 855, 856, 10, 6, 0, 0, 856, 857, + 7, 18, 0, 0, 857, 870, 3, 134, 67, 7, 858, 859, 10, 5, 0, 0, 859, 860, + 7, 19, 0, 0, 860, 870, 3, 134, 67, 6, 861, 862, 10, 21, 0, 0, 862, 863, + 5, 17, 0, 0, 863, 870, 3, 154, 77, 0, 864, 865, 10, 20, 0, 0, 865, 866, + 5, 38, 0, 0, 866, 867, 3, 126, 63, 0, 867, 868, 5, 40, 0, 0, 868, 870, + 1, 0, 0, 0, 869, 852, 1, 0, 0, 0, 869, 855, 1, 0, 0, 0, 869, 858, 1, 0, + 0, 0, 869, 861, 1, 0, 0, 0, 869, 864, 1, 0, 0, 0, 870, 873, 1, 0, 0, 0, + 871, 869, 1, 0, 0, 0, 871, 872, 1, 0, 0, 0, 872, 135, 1, 0, 0, 0, 873, + 871, 1, 0, 0, 0, 874, 875, 5, 130, 0, 0, 875, 876, 3, 126, 63, 0, 876, + 877, 5, 123, 0, 0, 877, 878, 3, 126, 63, 0, 878, 137, 1, 0, 0, 0, 879, + 880, 3, 128, 64, 0, 880, 881, 5, 108, 0, 0, 881, 139, 1, 0, 0, 0, 882, + 883, 7, 20, 0, 0, 883, 141, 1, 0, 0, 0, 884, 885, 7, 21, 0, 0, 885, 143, + 1, 0, 0, 0, 886, 887, 7, 22, 0, 0, 887, 145, 1, 0, 0, 0, 888, 890, 3, 208, + 104, 0, 889, 891, 3, 142, 71, 0, 890, 889, 1, 0, 0, 0, 890, 891, 1, 0, + 0, 0, 891, 896, 1, 0, 0, 0, 892, 893, 3, 144, 72, 0, 893, 894, 3, 208, + 104, 0, 894, 896, 1, 0, 0, 0, 895, 888, 1, 0, 0, 0, 895, 892, 1, 0, 0, + 0, 896, 147, 1, 0, 0, 0, 897, 899, 5, 137, 0, 0, 898, 897, 1, 0, 0, 0, + 898, 899, 1, 0, 0, 0, 899, 900, 1, 0, 0, 0, 900, 906, 7, 23, 0, 0, 901, + 903, 7, 23, 0, 0, 902, 904, 5, 140, 0, 0, 903, 902, 1, 0, 0, 0, 903, 904, + 1, 0, 0, 0, 904, 906, 1, 0, 0, 0, 905, 898, 1, 0, 0, 0, 905, 901, 1, 0, + 0, 0, 906, 149, 1, 0, 0, 0, 907, 909, 7, 24, 0, 0, 908, 907, 1, 0, 0, 0, + 908, 909, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 910, 912, 5, 144, 0, 0, 911, + 913, 3, 128, 64, 0, 912, 911, 1, 0, 0, 0, 912, 913, 1, 0, 0, 0, 913, 916, + 1, 0, 0, 0, 914, 917, 3, 140, 70, 0, 915, 917, 5, 62, 0, 0, 916, 914, 1, + 0, 0, 0, 916, 915, 1, 0, 0, 0, 917, 919, 1, 0, 0, 0, 918, 920, 7, 14, 0, + 0, 919, 918, 1, 0, 0, 0, 919, 920, 1, 0, 0, 0, 920, 989, 1, 0, 0, 0, 921, + 923, 5, 65, 0, 0, 922, 921, 1, 0, 0, 0, 922, 923, 1, 0, 0, 0, 923, 924, + 1, 0, 0, 0, 924, 926, 5, 145, 0, 0, 925, 927, 3, 138, 69, 0, 926, 925, + 1, 0, 0, 0, 926, 927, 1, 0, 0, 0, 927, 929, 1, 0, 0, 0, 928, 930, 7, 14, + 0, 0, 929, 928, 1, 0, 0, 0, 929, 930, 1, 0, 0, 0, 930, 989, 1, 0, 0, 0, + 931, 933, 7, 24, 0, 0, 932, 931, 1, 0, 0, 0, 932, 933, 1, 0, 0, 0, 933, + 935, 1, 0, 0, 0, 934, 936, 5, 65, 0, 0, 935, 934, 1, 0, 0, 0, 935, 936, + 1, 0, 0, 0, 936, 937, 1, 0, 0, 0, 937, 939, 7, 25, 0, 0, 938, 940, 3, 138, + 69, 0, 939, 938, 1, 0, 0, 0, 939, 940, 1, 0, 0, 0, 940, 989, 1, 0, 0, 0, + 941, 943, 7, 24, 0, 0, 942, 941, 1, 0, 0, 0, 942, 943, 1, 0, 0, 0, 943, + 945, 1, 0, 0, 0, 944, 946, 3, 146, 73, 0, 945, 944, 1, 0, 0, 0, 945, 946, + 1, 0, 0, 0, 946, 947, 1, 0, 0, 0, 947, 949, 3, 148, 74, 0, 948, 950, 3, + 138, 69, 0, 949, 948, 1, 0, 0, 0, 949, 950, 1, 0, 0, 0, 950, 952, 1, 0, + 0, 0, 951, 953, 7, 14, 0, 0, 952, 951, 1, 0, 0, 0, 952, 953, 1, 0, 0, 0, + 953, 989, 1, 0, 0, 0, 954, 956, 7, 24, 0, 0, 955, 954, 1, 0, 0, 0, 955, + 956, 1, 0, 0, 0, 956, 958, 1, 0, 0, 0, 957, 959, 5, 65, 0, 0, 958, 957, + 1, 0, 0, 0, 958, 959, 1, 0, 0, 0, 959, 960, 1, 0, 0, 0, 960, 961, 5, 148, + 0, 0, 961, 962, 3, 208, 104, 0, 962, 964, 5, 108, 0, 0, 963, 965, 7, 14, + 0, 0, 964, 963, 1, 0, 0, 0, 964, 965, 1, 0, 0, 0, 965, 989, 1, 0, 0, 0, + 966, 968, 5, 149, 0, 0, 967, 969, 7, 23, 0, 0, 968, 967, 1, 0, 0, 0, 968, + 969, 1, 0, 0, 0, 969, 971, 1, 0, 0, 0, 970, 972, 3, 138, 69, 0, 971, 970, + 1, 0, 0, 0, 971, 972, 1, 0, 0, 0, 972, 989, 1, 0, 0, 0, 973, 975, 5, 150, + 0, 0, 974, 976, 7, 23, 0, 0, 975, 974, 1, 0, 0, 0, 975, 976, 1, 0, 0, 0, + 976, 978, 1, 0, 0, 0, 977, 979, 3, 138, 69, 0, 978, 977, 1, 0, 0, 0, 978, + 979, 1, 0, 0, 0, 979, 989, 1, 0, 0, 0, 980, 982, 5, 141, 0, 0, 981, 983, + 3, 138, 69, 0, 982, 981, 1, 0, 0, 0, 982, 983, 1, 0, 0, 0, 983, 989, 1, + 0, 0, 0, 984, 986, 5, 142, 0, 0, 985, 987, 3, 138, 69, 0, 986, 985, 1, + 0, 0, 0, 986, 987, 1, 0, 0, 0, 987, 989, 1, 0, 0, 0, 988, 908, 1, 0, 0, + 0, 988, 922, 1, 0, 0, 0, 988, 932, 1, 0, 0, 0, 988, 942, 1, 0, 0, 0, 988, + 955, 1, 0, 0, 0, 988, 966, 1, 0, 0, 0, 988, 973, 1, 0, 0, 0, 988, 980, + 1, 0, 0, 0, 988, 984, 1, 0, 0, 0, 989, 151, 1, 0, 0, 0, 990, 1004, 3, 158, + 79, 0, 991, 1004, 3, 164, 82, 0, 992, 1004, 3, 204, 102, 0, 993, 1004, + 3, 166, 83, 0, 994, 1004, 3, 168, 84, 0, 995, 1004, 3, 172, 86, 0, 996, + 1004, 3, 176, 88, 0, 997, 1004, 3, 180, 90, 0, 998, 1004, 3, 182, 91, 0, + 999, 1000, 5, 31, 0, 0, 1000, 1001, 3, 126, 63, 0, 1001, 1002, 5, 32, 0, + 0, 1002, 1004, 1, 0, 0, 0, 1003, 990, 1, 0, 0, 0, 1003, 991, 1, 0, 0, 0, + 1003, 992, 1, 0, 0, 0, 1003, 993, 1, 0, 0, 0, 1003, 994, 1, 0, 0, 0, 1003, + 995, 1, 0, 0, 0, 1003, 996, 1, 0, 0, 0, 1003, 997, 1, 0, 0, 0, 1003, 998, + 1, 0, 0, 0, 1003, 999, 1, 0, 0, 0, 1004, 153, 1, 0, 0, 0, 1005, 1008, 3, + 196, 98, 0, 1006, 1008, 3, 156, 78, 0, 1007, 1005, 1, 0, 0, 0, 1007, 1006, + 1, 0, 0, 0, 1008, 155, 1, 0, 0, 0, 1009, 1010, 3, 200, 100, 0, 1010, 1012, + 5, 31, 0, 0, 1011, 1013, 3, 206, 103, 0, 1012, 1011, 1, 0, 0, 0, 1012, + 1013, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 5, 32, 0, 0, 1015, + 157, 1, 0, 0, 0, 1016, 1022, 3, 196, 98, 0, 1017, 1022, 3, 160, 80, 0, + 1018, 1022, 5, 151, 0, 0, 1019, 1022, 5, 152, 0, 0, 1020, 1022, 5, 153, + 0, 0, 1021, 1016, 1, 0, 0, 0, 1021, 1017, 1, 0, 0, 0, 1021, 1018, 1, 0, + 0, 0, 1021, 1019, 1, 0, 0, 0, 1021, 1020, 1, 0, 0, 0, 1022, 159, 1, 0, + 0, 0, 1023, 1024, 3, 196, 98, 0, 1024, 1026, 5, 31, 0, 0, 1025, 1027, 3, + 206, 103, 0, 1026, 1025, 1, 0, 0, 0, 1026, 1027, 1, 0, 0, 0, 1027, 1028, + 1, 0, 0, 0, 1028, 1029, 5, 32, 0, 0, 1029, 161, 1, 0, 0, 0, 1030, 1031, + 3, 208, 104, 0, 1031, 1032, 5, 11, 0, 0, 1032, 1033, 3, 208, 104, 0, 1033, + 163, 1, 0, 0, 0, 1034, 1045, 7, 26, 0, 0, 1035, 1045, 5, 59, 0, 0, 1036, + 1045, 5, 165, 0, 0, 1037, 1045, 5, 166, 0, 0, 1038, 1045, 5, 160, 0, 0, + 1039, 1045, 5, 159, 0, 0, 1040, 1045, 5, 161, 0, 0, 1041, 1045, 5, 162, + 0, 0, 1042, 1045, 3, 208, 104, 0, 1043, 1045, 3, 162, 81, 0, 1044, 1034, + 1, 0, 0, 0, 1044, 1035, 1, 0, 0, 0, 1044, 1036, 1, 0, 0, 0, 1044, 1037, + 1, 0, 0, 0, 1044, 1038, 1, 0, 0, 0, 1044, 1039, 1, 0, 0, 0, 1044, 1040, + 1, 0, 0, 0, 1044, 1041, 1, 0, 0, 0, 1044, 1042, 1, 0, 0, 0, 1044, 1043, + 1, 0, 0, 0, 1045, 165, 1, 0, 0, 0, 1046, 1047, 5, 24, 0, 0, 1047, 1048, + 7, 27, 0, 0, 1048, 1049, 3, 126, 63, 0, 1049, 1050, 5, 15, 0, 0, 1050, + 1051, 3, 126, 63, 0, 1051, 1052, 7, 28, 0, 0, 1052, 167, 1, 0, 0, 0, 1053, + 1055, 5, 25, 0, 0, 1054, 1053, 1, 0, 0, 0, 1054, 1055, 1, 0, 0, 0, 1055, + 1056, 1, 0, 0, 0, 1056, 1066, 5, 14, 0, 0, 1057, 1067, 5, 11, 0, 0, 1058, + 1063, 3, 170, 85, 0, 1059, 1060, 5, 15, 0, 0, 1060, 1062, 3, 170, 85, 0, + 1061, 1059, 1, 0, 0, 0, 1062, 1065, 1, 0, 0, 0, 1063, 1061, 1, 0, 0, 0, + 1063, 1064, 1, 0, 0, 0, 1064, 1067, 1, 0, 0, 0, 1065, 1063, 1, 0, 0, 0, + 1066, 1057, 1, 0, 0, 0, 1066, 1058, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, + 1068, 1069, 5, 16, 0, 0, 1069, 169, 1, 0, 0, 0, 1070, 1071, 3, 196, 98, + 0, 1071, 1072, 5, 11, 0, 0, 1072, 1073, 3, 126, 63, 0, 1073, 171, 1, 0, + 0, 0, 1074, 1075, 3, 42, 21, 0, 1075, 1085, 5, 14, 0, 0, 1076, 1086, 5, + 11, 0, 0, 1077, 1082, 3, 174, 87, 0, 1078, 1079, 5, 15, 0, 0, 1079, 1081, + 3, 174, 87, 0, 1080, 1078, 1, 0, 0, 0, 1081, 1084, 1, 0, 0, 0, 1082, 1080, + 1, 0, 0, 0, 1082, 1083, 1, 0, 0, 0, 1083, 1086, 1, 0, 0, 0, 1084, 1082, + 1, 0, 0, 0, 1085, 1076, 1, 0, 0, 0, 1085, 1077, 1, 0, 0, 0, 1086, 1087, + 1, 0, 0, 0, 1087, 1088, 5, 16, 0, 0, 1088, 173, 1, 0, 0, 0, 1089, 1090, + 3, 196, 98, 0, 1090, 1091, 5, 11, 0, 0, 1091, 1092, 3, 126, 63, 0, 1092, + 175, 1, 0, 0, 0, 1093, 1098, 5, 21, 0, 0, 1094, 1095, 5, 22, 0, 0, 1095, + 1096, 3, 40, 20, 0, 1096, 1097, 5, 23, 0, 0, 1097, 1099, 1, 0, 0, 0, 1098, + 1094, 1, 0, 0, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1101, 1, 0, 0, 0, 1100, + 1093, 1, 0, 0, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1102, 1, 0, 0, 0, 1102, + 1111, 5, 14, 0, 0, 1103, 1108, 3, 126, 63, 0, 1104, 1105, 5, 15, 0, 0, + 1105, 1107, 3, 126, 63, 0, 1106, 1104, 1, 0, 0, 0, 1107, 1110, 1, 0, 0, + 0, 1108, 1106, 1, 0, 0, 0, 1108, 1109, 1, 0, 0, 0, 1109, 1112, 1, 0, 0, + 0, 1110, 1108, 1, 0, 0, 0, 1111, 1103, 1, 0, 0, 0, 1111, 1112, 1, 0, 0, + 0, 1112, 1113, 1, 0, 0, 0, 1113, 1114, 5, 16, 0, 0, 1114, 177, 1, 0, 0, + 0, 1115, 1116, 5, 154, 0, 0, 1116, 1117, 5, 165, 0, 0, 1117, 179, 1, 0, + 0, 0, 1118, 1119, 5, 155, 0, 0, 1119, 1120, 5, 165, 0, 0, 1120, 1121, 5, + 19, 0, 0, 1121, 1123, 3, 22, 11, 0, 1122, 1124, 3, 178, 89, 0, 1123, 1122, + 1, 0, 0, 0, 1123, 1124, 1, 0, 0, 0, 1124, 181, 1, 0, 0, 0, 1125, 1126, + 5, 156, 0, 0, 1126, 1127, 5, 14, 0, 0, 1127, 1132, 3, 180, 90, 0, 1128, + 1129, 5, 15, 0, 0, 1129, 1131, 3, 180, 90, 0, 1130, 1128, 1, 0, 0, 0, 1131, + 1134, 1, 0, 0, 0, 1132, 1130, 1, 0, 0, 0, 1132, 1133, 1, 0, 0, 0, 1133, + 1135, 1, 0, 0, 0, 1134, 1132, 1, 0, 0, 0, 1135, 1137, 5, 16, 0, 0, 1136, + 1138, 3, 178, 89, 0, 1137, 1136, 1, 0, 0, 0, 1137, 1138, 1, 0, 0, 0, 1138, + 183, 1, 0, 0, 0, 1139, 1140, 7, 29, 0, 0, 1140, 185, 1, 0, 0, 0, 1141, + 1142, 7, 30, 0, 0, 1142, 187, 1, 0, 0, 0, 1143, 1144, 7, 31, 0, 0, 1144, + 189, 1, 0, 0, 0, 1145, 1146, 7, 32, 0, 0, 1146, 191, 1, 0, 0, 0, 1147, + 1148, 7, 33, 0, 0, 1148, 193, 1, 0, 0, 0, 1149, 1150, 7, 34, 0, 0, 1150, + 195, 1, 0, 0, 0, 1151, 1154, 3, 202, 101, 0, 1152, 1154, 3, 188, 94, 0, + 1153, 1151, 1, 0, 0, 0, 1153, 1152, 1, 0, 0, 0, 1154, 197, 1, 0, 0, 0, + 1155, 1158, 3, 196, 98, 0, 1156, 1158, 3, 194, 97, 0, 1157, 1155, 1, 0, + 0, 0, 1157, 1156, 1, 0, 0, 0, 1158, 199, 1, 0, 0, 0, 1159, 1162, 3, 202, + 101, 0, 1160, 1162, 3, 192, 96, 0, 1161, 1159, 1, 0, 0, 0, 1161, 1160, + 1, 0, 0, 0, 1162, 201, 1, 0, 0, 0, 1163, 1164, 7, 35, 0, 0, 1164, 203, + 1, 0, 0, 0, 1165, 1168, 5, 157, 0, 0, 1166, 1169, 3, 202, 101, 0, 1167, + 1169, 5, 165, 0, 0, 1168, 1166, 1, 0, 0, 0, 1168, 1167, 1, 0, 0, 0, 1169, + 205, 1, 0, 0, 0, 1170, 1175, 3, 126, 63, 0, 1171, 1172, 5, 15, 0, 0, 1172, + 1174, 3, 126, 63, 0, 1173, 1171, 1, 0, 0, 0, 1174, 1177, 1, 0, 0, 0, 1175, + 1173, 1, 0, 0, 0, 1175, 1176, 1, 0, 0, 0, 1176, 207, 1, 0, 0, 0, 1177, + 1175, 1, 0, 0, 0, 1178, 1180, 5, 166, 0, 0, 1179, 1181, 3, 210, 105, 0, + 1180, 1179, 1, 0, 0, 0, 1180, 1181, 1, 0, 0, 0, 1181, 209, 1, 0, 0, 0, + 1182, 1186, 3, 128, 64, 0, 1183, 1186, 3, 132, 66, 0, 1184, 1186, 5, 165, + 0, 0, 1185, 1182, 1, 0, 0, 0, 1185, 1183, 1, 0, 0, 0, 1185, 1184, 1, 0, + 0, 0, 1186, 211, 1, 0, 0, 0, 134, 219, 222, 227, 233, 242, 248, 252, 258, + 262, 269, 274, 278, 281, 289, 292, 300, 303, 312, 320, 327, 336, 339, 350, + 355, 360, 377, 384, 408, 423, 431, 435, 445, 453, 456, 466, 469, 474, 479, + 492, 501, 517, 524, 527, 539, 545, 550, 554, 558, 561, 564, 571, 580, 592, + 598, 602, 614, 624, 627, 633, 640, 650, 668, 670, 675, 692, 708, 723, 741, + 749, 756, 758, 768, 779, 828, 833, 846, 848, 850, 869, 871, 890, 895, 898, + 903, 905, 908, 912, 916, 919, 922, 926, 929, 932, 935, 939, 942, 945, 949, + 952, 955, 958, 964, 968, 971, 975, 978, 982, 986, 988, 1003, 1007, 1012, + 1021, 1026, 1044, 1054, 1063, 1066, 1082, 1085, 1098, 1100, 1108, 1111, + 1123, 1132, 1137, 1153, 1157, 1161, 1168, 1175, 1180, 1185, +} + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// CqlParserInit initializes any static state used to implement CqlParser. By default the +// static state used to implement the parser is lazily initialized during the first call to +// NewCqlParser(). You can call this function if you wish to initialize the static state ahead +// of time. +func CqlParserInit() { + staticData := &CqlParserStaticData + staticData.once.Do(cqlParserInit) +} + +// NewCqlParser produces a new parser instance for the optional input antlr.TokenStream. +func NewCqlParser(input antlr.TokenStream) *CqlParser { + CqlParserInit() + this := new(CqlParser) + this.BaseParser = antlr.NewBaseParser(input) + staticData := &CqlParserStaticData + this.Interpreter = antlr.NewParserATNSimulator(this, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + this.RuleNames = staticData.RuleNames + this.LiteralNames = staticData.LiteralNames + this.SymbolicNames = staticData.SymbolicNames + this.GrammarFileName = "Cql.g4" + + return this +} + + +// CqlParser tokens. +const ( + CqlParserEOF = antlr.TokenEOF + CqlParserT__0 = 1 + CqlParserT__1 = 2 + CqlParserT__2 = 3 + CqlParserT__3 = 4 + CqlParserT__4 = 5 + CqlParserT__5 = 6 + CqlParserT__6 = 7 + CqlParserT__7 = 8 + CqlParserT__8 = 9 + CqlParserT__9 = 10 + CqlParserT__10 = 11 + CqlParserT__11 = 12 + CqlParserT__12 = 13 + CqlParserT__13 = 14 + CqlParserT__14 = 15 + CqlParserT__15 = 16 + CqlParserT__16 = 17 + CqlParserT__17 = 18 + CqlParserT__18 = 19 + CqlParserT__19 = 20 + CqlParserT__20 = 21 + CqlParserT__21 = 22 + CqlParserT__22 = 23 + CqlParserT__23 = 24 + CqlParserT__24 = 25 + CqlParserT__25 = 26 + CqlParserT__26 = 27 + CqlParserT__27 = 28 + CqlParserT__28 = 29 + CqlParserT__29 = 30 + CqlParserT__30 = 31 + CqlParserT__31 = 32 + CqlParserT__32 = 33 + CqlParserT__33 = 34 + CqlParserT__34 = 35 + CqlParserT__35 = 36 + CqlParserT__36 = 37 + CqlParserT__37 = 38 + CqlParserT__38 = 39 + CqlParserT__39 = 40 + CqlParserT__40 = 41 + CqlParserT__41 = 42 + CqlParserT__42 = 43 + CqlParserT__43 = 44 + CqlParserT__44 = 45 + CqlParserT__45 = 46 + CqlParserT__46 = 47 + CqlParserT__47 = 48 + CqlParserT__48 = 49 + CqlParserT__49 = 50 + CqlParserT__50 = 51 + CqlParserT__51 = 52 + CqlParserT__52 = 53 + CqlParserT__53 = 54 + CqlParserT__54 = 55 + CqlParserT__55 = 56 + CqlParserT__56 = 57 + CqlParserT__57 = 58 + CqlParserT__58 = 59 + CqlParserT__59 = 60 + CqlParserT__60 = 61 + CqlParserT__61 = 62 + CqlParserT__62 = 63 + CqlParserT__63 = 64 + CqlParserT__64 = 65 + CqlParserT__65 = 66 + CqlParserT__66 = 67 + CqlParserT__67 = 68 + CqlParserT__68 = 69 + CqlParserT__69 = 70 + CqlParserT__70 = 71 + CqlParserT__71 = 72 + CqlParserT__72 = 73 + CqlParserT__73 = 74 + CqlParserT__74 = 75 + CqlParserT__75 = 76 + CqlParserT__76 = 77 + CqlParserT__77 = 78 + CqlParserT__78 = 79 + CqlParserT__79 = 80 + CqlParserT__80 = 81 + CqlParserT__81 = 82 + CqlParserT__82 = 83 + CqlParserT__83 = 84 + CqlParserT__84 = 85 + CqlParserT__85 = 86 + CqlParserT__86 = 87 + CqlParserT__87 = 88 + CqlParserT__88 = 89 + CqlParserT__89 = 90 + CqlParserT__90 = 91 + CqlParserT__91 = 92 + CqlParserT__92 = 93 + CqlParserT__93 = 94 + CqlParserT__94 = 95 + CqlParserT__95 = 96 + CqlParserT__96 = 97 + CqlParserT__97 = 98 + CqlParserT__98 = 99 + CqlParserT__99 = 100 + CqlParserT__100 = 101 + CqlParserT__101 = 102 + CqlParserT__102 = 103 + CqlParserT__103 = 104 + CqlParserT__104 = 105 + CqlParserT__105 = 106 + CqlParserT__106 = 107 + CqlParserT__107 = 108 + CqlParserT__108 = 109 + CqlParserT__109 = 110 + CqlParserT__110 = 111 + CqlParserT__111 = 112 + CqlParserT__112 = 113 + CqlParserT__113 = 114 + CqlParserT__114 = 115 + CqlParserT__115 = 116 + CqlParserT__116 = 117 + CqlParserT__117 = 118 + CqlParserT__118 = 119 + CqlParserT__119 = 120 + CqlParserT__120 = 121 + CqlParserT__121 = 122 + CqlParserT__122 = 123 + CqlParserT__123 = 124 + CqlParserT__124 = 125 + CqlParserT__125 = 126 + CqlParserT__126 = 127 + CqlParserT__127 = 128 + CqlParserT__128 = 129 + CqlParserT__129 = 130 + CqlParserT__130 = 131 + CqlParserT__131 = 132 + CqlParserT__132 = 133 + CqlParserT__133 = 134 + CqlParserT__134 = 135 + CqlParserT__135 = 136 + CqlParserT__136 = 137 + CqlParserT__137 = 138 + CqlParserT__138 = 139 + CqlParserT__139 = 140 + CqlParserT__140 = 141 + CqlParserT__141 = 142 + CqlParserT__142 = 143 + CqlParserT__143 = 144 + CqlParserT__144 = 145 + CqlParserT__145 = 146 + CqlParserT__146 = 147 + CqlParserT__147 = 148 + CqlParserT__148 = 149 + CqlParserT__149 = 150 + CqlParserT__150 = 151 + CqlParserT__151 = 152 + CqlParserT__152 = 153 + CqlParserT__153 = 154 + CqlParserT__154 = 155 + CqlParserT__155 = 156 + CqlParserT__156 = 157 + CqlParserQUOTEDIDENTIFIER = 158 + CqlParserDATETIME = 159 + CqlParserLONGNUMBER = 160 + CqlParserDATE = 161 + CqlParserTIME = 162 + CqlParserIDENTIFIER = 163 + CqlParserDELIMITEDIDENTIFIER = 164 + CqlParserSTRING = 165 + CqlParserNUMBER = 166 + CqlParserWS = 167 + CqlParserCOMMENT = 168 + CqlParserLINE_COMMENT = 169 +) + +// CqlParser rules. +const ( + CqlParserRULE_definition = 0 + CqlParserRULE_library = 1 + CqlParserRULE_libraryDefinition = 2 + CqlParserRULE_usingDefinition = 3 + CqlParserRULE_includeDefinition = 4 + CqlParserRULE_localIdentifier = 5 + CqlParserRULE_accessModifier = 6 + CqlParserRULE_parameterDefinition = 7 + CqlParserRULE_codesystemDefinition = 8 + CqlParserRULE_valuesetDefinition = 9 + CqlParserRULE_codesystems = 10 + CqlParserRULE_codesystemIdentifier = 11 + CqlParserRULE_libraryIdentifier = 12 + CqlParserRULE_codeDefinition = 13 + CqlParserRULE_conceptDefinition = 14 + CqlParserRULE_codeIdentifier = 15 + CqlParserRULE_codesystemId = 16 + CqlParserRULE_valuesetId = 17 + CqlParserRULE_versionSpecifier = 18 + CqlParserRULE_codeId = 19 + CqlParserRULE_typeSpecifier = 20 + CqlParserRULE_namedTypeSpecifier = 21 + CqlParserRULE_modelIdentifier = 22 + CqlParserRULE_listTypeSpecifier = 23 + CqlParserRULE_intervalTypeSpecifier = 24 + CqlParserRULE_tupleTypeSpecifier = 25 + CqlParserRULE_tupleElementDefinition = 26 + CqlParserRULE_choiceTypeSpecifier = 27 + CqlParserRULE_statement = 28 + CqlParserRULE_expressionDefinition = 29 + CqlParserRULE_contextDefinition = 30 + CqlParserRULE_fluentModifier = 31 + CqlParserRULE_functionDefinition = 32 + CqlParserRULE_operandDefinition = 33 + CqlParserRULE_functionBody = 34 + CqlParserRULE_querySource = 35 + CqlParserRULE_aliasedQuerySource = 36 + CqlParserRULE_alias = 37 + CqlParserRULE_queryInclusionClause = 38 + CqlParserRULE_withClause = 39 + CqlParserRULE_withoutClause = 40 + CqlParserRULE_retrieve = 41 + CqlParserRULE_contextIdentifier = 42 + CqlParserRULE_codePath = 43 + CqlParserRULE_codeComparator = 44 + CqlParserRULE_terminology = 45 + CqlParserRULE_qualifier = 46 + CqlParserRULE_query = 47 + CqlParserRULE_sourceClause = 48 + CqlParserRULE_letClause = 49 + CqlParserRULE_letClauseItem = 50 + CqlParserRULE_whereClause = 51 + CqlParserRULE_returnClause = 52 + CqlParserRULE_aggregateClause = 53 + CqlParserRULE_startingClause = 54 + CqlParserRULE_sortClause = 55 + CqlParserRULE_sortDirection = 56 + CqlParserRULE_sortByItem = 57 + CqlParserRULE_qualifiedIdentifier = 58 + CqlParserRULE_qualifiedIdentifierExpression = 59 + CqlParserRULE_qualifierExpression = 60 + CqlParserRULE_simplePath = 61 + CqlParserRULE_simpleLiteral = 62 + CqlParserRULE_expression = 63 + CqlParserRULE_dateTimePrecision = 64 + CqlParserRULE_dateTimeComponent = 65 + CqlParserRULE_pluralDateTimePrecision = 66 + CqlParserRULE_expressionTerm = 67 + CqlParserRULE_caseExpressionItem = 68 + CqlParserRULE_dateTimePrecisionSpecifier = 69 + CqlParserRULE_relativeQualifier = 70 + CqlParserRULE_offsetRelativeQualifier = 71 + CqlParserRULE_exclusiveRelativeQualifier = 72 + CqlParserRULE_quantityOffset = 73 + CqlParserRULE_temporalRelationship = 74 + CqlParserRULE_intervalOperatorPhrase = 75 + CqlParserRULE_term = 76 + CqlParserRULE_qualifiedInvocation = 77 + CqlParserRULE_qualifiedFunction = 78 + CqlParserRULE_invocation = 79 + CqlParserRULE_function = 80 + CqlParserRULE_ratio = 81 + CqlParserRULE_literal = 82 + CqlParserRULE_intervalSelector = 83 + CqlParserRULE_tupleSelector = 84 + CqlParserRULE_tupleElementSelector = 85 + CqlParserRULE_instanceSelector = 86 + CqlParserRULE_instanceElementSelector = 87 + CqlParserRULE_listSelector = 88 + CqlParserRULE_displayClause = 89 + CqlParserRULE_codeSelector = 90 + CqlParserRULE_conceptSelector = 91 + CqlParserRULE_keyword = 92 + CqlParserRULE_reservedWord = 93 + CqlParserRULE_keywordIdentifier = 94 + CqlParserRULE_obsoleteIdentifier = 95 + CqlParserRULE_functionIdentifier = 96 + CqlParserRULE_typeNameIdentifier = 97 + CqlParserRULE_referentialIdentifier = 98 + CqlParserRULE_referentialOrTypeNameIdentifier = 99 + CqlParserRULE_identifierOrFunctionIdentifier = 100 + CqlParserRULE_identifier = 101 + CqlParserRULE_externalConstant = 102 + CqlParserRULE_paramList = 103 + CqlParserRULE_quantity = 104 + CqlParserRULE_unit = 105 +) + +// IDefinitionContext is an interface to support dynamic dispatch. +type IDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + UsingDefinition() IUsingDefinitionContext + IncludeDefinition() IIncludeDefinitionContext + CodesystemDefinition() ICodesystemDefinitionContext + ValuesetDefinition() IValuesetDefinitionContext + CodeDefinition() ICodeDefinitionContext + ConceptDefinition() IConceptDefinitionContext + ParameterDefinition() IParameterDefinitionContext + + // IsDefinitionContext differentiates from other interfaces. + IsDefinitionContext() +} + +type DefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyDefinitionContext() *DefinitionContext { + var p = new(DefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_definition + return p +} + +func InitEmptyDefinitionContext(p *DefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_definition +} + +func (*DefinitionContext) IsDefinitionContext() {} + +func NewDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DefinitionContext { + var p = new(DefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_definition + + return p +} + +func (s *DefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *DefinitionContext) UsingDefinition() IUsingDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IUsingDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IUsingDefinitionContext) +} + +func (s *DefinitionContext) IncludeDefinition() IIncludeDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIncludeDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIncludeDefinitionContext) +} + +func (s *DefinitionContext) CodesystemDefinition() ICodesystemDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodesystemDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodesystemDefinitionContext) +} + +func (s *DefinitionContext) ValuesetDefinition() IValuesetDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IValuesetDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IValuesetDefinitionContext) +} + +func (s *DefinitionContext) CodeDefinition() ICodeDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodeDefinitionContext) +} + +func (s *DefinitionContext) ConceptDefinition() IConceptDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IConceptDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IConceptDefinitionContext) +} + +func (s *DefinitionContext) ParameterDefinition() IParameterDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IParameterDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IParameterDefinitionContext) +} + +func (s *DefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *DefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Definition() (localctx IDefinitionContext) { + localctx = NewDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 0, CqlParserRULE_definition) + p.SetState(219) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 0, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(212) + p.UsingDefinition() + } + + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(213) + p.IncludeDefinition() + } + + + case 3: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(214) + p.CodesystemDefinition() + } + + + case 4: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(215) + p.ValuesetDefinition() + } + + + case 5: + p.EnterOuterAlt(localctx, 5) + { + p.SetState(216) + p.CodeDefinition() + } + + + case 6: + p.EnterOuterAlt(localctx, 6) + { + p.SetState(217) + p.ConceptDefinition() + } + + + case 7: + p.EnterOuterAlt(localctx, 7) + { + p.SetState(218) + p.ParameterDefinition() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ILibraryContext is an interface to support dynamic dispatch. +type ILibraryContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EOF() antlr.TerminalNode + LibraryDefinition() ILibraryDefinitionContext + AllDefinition() []IDefinitionContext + Definition(i int) IDefinitionContext + AllStatement() []IStatementContext + Statement(i int) IStatementContext + + // IsLibraryContext differentiates from other interfaces. + IsLibraryContext() +} + +type LibraryContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLibraryContext() *LibraryContext { + var p = new(LibraryContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_library + return p +} + +func InitEmptyLibraryContext(p *LibraryContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_library +} + +func (*LibraryContext) IsLibraryContext() {} + +func NewLibraryContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LibraryContext { + var p = new(LibraryContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_library + + return p +} + +func (s *LibraryContext) GetParser() antlr.Parser { return s.parser } + +func (s *LibraryContext) EOF() antlr.TerminalNode { + return s.GetToken(CqlParserEOF, 0) +} + +func (s *LibraryContext) LibraryDefinition() ILibraryDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILibraryDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ILibraryDefinitionContext) +} + +func (s *LibraryContext) AllDefinition() []IDefinitionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IDefinitionContext); ok { + len++ + } + } + + tst := make([]IDefinitionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IDefinitionContext); ok { + tst[i] = t.(IDefinitionContext) + i++ + } + } + + return tst +} + +func (s *LibraryContext) Definition(i int) IDefinitionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDefinitionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IDefinitionContext) +} + +func (s *LibraryContext) AllStatement() []IStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IStatementContext); ok { + len++ + } + } + + tst := make([]IStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IStatementContext); ok { + tst[i] = t.(IStatementContext) + i++ + } + } + + return tst +} + +func (s *LibraryContext) Statement(i int) IStatementContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IStatementContext) +} + +func (s *LibraryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LibraryContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *LibraryContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLibrary(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Library() (localctx ILibraryContext) { + localctx = NewLibraryContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 2, CqlParserRULE_library) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(222) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__0 { + { + p.SetState(221) + p.LibraryDefinition() + } + + } + p.SetState(227) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for ((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & 1316328) != 0) { + { + p.SetState(224) + p.Definition() + } + + + p.SetState(229) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + p.SetState(233) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__26 || _la == CqlParserT__27 { + { + p.SetState(230) + p.Statement() + } + + + p.SetState(235) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(236) + p.Match(CqlParserEOF) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ILibraryDefinitionContext is an interface to support dynamic dispatch. +type ILibraryDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QualifiedIdentifier() IQualifiedIdentifierContext + VersionSpecifier() IVersionSpecifierContext + + // IsLibraryDefinitionContext differentiates from other interfaces. + IsLibraryDefinitionContext() +} + +type LibraryDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLibraryDefinitionContext() *LibraryDefinitionContext { + var p = new(LibraryDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_libraryDefinition + return p +} + +func InitEmptyLibraryDefinitionContext(p *LibraryDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_libraryDefinition +} + +func (*LibraryDefinitionContext) IsLibraryDefinitionContext() {} + +func NewLibraryDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LibraryDefinitionContext { + var p = new(LibraryDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_libraryDefinition + + return p +} + +func (s *LibraryDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *LibraryDefinitionContext) QualifiedIdentifier() IQualifiedIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedIdentifierContext) +} + +func (s *LibraryDefinitionContext) VersionSpecifier() IVersionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IVersionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IVersionSpecifierContext) +} + +func (s *LibraryDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LibraryDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *LibraryDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLibraryDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) LibraryDefinition() (localctx ILibraryDefinitionContext) { + localctx = NewLibraryDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, CqlParserRULE_libraryDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(238) + p.Match(CqlParserT__0) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(239) + p.QualifiedIdentifier() + } + p.SetState(242) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__1 { + { + p.SetState(240) + p.Match(CqlParserT__1) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(241) + p.VersionSpecifier() + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IUsingDefinitionContext is an interface to support dynamic dispatch. +type IUsingDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QualifiedIdentifier() IQualifiedIdentifierContext + VersionSpecifier() IVersionSpecifierContext + LocalIdentifier() ILocalIdentifierContext + + // IsUsingDefinitionContext differentiates from other interfaces. + IsUsingDefinitionContext() +} + +type UsingDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyUsingDefinitionContext() *UsingDefinitionContext { + var p = new(UsingDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_usingDefinition + return p +} + +func InitEmptyUsingDefinitionContext(p *UsingDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_usingDefinition +} + +func (*UsingDefinitionContext) IsUsingDefinitionContext() {} + +func NewUsingDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *UsingDefinitionContext { + var p = new(UsingDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_usingDefinition + + return p +} + +func (s *UsingDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *UsingDefinitionContext) QualifiedIdentifier() IQualifiedIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedIdentifierContext) +} + +func (s *UsingDefinitionContext) VersionSpecifier() IVersionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IVersionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IVersionSpecifierContext) +} + +func (s *UsingDefinitionContext) LocalIdentifier() ILocalIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILocalIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ILocalIdentifierContext) +} + +func (s *UsingDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *UsingDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *UsingDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitUsingDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) UsingDefinition() (localctx IUsingDefinitionContext) { + localctx = NewUsingDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 6, CqlParserRULE_usingDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(244) + p.Match(CqlParserT__2) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(245) + p.QualifiedIdentifier() + } + p.SetState(248) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__1 { + { + p.SetState(246) + p.Match(CqlParserT__1) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(247) + p.VersionSpecifier() + } + + } + p.SetState(252) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__3 { + { + p.SetState(250) + p.Match(CqlParserT__3) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(251) + p.LocalIdentifier() + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IIncludeDefinitionContext is an interface to support dynamic dispatch. +type IIncludeDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QualifiedIdentifier() IQualifiedIdentifierContext + VersionSpecifier() IVersionSpecifierContext + LocalIdentifier() ILocalIdentifierContext + + // IsIncludeDefinitionContext differentiates from other interfaces. + IsIncludeDefinitionContext() +} + +type IncludeDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIncludeDefinitionContext() *IncludeDefinitionContext { + var p = new(IncludeDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_includeDefinition + return p +} + +func InitEmptyIncludeDefinitionContext(p *IncludeDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_includeDefinition +} + +func (*IncludeDefinitionContext) IsIncludeDefinitionContext() {} + +func NewIncludeDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IncludeDefinitionContext { + var p = new(IncludeDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_includeDefinition + + return p +} + +func (s *IncludeDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *IncludeDefinitionContext) QualifiedIdentifier() IQualifiedIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedIdentifierContext) +} + +func (s *IncludeDefinitionContext) VersionSpecifier() IVersionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IVersionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IVersionSpecifierContext) +} + +func (s *IncludeDefinitionContext) LocalIdentifier() ILocalIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILocalIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ILocalIdentifierContext) +} + +func (s *IncludeDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IncludeDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *IncludeDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIncludeDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) IncludeDefinition() (localctx IIncludeDefinitionContext) { + localctx = NewIncludeDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 8, CqlParserRULE_includeDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(254) + p.Match(CqlParserT__4) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(255) + p.QualifiedIdentifier() + } + p.SetState(258) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__1 { + { + p.SetState(256) + p.Match(CqlParserT__1) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(257) + p.VersionSpecifier() + } + + } + p.SetState(262) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__3 { + { + p.SetState(260) + p.Match(CqlParserT__3) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(261) + p.LocalIdentifier() + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ILocalIdentifierContext is an interface to support dynamic dispatch. +type ILocalIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + + // IsLocalIdentifierContext differentiates from other interfaces. + IsLocalIdentifierContext() +} + +type LocalIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLocalIdentifierContext() *LocalIdentifierContext { + var p = new(LocalIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_localIdentifier + return p +} + +func InitEmptyLocalIdentifierContext(p *LocalIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_localIdentifier +} + +func (*LocalIdentifierContext) IsLocalIdentifierContext() {} + +func NewLocalIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LocalIdentifierContext { + var p = new(LocalIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_localIdentifier + + return p +} + +func (s *LocalIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *LocalIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *LocalIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LocalIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *LocalIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLocalIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) LocalIdentifier() (localctx ILocalIdentifierContext) { + localctx = NewLocalIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 10, CqlParserRULE_localIdentifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(264) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IAccessModifierContext is an interface to support dynamic dispatch. +type IAccessModifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsAccessModifierContext differentiates from other interfaces. + IsAccessModifierContext() +} + +type AccessModifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyAccessModifierContext() *AccessModifierContext { + var p = new(AccessModifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_accessModifier + return p +} + +func InitEmptyAccessModifierContext(p *AccessModifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_accessModifier +} + +func (*AccessModifierContext) IsAccessModifierContext() {} + +func NewAccessModifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AccessModifierContext { + var p = new(AccessModifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_accessModifier + + return p +} + +func (s *AccessModifierContext) GetParser() antlr.Parser { return s.parser } +func (s *AccessModifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AccessModifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *AccessModifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitAccessModifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) AccessModifier() (localctx IAccessModifierContext) { + localctx = NewAccessModifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 12, CqlParserRULE_accessModifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(266) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__5 || _la == CqlParserT__6) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IParameterDefinitionContext is an interface to support dynamic dispatch. +type IParameterDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + AccessModifier() IAccessModifierContext + TypeSpecifier() ITypeSpecifierContext + Expression() IExpressionContext + + // IsParameterDefinitionContext differentiates from other interfaces. + IsParameterDefinitionContext() +} + +type ParameterDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyParameterDefinitionContext() *ParameterDefinitionContext { + var p = new(ParameterDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_parameterDefinition + return p +} + +func InitEmptyParameterDefinitionContext(p *ParameterDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_parameterDefinition +} + +func (*ParameterDefinitionContext) IsParameterDefinitionContext() {} + +func NewParameterDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ParameterDefinitionContext { + var p = new(ParameterDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_parameterDefinition + + return p +} + +func (s *ParameterDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *ParameterDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ParameterDefinitionContext) AccessModifier() IAccessModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAccessModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAccessModifierContext) +} + +func (s *ParameterDefinitionContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ParameterDefinitionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *ParameterDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ParameterDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ParameterDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitParameterDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ParameterDefinition() (localctx IParameterDefinitionContext) { + localctx = NewParameterDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 14, CqlParserRULE_parameterDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(269) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__5 || _la == CqlParserT__6 { + { + p.SetState(268) + p.AccessModifier() + } + + } + { + p.SetState(271) + p.Match(CqlParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(272) + p.Identifier() + } + p.SetState(274) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 10, p.GetParserRuleContext()) == 1 { + { + p.SetState(273) + p.TypeSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(278) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__8 { + { + p.SetState(276) + p.Match(CqlParserT__8) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(277) + p.expression(0) + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodesystemDefinitionContext is an interface to support dynamic dispatch. +type ICodesystemDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + CodesystemId() ICodesystemIdContext + AccessModifier() IAccessModifierContext + VersionSpecifier() IVersionSpecifierContext + + // IsCodesystemDefinitionContext differentiates from other interfaces. + IsCodesystemDefinitionContext() +} + +type CodesystemDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodesystemDefinitionContext() *CodesystemDefinitionContext { + var p = new(CodesystemDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystemDefinition + return p +} + +func InitEmptyCodesystemDefinitionContext(p *CodesystemDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystemDefinition +} + +func (*CodesystemDefinitionContext) IsCodesystemDefinitionContext() {} + +func NewCodesystemDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodesystemDefinitionContext { + var p = new(CodesystemDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codesystemDefinition + + return p +} + +func (s *CodesystemDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodesystemDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *CodesystemDefinitionContext) CodesystemId() ICodesystemIdContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodesystemIdContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodesystemIdContext) +} + +func (s *CodesystemDefinitionContext) AccessModifier() IAccessModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAccessModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAccessModifierContext) +} + +func (s *CodesystemDefinitionContext) VersionSpecifier() IVersionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IVersionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IVersionSpecifierContext) +} + +func (s *CodesystemDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodesystemDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodesystemDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodesystemDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodesystemDefinition() (localctx ICodesystemDefinitionContext) { + localctx = NewCodesystemDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 16, CqlParserRULE_codesystemDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(281) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__5 || _la == CqlParserT__6 { + { + p.SetState(280) + p.AccessModifier() + } + + } + { + p.SetState(283) + p.Match(CqlParserT__9) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(284) + p.Identifier() + } + { + p.SetState(285) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(286) + p.CodesystemId() + } + p.SetState(289) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__1 { + { + p.SetState(287) + p.Match(CqlParserT__1) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(288) + p.VersionSpecifier() + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IValuesetDefinitionContext is an interface to support dynamic dispatch. +type IValuesetDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + ValuesetId() IValuesetIdContext + AccessModifier() IAccessModifierContext + VersionSpecifier() IVersionSpecifierContext + Codesystems() ICodesystemsContext + + // IsValuesetDefinitionContext differentiates from other interfaces. + IsValuesetDefinitionContext() +} + +type ValuesetDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyValuesetDefinitionContext() *ValuesetDefinitionContext { + var p = new(ValuesetDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_valuesetDefinition + return p +} + +func InitEmptyValuesetDefinitionContext(p *ValuesetDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_valuesetDefinition +} + +func (*ValuesetDefinitionContext) IsValuesetDefinitionContext() {} + +func NewValuesetDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ValuesetDefinitionContext { + var p = new(ValuesetDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_valuesetDefinition + + return p +} + +func (s *ValuesetDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *ValuesetDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ValuesetDefinitionContext) ValuesetId() IValuesetIdContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IValuesetIdContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IValuesetIdContext) +} + +func (s *ValuesetDefinitionContext) AccessModifier() IAccessModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAccessModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAccessModifierContext) +} + +func (s *ValuesetDefinitionContext) VersionSpecifier() IVersionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IVersionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IVersionSpecifierContext) +} + +func (s *ValuesetDefinitionContext) Codesystems() ICodesystemsContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodesystemsContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodesystemsContext) +} + +func (s *ValuesetDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ValuesetDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ValuesetDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitValuesetDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ValuesetDefinition() (localctx IValuesetDefinitionContext) { + localctx = NewValuesetDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 18, CqlParserRULE_valuesetDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(292) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__5 || _la == CqlParserT__6 { + { + p.SetState(291) + p.AccessModifier() + } + + } + { + p.SetState(294) + p.Match(CqlParserT__11) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(295) + p.Identifier() + } + { + p.SetState(296) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(297) + p.ValuesetId() + } + p.SetState(300) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__1 { + { + p.SetState(298) + p.Match(CqlParserT__1) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(299) + p.VersionSpecifier() + } + + } + p.SetState(303) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__12 { + { + p.SetState(302) + p.Codesystems() + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodesystemsContext is an interface to support dynamic dispatch. +type ICodesystemsContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllCodesystemIdentifier() []ICodesystemIdentifierContext + CodesystemIdentifier(i int) ICodesystemIdentifierContext + + // IsCodesystemsContext differentiates from other interfaces. + IsCodesystemsContext() +} + +type CodesystemsContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodesystemsContext() *CodesystemsContext { + var p = new(CodesystemsContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystems + return p +} + +func InitEmptyCodesystemsContext(p *CodesystemsContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystems +} + +func (*CodesystemsContext) IsCodesystemsContext() {} + +func NewCodesystemsContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodesystemsContext { + var p = new(CodesystemsContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codesystems + + return p +} + +func (s *CodesystemsContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodesystemsContext) AllCodesystemIdentifier() []ICodesystemIdentifierContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ICodesystemIdentifierContext); ok { + len++ + } + } + + tst := make([]ICodesystemIdentifierContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ICodesystemIdentifierContext); ok { + tst[i] = t.(ICodesystemIdentifierContext) + i++ + } + } + + return tst +} + +func (s *CodesystemsContext) CodesystemIdentifier(i int) ICodesystemIdentifierContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodesystemIdentifierContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ICodesystemIdentifierContext) +} + +func (s *CodesystemsContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodesystemsContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodesystemsContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodesystems(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Codesystems() (localctx ICodesystemsContext) { + localctx = NewCodesystemsContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 20, CqlParserRULE_codesystems) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(305) + p.Match(CqlParserT__12) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(306) + p.Match(CqlParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(307) + p.CodesystemIdentifier() + } + p.SetState(312) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(308) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(309) + p.CodesystemIdentifier() + } + + + p.SetState(314) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(315) + p.Match(CqlParserT__15) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodesystemIdentifierContext is an interface to support dynamic dispatch. +type ICodesystemIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + LibraryIdentifier() ILibraryIdentifierContext + + // IsCodesystemIdentifierContext differentiates from other interfaces. + IsCodesystemIdentifierContext() +} + +type CodesystemIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodesystemIdentifierContext() *CodesystemIdentifierContext { + var p = new(CodesystemIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystemIdentifier + return p +} + +func InitEmptyCodesystemIdentifierContext(p *CodesystemIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystemIdentifier +} + +func (*CodesystemIdentifierContext) IsCodesystemIdentifierContext() {} + +func NewCodesystemIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodesystemIdentifierContext { + var p = new(CodesystemIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codesystemIdentifier + + return p +} + +func (s *CodesystemIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodesystemIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *CodesystemIdentifierContext) LibraryIdentifier() ILibraryIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILibraryIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ILibraryIdentifierContext) +} + +func (s *CodesystemIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodesystemIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodesystemIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodesystemIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodesystemIdentifier() (localctx ICodesystemIdentifierContext) { + localctx = NewCodesystemIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 22, CqlParserRULE_codesystemIdentifier) + p.EnterOuterAlt(localctx, 1) + p.SetState(320) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 18, p.GetParserRuleContext()) == 1 { + { + p.SetState(317) + p.LibraryIdentifier() + } + { + p.SetState(318) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(322) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ILibraryIdentifierContext is an interface to support dynamic dispatch. +type ILibraryIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + + // IsLibraryIdentifierContext differentiates from other interfaces. + IsLibraryIdentifierContext() +} + +type LibraryIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLibraryIdentifierContext() *LibraryIdentifierContext { + var p = new(LibraryIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_libraryIdentifier + return p +} + +func InitEmptyLibraryIdentifierContext(p *LibraryIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_libraryIdentifier +} + +func (*LibraryIdentifierContext) IsLibraryIdentifierContext() {} + +func NewLibraryIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LibraryIdentifierContext { + var p = new(LibraryIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_libraryIdentifier + + return p +} + +func (s *LibraryIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *LibraryIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *LibraryIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LibraryIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *LibraryIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLibraryIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) LibraryIdentifier() (localctx ILibraryIdentifierContext) { + localctx = NewLibraryIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 24, CqlParserRULE_libraryIdentifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(324) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodeDefinitionContext is an interface to support dynamic dispatch. +type ICodeDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + CodeId() ICodeIdContext + CodesystemIdentifier() ICodesystemIdentifierContext + AccessModifier() IAccessModifierContext + DisplayClause() IDisplayClauseContext + + // IsCodeDefinitionContext differentiates from other interfaces. + IsCodeDefinitionContext() +} + +type CodeDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodeDefinitionContext() *CodeDefinitionContext { + var p = new(CodeDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeDefinition + return p +} + +func InitEmptyCodeDefinitionContext(p *CodeDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeDefinition +} + +func (*CodeDefinitionContext) IsCodeDefinitionContext() {} + +func NewCodeDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodeDefinitionContext { + var p = new(CodeDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codeDefinition + + return p +} + +func (s *CodeDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodeDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *CodeDefinitionContext) CodeId() ICodeIdContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeIdContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodeIdContext) +} + +func (s *CodeDefinitionContext) CodesystemIdentifier() ICodesystemIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodesystemIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodesystemIdentifierContext) +} + +func (s *CodeDefinitionContext) AccessModifier() IAccessModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAccessModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAccessModifierContext) +} + +func (s *CodeDefinitionContext) DisplayClause() IDisplayClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDisplayClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDisplayClauseContext) +} + +func (s *CodeDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodeDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodeDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodeDefinition() (localctx ICodeDefinitionContext) { + localctx = NewCodeDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 26, CqlParserRULE_codeDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(327) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__5 || _la == CqlParserT__6 { + { + p.SetState(326) + p.AccessModifier() + } + + } + { + p.SetState(329) + p.Match(CqlParserT__17) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(330) + p.Identifier() + } + { + p.SetState(331) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(332) + p.CodeId() + } + { + p.SetState(333) + p.Match(CqlParserT__18) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(334) + p.CodesystemIdentifier() + } + p.SetState(336) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__153 { + { + p.SetState(335) + p.DisplayClause() + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IConceptDefinitionContext is an interface to support dynamic dispatch. +type IConceptDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + AllCodeIdentifier() []ICodeIdentifierContext + CodeIdentifier(i int) ICodeIdentifierContext + AccessModifier() IAccessModifierContext + DisplayClause() IDisplayClauseContext + + // IsConceptDefinitionContext differentiates from other interfaces. + IsConceptDefinitionContext() +} + +type ConceptDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyConceptDefinitionContext() *ConceptDefinitionContext { + var p = new(ConceptDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_conceptDefinition + return p +} + +func InitEmptyConceptDefinitionContext(p *ConceptDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_conceptDefinition +} + +func (*ConceptDefinitionContext) IsConceptDefinitionContext() {} + +func NewConceptDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ConceptDefinitionContext { + var p = new(ConceptDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_conceptDefinition + + return p +} + +func (s *ConceptDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *ConceptDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ConceptDefinitionContext) AllCodeIdentifier() []ICodeIdentifierContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ICodeIdentifierContext); ok { + len++ + } + } + + tst := make([]ICodeIdentifierContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ICodeIdentifierContext); ok { + tst[i] = t.(ICodeIdentifierContext) + i++ + } + } + + return tst +} + +func (s *ConceptDefinitionContext) CodeIdentifier(i int) ICodeIdentifierContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeIdentifierContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ICodeIdentifierContext) +} + +func (s *ConceptDefinitionContext) AccessModifier() IAccessModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAccessModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAccessModifierContext) +} + +func (s *ConceptDefinitionContext) DisplayClause() IDisplayClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDisplayClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDisplayClauseContext) +} + +func (s *ConceptDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConceptDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ConceptDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitConceptDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ConceptDefinition() (localctx IConceptDefinitionContext) { + localctx = NewConceptDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 28, CqlParserRULE_conceptDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(339) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__5 || _la == CqlParserT__6 { + { + p.SetState(338) + p.AccessModifier() + } + + } + { + p.SetState(341) + p.Match(CqlParserT__19) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(342) + p.Identifier() + } + { + p.SetState(343) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(344) + p.Match(CqlParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(345) + p.CodeIdentifier() + } + p.SetState(350) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(346) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(347) + p.CodeIdentifier() + } + + + p.SetState(352) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(353) + p.Match(CqlParserT__15) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(355) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__153 { + { + p.SetState(354) + p.DisplayClause() + } + + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodeIdentifierContext is an interface to support dynamic dispatch. +type ICodeIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + LibraryIdentifier() ILibraryIdentifierContext + + // IsCodeIdentifierContext differentiates from other interfaces. + IsCodeIdentifierContext() +} + +type CodeIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodeIdentifierContext() *CodeIdentifierContext { + var p = new(CodeIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeIdentifier + return p +} + +func InitEmptyCodeIdentifierContext(p *CodeIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeIdentifier +} + +func (*CodeIdentifierContext) IsCodeIdentifierContext() {} + +func NewCodeIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodeIdentifierContext { + var p = new(CodeIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codeIdentifier + + return p +} + +func (s *CodeIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodeIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *CodeIdentifierContext) LibraryIdentifier() ILibraryIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILibraryIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ILibraryIdentifierContext) +} + +func (s *CodeIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodeIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodeIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodeIdentifier() (localctx ICodeIdentifierContext) { + localctx = NewCodeIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 30, CqlParserRULE_codeIdentifier) + p.EnterOuterAlt(localctx, 1) + p.SetState(360) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 24, p.GetParserRuleContext()) == 1 { + { + p.SetState(357) + p.LibraryIdentifier() + } + { + p.SetState(358) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(362) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodesystemIdContext is an interface to support dynamic dispatch. +type ICodesystemIdContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsCodesystemIdContext differentiates from other interfaces. + IsCodesystemIdContext() +} + +type CodesystemIdContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodesystemIdContext() *CodesystemIdContext { + var p = new(CodesystemIdContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystemId + return p +} + +func InitEmptyCodesystemIdContext(p *CodesystemIdContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codesystemId +} + +func (*CodesystemIdContext) IsCodesystemIdContext() {} + +func NewCodesystemIdContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodesystemIdContext { + var p = new(CodesystemIdContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codesystemId + + return p +} + +func (s *CodesystemIdContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodesystemIdContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *CodesystemIdContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodesystemIdContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodesystemIdContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodesystemId(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodesystemId() (localctx ICodesystemIdContext) { + localctx = NewCodesystemIdContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 32, CqlParserRULE_codesystemId) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(364) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IValuesetIdContext is an interface to support dynamic dispatch. +type IValuesetIdContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsValuesetIdContext differentiates from other interfaces. + IsValuesetIdContext() +} + +type ValuesetIdContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyValuesetIdContext() *ValuesetIdContext { + var p = new(ValuesetIdContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_valuesetId + return p +} + +func InitEmptyValuesetIdContext(p *ValuesetIdContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_valuesetId +} + +func (*ValuesetIdContext) IsValuesetIdContext() {} + +func NewValuesetIdContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ValuesetIdContext { + var p = new(ValuesetIdContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_valuesetId + + return p +} + +func (s *ValuesetIdContext) GetParser() antlr.Parser { return s.parser } + +func (s *ValuesetIdContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *ValuesetIdContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ValuesetIdContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ValuesetIdContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitValuesetId(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ValuesetId() (localctx IValuesetIdContext) { + localctx = NewValuesetIdContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 34, CqlParserRULE_valuesetId) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(366) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IVersionSpecifierContext is an interface to support dynamic dispatch. +type IVersionSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsVersionSpecifierContext differentiates from other interfaces. + IsVersionSpecifierContext() +} + +type VersionSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyVersionSpecifierContext() *VersionSpecifierContext { + var p = new(VersionSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_versionSpecifier + return p +} + +func InitEmptyVersionSpecifierContext(p *VersionSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_versionSpecifier +} + +func (*VersionSpecifierContext) IsVersionSpecifierContext() {} + +func NewVersionSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *VersionSpecifierContext { + var p = new(VersionSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_versionSpecifier + + return p +} + +func (s *VersionSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *VersionSpecifierContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *VersionSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *VersionSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *VersionSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitVersionSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) VersionSpecifier() (localctx IVersionSpecifierContext) { + localctx = NewVersionSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 36, CqlParserRULE_versionSpecifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(368) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodeIdContext is an interface to support dynamic dispatch. +type ICodeIdContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsCodeIdContext differentiates from other interfaces. + IsCodeIdContext() +} + +type CodeIdContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodeIdContext() *CodeIdContext { + var p = new(CodeIdContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeId + return p +} + +func InitEmptyCodeIdContext(p *CodeIdContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeId +} + +func (*CodeIdContext) IsCodeIdContext() {} + +func NewCodeIdContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodeIdContext { + var p = new(CodeIdContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codeId + + return p +} + +func (s *CodeIdContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodeIdContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *CodeIdContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeIdContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodeIdContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodeId(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodeId() (localctx ICodeIdContext) { + localctx = NewCodeIdContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 38, CqlParserRULE_codeId) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(370) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITypeSpecifierContext is an interface to support dynamic dispatch. +type ITypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + NamedTypeSpecifier() INamedTypeSpecifierContext + ListTypeSpecifier() IListTypeSpecifierContext + IntervalTypeSpecifier() IIntervalTypeSpecifierContext + TupleTypeSpecifier() ITupleTypeSpecifierContext + ChoiceTypeSpecifier() IChoiceTypeSpecifierContext + + // IsTypeSpecifierContext differentiates from other interfaces. + IsTypeSpecifierContext() +} + +type TypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTypeSpecifierContext() *TypeSpecifierContext { + var p = new(TypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_typeSpecifier + return p +} + +func InitEmptyTypeSpecifierContext(p *TypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_typeSpecifier +} + +func (*TypeSpecifierContext) IsTypeSpecifierContext() {} + +func NewTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeSpecifierContext { + var p = new(TypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_typeSpecifier + + return p +} + +func (s *TypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *TypeSpecifierContext) NamedTypeSpecifier() INamedTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(INamedTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(INamedTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) ListTypeSpecifier() IListTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IListTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IListTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) IntervalTypeSpecifier() IIntervalTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntervalTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIntervalTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) TupleTypeSpecifier() ITupleTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITupleTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) ChoiceTypeSpecifier() IChoiceTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IChoiceTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IChoiceTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) TypeSpecifier() (localctx ITypeSpecifierContext) { + localctx = NewTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 40, CqlParserRULE_typeSpecifier) + p.SetState(377) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__17, CqlParserT__19, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__44, CqlParserT__49, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__73, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__105, CqlParserT__106, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__118, CqlParserT__119, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__140, CqlParserT__141, CqlParserT__144, CqlParserT__148, CqlParserT__149, CqlParserT__153, CqlParserT__154, CqlParserT__155, CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(372) + p.NamedTypeSpecifier() + } + + + case CqlParserT__20: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(373) + p.ListTypeSpecifier() + } + + + case CqlParserT__23: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(374) + p.IntervalTypeSpecifier() + } + + + case CqlParserT__24: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(375) + p.TupleTypeSpecifier() + } + + + case CqlParserT__25: + p.EnterOuterAlt(localctx, 5) + { + p.SetState(376) + p.ChoiceTypeSpecifier() + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// INamedTypeSpecifierContext is an interface to support dynamic dispatch. +type INamedTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialOrTypeNameIdentifier() IReferentialOrTypeNameIdentifierContext + AllQualifier() []IQualifierContext + Qualifier(i int) IQualifierContext + + // IsNamedTypeSpecifierContext differentiates from other interfaces. + IsNamedTypeSpecifierContext() +} + +type NamedTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyNamedTypeSpecifierContext() *NamedTypeSpecifierContext { + var p = new(NamedTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_namedTypeSpecifier + return p +} + +func InitEmptyNamedTypeSpecifierContext(p *NamedTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_namedTypeSpecifier +} + +func (*NamedTypeSpecifierContext) IsNamedTypeSpecifierContext() {} + +func NewNamedTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *NamedTypeSpecifierContext { + var p = new(NamedTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_namedTypeSpecifier + + return p +} + +func (s *NamedTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *NamedTypeSpecifierContext) ReferentialOrTypeNameIdentifier() IReferentialOrTypeNameIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialOrTypeNameIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialOrTypeNameIdentifierContext) +} + +func (s *NamedTypeSpecifierContext) AllQualifier() []IQualifierContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IQualifierContext); ok { + len++ + } + } + + tst := make([]IQualifierContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IQualifierContext); ok { + tst[i] = t.(IQualifierContext) + i++ + } + } + + return tst +} + +func (s *NamedTypeSpecifierContext) Qualifier(i int) IQualifierContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifierContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IQualifierContext) +} + +func (s *NamedTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *NamedTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *NamedTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitNamedTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) NamedTypeSpecifier() (localctx INamedTypeSpecifierContext) { + localctx = NewNamedTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 42, CqlParserRULE_namedTypeSpecifier) + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(384) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 26, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(379) + p.Qualifier() + } + { + p.SetState(380) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + } + p.SetState(386) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 26, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + { + p.SetState(387) + p.ReferentialOrTypeNameIdentifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IModelIdentifierContext is an interface to support dynamic dispatch. +type IModelIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + + // IsModelIdentifierContext differentiates from other interfaces. + IsModelIdentifierContext() +} + +type ModelIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyModelIdentifierContext() *ModelIdentifierContext { + var p = new(ModelIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_modelIdentifier + return p +} + +func InitEmptyModelIdentifierContext(p *ModelIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_modelIdentifier +} + +func (*ModelIdentifierContext) IsModelIdentifierContext() {} + +func NewModelIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ModelIdentifierContext { + var p = new(ModelIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_modelIdentifier + + return p +} + +func (s *ModelIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ModelIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ModelIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ModelIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ModelIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitModelIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ModelIdentifier() (localctx IModelIdentifierContext) { + localctx = NewModelIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 44, CqlParserRULE_modelIdentifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(389) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IListTypeSpecifierContext is an interface to support dynamic dispatch. +type IListTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + TypeSpecifier() ITypeSpecifierContext + + // IsListTypeSpecifierContext differentiates from other interfaces. + IsListTypeSpecifierContext() +} + +type ListTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyListTypeSpecifierContext() *ListTypeSpecifierContext { + var p = new(ListTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_listTypeSpecifier + return p +} + +func InitEmptyListTypeSpecifierContext(p *ListTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_listTypeSpecifier +} + +func (*ListTypeSpecifierContext) IsListTypeSpecifierContext() {} + +func NewListTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ListTypeSpecifierContext { + var p = new(ListTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_listTypeSpecifier + + return p +} + +func (s *ListTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ListTypeSpecifierContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ListTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ListTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ListTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitListTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ListTypeSpecifier() (localctx IListTypeSpecifierContext) { + localctx = NewListTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 46, CqlParserRULE_listTypeSpecifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(391) + p.Match(CqlParserT__20) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(392) + p.Match(CqlParserT__21) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(393) + p.TypeSpecifier() + } + { + p.SetState(394) + p.Match(CqlParserT__22) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IIntervalTypeSpecifierContext is an interface to support dynamic dispatch. +type IIntervalTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + TypeSpecifier() ITypeSpecifierContext + + // IsIntervalTypeSpecifierContext differentiates from other interfaces. + IsIntervalTypeSpecifierContext() +} + +type IntervalTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIntervalTypeSpecifierContext() *IntervalTypeSpecifierContext { + var p = new(IntervalTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_intervalTypeSpecifier + return p +} + +func InitEmptyIntervalTypeSpecifierContext(p *IntervalTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_intervalTypeSpecifier +} + +func (*IntervalTypeSpecifierContext) IsIntervalTypeSpecifierContext() {} + +func NewIntervalTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IntervalTypeSpecifierContext { + var p = new(IntervalTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_intervalTypeSpecifier + + return p +} + +func (s *IntervalTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *IntervalTypeSpecifierContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *IntervalTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntervalTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *IntervalTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIntervalTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) IntervalTypeSpecifier() (localctx IIntervalTypeSpecifierContext) { + localctx = NewIntervalTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 48, CqlParserRULE_intervalTypeSpecifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(396) + p.Match(CqlParserT__23) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(397) + p.Match(CqlParserT__21) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(398) + p.TypeSpecifier() + } + { + p.SetState(399) + p.Match(CqlParserT__22) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITupleTypeSpecifierContext is an interface to support dynamic dispatch. +type ITupleTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllTupleElementDefinition() []ITupleElementDefinitionContext + TupleElementDefinition(i int) ITupleElementDefinitionContext + + // IsTupleTypeSpecifierContext differentiates from other interfaces. + IsTupleTypeSpecifierContext() +} + +type TupleTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleTypeSpecifierContext() *TupleTypeSpecifierContext { + var p = new(TupleTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleTypeSpecifier + return p +} + +func InitEmptyTupleTypeSpecifierContext(p *TupleTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleTypeSpecifier +} + +func (*TupleTypeSpecifierContext) IsTupleTypeSpecifierContext() {} + +func NewTupleTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleTypeSpecifierContext { + var p = new(TupleTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_tupleTypeSpecifier + + return p +} + +func (s *TupleTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleTypeSpecifierContext) AllTupleElementDefinition() []ITupleElementDefinitionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITupleElementDefinitionContext); ok { + len++ + } + } + + tst := make([]ITupleElementDefinitionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITupleElementDefinitionContext); ok { + tst[i] = t.(ITupleElementDefinitionContext) + i++ + } + } + + return tst +} + +func (s *TupleTypeSpecifierContext) TupleElementDefinition(i int) ITupleElementDefinitionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleElementDefinitionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITupleElementDefinitionContext) +} + +func (s *TupleTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TupleTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTupleTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) TupleTypeSpecifier() (localctx ITupleTypeSpecifierContext) { + localctx = NewTupleTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 50, CqlParserRULE_tupleTypeSpecifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(401) + p.Match(CqlParserT__24) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(402) + p.Match(CqlParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(403) + p.TupleElementDefinition() + } + p.SetState(408) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(404) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(405) + p.TupleElementDefinition() + } + + + p.SetState(410) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(411) + p.Match(CqlParserT__15) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITupleElementDefinitionContext is an interface to support dynamic dispatch. +type ITupleElementDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + TypeSpecifier() ITypeSpecifierContext + + // IsTupleElementDefinitionContext differentiates from other interfaces. + IsTupleElementDefinitionContext() +} + +type TupleElementDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleElementDefinitionContext() *TupleElementDefinitionContext { + var p = new(TupleElementDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleElementDefinition + return p +} + +func InitEmptyTupleElementDefinitionContext(p *TupleElementDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleElementDefinition +} + +func (*TupleElementDefinitionContext) IsTupleElementDefinitionContext() {} + +func NewTupleElementDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleElementDefinitionContext { + var p = new(TupleElementDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_tupleElementDefinition + + return p +} + +func (s *TupleElementDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleElementDefinitionContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *TupleElementDefinitionContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *TupleElementDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleElementDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TupleElementDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTupleElementDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) TupleElementDefinition() (localctx ITupleElementDefinitionContext) { + localctx = NewTupleElementDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 52, CqlParserRULE_tupleElementDefinition) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(413) + p.ReferentialIdentifier() + } + { + p.SetState(414) + p.TypeSpecifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IChoiceTypeSpecifierContext is an interface to support dynamic dispatch. +type IChoiceTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllTypeSpecifier() []ITypeSpecifierContext + TypeSpecifier(i int) ITypeSpecifierContext + + // IsChoiceTypeSpecifierContext differentiates from other interfaces. + IsChoiceTypeSpecifierContext() +} + +type ChoiceTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyChoiceTypeSpecifierContext() *ChoiceTypeSpecifierContext { + var p = new(ChoiceTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_choiceTypeSpecifier + return p +} + +func InitEmptyChoiceTypeSpecifierContext(p *ChoiceTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_choiceTypeSpecifier +} + +func (*ChoiceTypeSpecifierContext) IsChoiceTypeSpecifierContext() {} + +func NewChoiceTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ChoiceTypeSpecifierContext { + var p = new(ChoiceTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_choiceTypeSpecifier + + return p +} + +func (s *ChoiceTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ChoiceTypeSpecifierContext) AllTypeSpecifier() []ITypeSpecifierContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITypeSpecifierContext); ok { + len++ + } + } + + tst := make([]ITypeSpecifierContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITypeSpecifierContext); ok { + tst[i] = t.(ITypeSpecifierContext) + i++ + } + } + + return tst +} + +func (s *ChoiceTypeSpecifierContext) TypeSpecifier(i int) ITypeSpecifierContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ChoiceTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ChoiceTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ChoiceTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitChoiceTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ChoiceTypeSpecifier() (localctx IChoiceTypeSpecifierContext) { + localctx = NewChoiceTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 54, CqlParserRULE_choiceTypeSpecifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(416) + p.Match(CqlParserT__25) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(417) + p.Match(CqlParserT__21) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(418) + p.TypeSpecifier() + } + p.SetState(423) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(419) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(420) + p.TypeSpecifier() + } + + + p.SetState(425) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(426) + p.Match(CqlParserT__22) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IStatementContext is an interface to support dynamic dispatch. +type IStatementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ExpressionDefinition() IExpressionDefinitionContext + ContextDefinition() IContextDefinitionContext + FunctionDefinition() IFunctionDefinitionContext + + // IsStatementContext differentiates from other interfaces. + IsStatementContext() +} + +type StatementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyStatementContext() *StatementContext { + var p = new(StatementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_statement + return p +} + +func InitEmptyStatementContext(p *StatementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_statement +} + +func (*StatementContext) IsStatementContext() {} + +func NewStatementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *StatementContext { + var p = new(StatementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_statement + + return p +} + +func (s *StatementContext) GetParser() antlr.Parser { return s.parser } + +func (s *StatementContext) ExpressionDefinition() IExpressionDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionDefinitionContext) +} + +func (s *StatementContext) ContextDefinition() IContextDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IContextDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IContextDefinitionContext) +} + +func (s *StatementContext) FunctionDefinition() IFunctionDefinitionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFunctionDefinitionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IFunctionDefinitionContext) +} + +func (s *StatementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StatementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *StatementContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitStatement(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Statement() (localctx IStatementContext) { + localctx = NewStatementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 56, CqlParserRULE_statement) + p.SetState(431) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 29, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(428) + p.ExpressionDefinition() + } + + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(429) + p.ContextDefinition() + } + + + case 3: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(430) + p.FunctionDefinition() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IExpressionDefinitionContext is an interface to support dynamic dispatch. +type IExpressionDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + Expression() IExpressionContext + AccessModifier() IAccessModifierContext + + // IsExpressionDefinitionContext differentiates from other interfaces. + IsExpressionDefinitionContext() +} + +type ExpressionDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyExpressionDefinitionContext() *ExpressionDefinitionContext { + var p = new(ExpressionDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_expressionDefinition + return p +} + +func InitEmptyExpressionDefinitionContext(p *ExpressionDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_expressionDefinition +} + +func (*ExpressionDefinitionContext) IsExpressionDefinitionContext() {} + +func NewExpressionDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExpressionDefinitionContext { + var p = new(ExpressionDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_expressionDefinition + + return p +} + +func (s *ExpressionDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *ExpressionDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ExpressionDefinitionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *ExpressionDefinitionContext) AccessModifier() IAccessModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAccessModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAccessModifierContext) +} + +func (s *ExpressionDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExpressionDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ExpressionDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitExpressionDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ExpressionDefinition() (localctx IExpressionDefinitionContext) { + localctx = NewExpressionDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 58, CqlParserRULE_expressionDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(433) + p.Match(CqlParserT__26) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(435) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__5 || _la == CqlParserT__6 { + { + p.SetState(434) + p.AccessModifier() + } + + } + { + p.SetState(437) + p.Identifier() + } + { + p.SetState(438) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(439) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IContextDefinitionContext is an interface to support dynamic dispatch. +type IContextDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + ModelIdentifier() IModelIdentifierContext + + // IsContextDefinitionContext differentiates from other interfaces. + IsContextDefinitionContext() +} + +type ContextDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyContextDefinitionContext() *ContextDefinitionContext { + var p = new(ContextDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_contextDefinition + return p +} + +func InitEmptyContextDefinitionContext(p *ContextDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_contextDefinition +} + +func (*ContextDefinitionContext) IsContextDefinitionContext() {} + +func NewContextDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ContextDefinitionContext { + var p = new(ContextDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_contextDefinition + + return p +} + +func (s *ContextDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *ContextDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ContextDefinitionContext) ModelIdentifier() IModelIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IModelIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IModelIdentifierContext) +} + +func (s *ContextDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ContextDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ContextDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitContextDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ContextDefinition() (localctx IContextDefinitionContext) { + localctx = NewContextDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 60, CqlParserRULE_contextDefinition) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(441) + p.Match(CqlParserT__27) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(445) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 31, p.GetParserRuleContext()) == 1 { + { + p.SetState(442) + p.ModelIdentifier() + } + { + p.SetState(443) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(447) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IFluentModifierContext is an interface to support dynamic dispatch. +type IFluentModifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsFluentModifierContext differentiates from other interfaces. + IsFluentModifierContext() +} + +type FluentModifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFluentModifierContext() *FluentModifierContext { + var p = new(FluentModifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_fluentModifier + return p +} + +func InitEmptyFluentModifierContext(p *FluentModifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_fluentModifier +} + +func (*FluentModifierContext) IsFluentModifierContext() {} + +func NewFluentModifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FluentModifierContext { + var p = new(FluentModifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_fluentModifier + + return p +} + +func (s *FluentModifierContext) GetParser() antlr.Parser { return s.parser } +func (s *FluentModifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FluentModifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *FluentModifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitFluentModifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) FluentModifier() (localctx IFluentModifierContext) { + localctx = NewFluentModifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 62, CqlParserRULE_fluentModifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(449) + p.Match(CqlParserT__28) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IFunctionDefinitionContext is an interface to support dynamic dispatch. +type IFunctionDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IdentifierOrFunctionIdentifier() IIdentifierOrFunctionIdentifierContext + FunctionBody() IFunctionBodyContext + AccessModifier() IAccessModifierContext + FluentModifier() IFluentModifierContext + AllOperandDefinition() []IOperandDefinitionContext + OperandDefinition(i int) IOperandDefinitionContext + TypeSpecifier() ITypeSpecifierContext + + // IsFunctionDefinitionContext differentiates from other interfaces. + IsFunctionDefinitionContext() +} + +type FunctionDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFunctionDefinitionContext() *FunctionDefinitionContext { + var p = new(FunctionDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_functionDefinition + return p +} + +func InitEmptyFunctionDefinitionContext(p *FunctionDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_functionDefinition +} + +func (*FunctionDefinitionContext) IsFunctionDefinitionContext() {} + +func NewFunctionDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FunctionDefinitionContext { + var p = new(FunctionDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_functionDefinition + + return p +} + +func (s *FunctionDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *FunctionDefinitionContext) IdentifierOrFunctionIdentifier() IIdentifierOrFunctionIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierOrFunctionIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierOrFunctionIdentifierContext) +} + +func (s *FunctionDefinitionContext) FunctionBody() IFunctionBodyContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFunctionBodyContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IFunctionBodyContext) +} + +func (s *FunctionDefinitionContext) AccessModifier() IAccessModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAccessModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAccessModifierContext) +} + +func (s *FunctionDefinitionContext) FluentModifier() IFluentModifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFluentModifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IFluentModifierContext) +} + +func (s *FunctionDefinitionContext) AllOperandDefinition() []IOperandDefinitionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IOperandDefinitionContext); ok { + len++ + } + } + + tst := make([]IOperandDefinitionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IOperandDefinitionContext); ok { + tst[i] = t.(IOperandDefinitionContext) + i++ + } + } + + return tst +} + +func (s *FunctionDefinitionContext) OperandDefinition(i int) IOperandDefinitionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOperandDefinitionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IOperandDefinitionContext) +} + +func (s *FunctionDefinitionContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *FunctionDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FunctionDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *FunctionDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitFunctionDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) FunctionDefinition() (localctx IFunctionDefinitionContext) { + localctx = NewFunctionDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 64, CqlParserRULE_functionDefinition) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(451) + p.Match(CqlParserT__26) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(453) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__5 || _la == CqlParserT__6 { + { + p.SetState(452) + p.AccessModifier() + } + + } + p.SetState(456) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__28 { + { + p.SetState(455) + p.FluentModifier() + } + + } + { + p.SetState(458) + p.Match(CqlParserT__29) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(459) + p.IdentifierOrFunctionIdentifier() + } + { + p.SetState(460) + p.Match(CqlParserT__30) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(469) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & 140772674742007806) != 0) || ((int64((_la - 74)) & ^0x3f) == 0 && ((int64(1) << (_la - 74)) & 2161833627658158317) != 0) || ((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 12722963) != 0) { + { + p.SetState(461) + p.OperandDefinition() + } + p.SetState(466) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(462) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(463) + p.OperandDefinition() + } + + + p.SetState(468) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + } + { + p.SetState(471) + p.Match(CqlParserT__31) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(474) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__32 { + { + p.SetState(472) + p.Match(CqlParserT__32) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(473) + p.TypeSpecifier() + } + + } + { + p.SetState(476) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(479) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__13, CqlParserT__17, CqlParserT__18, CqlParserT__19, CqlParserT__20, CqlParserT__23, CqlParserT__24, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__30, CqlParserT__37, CqlParserT__44, CqlParserT__47, CqlParserT__49, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__57, CqlParserT__58, CqlParserT__59, CqlParserT__60, CqlParserT__62, CqlParserT__63, CqlParserT__67, CqlParserT__68, CqlParserT__73, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__81, CqlParserT__82, CqlParserT__83, CqlParserT__84, CqlParserT__85, CqlParserT__86, CqlParserT__87, CqlParserT__88, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__93, CqlParserT__94, CqlParserT__95, CqlParserT__96, CqlParserT__97, CqlParserT__98, CqlParserT__99, CqlParserT__100, CqlParserT__101, CqlParserT__103, CqlParserT__104, CqlParserT__105, CqlParserT__106, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__111, CqlParserT__112, CqlParserT__113, CqlParserT__114, CqlParserT__118, CqlParserT__119, CqlParserT__121, CqlParserT__124, CqlParserT__125, CqlParserT__126, CqlParserT__127, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__140, CqlParserT__141, CqlParserT__144, CqlParserT__148, CqlParserT__149, CqlParserT__150, CqlParserT__151, CqlParserT__152, CqlParserT__153, CqlParserT__154, CqlParserT__155, CqlParserT__156, CqlParserQUOTEDIDENTIFIER, CqlParserDATETIME, CqlParserLONGNUMBER, CqlParserDATE, CqlParserTIME, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER, CqlParserSTRING, CqlParserNUMBER: + { + p.SetState(477) + p.FunctionBody() + } + + + case CqlParserT__33: + { + p.SetState(478) + p.Match(CqlParserT__33) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IOperandDefinitionContext is an interface to support dynamic dispatch. +type IOperandDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + TypeSpecifier() ITypeSpecifierContext + + // IsOperandDefinitionContext differentiates from other interfaces. + IsOperandDefinitionContext() +} + +type OperandDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOperandDefinitionContext() *OperandDefinitionContext { + var p = new(OperandDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_operandDefinition + return p +} + +func InitEmptyOperandDefinitionContext(p *OperandDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_operandDefinition +} + +func (*OperandDefinitionContext) IsOperandDefinitionContext() {} + +func NewOperandDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OperandDefinitionContext { + var p = new(OperandDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_operandDefinition + + return p +} + +func (s *OperandDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *OperandDefinitionContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *OperandDefinitionContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *OperandDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OperandDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *OperandDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitOperandDefinition(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) OperandDefinition() (localctx IOperandDefinitionContext) { + localctx = NewOperandDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 66, CqlParserRULE_operandDefinition) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(481) + p.ReferentialIdentifier() + } + { + p.SetState(482) + p.TypeSpecifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IFunctionBodyContext is an interface to support dynamic dispatch. +type IFunctionBodyContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Expression() IExpressionContext + + // IsFunctionBodyContext differentiates from other interfaces. + IsFunctionBodyContext() +} + +type FunctionBodyContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFunctionBodyContext() *FunctionBodyContext { + var p = new(FunctionBodyContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_functionBody + return p +} + +func InitEmptyFunctionBodyContext(p *FunctionBodyContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_functionBody +} + +func (*FunctionBodyContext) IsFunctionBodyContext() {} + +func NewFunctionBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FunctionBodyContext { + var p = new(FunctionBodyContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_functionBody + + return p +} + +func (s *FunctionBodyContext) GetParser() antlr.Parser { return s.parser } + +func (s *FunctionBodyContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *FunctionBodyContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FunctionBodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *FunctionBodyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitFunctionBody(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) FunctionBody() (localctx IFunctionBodyContext) { + localctx = NewFunctionBodyContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 68, CqlParserRULE_functionBody) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(484) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQuerySourceContext is an interface to support dynamic dispatch. +type IQuerySourceContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Retrieve() IRetrieveContext + QualifiedIdentifierExpression() IQualifiedIdentifierExpressionContext + Expression() IExpressionContext + + // IsQuerySourceContext differentiates from other interfaces. + IsQuerySourceContext() +} + +type QuerySourceContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQuerySourceContext() *QuerySourceContext { + var p = new(QuerySourceContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_querySource + return p +} + +func InitEmptyQuerySourceContext(p *QuerySourceContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_querySource +} + +func (*QuerySourceContext) IsQuerySourceContext() {} + +func NewQuerySourceContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QuerySourceContext { + var p = new(QuerySourceContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_querySource + + return p +} + +func (s *QuerySourceContext) GetParser() antlr.Parser { return s.parser } + +func (s *QuerySourceContext) Retrieve() IRetrieveContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRetrieveContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IRetrieveContext) +} + +func (s *QuerySourceContext) QualifiedIdentifierExpression() IQualifiedIdentifierExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedIdentifierExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedIdentifierExpressionContext) +} + +func (s *QuerySourceContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *QuerySourceContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QuerySourceContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QuerySourceContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQuerySource(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) QuerySource() (localctx IQuerySourceContext) { + localctx = NewQuerySourceContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 70, CqlParserRULE_querySource) + p.SetState(492) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__37: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(486) + p.Retrieve() + } + + + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__17, CqlParserT__19, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__44, CqlParserT__49, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__73, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__105, CqlParserT__106, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__118, CqlParserT__119, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__140, CqlParserT__141, CqlParserT__144, CqlParserT__148, CqlParserT__149, CqlParserT__153, CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(487) + p.QualifiedIdentifierExpression() + } + + + case CqlParserT__30: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(488) + p.Match(CqlParserT__30) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(489) + p.expression(0) + } + { + p.SetState(490) + p.Match(CqlParserT__31) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IAliasedQuerySourceContext is an interface to support dynamic dispatch. +type IAliasedQuerySourceContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QuerySource() IQuerySourceContext + Alias() IAliasContext + + // IsAliasedQuerySourceContext differentiates from other interfaces. + IsAliasedQuerySourceContext() +} + +type AliasedQuerySourceContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyAliasedQuerySourceContext() *AliasedQuerySourceContext { + var p = new(AliasedQuerySourceContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_aliasedQuerySource + return p +} + +func InitEmptyAliasedQuerySourceContext(p *AliasedQuerySourceContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_aliasedQuerySource +} + +func (*AliasedQuerySourceContext) IsAliasedQuerySourceContext() {} + +func NewAliasedQuerySourceContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AliasedQuerySourceContext { + var p = new(AliasedQuerySourceContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_aliasedQuerySource + + return p +} + +func (s *AliasedQuerySourceContext) GetParser() antlr.Parser { return s.parser } + +func (s *AliasedQuerySourceContext) QuerySource() IQuerySourceContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuerySourceContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQuerySourceContext) +} + +func (s *AliasedQuerySourceContext) Alias() IAliasContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAliasContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAliasContext) +} + +func (s *AliasedQuerySourceContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AliasedQuerySourceContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *AliasedQuerySourceContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitAliasedQuerySource(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) AliasedQuerySource() (localctx IAliasedQuerySourceContext) { + localctx = NewAliasedQuerySourceContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 72, CqlParserRULE_aliasedQuerySource) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(494) + p.QuerySource() + } + { + p.SetState(495) + p.Alias() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IAliasContext is an interface to support dynamic dispatch. +type IAliasContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + + // IsAliasContext differentiates from other interfaces. + IsAliasContext() +} + +type AliasContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyAliasContext() *AliasContext { + var p = new(AliasContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_alias + return p +} + +func InitEmptyAliasContext(p *AliasContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_alias +} + +func (*AliasContext) IsAliasContext() {} + +func NewAliasContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AliasContext { + var p = new(AliasContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_alias + + return p +} + +func (s *AliasContext) GetParser() antlr.Parser { return s.parser } + +func (s *AliasContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *AliasContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AliasContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *AliasContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitAlias(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Alias() (localctx IAliasContext) { + localctx = NewAliasContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 74, CqlParserRULE_alias) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(497) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQueryInclusionClauseContext is an interface to support dynamic dispatch. +type IQueryInclusionClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + WithClause() IWithClauseContext + WithoutClause() IWithoutClauseContext + + // IsQueryInclusionClauseContext differentiates from other interfaces. + IsQueryInclusionClauseContext() +} + +type QueryInclusionClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQueryInclusionClauseContext() *QueryInclusionClauseContext { + var p = new(QueryInclusionClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_queryInclusionClause + return p +} + +func InitEmptyQueryInclusionClauseContext(p *QueryInclusionClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_queryInclusionClause +} + +func (*QueryInclusionClauseContext) IsQueryInclusionClauseContext() {} + +func NewQueryInclusionClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QueryInclusionClauseContext { + var p = new(QueryInclusionClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_queryInclusionClause + + return p +} + +func (s *QueryInclusionClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *QueryInclusionClauseContext) WithClause() IWithClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IWithClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IWithClauseContext) +} + +func (s *QueryInclusionClauseContext) WithoutClause() IWithoutClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IWithoutClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IWithoutClauseContext) +} + +func (s *QueryInclusionClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QueryInclusionClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QueryInclusionClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQueryInclusionClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) QueryInclusionClause() (localctx IQueryInclusionClauseContext) { + localctx = NewQueryInclusionClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 76, CqlParserRULE_queryInclusionClause) + p.SetState(501) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__34: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(499) + p.WithClause() + } + + + case CqlParserT__36: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(500) + p.WithoutClause() + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IWithClauseContext is an interface to support dynamic dispatch. +type IWithClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AliasedQuerySource() IAliasedQuerySourceContext + Expression() IExpressionContext + + // IsWithClauseContext differentiates from other interfaces. + IsWithClauseContext() +} + +type WithClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyWithClauseContext() *WithClauseContext { + var p = new(WithClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_withClause + return p +} + +func InitEmptyWithClauseContext(p *WithClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_withClause +} + +func (*WithClauseContext) IsWithClauseContext() {} + +func NewWithClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *WithClauseContext { + var p = new(WithClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_withClause + + return p +} + +func (s *WithClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *WithClauseContext) AliasedQuerySource() IAliasedQuerySourceContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAliasedQuerySourceContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAliasedQuerySourceContext) +} + +func (s *WithClauseContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *WithClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *WithClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *WithClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitWithClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) WithClause() (localctx IWithClauseContext) { + localctx = NewWithClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 78, CqlParserRULE_withClause) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(503) + p.Match(CqlParserT__34) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(504) + p.AliasedQuerySource() + } + { + p.SetState(505) + p.Match(CqlParserT__35) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(506) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IWithoutClauseContext is an interface to support dynamic dispatch. +type IWithoutClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AliasedQuerySource() IAliasedQuerySourceContext + Expression() IExpressionContext + + // IsWithoutClauseContext differentiates from other interfaces. + IsWithoutClauseContext() +} + +type WithoutClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyWithoutClauseContext() *WithoutClauseContext { + var p = new(WithoutClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_withoutClause + return p +} + +func InitEmptyWithoutClauseContext(p *WithoutClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_withoutClause +} + +func (*WithoutClauseContext) IsWithoutClauseContext() {} + +func NewWithoutClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *WithoutClauseContext { + var p = new(WithoutClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_withoutClause + + return p +} + +func (s *WithoutClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *WithoutClauseContext) AliasedQuerySource() IAliasedQuerySourceContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAliasedQuerySourceContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAliasedQuerySourceContext) +} + +func (s *WithoutClauseContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *WithoutClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *WithoutClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *WithoutClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitWithoutClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) WithoutClause() (localctx IWithoutClauseContext) { + localctx = NewWithoutClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 80, CqlParserRULE_withoutClause) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(508) + p.Match(CqlParserT__36) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(509) + p.AliasedQuerySource() + } + { + p.SetState(510) + p.Match(CqlParserT__35) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(511) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IRetrieveContext is an interface to support dynamic dispatch. +type IRetrieveContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + NamedTypeSpecifier() INamedTypeSpecifierContext + ContextIdentifier() IContextIdentifierContext + Terminology() ITerminologyContext + CodePath() ICodePathContext + CodeComparator() ICodeComparatorContext + + // IsRetrieveContext differentiates from other interfaces. + IsRetrieveContext() +} + +type RetrieveContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRetrieveContext() *RetrieveContext { + var p = new(RetrieveContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_retrieve + return p +} + +func InitEmptyRetrieveContext(p *RetrieveContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_retrieve +} + +func (*RetrieveContext) IsRetrieveContext() {} + +func NewRetrieveContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RetrieveContext { + var p = new(RetrieveContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_retrieve + + return p +} + +func (s *RetrieveContext) GetParser() antlr.Parser { return s.parser } + +func (s *RetrieveContext) NamedTypeSpecifier() INamedTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(INamedTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(INamedTypeSpecifierContext) +} + +func (s *RetrieveContext) ContextIdentifier() IContextIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IContextIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IContextIdentifierContext) +} + +func (s *RetrieveContext) Terminology() ITerminologyContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITerminologyContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITerminologyContext) +} + +func (s *RetrieveContext) CodePath() ICodePathContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodePathContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodePathContext) +} + +func (s *RetrieveContext) CodeComparator() ICodeComparatorContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeComparatorContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodeComparatorContext) +} + +func (s *RetrieveContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RetrieveContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *RetrieveContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitRetrieve(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Retrieve() (localctx IRetrieveContext) { + localctx = NewRetrieveContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 82, CqlParserRULE_retrieve) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(513) + p.Match(CqlParserT__37) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(517) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 40, p.GetParserRuleContext()) == 1 { + { + p.SetState(514) + p.ContextIdentifier() + } + { + p.SetState(515) + p.Match(CqlParserT__38) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(519) + p.NamedTypeSpecifier() + } + p.SetState(527) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__10 { + { + p.SetState(520) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(524) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 41, p.GetParserRuleContext()) == 1 { + { + p.SetState(521) + p.CodePath() + } + { + p.SetState(522) + p.CodeComparator() + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(526) + p.Terminology() + } + + } + { + p.SetState(529) + p.Match(CqlParserT__39) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IContextIdentifierContext is an interface to support dynamic dispatch. +type IContextIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QualifiedIdentifierExpression() IQualifiedIdentifierExpressionContext + + // IsContextIdentifierContext differentiates from other interfaces. + IsContextIdentifierContext() +} + +type ContextIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyContextIdentifierContext() *ContextIdentifierContext { + var p = new(ContextIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_contextIdentifier + return p +} + +func InitEmptyContextIdentifierContext(p *ContextIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_contextIdentifier +} + +func (*ContextIdentifierContext) IsContextIdentifierContext() {} + +func NewContextIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ContextIdentifierContext { + var p = new(ContextIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_contextIdentifier + + return p +} + +func (s *ContextIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ContextIdentifierContext) QualifiedIdentifierExpression() IQualifiedIdentifierExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedIdentifierExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedIdentifierExpressionContext) +} + +func (s *ContextIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ContextIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ContextIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitContextIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ContextIdentifier() (localctx IContextIdentifierContext) { + localctx = NewContextIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 84, CqlParserRULE_contextIdentifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(531) + p.QualifiedIdentifierExpression() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodePathContext is an interface to support dynamic dispatch. +type ICodePathContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + SimplePath() ISimplePathContext + + // IsCodePathContext differentiates from other interfaces. + IsCodePathContext() +} + +type CodePathContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodePathContext() *CodePathContext { + var p = new(CodePathContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codePath + return p +} + +func InitEmptyCodePathContext(p *CodePathContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codePath +} + +func (*CodePathContext) IsCodePathContext() {} + +func NewCodePathContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodePathContext { + var p = new(CodePathContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codePath + + return p +} + +func (s *CodePathContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodePathContext) SimplePath() ISimplePathContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISimplePathContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISimplePathContext) +} + +func (s *CodePathContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodePathContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodePathContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodePath(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodePath() (localctx ICodePathContext) { + localctx = NewCodePathContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 86, CqlParserRULE_codePath) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(533) + p.simplePath(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodeComparatorContext is an interface to support dynamic dispatch. +type ICodeComparatorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsCodeComparatorContext differentiates from other interfaces. + IsCodeComparatorContext() +} + +type CodeComparatorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodeComparatorContext() *CodeComparatorContext { + var p = new(CodeComparatorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeComparator + return p +} + +func InitEmptyCodeComparatorContext(p *CodeComparatorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeComparator +} + +func (*CodeComparatorContext) IsCodeComparatorContext() {} + +func NewCodeComparatorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodeComparatorContext { + var p = new(CodeComparatorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codeComparator + + return p +} + +func (s *CodeComparatorContext) GetParser() antlr.Parser { return s.parser } +func (s *CodeComparatorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeComparatorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodeComparatorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodeComparator(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodeComparator() (localctx ICodeComparatorContext) { + localctx = NewCodeComparatorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 88, CqlParserRULE_codeComparator) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(535) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & 15393162788864) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITerminologyContext is an interface to support dynamic dispatch. +type ITerminologyContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QualifiedIdentifierExpression() IQualifiedIdentifierExpressionContext + Expression() IExpressionContext + + // IsTerminologyContext differentiates from other interfaces. + IsTerminologyContext() +} + +type TerminologyContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTerminologyContext() *TerminologyContext { + var p = new(TerminologyContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_terminology + return p +} + +func InitEmptyTerminologyContext(p *TerminologyContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_terminology +} + +func (*TerminologyContext) IsTerminologyContext() {} + +func NewTerminologyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TerminologyContext { + var p = new(TerminologyContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_terminology + + return p +} + +func (s *TerminologyContext) GetParser() antlr.Parser { return s.parser } + +func (s *TerminologyContext) QualifiedIdentifierExpression() IQualifiedIdentifierExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedIdentifierExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedIdentifierExpressionContext) +} + +func (s *TerminologyContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *TerminologyContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TerminologyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TerminologyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTerminology(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Terminology() (localctx ITerminologyContext) { + localctx = NewTerminologyContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 90, CqlParserRULE_terminology) + p.SetState(539) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 43, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(537) + p.QualifiedIdentifierExpression() + } + + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(538) + p.expression(0) + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQualifierContext is an interface to support dynamic dispatch. +type IQualifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + + // IsQualifierContext differentiates from other interfaces. + IsQualifierContext() +} + +type QualifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQualifierContext() *QualifierContext { + var p = new(QualifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifier + return p +} + +func InitEmptyQualifierContext(p *QualifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifier +} + +func (*QualifierContext) IsQualifierContext() {} + +func NewQualifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QualifierContext { + var p = new(QualifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_qualifier + + return p +} + +func (s *QualifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *QualifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *QualifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QualifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQualifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Qualifier() (localctx IQualifierContext) { + localctx = NewQualifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 92, CqlParserRULE_qualifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(541) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQueryContext is an interface to support dynamic dispatch. +type IQueryContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + SourceClause() ISourceClauseContext + LetClause() ILetClauseContext + AllQueryInclusionClause() []IQueryInclusionClauseContext + QueryInclusionClause(i int) IQueryInclusionClauseContext + WhereClause() IWhereClauseContext + AggregateClause() IAggregateClauseContext + ReturnClause() IReturnClauseContext + SortClause() ISortClauseContext + + // IsQueryContext differentiates from other interfaces. + IsQueryContext() +} + +type QueryContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQueryContext() *QueryContext { + var p = new(QueryContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_query + return p +} + +func InitEmptyQueryContext(p *QueryContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_query +} + +func (*QueryContext) IsQueryContext() {} + +func NewQueryContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QueryContext { + var p = new(QueryContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_query + + return p +} + +func (s *QueryContext) GetParser() antlr.Parser { return s.parser } + +func (s *QueryContext) SourceClause() ISourceClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISourceClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISourceClauseContext) +} + +func (s *QueryContext) LetClause() ILetClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILetClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ILetClauseContext) +} + +func (s *QueryContext) AllQueryInclusionClause() []IQueryInclusionClauseContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IQueryInclusionClauseContext); ok { + len++ + } + } + + tst := make([]IQueryInclusionClauseContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IQueryInclusionClauseContext); ok { + tst[i] = t.(IQueryInclusionClauseContext) + i++ + } + } + + return tst +} + +func (s *QueryContext) QueryInclusionClause(i int) IQueryInclusionClauseContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQueryInclusionClauseContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IQueryInclusionClauseContext) +} + +func (s *QueryContext) WhereClause() IWhereClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IWhereClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IWhereClauseContext) +} + +func (s *QueryContext) AggregateClause() IAggregateClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAggregateClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IAggregateClauseContext) +} + +func (s *QueryContext) ReturnClause() IReturnClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReturnClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReturnClauseContext) +} + +func (s *QueryContext) SortClause() ISortClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISortClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISortClauseContext) +} + +func (s *QueryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QueryContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QueryContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQuery(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Query() (localctx IQueryContext) { + localctx = NewQueryContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 94, CqlParserRULE_query) + var _alt int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(543) + p.SourceClause() + } + p.SetState(545) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 44, p.GetParserRuleContext()) == 1 { + { + p.SetState(544) + p.LetClause() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(550) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 45, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(547) + p.QueryInclusionClause() + } + + + } + p.SetState(552) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 45, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + p.SetState(554) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 46, p.GetParserRuleContext()) == 1 { + { + p.SetState(553) + p.WhereClause() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(558) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 47, p.GetParserRuleContext()) == 1 { + { + p.SetState(556) + p.AggregateClause() + } + + } else if p.HasError() { // JIM + goto errorExit} else if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 47, p.GetParserRuleContext()) == 2 { + { + p.SetState(557) + p.ReturnClause() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(561) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 48, p.GetParserRuleContext()) == 1 { + { + p.SetState(560) + p.SortClause() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ISourceClauseContext is an interface to support dynamic dispatch. +type ISourceClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllAliasedQuerySource() []IAliasedQuerySourceContext + AliasedQuerySource(i int) IAliasedQuerySourceContext + + // IsSourceClauseContext differentiates from other interfaces. + IsSourceClauseContext() +} + +type SourceClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySourceClauseContext() *SourceClauseContext { + var p = new(SourceClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sourceClause + return p +} + +func InitEmptySourceClauseContext(p *SourceClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sourceClause +} + +func (*SourceClauseContext) IsSourceClauseContext() {} + +func NewSourceClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SourceClauseContext { + var p = new(SourceClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_sourceClause + + return p +} + +func (s *SourceClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *SourceClauseContext) AllAliasedQuerySource() []IAliasedQuerySourceContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IAliasedQuerySourceContext); ok { + len++ + } + } + + tst := make([]IAliasedQuerySourceContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IAliasedQuerySourceContext); ok { + tst[i] = t.(IAliasedQuerySourceContext) + i++ + } + } + + return tst +} + +func (s *SourceClauseContext) AliasedQuerySource(i int) IAliasedQuerySourceContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IAliasedQuerySourceContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IAliasedQuerySourceContext) +} + +func (s *SourceClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SourceClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *SourceClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSourceClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) SourceClause() (localctx ISourceClauseContext) { + localctx = NewSourceClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 96, CqlParserRULE_sourceClause) + var _la int + + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(564) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__18 { + { + p.SetState(563) + p.Match(CqlParserT__18) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(566) + p.AliasedQuerySource() + } + p.SetState(571) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 50, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(567) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(568) + p.AliasedQuerySource() + } + + + } + p.SetState(573) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 50, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ILetClauseContext is an interface to support dynamic dispatch. +type ILetClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllLetClauseItem() []ILetClauseItemContext + LetClauseItem(i int) ILetClauseItemContext + + // IsLetClauseContext differentiates from other interfaces. + IsLetClauseContext() +} + +type LetClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLetClauseContext() *LetClauseContext { + var p = new(LetClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_letClause + return p +} + +func InitEmptyLetClauseContext(p *LetClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_letClause +} + +func (*LetClauseContext) IsLetClauseContext() {} + +func NewLetClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LetClauseContext { + var p = new(LetClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_letClause + + return p +} + +func (s *LetClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *LetClauseContext) AllLetClauseItem() []ILetClauseItemContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ILetClauseItemContext); ok { + len++ + } + } + + tst := make([]ILetClauseItemContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ILetClauseItemContext); ok { + tst[i] = t.(ILetClauseItemContext) + i++ + } + } + + return tst +} + +func (s *LetClauseContext) LetClauseItem(i int) ILetClauseItemContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILetClauseItemContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ILetClauseItemContext) +} + +func (s *LetClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LetClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *LetClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLetClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) LetClause() (localctx ILetClauseContext) { + localctx = NewLetClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 98, CqlParserRULE_letClause) + var _alt int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(574) + p.Match(CqlParserT__43) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(575) + p.LetClauseItem() + } + p.SetState(580) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 51, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(576) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(577) + p.LetClauseItem() + } + + + } + p.SetState(582) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 51, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ILetClauseItemContext is an interface to support dynamic dispatch. +type ILetClauseItemContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + Expression() IExpressionContext + + // IsLetClauseItemContext differentiates from other interfaces. + IsLetClauseItemContext() +} + +type LetClauseItemContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLetClauseItemContext() *LetClauseItemContext { + var p = new(LetClauseItemContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_letClauseItem + return p +} + +func InitEmptyLetClauseItemContext(p *LetClauseItemContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_letClauseItem +} + +func (*LetClauseItemContext) IsLetClauseItemContext() {} + +func NewLetClauseItemContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LetClauseItemContext { + var p = new(LetClauseItemContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_letClauseItem + + return p +} + +func (s *LetClauseItemContext) GetParser() antlr.Parser { return s.parser } + +func (s *LetClauseItemContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *LetClauseItemContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *LetClauseItemContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LetClauseItemContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *LetClauseItemContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLetClauseItem(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) LetClauseItem() (localctx ILetClauseItemContext) { + localctx = NewLetClauseItemContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 100, CqlParserRULE_letClauseItem) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(583) + p.Identifier() + } + { + p.SetState(584) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(585) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IWhereClauseContext is an interface to support dynamic dispatch. +type IWhereClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Expression() IExpressionContext + + // IsWhereClauseContext differentiates from other interfaces. + IsWhereClauseContext() +} + +type WhereClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyWhereClauseContext() *WhereClauseContext { + var p = new(WhereClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_whereClause + return p +} + +func InitEmptyWhereClauseContext(p *WhereClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_whereClause +} + +func (*WhereClauseContext) IsWhereClauseContext() {} + +func NewWhereClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *WhereClauseContext { + var p = new(WhereClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_whereClause + + return p +} + +func (s *WhereClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *WhereClauseContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *WhereClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *WhereClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *WhereClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitWhereClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) WhereClause() (localctx IWhereClauseContext) { + localctx = NewWhereClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 102, CqlParserRULE_whereClause) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(587) + p.Match(CqlParserT__44) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(588) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IReturnClauseContext is an interface to support dynamic dispatch. +type IReturnClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Expression() IExpressionContext + + // IsReturnClauseContext differentiates from other interfaces. + IsReturnClauseContext() +} + +type ReturnClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyReturnClauseContext() *ReturnClauseContext { + var p = new(ReturnClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_returnClause + return p +} + +func InitEmptyReturnClauseContext(p *ReturnClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_returnClause +} + +func (*ReturnClauseContext) IsReturnClauseContext() {} + +func NewReturnClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ReturnClauseContext { + var p = new(ReturnClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_returnClause + + return p +} + +func (s *ReturnClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *ReturnClauseContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *ReturnClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ReturnClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ReturnClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitReturnClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ReturnClause() (localctx IReturnClauseContext) { + localctx = NewReturnClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 104, CqlParserRULE_returnClause) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(590) + p.Match(CqlParserT__45) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(592) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 52, p.GetParserRuleContext()) == 1 { + { + p.SetState(591) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__46 || _la == CqlParserT__47) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(594) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IAggregateClauseContext is an interface to support dynamic dispatch. +type IAggregateClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + Expression() IExpressionContext + StartingClause() IStartingClauseContext + + // IsAggregateClauseContext differentiates from other interfaces. + IsAggregateClauseContext() +} + +type AggregateClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyAggregateClauseContext() *AggregateClauseContext { + var p = new(AggregateClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_aggregateClause + return p +} + +func InitEmptyAggregateClauseContext(p *AggregateClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_aggregateClause +} + +func (*AggregateClauseContext) IsAggregateClauseContext() {} + +func NewAggregateClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AggregateClauseContext { + var p = new(AggregateClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_aggregateClause + + return p +} + +func (s *AggregateClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *AggregateClauseContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *AggregateClauseContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *AggregateClauseContext) StartingClause() IStartingClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStartingClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IStartingClauseContext) +} + +func (s *AggregateClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AggregateClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *AggregateClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitAggregateClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) AggregateClause() (localctx IAggregateClauseContext) { + localctx = NewAggregateClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 106, CqlParserRULE_aggregateClause) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(596) + p.Match(CqlParserT__48) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(598) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__46 || _la == CqlParserT__47 { + { + p.SetState(597) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__46 || _la == CqlParserT__47) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + { + p.SetState(600) + p.Identifier() + } + p.SetState(602) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__49 { + { + p.SetState(601) + p.StartingClause() + } + + } + { + p.SetState(604) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(605) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IStartingClauseContext is an interface to support dynamic dispatch. +type IStartingClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + SimpleLiteral() ISimpleLiteralContext + Quantity() IQuantityContext + Expression() IExpressionContext + + // IsStartingClauseContext differentiates from other interfaces. + IsStartingClauseContext() +} + +type StartingClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyStartingClauseContext() *StartingClauseContext { + var p = new(StartingClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_startingClause + return p +} + +func InitEmptyStartingClauseContext(p *StartingClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_startingClause +} + +func (*StartingClauseContext) IsStartingClauseContext() {} + +func NewStartingClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *StartingClauseContext { + var p = new(StartingClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_startingClause + + return p +} + +func (s *StartingClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *StartingClauseContext) SimpleLiteral() ISimpleLiteralContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISimpleLiteralContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISimpleLiteralContext) +} + +func (s *StartingClauseContext) Quantity() IQuantityContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQuantityContext) +} + +func (s *StartingClauseContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *StartingClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StartingClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *StartingClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitStartingClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) StartingClause() (localctx IStartingClauseContext) { + localctx = NewStartingClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 108, CqlParserRULE_startingClause) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(607) + p.Match(CqlParserT__49) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(614) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 55, p.GetParserRuleContext()) { + case 1: + { + p.SetState(608) + p.SimpleLiteral() + } + + + case 2: + { + p.SetState(609) + p.Quantity() + } + + + case 3: + { + p.SetState(610) + p.Match(CqlParserT__30) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(611) + p.expression(0) + } + { + p.SetState(612) + p.Match(CqlParserT__31) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ISortClauseContext is an interface to support dynamic dispatch. +type ISortClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + SortDirection() ISortDirectionContext + AllSortByItem() []ISortByItemContext + SortByItem(i int) ISortByItemContext + + // IsSortClauseContext differentiates from other interfaces. + IsSortClauseContext() +} + +type SortClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySortClauseContext() *SortClauseContext { + var p = new(SortClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sortClause + return p +} + +func InitEmptySortClauseContext(p *SortClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sortClause +} + +func (*SortClauseContext) IsSortClauseContext() {} + +func NewSortClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SortClauseContext { + var p = new(SortClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_sortClause + + return p +} + +func (s *SortClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *SortClauseContext) SortDirection() ISortDirectionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISortDirectionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISortDirectionContext) +} + +func (s *SortClauseContext) AllSortByItem() []ISortByItemContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ISortByItemContext); ok { + len++ + } + } + + tst := make([]ISortByItemContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ISortByItemContext); ok { + tst[i] = t.(ISortByItemContext) + i++ + } + } + + return tst +} + +func (s *SortClauseContext) SortByItem(i int) ISortByItemContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISortByItemContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ISortByItemContext) +} + +func (s *SortClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SortClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *SortClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSortClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) SortClause() (localctx ISortClauseContext) { + localctx = NewSortClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 110, CqlParserRULE_sortClause) + var _alt int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(616) + p.Match(CqlParserT__50) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(627) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55: + { + p.SetState(617) + p.SortDirection() + } + + + case CqlParserT__51: + { + p.SetState(618) + p.Match(CqlParserT__51) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(619) + p.SortByItem() + } + p.SetState(624) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 56, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(620) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(621) + p.SortByItem() + } + + + } + p.SetState(626) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 56, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ISortDirectionContext is an interface to support dynamic dispatch. +type ISortDirectionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsSortDirectionContext differentiates from other interfaces. + IsSortDirectionContext() +} + +type SortDirectionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySortDirectionContext() *SortDirectionContext { + var p = new(SortDirectionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sortDirection + return p +} + +func InitEmptySortDirectionContext(p *SortDirectionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sortDirection +} + +func (*SortDirectionContext) IsSortDirectionContext() {} + +func NewSortDirectionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SortDirectionContext { + var p = new(SortDirectionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_sortDirection + + return p +} + +func (s *SortDirectionContext) GetParser() antlr.Parser { return s.parser } +func (s *SortDirectionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SortDirectionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *SortDirectionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSortDirection(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) SortDirection() (localctx ISortDirectionContext) { + localctx = NewSortDirectionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 112, CqlParserRULE_sortDirection) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(629) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & 135107988821114880) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ISortByItemContext is an interface to support dynamic dispatch. +type ISortByItemContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ExpressionTerm() IExpressionTermContext + SortDirection() ISortDirectionContext + + // IsSortByItemContext differentiates from other interfaces. + IsSortByItemContext() +} + +type SortByItemContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySortByItemContext() *SortByItemContext { + var p = new(SortByItemContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sortByItem + return p +} + +func InitEmptySortByItemContext(p *SortByItemContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_sortByItem +} + +func (*SortByItemContext) IsSortByItemContext() {} + +func NewSortByItemContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SortByItemContext { + var p = new(SortByItemContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_sortByItem + + return p +} + +func (s *SortByItemContext) GetParser() antlr.Parser { return s.parser } + +func (s *SortByItemContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + +func (s *SortByItemContext) SortDirection() ISortDirectionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISortDirectionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISortDirectionContext) +} + +func (s *SortByItemContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SortByItemContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *SortByItemContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSortByItem(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) SortByItem() (localctx ISortByItemContext) { + localctx = NewSortByItemContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 114, CqlParserRULE_sortByItem) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(631) + p.expressionTerm(0) + } + p.SetState(633) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 58, p.GetParserRuleContext()) == 1 { + { + p.SetState(632) + p.SortDirection() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQualifiedIdentifierContext is an interface to support dynamic dispatch. +type IQualifiedIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + AllQualifier() []IQualifierContext + Qualifier(i int) IQualifierContext + + // IsQualifiedIdentifierContext differentiates from other interfaces. + IsQualifiedIdentifierContext() +} + +type QualifiedIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQualifiedIdentifierContext() *QualifiedIdentifierContext { + var p = new(QualifiedIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedIdentifier + return p +} + +func InitEmptyQualifiedIdentifierContext(p *QualifiedIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedIdentifier +} + +func (*QualifiedIdentifierContext) IsQualifiedIdentifierContext() {} + +func NewQualifiedIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QualifiedIdentifierContext { + var p = new(QualifiedIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_qualifiedIdentifier + + return p +} + +func (s *QualifiedIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *QualifiedIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *QualifiedIdentifierContext) AllQualifier() []IQualifierContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IQualifierContext); ok { + len++ + } + } + + tst := make([]IQualifierContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IQualifierContext); ok { + tst[i] = t.(IQualifierContext) + i++ + } + } + + return tst +} + +func (s *QualifiedIdentifierContext) Qualifier(i int) IQualifierContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifierContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IQualifierContext) +} + +func (s *QualifiedIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifiedIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QualifiedIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQualifiedIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) QualifiedIdentifier() (localctx IQualifiedIdentifierContext) { + localctx = NewQualifiedIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 116, CqlParserRULE_qualifiedIdentifier) + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(640) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 59, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(635) + p.Qualifier() + } + { + p.SetState(636) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + } + p.SetState(642) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 59, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + { + p.SetState(643) + p.Identifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQualifiedIdentifierExpressionContext is an interface to support dynamic dispatch. +type IQualifiedIdentifierExpressionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + AllQualifierExpression() []IQualifierExpressionContext + QualifierExpression(i int) IQualifierExpressionContext + + // IsQualifiedIdentifierExpressionContext differentiates from other interfaces. + IsQualifiedIdentifierExpressionContext() +} + +type QualifiedIdentifierExpressionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQualifiedIdentifierExpressionContext() *QualifiedIdentifierExpressionContext { + var p = new(QualifiedIdentifierExpressionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedIdentifierExpression + return p +} + +func InitEmptyQualifiedIdentifierExpressionContext(p *QualifiedIdentifierExpressionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedIdentifierExpression +} + +func (*QualifiedIdentifierExpressionContext) IsQualifiedIdentifierExpressionContext() {} + +func NewQualifiedIdentifierExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QualifiedIdentifierExpressionContext { + var p = new(QualifiedIdentifierExpressionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_qualifiedIdentifierExpression + + return p +} + +func (s *QualifiedIdentifierExpressionContext) GetParser() antlr.Parser { return s.parser } + +func (s *QualifiedIdentifierExpressionContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *QualifiedIdentifierExpressionContext) AllQualifierExpression() []IQualifierExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IQualifierExpressionContext); ok { + len++ + } + } + + tst := make([]IQualifierExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IQualifierExpressionContext); ok { + tst[i] = t.(IQualifierExpressionContext) + i++ + } + } + + return tst +} + +func (s *QualifiedIdentifierExpressionContext) QualifierExpression(i int) IQualifierExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifierExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IQualifierExpressionContext) +} + +func (s *QualifiedIdentifierExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifiedIdentifierExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QualifiedIdentifierExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQualifiedIdentifierExpression(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) QualifiedIdentifierExpression() (localctx IQualifiedIdentifierExpressionContext) { + localctx = NewQualifiedIdentifierExpressionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 118, CqlParserRULE_qualifiedIdentifierExpression) + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(650) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 60, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(645) + p.QualifierExpression() + } + { + p.SetState(646) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + } + p.SetState(652) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 60, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + { + p.SetState(653) + p.ReferentialIdentifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQualifierExpressionContext is an interface to support dynamic dispatch. +type IQualifierExpressionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + + // IsQualifierExpressionContext differentiates from other interfaces. + IsQualifierExpressionContext() +} + +type QualifierExpressionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQualifierExpressionContext() *QualifierExpressionContext { + var p = new(QualifierExpressionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifierExpression + return p +} + +func InitEmptyQualifierExpressionContext(p *QualifierExpressionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifierExpression +} + +func (*QualifierExpressionContext) IsQualifierExpressionContext() {} + +func NewQualifierExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QualifierExpressionContext { + var p = new(QualifierExpressionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_qualifierExpression + + return p +} + +func (s *QualifierExpressionContext) GetParser() antlr.Parser { return s.parser } + +func (s *QualifierExpressionContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *QualifierExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifierExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QualifierExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQualifierExpression(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) QualifierExpression() (localctx IQualifierExpressionContext) { + localctx = NewQualifierExpressionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 120, CqlParserRULE_qualifierExpression) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(655) + p.ReferentialIdentifier() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ISimplePathContext is an interface to support dynamic dispatch. +type ISimplePathContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsSimplePathContext differentiates from other interfaces. + IsSimplePathContext() +} + +type SimplePathContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySimplePathContext() *SimplePathContext { + var p = new(SimplePathContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_simplePath + return p +} + +func InitEmptySimplePathContext(p *SimplePathContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_simplePath +} + +func (*SimplePathContext) IsSimplePathContext() {} + +func NewSimplePathContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SimplePathContext { + var p = new(SimplePathContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_simplePath + + return p +} + +func (s *SimplePathContext) GetParser() antlr.Parser { return s.parser } + +func (s *SimplePathContext) CopyAll(ctx *SimplePathContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *SimplePathContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SimplePathContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + + +type SimplePathIndexerContext struct { + SimplePathContext +} + +func NewSimplePathIndexerContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *SimplePathIndexerContext { + var p = new(SimplePathIndexerContext) + + InitEmptySimplePathContext(&p.SimplePathContext) + p.parser = parser + p.CopyAll(ctx.(*SimplePathContext)) + + return p +} + +func (s *SimplePathIndexerContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SimplePathIndexerContext) SimplePath() ISimplePathContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISimplePathContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISimplePathContext) +} + +func (s *SimplePathIndexerContext) SimpleLiteral() ISimpleLiteralContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISimpleLiteralContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISimpleLiteralContext) +} + + +func (s *SimplePathIndexerContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSimplePathIndexer(s) + + default: + return t.VisitChildren(s) + } +} + + +type SimplePathQualifiedIdentifierContext struct { + SimplePathContext +} + +func NewSimplePathQualifiedIdentifierContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *SimplePathQualifiedIdentifierContext { + var p = new(SimplePathQualifiedIdentifierContext) + + InitEmptySimplePathContext(&p.SimplePathContext) + p.parser = parser + p.CopyAll(ctx.(*SimplePathContext)) + + return p +} + +func (s *SimplePathQualifiedIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SimplePathQualifiedIdentifierContext) SimplePath() ISimplePathContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISimplePathContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ISimplePathContext) +} + +func (s *SimplePathQualifiedIdentifierContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + + +func (s *SimplePathQualifiedIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSimplePathQualifiedIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + +type SimplePathReferentialIdentifierContext struct { + SimplePathContext +} + +func NewSimplePathReferentialIdentifierContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *SimplePathReferentialIdentifierContext { + var p = new(SimplePathReferentialIdentifierContext) + + InitEmptySimplePathContext(&p.SimplePathContext) + p.parser = parser + p.CopyAll(ctx.(*SimplePathContext)) + + return p +} + +func (s *SimplePathReferentialIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SimplePathReferentialIdentifierContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + + +func (s *SimplePathReferentialIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSimplePathReferentialIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) SimplePath() (localctx ISimplePathContext) { + return p.simplePath(0) +} + +func (p *CqlParser) simplePath(_p int) (localctx ISimplePathContext) { + var _parentctx antlr.ParserRuleContext = p.GetParserRuleContext() + + _parentState := p.GetState() + localctx = NewSimplePathContext(p, p.GetParserRuleContext(), _parentState) + var _prevctx ISimplePathContext = localctx + var _ antlr.ParserRuleContext = _prevctx // TODO: To prevent unused variable warning. + _startState := 122 + p.EnterRecursionRule(localctx, 122, CqlParserRULE_simplePath, _p) + var _alt int + + p.EnterOuterAlt(localctx, 1) + localctx = NewSimplePathReferentialIdentifierContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + + { + p.SetState(658) + p.ReferentialIdentifier() + } + + p.GetParserRuleContext().SetStop(p.GetTokenStream().LT(-1)) + p.SetState(670) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 62, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + if p.GetParseListeners() != nil { + p.TriggerExitRuleEvent() + } + _prevctx = localctx + p.SetState(668) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 61, p.GetParserRuleContext()) { + case 1: + localctx = NewSimplePathQualifiedIdentifierContext(p, NewSimplePathContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_simplePath) + p.SetState(660) + + if !(p.Precpred(p.GetParserRuleContext(), 2)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 2)", "")) + goto errorExit + } + { + p.SetState(661) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(662) + p.ReferentialIdentifier() + } + + + case 2: + localctx = NewSimplePathIndexerContext(p, NewSimplePathContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_simplePath) + p.SetState(663) + + if !(p.Precpred(p.GetParserRuleContext(), 1)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 1)", "")) + goto errorExit + } + { + p.SetState(664) + p.Match(CqlParserT__37) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(665) + p.SimpleLiteral() + } + { + p.SetState(666) + p.Match(CqlParserT__39) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + } + p.SetState(672) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 62, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + + + + errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.UnrollRecursionContexts(_parentctx) + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ISimpleLiteralContext is an interface to support dynamic dispatch. +type ISimpleLiteralContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsSimpleLiteralContext differentiates from other interfaces. + IsSimpleLiteralContext() +} + +type SimpleLiteralContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySimpleLiteralContext() *SimpleLiteralContext { + var p = new(SimpleLiteralContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_simpleLiteral + return p +} + +func InitEmptySimpleLiteralContext(p *SimpleLiteralContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_simpleLiteral +} + +func (*SimpleLiteralContext) IsSimpleLiteralContext() {} + +func NewSimpleLiteralContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SimpleLiteralContext { + var p = new(SimpleLiteralContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_simpleLiteral + + return p +} + +func (s *SimpleLiteralContext) GetParser() antlr.Parser { return s.parser } + +func (s *SimpleLiteralContext) CopyAll(ctx *SimpleLiteralContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *SimpleLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SimpleLiteralContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + +type SimpleNumberLiteralContext struct { + SimpleLiteralContext +} + +func NewSimpleNumberLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *SimpleNumberLiteralContext { + var p = new(SimpleNumberLiteralContext) + + InitEmptySimpleLiteralContext(&p.SimpleLiteralContext) + p.parser = parser + p.CopyAll(ctx.(*SimpleLiteralContext)) + + return p +} + +func (s *SimpleNumberLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SimpleNumberLiteralContext) NUMBER() antlr.TerminalNode { + return s.GetToken(CqlParserNUMBER, 0) +} + + +func (s *SimpleNumberLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSimpleNumberLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type SimpleStringLiteralContext struct { + SimpleLiteralContext +} + +func NewSimpleStringLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *SimpleStringLiteralContext { + var p = new(SimpleStringLiteralContext) + + InitEmptySimpleLiteralContext(&p.SimpleLiteralContext) + p.parser = parser + p.CopyAll(ctx.(*SimpleLiteralContext)) + + return p +} + +func (s *SimpleStringLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SimpleStringLiteralContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + + +func (s *SimpleStringLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSimpleStringLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) SimpleLiteral() (localctx ISimpleLiteralContext) { + localctx = NewSimpleLiteralContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 124, CqlParserRULE_simpleLiteral) + p.SetState(675) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserSTRING: + localctx = NewSimpleStringLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(673) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case CqlParserNUMBER: + localctx = NewSimpleNumberLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + { + p.SetState(674) + p.Match(CqlParserNUMBER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IExpressionContext is an interface to support dynamic dispatch. +type IExpressionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsExpressionContext differentiates from other interfaces. + IsExpressionContext() +} + +type ExpressionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyExpressionContext() *ExpressionContext { + var p = new(ExpressionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_expression + return p +} + +func InitEmptyExpressionContext(p *ExpressionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_expression +} + +func (*ExpressionContext) IsExpressionContext() {} + +func NewExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExpressionContext { + var p = new(ExpressionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_expression + + return p +} + +func (s *ExpressionContext) GetParser() antlr.Parser { return s.parser } + +func (s *ExpressionContext) CopyAll(ctx *ExpressionContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *ExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + + +type DurationBetweenExpressionContext struct { + ExpressionContext +} + +func NewDurationBetweenExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DurationBetweenExpressionContext { + var p = new(DurationBetweenExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *DurationBetweenExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DurationBetweenExpressionContext) PluralDateTimePrecision() IPluralDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPluralDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IPluralDateTimePrecisionContext) +} + +func (s *DurationBetweenExpressionContext) AllExpressionTerm() []IExpressionTermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionTermContext); ok { + len++ + } + } + + tst := make([]IExpressionTermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionTermContext); ok { + tst[i] = t.(IExpressionTermContext) + i++ + } + } + + return tst +} + +func (s *DurationBetweenExpressionContext) ExpressionTerm(i int) IExpressionTermContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *DurationBetweenExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDurationBetweenExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type InFixSetExpressionContext struct { + ExpressionContext +} + +func NewInFixSetExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *InFixSetExpressionContext { + var p = new(InFixSetExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *InFixSetExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InFixSetExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *InFixSetExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *InFixSetExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitInFixSetExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type RetrieveExpressionContext struct { + ExpressionContext +} + +func NewRetrieveExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *RetrieveExpressionContext { + var p = new(RetrieveExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *RetrieveExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RetrieveExpressionContext) Retrieve() IRetrieveContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRetrieveContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IRetrieveContext) +} + + +func (s *RetrieveExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitRetrieveExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type TimingExpressionContext struct { + ExpressionContext +} + +func NewTimingExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TimingExpressionContext { + var p = new(TimingExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *TimingExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TimingExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *TimingExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *TimingExpressionContext) IntervalOperatorPhrase() IIntervalOperatorPhraseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntervalOperatorPhraseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIntervalOperatorPhraseContext) +} + + +func (s *TimingExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTimingExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type QueryExpressionContext struct { + ExpressionContext +} + +func NewQueryExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *QueryExpressionContext { + var p = new(QueryExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *QueryExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QueryExpressionContext) Query() IQueryContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQueryContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQueryContext) +} + + +func (s *QueryExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQueryExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type NotExpressionContext struct { + ExpressionContext +} + +func NewNotExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *NotExpressionContext { + var p = new(NotExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *NotExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *NotExpressionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *NotExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitNotExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type BooleanExpressionContext struct { + ExpressionContext +} + +func NewBooleanExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *BooleanExpressionContext { + var p = new(BooleanExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *BooleanExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *BooleanExpressionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *BooleanExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitBooleanExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type OrExpressionContext struct { + ExpressionContext +} + +func NewOrExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *OrExpressionContext { + var p = new(OrExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *OrExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OrExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *OrExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *OrExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitOrExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type CastExpressionContext struct { + ExpressionContext +} + +func NewCastExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *CastExpressionContext { + var p = new(CastExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *CastExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CastExpressionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *CastExpressionContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + + +func (s *CastExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCastExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type AndExpressionContext struct { + ExpressionContext +} + +func NewAndExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *AndExpressionContext { + var p = new(AndExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *AndExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AndExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *AndExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *AndExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitAndExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type BetweenExpressionContext struct { + ExpressionContext +} + +func NewBetweenExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *BetweenExpressionContext { + var p = new(BetweenExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *BetweenExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *BetweenExpressionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *BetweenExpressionContext) AllExpressionTerm() []IExpressionTermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionTermContext); ok { + len++ + } + } + + tst := make([]IExpressionTermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionTermContext); ok { + tst[i] = t.(IExpressionTermContext) + i++ + } + } + + return tst +} + +func (s *BetweenExpressionContext) ExpressionTerm(i int) IExpressionTermContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *BetweenExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitBetweenExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type MembershipExpressionContext struct { + ExpressionContext +} + +func NewMembershipExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *MembershipExpressionContext { + var p = new(MembershipExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *MembershipExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MembershipExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *MembershipExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *MembershipExpressionContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *MembershipExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitMembershipExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type DifferenceBetweenExpressionContext struct { + ExpressionContext +} + +func NewDifferenceBetweenExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DifferenceBetweenExpressionContext { + var p = new(DifferenceBetweenExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *DifferenceBetweenExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DifferenceBetweenExpressionContext) PluralDateTimePrecision() IPluralDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPluralDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IPluralDateTimePrecisionContext) +} + +func (s *DifferenceBetweenExpressionContext) AllExpressionTerm() []IExpressionTermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionTermContext); ok { + len++ + } + } + + tst := make([]IExpressionTermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionTermContext); ok { + tst[i] = t.(IExpressionTermContext) + i++ + } + } + + return tst +} + +func (s *DifferenceBetweenExpressionContext) ExpressionTerm(i int) IExpressionTermContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *DifferenceBetweenExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDifferenceBetweenExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type InequalityExpressionContext struct { + ExpressionContext +} + +func NewInequalityExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *InequalityExpressionContext { + var p = new(InequalityExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *InequalityExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InequalityExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *InequalityExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *InequalityExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitInequalityExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type EqualityExpressionContext struct { + ExpressionContext +} + +func NewEqualityExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *EqualityExpressionContext { + var p = new(EqualityExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *EqualityExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EqualityExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *EqualityExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *EqualityExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitEqualityExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type ExistenceExpressionContext struct { + ExpressionContext +} + +func NewExistenceExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ExistenceExpressionContext { + var p = new(ExistenceExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *ExistenceExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExistenceExpressionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *ExistenceExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitExistenceExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type ImpliesExpressionContext struct { + ExpressionContext +} + +func NewImpliesExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ImpliesExpressionContext { + var p = new(ImpliesExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *ImpliesExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ImpliesExpressionContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *ImpliesExpressionContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *ImpliesExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitImpliesExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type TermExpressionContext struct { + ExpressionContext +} + +func NewTermExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TermExpressionContext { + var p = new(TermExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *TermExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TermExpressionContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *TermExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTermExpression(s) + + default: + return t.VisitChildren(s) + } +} + + +type TypeExpressionContext struct { + ExpressionContext +} + +func NewTypeExpressionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TypeExpressionContext { + var p = new(TypeExpressionContext) + + InitEmptyExpressionContext(&p.ExpressionContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionContext)) + + return p +} + +func (s *TypeExpressionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TypeExpressionContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *TypeExpressionContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + + +func (s *TypeExpressionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTypeExpression(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) Expression() (localctx IExpressionContext) { + return p.expression(0) +} + +func (p *CqlParser) expression(_p int) (localctx IExpressionContext) { + var _parentctx antlr.ParserRuleContext = p.GetParserRuleContext() + + _parentState := p.GetState() + localctx = NewExpressionContext(p, p.GetParserRuleContext(), _parentState) + var _prevctx IExpressionContext = localctx + var _ antlr.ParserRuleContext = _prevctx // TODO: To prevent unused variable warning. + _startState := 126 + p.EnterRecursionRule(localctx, 126, CqlParserRULE_expression, _p) + var _la int + + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(708) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 65, p.GetParserRuleContext()) { + case 1: + localctx = NewTermExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + + { + p.SetState(678) + p.expressionTerm(0) + } + + + case 2: + localctx = NewRetrieveExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(679) + p.Retrieve() + } + + + case 3: + localctx = NewQueryExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(680) + p.Query() + } + + + case 4: + localctx = NewCastExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(681) + p.Match(CqlParserT__62) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(682) + p.expression(0) + } + { + p.SetState(683) + p.Match(CqlParserT__61) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(684) + p.TypeSpecifier() + } + + + case 5: + localctx = NewNotExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(686) + p.Match(CqlParserT__57) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(687) + p.expression(13) + } + + + case 6: + localctx = NewExistenceExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(688) + p.Match(CqlParserT__63) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(689) + p.expression(12) + } + + + case 7: + localctx = NewDurationBetweenExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + p.SetState(692) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__67 { + { + p.SetState(690) + p.Match(CqlParserT__67) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(691) + p.Match(CqlParserT__40) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(694) + p.PluralDateTimePrecision() + } + { + p.SetState(695) + p.Match(CqlParserT__65) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(696) + p.expressionTerm(0) + } + { + p.SetState(697) + p.Match(CqlParserT__66) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(698) + p.expressionTerm(0) + } + + + case 8: + localctx = NewDifferenceBetweenExpressionContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(700) + p.Match(CqlParserT__68) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(701) + p.Match(CqlParserT__40) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(702) + p.PluralDateTimePrecision() + } + { + p.SetState(703) + p.Match(CqlParserT__65) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(704) + p.expressionTerm(0) + } + { + p.SetState(705) + p.Match(CqlParserT__66) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(706) + p.expressionTerm(0) + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + p.GetParserRuleContext().SetStop(p.GetTokenStream().LT(-1)) + p.SetState(758) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 70, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + if p.GetParseListeners() != nil { + p.TriggerExitRuleEvent() + } + _prevctx = localctx + p.SetState(756) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 69, p.GetParserRuleContext()) { + case 1: + localctx = NewInequalityExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(710) + + if !(p.Precpred(p.GetParserRuleContext(), 8)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 8)", "")) + goto errorExit + } + { + p.SetState(711) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 22)) & ^0x3f) == 0 && ((int64(1) << (_la - 22)) & 844424930131971) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(712) + p.expression(9) + } + + + case 2: + localctx = NewTimingExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(713) + + if !(p.Precpred(p.GetParserRuleContext(), 7)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 7)", "")) + goto errorExit + } + { + p.SetState(714) + p.IntervalOperatorPhrase() + } + { + p.SetState(715) + p.expression(8) + } + + + case 3: + localctx = NewEqualityExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(717) + + if !(p.Precpred(p.GetParserRuleContext(), 6)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 6)", "")) + goto errorExit + } + { + p.SetState(718) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 42)) & ^0x3f) == 0 && ((int64(1) << (_la - 42)) & 3221225475) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(719) + p.expression(7) + } + + + case 4: + localctx = NewMembershipExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(720) + + if !(p.Precpred(p.GetParserRuleContext(), 5)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 5)", "")) + goto errorExit + } + { + p.SetState(721) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__40 || _la == CqlParserT__73) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + p.SetState(723) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 66, p.GetParserRuleContext()) == 1 { + { + p.SetState(722) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(725) + p.expression(6) + } + + + case 5: + localctx = NewAndExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(726) + + if !(p.Precpred(p.GetParserRuleContext(), 4)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 4)", "")) + goto errorExit + } + { + p.SetState(727) + p.Match(CqlParserT__66) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(728) + p.expression(5) + } + + + case 6: + localctx = NewOrExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(729) + + if !(p.Precpred(p.GetParserRuleContext(), 3)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 3)", "")) + goto errorExit + } + { + p.SetState(730) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__74 || _la == CqlParserT__75) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(731) + p.expression(4) + } + + + case 7: + localctx = NewImpliesExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(732) + + if !(p.Precpred(p.GetParserRuleContext(), 2)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 2)", "")) + goto errorExit + } + { + p.SetState(733) + p.Match(CqlParserT__76) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(734) + p.expression(3) + } + + + case 8: + localctx = NewInFixSetExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(735) + + if !(p.Precpred(p.GetParserRuleContext(), 1)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 1)", "")) + goto errorExit + } + { + p.SetState(736) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 78)) & ^0x3f) == 0 && ((int64(1) << (_la - 78)) & 15) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(737) + p.expression(2) + } + + + case 9: + localctx = NewBooleanExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(738) + + if !(p.Precpred(p.GetParserRuleContext(), 16)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 16)", "")) + goto errorExit + } + { + p.SetState(739) + p.Match(CqlParserT__56) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(741) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__57 { + { + p.SetState(740) + p.Match(CqlParserT__57) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(743) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & 4035225266123964416) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + case 10: + localctx = NewTypeExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(744) + + if !(p.Precpred(p.GetParserRuleContext(), 15)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 15)", "")) + goto errorExit + } + { + p.SetState(745) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__56 || _la == CqlParserT__61) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(746) + p.TypeSpecifier() + } + + + case 11: + localctx = NewBetweenExpressionContext(p, NewExpressionContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expression) + p.SetState(747) + + if !(p.Precpred(p.GetParserRuleContext(), 11)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 11)", "")) + goto errorExit + } + p.SetState(749) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__64 { + { + p.SetState(748) + p.Match(CqlParserT__64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(751) + p.Match(CqlParserT__65) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(752) + p.expressionTerm(0) + } + { + p.SetState(753) + p.Match(CqlParserT__66) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(754) + p.expressionTerm(0) + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + } + p.SetState(760) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 70, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + + + + errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.UnrollRecursionContexts(_parentctx) + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IDateTimePrecisionContext is an interface to support dynamic dispatch. +type IDateTimePrecisionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsDateTimePrecisionContext differentiates from other interfaces. + IsDateTimePrecisionContext() +} + +type DateTimePrecisionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyDateTimePrecisionContext() *DateTimePrecisionContext { + var p = new(DateTimePrecisionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_dateTimePrecision + return p +} + +func InitEmptyDateTimePrecisionContext(p *DateTimePrecisionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_dateTimePrecision +} + +func (*DateTimePrecisionContext) IsDateTimePrecisionContext() {} + +func NewDateTimePrecisionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DateTimePrecisionContext { + var p = new(DateTimePrecisionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_dateTimePrecision + + return p +} + +func (s *DateTimePrecisionContext) GetParser() antlr.Parser { return s.parser } +func (s *DateTimePrecisionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateTimePrecisionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *DateTimePrecisionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDateTimePrecision(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) DateTimePrecision() (localctx IDateTimePrecisionContext) { + localctx = NewDateTimePrecisionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 128, CqlParserRULE_dateTimePrecision) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(761) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 82)) & ^0x3f) == 0 && ((int64(1) << (_la - 82)) & 255) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IDateTimeComponentContext is an interface to support dynamic dispatch. +type IDateTimeComponentContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DateTimePrecision() IDateTimePrecisionContext + + // IsDateTimeComponentContext differentiates from other interfaces. + IsDateTimeComponentContext() +} + +type DateTimeComponentContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyDateTimeComponentContext() *DateTimeComponentContext { + var p = new(DateTimeComponentContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_dateTimeComponent + return p +} + +func InitEmptyDateTimeComponentContext(p *DateTimeComponentContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_dateTimeComponent +} + +func (*DateTimeComponentContext) IsDateTimeComponentContext() {} + +func NewDateTimeComponentContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DateTimeComponentContext { + var p = new(DateTimeComponentContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_dateTimeComponent + + return p +} + +func (s *DateTimeComponentContext) GetParser() antlr.Parser { return s.parser } + +func (s *DateTimeComponentContext) DateTimePrecision() IDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionContext) +} + +func (s *DateTimeComponentContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateTimeComponentContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *DateTimeComponentContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDateTimeComponent(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) DateTimeComponent() (localctx IDateTimeComponentContext) { + localctx = NewDateTimeComponentContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 130, CqlParserRULE_dateTimeComponent) + p.SetState(768) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__81, CqlParserT__82, CqlParserT__83, CqlParserT__84, CqlParserT__85, CqlParserT__86, CqlParserT__87, CqlParserT__88: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(763) + p.DateTimePrecision() + } + + + case CqlParserT__89: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(764) + p.Match(CqlParserT__89) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case CqlParserT__90: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(765) + p.Match(CqlParserT__90) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case CqlParserT__91: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(766) + p.Match(CqlParserT__91) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case CqlParserT__92: + p.EnterOuterAlt(localctx, 5) + { + p.SetState(767) + p.Match(CqlParserT__92) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IPluralDateTimePrecisionContext is an interface to support dynamic dispatch. +type IPluralDateTimePrecisionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsPluralDateTimePrecisionContext differentiates from other interfaces. + IsPluralDateTimePrecisionContext() +} + +type PluralDateTimePrecisionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyPluralDateTimePrecisionContext() *PluralDateTimePrecisionContext { + var p = new(PluralDateTimePrecisionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_pluralDateTimePrecision + return p +} + +func InitEmptyPluralDateTimePrecisionContext(p *PluralDateTimePrecisionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_pluralDateTimePrecision +} + +func (*PluralDateTimePrecisionContext) IsPluralDateTimePrecisionContext() {} + +func NewPluralDateTimePrecisionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *PluralDateTimePrecisionContext { + var p = new(PluralDateTimePrecisionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_pluralDateTimePrecision + + return p +} + +func (s *PluralDateTimePrecisionContext) GetParser() antlr.Parser { return s.parser } +func (s *PluralDateTimePrecisionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PluralDateTimePrecisionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *PluralDateTimePrecisionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitPluralDateTimePrecision(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) PluralDateTimePrecision() (localctx IPluralDateTimePrecisionContext) { + localctx = NewPluralDateTimePrecisionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 132, CqlParserRULE_pluralDateTimePrecision) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(770) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 94)) & ^0x3f) == 0 && ((int64(1) << (_la - 94)) & 255) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IExpressionTermContext is an interface to support dynamic dispatch. +type IExpressionTermContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsExpressionTermContext differentiates from other interfaces. + IsExpressionTermContext() +} + +type ExpressionTermContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyExpressionTermContext() *ExpressionTermContext { + var p = new(ExpressionTermContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_expressionTerm + return p +} + +func InitEmptyExpressionTermContext(p *ExpressionTermContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_expressionTerm +} + +func (*ExpressionTermContext) IsExpressionTermContext() {} + +func NewExpressionTermContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExpressionTermContext { + var p = new(ExpressionTermContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_expressionTerm + + return p +} + +func (s *ExpressionTermContext) GetParser() antlr.Parser { return s.parser } + +func (s *ExpressionTermContext) CopyAll(ctx *ExpressionTermContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *ExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExpressionTermContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + + +type AdditionExpressionTermContext struct { + ExpressionTermContext +} + +func NewAdditionExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *AdditionExpressionTermContext { + var p = new(AdditionExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *AdditionExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AdditionExpressionTermContext) AllExpressionTerm() []IExpressionTermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionTermContext); ok { + len++ + } + } + + tst := make([]IExpressionTermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionTermContext); ok { + tst[i] = t.(IExpressionTermContext) + i++ + } + } + + return tst +} + +func (s *AdditionExpressionTermContext) ExpressionTerm(i int) IExpressionTermContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *AdditionExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitAdditionExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type IndexedExpressionTermContext struct { + ExpressionTermContext +} + +func NewIndexedExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *IndexedExpressionTermContext { + var p = new(IndexedExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *IndexedExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IndexedExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + +func (s *IndexedExpressionTermContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *IndexedExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIndexedExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type WidthExpressionTermContext struct { + ExpressionTermContext +} + +func NewWidthExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *WidthExpressionTermContext { + var p = new(WidthExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *WidthExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *WidthExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *WidthExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitWidthExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type SetAggregateExpressionTermContext struct { + ExpressionTermContext +} + +func NewSetAggregateExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *SetAggregateExpressionTermContext { + var p = new(SetAggregateExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *SetAggregateExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SetAggregateExpressionTermContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *SetAggregateExpressionTermContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *SetAggregateExpressionTermContext) DateTimePrecision() IDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionContext) +} + + +func (s *SetAggregateExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSetAggregateExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type TimeUnitExpressionTermContext struct { + ExpressionTermContext +} + +func NewTimeUnitExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TimeUnitExpressionTermContext { + var p = new(TimeUnitExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *TimeUnitExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TimeUnitExpressionTermContext) DateTimeComponent() IDateTimeComponentContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimeComponentContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimeComponentContext) +} + +func (s *TimeUnitExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *TimeUnitExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTimeUnitExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type IfThenElseExpressionTermContext struct { + ExpressionTermContext +} + +func NewIfThenElseExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *IfThenElseExpressionTermContext { + var p = new(IfThenElseExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *IfThenElseExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IfThenElseExpressionTermContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *IfThenElseExpressionTermContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *IfThenElseExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIfThenElseExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type TimeBoundaryExpressionTermContext struct { + ExpressionTermContext +} + +func NewTimeBoundaryExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TimeBoundaryExpressionTermContext { + var p = new(TimeBoundaryExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *TimeBoundaryExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TimeBoundaryExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *TimeBoundaryExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTimeBoundaryExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type ElementExtractorExpressionTermContext struct { + ExpressionTermContext +} + +func NewElementExtractorExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ElementExtractorExpressionTermContext { + var p = new(ElementExtractorExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *ElementExtractorExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ElementExtractorExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *ElementExtractorExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitElementExtractorExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type ConversionExpressionTermContext struct { + ExpressionTermContext +} + +func NewConversionExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ConversionExpressionTermContext { + var p = new(ConversionExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *ConversionExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConversionExpressionTermContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *ConversionExpressionTermContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ConversionExpressionTermContext) Unit() IUnitContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IUnitContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IUnitContext) +} + + +func (s *ConversionExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitConversionExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type TypeExtentExpressionTermContext struct { + ExpressionTermContext +} + +func NewTypeExtentExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TypeExtentExpressionTermContext { + var p = new(TypeExtentExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *TypeExtentExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TypeExtentExpressionTermContext) NamedTypeSpecifier() INamedTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(INamedTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(INamedTypeSpecifierContext) +} + + +func (s *TypeExtentExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTypeExtentExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type PredecessorExpressionTermContext struct { + ExpressionTermContext +} + +func NewPredecessorExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *PredecessorExpressionTermContext { + var p = new(PredecessorExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *PredecessorExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PredecessorExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *PredecessorExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitPredecessorExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type PointExtractorExpressionTermContext struct { + ExpressionTermContext +} + +func NewPointExtractorExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *PointExtractorExpressionTermContext { + var p = new(PointExtractorExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *PointExtractorExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PointExtractorExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *PointExtractorExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitPointExtractorExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type MultiplicationExpressionTermContext struct { + ExpressionTermContext +} + +func NewMultiplicationExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *MultiplicationExpressionTermContext { + var p = new(MultiplicationExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *MultiplicationExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MultiplicationExpressionTermContext) AllExpressionTerm() []IExpressionTermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionTermContext); ok { + len++ + } + } + + tst := make([]IExpressionTermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionTermContext); ok { + tst[i] = t.(IExpressionTermContext) + i++ + } + } + + return tst +} + +func (s *MultiplicationExpressionTermContext) ExpressionTerm(i int) IExpressionTermContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *MultiplicationExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitMultiplicationExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type AggregateExpressionTermContext struct { + ExpressionTermContext +} + +func NewAggregateExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *AggregateExpressionTermContext { + var p = new(AggregateExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *AggregateExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AggregateExpressionTermContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *AggregateExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitAggregateExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type DurationExpressionTermContext struct { + ExpressionTermContext +} + +func NewDurationExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DurationExpressionTermContext { + var p = new(DurationExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *DurationExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DurationExpressionTermContext) PluralDateTimePrecision() IPluralDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPluralDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IPluralDateTimePrecisionContext) +} + +func (s *DurationExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *DurationExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDurationExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type DifferenceExpressionTermContext struct { + ExpressionTermContext +} + +func NewDifferenceExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DifferenceExpressionTermContext { + var p = new(DifferenceExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *DifferenceExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DifferenceExpressionTermContext) PluralDateTimePrecision() IPluralDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPluralDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IPluralDateTimePrecisionContext) +} + +func (s *DifferenceExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *DifferenceExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDifferenceExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type CaseExpressionTermContext struct { + ExpressionTermContext +} + +func NewCaseExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *CaseExpressionTermContext { + var p = new(CaseExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *CaseExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CaseExpressionTermContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *CaseExpressionTermContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *CaseExpressionTermContext) AllCaseExpressionItem() []ICaseExpressionItemContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ICaseExpressionItemContext); ok { + len++ + } + } + + tst := make([]ICaseExpressionItemContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ICaseExpressionItemContext); ok { + tst[i] = t.(ICaseExpressionItemContext) + i++ + } + } + + return tst +} + +func (s *CaseExpressionTermContext) CaseExpressionItem(i int) ICaseExpressionItemContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICaseExpressionItemContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ICaseExpressionItemContext) +} + + +func (s *CaseExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCaseExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type PowerExpressionTermContext struct { + ExpressionTermContext +} + +func NewPowerExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *PowerExpressionTermContext { + var p = new(PowerExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *PowerExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PowerExpressionTermContext) AllExpressionTerm() []IExpressionTermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionTermContext); ok { + len++ + } + } + + tst := make([]IExpressionTermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionTermContext); ok { + tst[i] = t.(IExpressionTermContext) + i++ + } + } + + return tst +} + +func (s *PowerExpressionTermContext) ExpressionTerm(i int) IExpressionTermContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *PowerExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitPowerExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type SuccessorExpressionTermContext struct { + ExpressionTermContext +} + +func NewSuccessorExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *SuccessorExpressionTermContext { + var p = new(SuccessorExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *SuccessorExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SuccessorExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *SuccessorExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitSuccessorExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type PolarityExpressionTermContext struct { + ExpressionTermContext +} + +func NewPolarityExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *PolarityExpressionTermContext { + var p = new(PolarityExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *PolarityExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PolarityExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + + +func (s *PolarityExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitPolarityExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type TermExpressionTermContext struct { + ExpressionTermContext +} + +func NewTermExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TermExpressionTermContext { + var p = new(TermExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *TermExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TermExpressionTermContext) Term() ITermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITermContext) +} + + +func (s *TermExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTermExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type InvocationExpressionTermContext struct { + ExpressionTermContext +} + +func NewInvocationExpressionTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *InvocationExpressionTermContext { + var p = new(InvocationExpressionTermContext) + + InitEmptyExpressionTermContext(&p.ExpressionTermContext) + p.parser = parser + p.CopyAll(ctx.(*ExpressionTermContext)) + + return p +} + +func (s *InvocationExpressionTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InvocationExpressionTermContext) ExpressionTerm() IExpressionTermContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionTermContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionTermContext) +} + +func (s *InvocationExpressionTermContext) QualifiedInvocation() IQualifiedInvocationContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedInvocationContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedInvocationContext) +} + + +func (s *InvocationExpressionTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitInvocationExpressionTerm(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) ExpressionTerm() (localctx IExpressionTermContext) { + return p.expressionTerm(0) +} + +func (p *CqlParser) expressionTerm(_p int) (localctx IExpressionTermContext) { + var _parentctx antlr.ParserRuleContext = p.GetParserRuleContext() + + _parentState := p.GetState() + localctx = NewExpressionTermContext(p, p.GetParserRuleContext(), _parentState) + var _prevctx IExpressionTermContext = localctx + var _ antlr.ParserRuleContext = _prevctx // TODO: To prevent unused variable warning. + _startState := 134 + p.EnterRecursionRule(localctx, 134, CqlParserRULE_expressionTerm, _p) + var _la int + + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(850) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 77, p.GetParserRuleContext()) { + case 1: + localctx = NewTermExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + + { + p.SetState(773) + p.Term() + } + + + case 2: + localctx = NewConversionExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(774) + p.Match(CqlParserT__101) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(775) + p.expression(0) + } + { + p.SetState(776) + p.Match(CqlParserT__102) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(779) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__17, CqlParserT__19, CqlParserT__20, CqlParserT__23, CqlParserT__24, CqlParserT__25, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__44, CqlParserT__49, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__73, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__105, CqlParserT__106, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__118, CqlParserT__119, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__140, CqlParserT__141, CqlParserT__144, CqlParserT__148, CqlParserT__149, CqlParserT__153, CqlParserT__154, CqlParserT__155, CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + { + p.SetState(777) + p.TypeSpecifier() + } + + + case CqlParserT__81, CqlParserT__82, CqlParserT__83, CqlParserT__84, CqlParserT__85, CqlParserT__86, CqlParserT__87, CqlParserT__88, CqlParserT__93, CqlParserT__94, CqlParserT__95, CqlParserT__96, CqlParserT__97, CqlParserT__98, CqlParserT__99, CqlParserT__100, CqlParserSTRING: + { + p.SetState(778) + p.Unit() + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + + case 3: + localctx = NewPolarityExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(781) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__103 || _la == CqlParserT__104) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(782) + p.expressionTerm(18) + } + + + case 4: + localctx = NewTimeBoundaryExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(783) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__105 || _la == CqlParserT__106) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(784) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(785) + p.expressionTerm(17) + } + + + case 5: + localctx = NewTimeUnitExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(786) + p.DateTimeComponent() + } + { + p.SetState(787) + p.Match(CqlParserT__18) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(788) + p.expressionTerm(16) + } + + + case 6: + localctx = NewDurationExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(790) + p.Match(CqlParserT__67) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(791) + p.Match(CqlParserT__40) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(792) + p.PluralDateTimePrecision() + } + { + p.SetState(793) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(794) + p.expressionTerm(15) + } + + + case 7: + localctx = NewDifferenceExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(796) + p.Match(CqlParserT__68) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(797) + p.Match(CqlParserT__40) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(798) + p.PluralDateTimePrecision() + } + { + p.SetState(799) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(800) + p.expressionTerm(14) + } + + + case 8: + localctx = NewWidthExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(802) + p.Match(CqlParserT__108) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(803) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(804) + p.expressionTerm(13) + } + + + case 9: + localctx = NewSuccessorExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(805) + p.Match(CqlParserT__109) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(806) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(807) + p.expressionTerm(12) + } + + + case 10: + localctx = NewPredecessorExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(808) + p.Match(CqlParserT__110) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(809) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(810) + p.expressionTerm(11) + } + + + case 11: + localctx = NewElementExtractorExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(811) + p.Match(CqlParserT__111) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(812) + p.Match(CqlParserT__18) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(813) + p.expressionTerm(10) + } + + + case 12: + localctx = NewPointExtractorExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(814) + p.Match(CqlParserT__112) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(815) + p.Match(CqlParserT__18) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(816) + p.expressionTerm(9) + } + + + case 13: + localctx = NewTypeExtentExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(817) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__113 || _la == CqlParserT__114) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(818) + p.NamedTypeSpecifier() + } + + + case 14: + localctx = NewIfThenElseExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(819) + p.Match(CqlParserT__121) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(820) + p.expression(0) + } + { + p.SetState(821) + p.Match(CqlParserT__122) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(822) + p.expression(0) + } + { + p.SetState(823) + p.Match(CqlParserT__123) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(824) + p.expression(0) + } + + + case 15: + localctx = NewCaseExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(826) + p.Match(CqlParserT__124) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(828) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & -4758861967782021122) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & -1905040784319597519) != 0) || ((int64((_la - 128)) & ^0x3f) == 0 && ((int64(1) << (_la - 128)) & 549753872505) != 0) { + { + p.SetState(827) + p.expression(0) + } + + } + p.SetState(831) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for ok := true; ok; ok = _la == CqlParserT__129 { + { + p.SetState(830) + p.CaseExpressionItem() + } + + + p.SetState(833) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(835) + p.Match(CqlParserT__123) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(836) + p.expression(0) + } + { + p.SetState(837) + p.Match(CqlParserT__106) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 16: + localctx = NewAggregateExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(839) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__47 || _la == CqlParserT__125) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(840) + p.expression(0) + } + + + case 17: + localctx = NewSetAggregateExpressionTermContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(841) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__126 || _la == CqlParserT__127) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(842) + p.expression(0) + } + p.SetState(848) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 76, p.GetParserRuleContext()) == 1 { + { + p.SetState(843) + p.Match(CqlParserT__128) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(846) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 75, p.GetParserRuleContext()) { + case 1: + { + p.SetState(844) + p.DateTimePrecision() + } + + + case 2: + { + p.SetState(845) + p.expression(0) + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + } else if p.HasError() { // JIM + goto errorExit + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + p.GetParserRuleContext().SetStop(p.GetTokenStream().LT(-1)) + p.SetState(871) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 79, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + if p.GetParseListeners() != nil { + p.TriggerExitRuleEvent() + } + _prevctx = localctx + p.SetState(869) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 78, p.GetParserRuleContext()) { + case 1: + localctx = NewPowerExpressionTermContext(p, NewExpressionTermContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expressionTerm) + p.SetState(852) + + if !(p.Precpred(p.GetParserRuleContext(), 7)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 7)", "")) + goto errorExit + } + { + p.SetState(853) + p.Match(CqlParserT__115) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(854) + p.expressionTerm(8) + } + + + case 2: + localctx = NewMultiplicationExpressionTermContext(p, NewExpressionTermContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expressionTerm) + p.SetState(855) + + if !(p.Precpred(p.GetParserRuleContext(), 6)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 6)", "")) + goto errorExit + } + { + p.SetState(856) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 117)) & ^0x3f) == 0 && ((int64(1) << (_la - 117)) & 15) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(857) + p.expressionTerm(7) + } + + + case 3: + localctx = NewAdditionExpressionTermContext(p, NewExpressionTermContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expressionTerm) + p.SetState(858) + + if !(p.Precpred(p.GetParserRuleContext(), 5)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 5)", "")) + goto errorExit + } + { + p.SetState(859) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 104)) & ^0x3f) == 0 && ((int64(1) << (_la - 104)) & 131075) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(860) + p.expressionTerm(6) + } + + + case 4: + localctx = NewInvocationExpressionTermContext(p, NewExpressionTermContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expressionTerm) + p.SetState(861) + + if !(p.Precpred(p.GetParserRuleContext(), 21)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 21)", "")) + goto errorExit + } + { + p.SetState(862) + p.Match(CqlParserT__16) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(863) + p.QualifiedInvocation() + } + + + case 5: + localctx = NewIndexedExpressionTermContext(p, NewExpressionTermContext(p, _parentctx, _parentState)) + p.PushNewRecursionContext(localctx, _startState, CqlParserRULE_expressionTerm) + p.SetState(864) + + if !(p.Precpred(p.GetParserRuleContext(), 20)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 20)", "")) + goto errorExit + } + { + p.SetState(865) + p.Match(CqlParserT__37) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(866) + p.expression(0) + } + { + p.SetState(867) + p.Match(CqlParserT__39) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + } + p.SetState(873) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 79, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + + + + errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.UnrollRecursionContexts(_parentctx) + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICaseExpressionItemContext is an interface to support dynamic dispatch. +type ICaseExpressionItemContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllExpression() []IExpressionContext + Expression(i int) IExpressionContext + + // IsCaseExpressionItemContext differentiates from other interfaces. + IsCaseExpressionItemContext() +} + +type CaseExpressionItemContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCaseExpressionItemContext() *CaseExpressionItemContext { + var p = new(CaseExpressionItemContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_caseExpressionItem + return p +} + +func InitEmptyCaseExpressionItemContext(p *CaseExpressionItemContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_caseExpressionItem +} + +func (*CaseExpressionItemContext) IsCaseExpressionItemContext() {} + +func NewCaseExpressionItemContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CaseExpressionItemContext { + var p = new(CaseExpressionItemContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_caseExpressionItem + + return p +} + +func (s *CaseExpressionItemContext) GetParser() antlr.Parser { return s.parser } + +func (s *CaseExpressionItemContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *CaseExpressionItemContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *CaseExpressionItemContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CaseExpressionItemContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CaseExpressionItemContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCaseExpressionItem(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CaseExpressionItem() (localctx ICaseExpressionItemContext) { + localctx = NewCaseExpressionItemContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 136, CqlParserRULE_caseExpressionItem) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(874) + p.Match(CqlParserT__129) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(875) + p.expression(0) + } + { + p.SetState(876) + p.Match(CqlParserT__122) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(877) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IDateTimePrecisionSpecifierContext is an interface to support dynamic dispatch. +type IDateTimePrecisionSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DateTimePrecision() IDateTimePrecisionContext + + // IsDateTimePrecisionSpecifierContext differentiates from other interfaces. + IsDateTimePrecisionSpecifierContext() +} + +type DateTimePrecisionSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyDateTimePrecisionSpecifierContext() *DateTimePrecisionSpecifierContext { + var p = new(DateTimePrecisionSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_dateTimePrecisionSpecifier + return p +} + +func InitEmptyDateTimePrecisionSpecifierContext(p *DateTimePrecisionSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_dateTimePrecisionSpecifier +} + +func (*DateTimePrecisionSpecifierContext) IsDateTimePrecisionSpecifierContext() {} + +func NewDateTimePrecisionSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DateTimePrecisionSpecifierContext { + var p = new(DateTimePrecisionSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_dateTimePrecisionSpecifier + + return p +} + +func (s *DateTimePrecisionSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *DateTimePrecisionSpecifierContext) DateTimePrecision() IDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionContext) +} + +func (s *DateTimePrecisionSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateTimePrecisionSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *DateTimePrecisionSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDateTimePrecisionSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) DateTimePrecisionSpecifier() (localctx IDateTimePrecisionSpecifierContext) { + localctx = NewDateTimePrecisionSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 138, CqlParserRULE_dateTimePrecisionSpecifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(879) + p.DateTimePrecision() + } + { + p.SetState(880) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IRelativeQualifierContext is an interface to support dynamic dispatch. +type IRelativeQualifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsRelativeQualifierContext differentiates from other interfaces. + IsRelativeQualifierContext() +} + +type RelativeQualifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRelativeQualifierContext() *RelativeQualifierContext { + var p = new(RelativeQualifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_relativeQualifier + return p +} + +func InitEmptyRelativeQualifierContext(p *RelativeQualifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_relativeQualifier +} + +func (*RelativeQualifierContext) IsRelativeQualifierContext() {} + +func NewRelativeQualifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RelativeQualifierContext { + var p = new(RelativeQualifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_relativeQualifier + + return p +} + +func (s *RelativeQualifierContext) GetParser() antlr.Parser { return s.parser } +func (s *RelativeQualifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RelativeQualifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *RelativeQualifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitRelativeQualifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) RelativeQualifier() (localctx IRelativeQualifierContext) { + localctx = NewRelativeQualifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 140, CqlParserRULE_relativeQualifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(882) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__130 || _la == CqlParserT__131) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IOffsetRelativeQualifierContext is an interface to support dynamic dispatch. +type IOffsetRelativeQualifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsOffsetRelativeQualifierContext differentiates from other interfaces. + IsOffsetRelativeQualifierContext() +} + +type OffsetRelativeQualifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOffsetRelativeQualifierContext() *OffsetRelativeQualifierContext { + var p = new(OffsetRelativeQualifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_offsetRelativeQualifier + return p +} + +func InitEmptyOffsetRelativeQualifierContext(p *OffsetRelativeQualifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_offsetRelativeQualifier +} + +func (*OffsetRelativeQualifierContext) IsOffsetRelativeQualifierContext() {} + +func NewOffsetRelativeQualifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OffsetRelativeQualifierContext { + var p = new(OffsetRelativeQualifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_offsetRelativeQualifier + + return p +} + +func (s *OffsetRelativeQualifierContext) GetParser() antlr.Parser { return s.parser } +func (s *OffsetRelativeQualifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OffsetRelativeQualifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *OffsetRelativeQualifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitOffsetRelativeQualifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) OffsetRelativeQualifier() (localctx IOffsetRelativeQualifierContext) { + localctx = NewOffsetRelativeQualifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 142, CqlParserRULE_offsetRelativeQualifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(884) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__132 || _la == CqlParserT__133) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IExclusiveRelativeQualifierContext is an interface to support dynamic dispatch. +type IExclusiveRelativeQualifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsExclusiveRelativeQualifierContext differentiates from other interfaces. + IsExclusiveRelativeQualifierContext() +} + +type ExclusiveRelativeQualifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyExclusiveRelativeQualifierContext() *ExclusiveRelativeQualifierContext { + var p = new(ExclusiveRelativeQualifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_exclusiveRelativeQualifier + return p +} + +func InitEmptyExclusiveRelativeQualifierContext(p *ExclusiveRelativeQualifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_exclusiveRelativeQualifier +} + +func (*ExclusiveRelativeQualifierContext) IsExclusiveRelativeQualifierContext() {} + +func NewExclusiveRelativeQualifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExclusiveRelativeQualifierContext { + var p = new(ExclusiveRelativeQualifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_exclusiveRelativeQualifier + + return p +} + +func (s *ExclusiveRelativeQualifierContext) GetParser() antlr.Parser { return s.parser } +func (s *ExclusiveRelativeQualifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExclusiveRelativeQualifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ExclusiveRelativeQualifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitExclusiveRelativeQualifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ExclusiveRelativeQualifier() (localctx IExclusiveRelativeQualifierContext) { + localctx = NewExclusiveRelativeQualifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 144, CqlParserRULE_exclusiveRelativeQualifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(886) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__134 || _la == CqlParserT__135) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQuantityOffsetContext is an interface to support dynamic dispatch. +type IQuantityOffsetContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Quantity() IQuantityContext + OffsetRelativeQualifier() IOffsetRelativeQualifierContext + ExclusiveRelativeQualifier() IExclusiveRelativeQualifierContext + + // IsQuantityOffsetContext differentiates from other interfaces. + IsQuantityOffsetContext() +} + +type QuantityOffsetContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQuantityOffsetContext() *QuantityOffsetContext { + var p = new(QuantityOffsetContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_quantityOffset + return p +} + +func InitEmptyQuantityOffsetContext(p *QuantityOffsetContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_quantityOffset +} + +func (*QuantityOffsetContext) IsQuantityOffsetContext() {} + +func NewQuantityOffsetContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QuantityOffsetContext { + var p = new(QuantityOffsetContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_quantityOffset + + return p +} + +func (s *QuantityOffsetContext) GetParser() antlr.Parser { return s.parser } + +func (s *QuantityOffsetContext) Quantity() IQuantityContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQuantityContext) +} + +func (s *QuantityOffsetContext) OffsetRelativeQualifier() IOffsetRelativeQualifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOffsetRelativeQualifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IOffsetRelativeQualifierContext) +} + +func (s *QuantityOffsetContext) ExclusiveRelativeQualifier() IExclusiveRelativeQualifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExclusiveRelativeQualifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExclusiveRelativeQualifierContext) +} + +func (s *QuantityOffsetContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QuantityOffsetContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QuantityOffsetContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQuantityOffset(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) QuantityOffset() (localctx IQuantityOffsetContext) { + localctx = NewQuantityOffsetContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 146, CqlParserRULE_quantityOffset) + var _la int + + p.SetState(895) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserNUMBER: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(888) + p.Quantity() + } + p.SetState(890) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__132 || _la == CqlParserT__133 { + { + p.SetState(889) + p.OffsetRelativeQualifier() + } + + } + + + + case CqlParserT__134, CqlParserT__135: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(892) + p.ExclusiveRelativeQualifier() + } + { + p.SetState(893) + p.Quantity() + } + + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITemporalRelationshipContext is an interface to support dynamic dispatch. +type ITemporalRelationshipContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsTemporalRelationshipContext differentiates from other interfaces. + IsTemporalRelationshipContext() +} + +type TemporalRelationshipContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTemporalRelationshipContext() *TemporalRelationshipContext { + var p = new(TemporalRelationshipContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_temporalRelationship + return p +} + +func InitEmptyTemporalRelationshipContext(p *TemporalRelationshipContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_temporalRelationship +} + +func (*TemporalRelationshipContext) IsTemporalRelationshipContext() {} + +func NewTemporalRelationshipContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TemporalRelationshipContext { + var p = new(TemporalRelationshipContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_temporalRelationship + + return p +} + +func (s *TemporalRelationshipContext) GetParser() antlr.Parser { return s.parser } +func (s *TemporalRelationshipContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TemporalRelationshipContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TemporalRelationshipContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTemporalRelationship(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) TemporalRelationship() (localctx ITemporalRelationshipContext) { + localctx = NewTemporalRelationshipContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 148, CqlParserRULE_temporalRelationship) + var _la int + + p.SetState(905) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 84, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + p.SetState(898) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__136 { + { + p.SetState(897) + p.Match(CqlParserT__136) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(900) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__137 || _la == CqlParserT__138) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(901) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__137 || _la == CqlParserT__138) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + p.SetState(903) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__139 { + { + p.SetState(902) + p.Match(CqlParserT__139) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IIntervalOperatorPhraseContext is an interface to support dynamic dispatch. +type IIntervalOperatorPhraseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsIntervalOperatorPhraseContext differentiates from other interfaces. + IsIntervalOperatorPhraseContext() +} + +type IntervalOperatorPhraseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIntervalOperatorPhraseContext() *IntervalOperatorPhraseContext { + var p = new(IntervalOperatorPhraseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_intervalOperatorPhrase + return p +} + +func InitEmptyIntervalOperatorPhraseContext(p *IntervalOperatorPhraseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_intervalOperatorPhrase +} + +func (*IntervalOperatorPhraseContext) IsIntervalOperatorPhraseContext() {} + +func NewIntervalOperatorPhraseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IntervalOperatorPhraseContext { + var p = new(IntervalOperatorPhraseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_intervalOperatorPhrase + + return p +} + +func (s *IntervalOperatorPhraseContext) GetParser() antlr.Parser { return s.parser } + +func (s *IntervalOperatorPhraseContext) CopyAll(ctx *IntervalOperatorPhraseContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *IntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntervalOperatorPhraseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + +type WithinIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewWithinIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *WithinIntervalOperatorPhraseContext { + var p = new(WithinIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *WithinIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *WithinIntervalOperatorPhraseContext) Quantity() IQuantityContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQuantityContext) +} + + +func (s *WithinIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitWithinIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type IncludedInIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewIncludedInIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *IncludedInIntervalOperatorPhraseContext { + var p = new(IncludedInIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *IncludedInIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IncludedInIntervalOperatorPhraseContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *IncludedInIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIncludedInIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type EndsIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewEndsIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *EndsIntervalOperatorPhraseContext { + var p = new(EndsIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *EndsIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EndsIntervalOperatorPhraseContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *EndsIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitEndsIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type ConcurrentWithIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewConcurrentWithIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ConcurrentWithIntervalOperatorPhraseContext { + var p = new(ConcurrentWithIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *ConcurrentWithIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConcurrentWithIntervalOperatorPhraseContext) RelativeQualifier() IRelativeQualifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRelativeQualifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IRelativeQualifierContext) +} + +func (s *ConcurrentWithIntervalOperatorPhraseContext) DateTimePrecision() IDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionContext) +} + + +func (s *ConcurrentWithIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitConcurrentWithIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type OverlapsIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewOverlapsIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *OverlapsIntervalOperatorPhraseContext { + var p = new(OverlapsIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *OverlapsIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OverlapsIntervalOperatorPhraseContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *OverlapsIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitOverlapsIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type IncludesIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewIncludesIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *IncludesIntervalOperatorPhraseContext { + var p = new(IncludesIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *IncludesIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IncludesIntervalOperatorPhraseContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *IncludesIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIncludesIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type BeforeOrAfterIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewBeforeOrAfterIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *BeforeOrAfterIntervalOperatorPhraseContext { + var p = new(BeforeOrAfterIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *BeforeOrAfterIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *BeforeOrAfterIntervalOperatorPhraseContext) TemporalRelationship() ITemporalRelationshipContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITemporalRelationshipContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITemporalRelationshipContext) +} + +func (s *BeforeOrAfterIntervalOperatorPhraseContext) QuantityOffset() IQuantityOffsetContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityOffsetContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQuantityOffsetContext) +} + +func (s *BeforeOrAfterIntervalOperatorPhraseContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *BeforeOrAfterIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitBeforeOrAfterIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type MeetsIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewMeetsIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *MeetsIntervalOperatorPhraseContext { + var p = new(MeetsIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *MeetsIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MeetsIntervalOperatorPhraseContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *MeetsIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitMeetsIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + +type StartsIntervalOperatorPhraseContext struct { + IntervalOperatorPhraseContext +} + +func NewStartsIntervalOperatorPhraseContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *StartsIntervalOperatorPhraseContext { + var p = new(StartsIntervalOperatorPhraseContext) + + InitEmptyIntervalOperatorPhraseContext(&p.IntervalOperatorPhraseContext) + p.parser = parser + p.CopyAll(ctx.(*IntervalOperatorPhraseContext)) + + return p +} + +func (s *StartsIntervalOperatorPhraseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StartsIntervalOperatorPhraseContext) DateTimePrecisionSpecifier() IDateTimePrecisionSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionSpecifierContext) +} + + +func (s *StartsIntervalOperatorPhraseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitStartsIntervalOperatorPhrase(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) IntervalOperatorPhrase() (localctx IIntervalOperatorPhraseContext) { + localctx = NewIntervalOperatorPhraseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 150, CqlParserRULE_intervalOperatorPhrase) + var _la int + + p.SetState(988) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 108, p.GetParserRuleContext()) { + case 1: + localctx = NewConcurrentWithIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + p.SetState(908) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0) { + { + p.SetState(907) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + { + p.SetState(910) + p.Match(CqlParserT__143) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(912) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64((_la - 82)) & ^0x3f) == 0 && ((int64(1) << (_la - 82)) & 255) != 0) { + { + p.SetState(911) + p.DateTimePrecision() + } + + } + p.SetState(916) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__130, CqlParserT__131: + { + p.SetState(914) + p.RelativeQualifier() + } + + + case CqlParserT__61: + { + p.SetState(915) + p.Match(CqlParserT__61) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + p.SetState(919) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 88, p.GetParserRuleContext()) == 1 { + { + p.SetState(918) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__105 || _la == CqlParserT__106) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 2: + localctx = NewIncludesIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + p.SetState(922) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__64 { + { + p.SetState(921) + p.Match(CqlParserT__64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(924) + p.Match(CqlParserT__144) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(926) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 90, p.GetParserRuleContext()) == 1 { + { + p.SetState(925) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(929) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 91, p.GetParserRuleContext()) == 1 { + { + p.SetState(928) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__105 || _la == CqlParserT__106) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 3: + localctx = NewIncludedInIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 3) + p.SetState(932) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0) { + { + p.SetState(931) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + p.SetState(935) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__64 { + { + p.SetState(934) + p.Match(CqlParserT__64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(937) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__145 || _la == CqlParserT__146) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + p.SetState(939) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 94, p.GetParserRuleContext()) == 1 { + { + p.SetState(938) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 4: + localctx = NewBeforeOrAfterIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 4) + p.SetState(942) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0) { + { + p.SetState(941) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + p.SetState(945) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64((_la - 135)) & ^0x3f) == 0 && ((int64(1) << (_la - 135)) & 2147483651) != 0) { + { + p.SetState(944) + p.QuantityOffset() + } + + } + { + p.SetState(947) + p.TemporalRelationship() + } + p.SetState(949) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 97, p.GetParserRuleContext()) == 1 { + { + p.SetState(948) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(952) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 98, p.GetParserRuleContext()) == 1 { + { + p.SetState(951) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__105 || _la == CqlParserT__106) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 5: + localctx = NewWithinIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 5) + p.SetState(955) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0) { + { + p.SetState(954) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 7) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + p.SetState(958) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__64 { + { + p.SetState(957) + p.Match(CqlParserT__64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(960) + p.Match(CqlParserT__147) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(961) + p.Quantity() + } + { + p.SetState(962) + p.Match(CqlParserT__107) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(964) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 101, p.GetParserRuleContext()) == 1 { + { + p.SetState(963) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__105 || _la == CqlParserT__106) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 6: + localctx = NewMeetsIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 6) + { + p.SetState(966) + p.Match(CqlParserT__148) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(968) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__137 || _la == CqlParserT__138 { + { + p.SetState(967) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__137 || _la == CqlParserT__138) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + p.SetState(971) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 103, p.GetParserRuleContext()) == 1 { + { + p.SetState(970) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 7: + localctx = NewOverlapsIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 7) + { + p.SetState(973) + p.Match(CqlParserT__149) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(975) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__137 || _la == CqlParserT__138 { + { + p.SetState(974) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__137 || _la == CqlParserT__138) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + p.SetState(978) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 105, p.GetParserRuleContext()) == 1 { + { + p.SetState(977) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 8: + localctx = NewStartsIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 8) + { + p.SetState(980) + p.Match(CqlParserT__140) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(982) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 106, p.GetParserRuleContext()) == 1 { + { + p.SetState(981) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + case 9: + localctx = NewEndsIntervalOperatorPhraseContext(p, localctx) + p.EnterOuterAlt(localctx, 9) + { + p.SetState(984) + p.Match(CqlParserT__141) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(986) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 107, p.GetParserRuleContext()) == 1 { + { + p.SetState(985) + p.DateTimePrecisionSpecifier() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITermContext is an interface to support dynamic dispatch. +type ITermContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsTermContext differentiates from other interfaces. + IsTermContext() +} + +type TermContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTermContext() *TermContext { + var p = new(TermContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_term + return p +} + +func InitEmptyTermContext(p *TermContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_term +} + +func (*TermContext) IsTermContext() {} + +func NewTermContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TermContext { + var p = new(TermContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_term + + return p +} + +func (s *TermContext) GetParser() antlr.Parser { return s.parser } + +func (s *TermContext) CopyAll(ctx *TermContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *TermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TermContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + +type ExternalConstantTermContext struct { + TermContext +} + +func NewExternalConstantTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ExternalConstantTermContext { + var p = new(ExternalConstantTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *ExternalConstantTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExternalConstantTermContext) ExternalConstant() IExternalConstantContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExternalConstantContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExternalConstantContext) +} + + +func (s *ExternalConstantTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitExternalConstantTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type TupleSelectorTermContext struct { + TermContext +} + +func NewTupleSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TupleSelectorTermContext { + var p = new(TupleSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *TupleSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleSelectorTermContext) TupleSelector() ITupleSelectorContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleSelectorContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITupleSelectorContext) +} + + +func (s *TupleSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTupleSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type LiteralTermContext struct { + TermContext +} + +func NewLiteralTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LiteralTermContext { + var p = new(LiteralTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *LiteralTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LiteralTermContext) Literal() ILiteralContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILiteralContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ILiteralContext) +} + + +func (s *LiteralTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLiteralTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type ConceptSelectorTermContext struct { + TermContext +} + +func NewConceptSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ConceptSelectorTermContext { + var p = new(ConceptSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *ConceptSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConceptSelectorTermContext) ConceptSelector() IConceptSelectorContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IConceptSelectorContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IConceptSelectorContext) +} + + +func (s *ConceptSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitConceptSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type ParenthesizedTermContext struct { + TermContext +} + +func NewParenthesizedTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ParenthesizedTermContext { + var p = new(ParenthesizedTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *ParenthesizedTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ParenthesizedTermContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + + +func (s *ParenthesizedTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitParenthesizedTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type CodeSelectorTermContext struct { + TermContext +} + +func NewCodeSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *CodeSelectorTermContext { + var p = new(CodeSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *CodeSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeSelectorTermContext) CodeSelector() ICodeSelectorContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeSelectorContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodeSelectorContext) +} + + +func (s *CodeSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodeSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type InvocationTermContext struct { + TermContext +} + +func NewInvocationTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *InvocationTermContext { + var p = new(InvocationTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *InvocationTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InvocationTermContext) Invocation() IInvocationContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IInvocationContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IInvocationContext) +} + + +func (s *InvocationTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitInvocationTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type InstanceSelectorTermContext struct { + TermContext +} + +func NewInstanceSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *InstanceSelectorTermContext { + var p = new(InstanceSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *InstanceSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InstanceSelectorTermContext) InstanceSelector() IInstanceSelectorContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IInstanceSelectorContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IInstanceSelectorContext) +} + + +func (s *InstanceSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitInstanceSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type IntervalSelectorTermContext struct { + TermContext +} + +func NewIntervalSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *IntervalSelectorTermContext { + var p = new(IntervalSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *IntervalSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntervalSelectorTermContext) IntervalSelector() IIntervalSelectorContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntervalSelectorContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIntervalSelectorContext) +} + + +func (s *IntervalSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIntervalSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + + +type ListSelectorTermContext struct { + TermContext +} + +func NewListSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ListSelectorTermContext { + var p = new(ListSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *ListSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ListSelectorTermContext) ListSelector() IListSelectorContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IListSelectorContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IListSelectorContext) +} + + +func (s *ListSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitListSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) Term() (localctx ITermContext) { + localctx = NewTermContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 152, CqlParserRULE_term) + p.SetState(1003) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 109, p.GetParserRuleContext()) { + case 1: + localctx = NewInvocationTermContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(990) + p.Invocation() + } + + + case 2: + localctx = NewLiteralTermContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + { + p.SetState(991) + p.Literal() + } + + + case 3: + localctx = NewExternalConstantTermContext(p, localctx) + p.EnterOuterAlt(localctx, 3) + { + p.SetState(992) + p.ExternalConstant() + } + + + case 4: + localctx = NewIntervalSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 4) + { + p.SetState(993) + p.IntervalSelector() + } + + + case 5: + localctx = NewTupleSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 5) + { + p.SetState(994) + p.TupleSelector() + } + + + case 6: + localctx = NewInstanceSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 6) + { + p.SetState(995) + p.InstanceSelector() + } + + + case 7: + localctx = NewListSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 7) + { + p.SetState(996) + p.ListSelector() + } + + + case 8: + localctx = NewCodeSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 8) + { + p.SetState(997) + p.CodeSelector() + } + + + case 9: + localctx = NewConceptSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 9) + { + p.SetState(998) + p.ConceptSelector() + } + + + case 10: + localctx = NewParenthesizedTermContext(p, localctx) + p.EnterOuterAlt(localctx, 10) + { + p.SetState(999) + p.Match(CqlParserT__30) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1000) + p.expression(0) + } + { + p.SetState(1001) + p.Match(CqlParserT__31) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQualifiedInvocationContext is an interface to support dynamic dispatch. +type IQualifiedInvocationContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsQualifiedInvocationContext differentiates from other interfaces. + IsQualifiedInvocationContext() +} + +type QualifiedInvocationContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQualifiedInvocationContext() *QualifiedInvocationContext { + var p = new(QualifiedInvocationContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedInvocation + return p +} + +func InitEmptyQualifiedInvocationContext(p *QualifiedInvocationContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedInvocation +} + +func (*QualifiedInvocationContext) IsQualifiedInvocationContext() {} + +func NewQualifiedInvocationContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QualifiedInvocationContext { + var p = new(QualifiedInvocationContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_qualifiedInvocation + + return p +} + +func (s *QualifiedInvocationContext) GetParser() antlr.Parser { return s.parser } + +func (s *QualifiedInvocationContext) CopyAll(ctx *QualifiedInvocationContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *QualifiedInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifiedInvocationContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + +type QualifiedFunctionInvocationContext struct { + QualifiedInvocationContext +} + +func NewQualifiedFunctionInvocationContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *QualifiedFunctionInvocationContext { + var p = new(QualifiedFunctionInvocationContext) + + InitEmptyQualifiedInvocationContext(&p.QualifiedInvocationContext) + p.parser = parser + p.CopyAll(ctx.(*QualifiedInvocationContext)) + + return p +} + +func (s *QualifiedFunctionInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifiedFunctionInvocationContext) QualifiedFunction() IQualifiedFunctionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQualifiedFunctionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQualifiedFunctionContext) +} + + +func (s *QualifiedFunctionInvocationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQualifiedFunctionInvocation(s) + + default: + return t.VisitChildren(s) + } +} + + +type QualifiedMemberInvocationContext struct { + QualifiedInvocationContext +} + +func NewQualifiedMemberInvocationContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *QualifiedMemberInvocationContext { + var p = new(QualifiedMemberInvocationContext) + + InitEmptyQualifiedInvocationContext(&p.QualifiedInvocationContext) + p.parser = parser + p.CopyAll(ctx.(*QualifiedInvocationContext)) + + return p +} + +func (s *QualifiedMemberInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifiedMemberInvocationContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + + +func (s *QualifiedMemberInvocationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQualifiedMemberInvocation(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) QualifiedInvocation() (localctx IQualifiedInvocationContext) { + localctx = NewQualifiedInvocationContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 154, CqlParserRULE_qualifiedInvocation) + p.SetState(1007) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 110, p.GetParserRuleContext()) { + case 1: + localctx = NewQualifiedMemberInvocationContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1005) + p.ReferentialIdentifier() + } + + + case 2: + localctx = NewQualifiedFunctionInvocationContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + { + p.SetState(1006) + p.QualifiedFunction() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQualifiedFunctionContext is an interface to support dynamic dispatch. +type IQualifiedFunctionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IdentifierOrFunctionIdentifier() IIdentifierOrFunctionIdentifierContext + ParamList() IParamListContext + + // IsQualifiedFunctionContext differentiates from other interfaces. + IsQualifiedFunctionContext() +} + +type QualifiedFunctionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQualifiedFunctionContext() *QualifiedFunctionContext { + var p = new(QualifiedFunctionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedFunction + return p +} + +func InitEmptyQualifiedFunctionContext(p *QualifiedFunctionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_qualifiedFunction +} + +func (*QualifiedFunctionContext) IsQualifiedFunctionContext() {} + +func NewQualifiedFunctionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QualifiedFunctionContext { + var p = new(QualifiedFunctionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_qualifiedFunction + + return p +} + +func (s *QualifiedFunctionContext) GetParser() antlr.Parser { return s.parser } + +func (s *QualifiedFunctionContext) IdentifierOrFunctionIdentifier() IIdentifierOrFunctionIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierOrFunctionIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierOrFunctionIdentifierContext) +} + +func (s *QualifiedFunctionContext) ParamList() IParamListContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IParamListContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IParamListContext) +} + +func (s *QualifiedFunctionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QualifiedFunctionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QualifiedFunctionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQualifiedFunction(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) QualifiedFunction() (localctx IQualifiedFunctionContext) { + localctx = NewQualifiedFunctionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 156, CqlParserRULE_qualifiedFunction) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1009) + p.IdentifierOrFunctionIdentifier() + } + { + p.SetState(1010) + p.Match(CqlParserT__30) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1012) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & -4758861967782021122) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & -1905040784319597519) != 0) || ((int64((_la - 128)) & ^0x3f) == 0 && ((int64(1) << (_la - 128)) & 549753872505) != 0) { + { + p.SetState(1011) + p.ParamList() + } + + } + { + p.SetState(1014) + p.Match(CqlParserT__31) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IInvocationContext is an interface to support dynamic dispatch. +type IInvocationContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsInvocationContext differentiates from other interfaces. + IsInvocationContext() +} + +type InvocationContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyInvocationContext() *InvocationContext { + var p = new(InvocationContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_invocation + return p +} + +func InitEmptyInvocationContext(p *InvocationContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_invocation +} + +func (*InvocationContext) IsInvocationContext() {} + +func NewInvocationContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InvocationContext { + var p = new(InvocationContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_invocation + + return p +} + +func (s *InvocationContext) GetParser() antlr.Parser { return s.parser } + +func (s *InvocationContext) CopyAll(ctx *InvocationContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *InvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InvocationContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + +type TotalInvocationContext struct { + InvocationContext +} + +func NewTotalInvocationContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TotalInvocationContext { + var p = new(TotalInvocationContext) + + InitEmptyInvocationContext(&p.InvocationContext) + p.parser = parser + p.CopyAll(ctx.(*InvocationContext)) + + return p +} + +func (s *TotalInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TotalInvocationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTotalInvocation(s) + + default: + return t.VisitChildren(s) + } +} + + +type ThisInvocationContext struct { + InvocationContext +} + +func NewThisInvocationContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ThisInvocationContext { + var p = new(ThisInvocationContext) + + InitEmptyInvocationContext(&p.InvocationContext) + p.parser = parser + p.CopyAll(ctx.(*InvocationContext)) + + return p +} + +func (s *ThisInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ThisInvocationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitThisInvocation(s) + + default: + return t.VisitChildren(s) + } +} + + +type IndexInvocationContext struct { + InvocationContext +} + +func NewIndexInvocationContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *IndexInvocationContext { + var p = new(IndexInvocationContext) + + InitEmptyInvocationContext(&p.InvocationContext) + p.parser = parser + p.CopyAll(ctx.(*InvocationContext)) + + return p +} + +func (s *IndexInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IndexInvocationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIndexInvocation(s) + + default: + return t.VisitChildren(s) + } +} + + +type FunctionInvocationContext struct { + InvocationContext +} + +func NewFunctionInvocationContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *FunctionInvocationContext { + var p = new(FunctionInvocationContext) + + InitEmptyInvocationContext(&p.InvocationContext) + p.parser = parser + p.CopyAll(ctx.(*InvocationContext)) + + return p +} + +func (s *FunctionInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FunctionInvocationContext) Function() IFunctionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFunctionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IFunctionContext) +} + + +func (s *FunctionInvocationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitFunctionInvocation(s) + + default: + return t.VisitChildren(s) + } +} + + +type MemberInvocationContext struct { + InvocationContext +} + +func NewMemberInvocationContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *MemberInvocationContext { + var p = new(MemberInvocationContext) + + InitEmptyInvocationContext(&p.InvocationContext) + p.parser = parser + p.CopyAll(ctx.(*InvocationContext)) + + return p +} + +func (s *MemberInvocationContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MemberInvocationContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + + +func (s *MemberInvocationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitMemberInvocation(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) Invocation() (localctx IInvocationContext) { + localctx = NewInvocationContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 158, CqlParserRULE_invocation) + p.SetState(1021) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 112, p.GetParserRuleContext()) { + case 1: + localctx = NewMemberInvocationContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1016) + p.ReferentialIdentifier() + } + + + case 2: + localctx = NewFunctionInvocationContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + { + p.SetState(1017) + p.Function() + } + + + case 3: + localctx = NewThisInvocationContext(p, localctx) + p.EnterOuterAlt(localctx, 3) + { + p.SetState(1018) + p.Match(CqlParserT__150) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 4: + localctx = NewIndexInvocationContext(p, localctx) + p.EnterOuterAlt(localctx, 4) + { + p.SetState(1019) + p.Match(CqlParserT__151) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 5: + localctx = NewTotalInvocationContext(p, localctx) + p.EnterOuterAlt(localctx, 5) + { + p.SetState(1020) + p.Match(CqlParserT__152) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IFunctionContext is an interface to support dynamic dispatch. +type IFunctionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + ParamList() IParamListContext + + // IsFunctionContext differentiates from other interfaces. + IsFunctionContext() +} + +type FunctionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFunctionContext() *FunctionContext { + var p = new(FunctionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_function + return p +} + +func InitEmptyFunctionContext(p *FunctionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_function +} + +func (*FunctionContext) IsFunctionContext() {} + +func NewFunctionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FunctionContext { + var p = new(FunctionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_function + + return p +} + +func (s *FunctionContext) GetParser() antlr.Parser { return s.parser } + +func (s *FunctionContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *FunctionContext) ParamList() IParamListContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IParamListContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IParamListContext) +} + +func (s *FunctionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FunctionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *FunctionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitFunction(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Function() (localctx IFunctionContext) { + localctx = NewFunctionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 160, CqlParserRULE_function) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1023) + p.ReferentialIdentifier() + } + { + p.SetState(1024) + p.Match(CqlParserT__30) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1026) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & -4758861967782021122) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & -1905040784319597519) != 0) || ((int64((_la - 128)) & ^0x3f) == 0 && ((int64(1) << (_la - 128)) & 549753872505) != 0) { + { + p.SetState(1025) + p.ParamList() + } + + } + { + p.SetState(1028) + p.Match(CqlParserT__31) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IRatioContext is an interface to support dynamic dispatch. +type IRatioContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllQuantity() []IQuantityContext + Quantity(i int) IQuantityContext + + // IsRatioContext differentiates from other interfaces. + IsRatioContext() +} + +type RatioContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRatioContext() *RatioContext { + var p = new(RatioContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_ratio + return p +} + +func InitEmptyRatioContext(p *RatioContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_ratio +} + +func (*RatioContext) IsRatioContext() {} + +func NewRatioContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RatioContext { + var p = new(RatioContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_ratio + + return p +} + +func (s *RatioContext) GetParser() antlr.Parser { return s.parser } + +func (s *RatioContext) AllQuantity() []IQuantityContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IQuantityContext); ok { + len++ + } + } + + tst := make([]IQuantityContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IQuantityContext); ok { + tst[i] = t.(IQuantityContext) + i++ + } + } + + return tst +} + +func (s *RatioContext) Quantity(i int) IQuantityContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IQuantityContext) +} + +func (s *RatioContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RatioContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *RatioContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitRatio(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Ratio() (localctx IRatioContext) { + localctx = NewRatioContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 162, CqlParserRULE_ratio) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1030) + p.Quantity() + } + { + p.SetState(1031) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1032) + p.Quantity() + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ILiteralContext is an interface to support dynamic dispatch. +type ILiteralContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsLiteralContext differentiates from other interfaces. + IsLiteralContext() +} + +type LiteralContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLiteralContext() *LiteralContext { + var p = new(LiteralContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_literal + return p +} + +func InitEmptyLiteralContext(p *LiteralContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_literal +} + +func (*LiteralContext) IsLiteralContext() {} + +func NewLiteralContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LiteralContext { + var p = new(LiteralContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_literal + + return p +} + +func (s *LiteralContext) GetParser() antlr.Parser { return s.parser } + +func (s *LiteralContext) CopyAll(ctx *LiteralContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *LiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LiteralContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + + + +type TimeLiteralContext struct { + LiteralContext +} + +func NewTimeLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TimeLiteralContext { + var p = new(TimeLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *TimeLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TimeLiteralContext) TIME() antlr.TerminalNode { + return s.GetToken(CqlParserTIME, 0) +} + + +func (s *TimeLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTimeLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type NullLiteralContext struct { + LiteralContext +} + +func NewNullLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *NullLiteralContext { + var p = new(NullLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *NullLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *NullLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitNullLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type RatioLiteralContext struct { + LiteralContext +} + +func NewRatioLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *RatioLiteralContext { + var p = new(RatioLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *RatioLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RatioLiteralContext) Ratio() IRatioContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRatioContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IRatioContext) +} + + +func (s *RatioLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitRatioLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type DateTimeLiteralContext struct { + LiteralContext +} + +func NewDateTimeLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DateTimeLiteralContext { + var p = new(DateTimeLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *DateTimeLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateTimeLiteralContext) DATETIME() antlr.TerminalNode { + return s.GetToken(CqlParserDATETIME, 0) +} + + +func (s *DateTimeLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDateTimeLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type StringLiteralContext struct { + LiteralContext +} + +func NewStringLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *StringLiteralContext { + var p = new(StringLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *StringLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StringLiteralContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + + +func (s *StringLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitStringLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type DateLiteralContext struct { + LiteralContext +} + +func NewDateLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DateLiteralContext { + var p = new(DateLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *DateLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateLiteralContext) DATE() antlr.TerminalNode { + return s.GetToken(CqlParserDATE, 0) +} + + +func (s *DateLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDateLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type BooleanLiteralContext struct { + LiteralContext +} + +func NewBooleanLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *BooleanLiteralContext { + var p = new(BooleanLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *BooleanLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *BooleanLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitBooleanLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type NumberLiteralContext struct { + LiteralContext +} + +func NewNumberLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *NumberLiteralContext { + var p = new(NumberLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *NumberLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *NumberLiteralContext) NUMBER() antlr.TerminalNode { + return s.GetToken(CqlParserNUMBER, 0) +} + + +func (s *NumberLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitNumberLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type LongNumberLiteralContext struct { + LiteralContext +} + +func NewLongNumberLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LongNumberLiteralContext { + var p = new(LongNumberLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *LongNumberLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LongNumberLiteralContext) LONGNUMBER() antlr.TerminalNode { + return s.GetToken(CqlParserLONGNUMBER, 0) +} + + +func (s *LongNumberLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitLongNumberLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + +type QuantityLiteralContext struct { + LiteralContext +} + +func NewQuantityLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *QuantityLiteralContext { + var p = new(QuantityLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *QuantityLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QuantityLiteralContext) Quantity() IQuantityContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IQuantityContext) +} + + +func (s *QuantityLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQuantityLiteral(s) + + default: + return t.VisitChildren(s) + } +} + + + +func (p *CqlParser) Literal() (localctx ILiteralContext) { + localctx = NewLiteralContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 164, CqlParserRULE_literal) + var _la int + + p.SetState(1044) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 114, p.GetParserRuleContext()) { + case 1: + localctx = NewBooleanLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1034) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__59 || _la == CqlParserT__60) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + case 2: + localctx = NewNullLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + { + p.SetState(1035) + p.Match(CqlParserT__58) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 3: + localctx = NewStringLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 3) + { + p.SetState(1036) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 4: + localctx = NewNumberLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 4) + { + p.SetState(1037) + p.Match(CqlParserNUMBER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 5: + localctx = NewLongNumberLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 5) + { + p.SetState(1038) + p.Match(CqlParserLONGNUMBER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 6: + localctx = NewDateTimeLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 6) + { + p.SetState(1039) + p.Match(CqlParserDATETIME) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 7: + localctx = NewDateLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 7) + { + p.SetState(1040) + p.Match(CqlParserDATE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 8: + localctx = NewTimeLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 8) + { + p.SetState(1041) + p.Match(CqlParserTIME) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case 9: + localctx = NewQuantityLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 9) + { + p.SetState(1042) + p.Quantity() + } + + + case 10: + localctx = NewRatioLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 10) + { + p.SetState(1043) + p.Ratio() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IIntervalSelectorContext is an interface to support dynamic dispatch. +type IIntervalSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllExpression() []IExpressionContext + Expression(i int) IExpressionContext + + // IsIntervalSelectorContext differentiates from other interfaces. + IsIntervalSelectorContext() +} + +type IntervalSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIntervalSelectorContext() *IntervalSelectorContext { + var p = new(IntervalSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_intervalSelector + return p +} + +func InitEmptyIntervalSelectorContext(p *IntervalSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_intervalSelector +} + +func (*IntervalSelectorContext) IsIntervalSelectorContext() {} + +func NewIntervalSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IntervalSelectorContext { + var p = new(IntervalSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_intervalSelector + + return p +} + +func (s *IntervalSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *IntervalSelectorContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *IntervalSelectorContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *IntervalSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntervalSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *IntervalSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIntervalSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) IntervalSelector() (localctx IIntervalSelectorContext) { + localctx = NewIntervalSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 166, CqlParserRULE_intervalSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1046) + p.Match(CqlParserT__23) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1047) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__30 || _la == CqlParserT__37) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(1048) + p.expression(0) + } + { + p.SetState(1049) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1050) + p.expression(0) + } + { + p.SetState(1051) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__31 || _la == CqlParserT__39) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITupleSelectorContext is an interface to support dynamic dispatch. +type ITupleSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllTupleElementSelector() []ITupleElementSelectorContext + TupleElementSelector(i int) ITupleElementSelectorContext + + // IsTupleSelectorContext differentiates from other interfaces. + IsTupleSelectorContext() +} + +type TupleSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleSelectorContext() *TupleSelectorContext { + var p = new(TupleSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleSelector + return p +} + +func InitEmptyTupleSelectorContext(p *TupleSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleSelector +} + +func (*TupleSelectorContext) IsTupleSelectorContext() {} + +func NewTupleSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleSelectorContext { + var p = new(TupleSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_tupleSelector + + return p +} + +func (s *TupleSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleSelectorContext) AllTupleElementSelector() []ITupleElementSelectorContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITupleElementSelectorContext); ok { + len++ + } + } + + tst := make([]ITupleElementSelectorContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITupleElementSelectorContext); ok { + tst[i] = t.(ITupleElementSelectorContext) + i++ + } + } + + return tst +} + +func (s *TupleSelectorContext) TupleElementSelector(i int) ITupleElementSelectorContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleElementSelectorContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITupleElementSelectorContext) +} + +func (s *TupleSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TupleSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTupleSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) TupleSelector() (localctx ITupleSelectorContext) { + localctx = NewTupleSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 168, CqlParserRULE_tupleSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(1054) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__24 { + { + p.SetState(1053) + p.Match(CqlParserT__24) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(1056) + p.Match(CqlParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1066) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__10: + { + p.SetState(1057) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__17, CqlParserT__19, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__44, CqlParserT__49, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__73, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__105, CqlParserT__106, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__118, CqlParserT__119, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__140, CqlParserT__141, CqlParserT__144, CqlParserT__148, CqlParserT__149, CqlParserT__153, CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + { + p.SetState(1058) + p.TupleElementSelector() + } + p.SetState(1063) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(1059) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1060) + p.TupleElementSelector() + } + + + p.SetState(1065) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + { + p.SetState(1068) + p.Match(CqlParserT__15) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITupleElementSelectorContext is an interface to support dynamic dispatch. +type ITupleElementSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + Expression() IExpressionContext + + // IsTupleElementSelectorContext differentiates from other interfaces. + IsTupleElementSelectorContext() +} + +type TupleElementSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleElementSelectorContext() *TupleElementSelectorContext { + var p = new(TupleElementSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleElementSelector + return p +} + +func InitEmptyTupleElementSelectorContext(p *TupleElementSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_tupleElementSelector +} + +func (*TupleElementSelectorContext) IsTupleElementSelectorContext() {} + +func NewTupleElementSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleElementSelectorContext { + var p = new(TupleElementSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_tupleElementSelector + + return p +} + +func (s *TupleElementSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleElementSelectorContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *TupleElementSelectorContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *TupleElementSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleElementSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TupleElementSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTupleElementSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) TupleElementSelector() (localctx ITupleElementSelectorContext) { + localctx = NewTupleElementSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 170, CqlParserRULE_tupleElementSelector) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1070) + p.ReferentialIdentifier() + } + { + p.SetState(1071) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1072) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IInstanceSelectorContext is an interface to support dynamic dispatch. +type IInstanceSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + NamedTypeSpecifier() INamedTypeSpecifierContext + AllInstanceElementSelector() []IInstanceElementSelectorContext + InstanceElementSelector(i int) IInstanceElementSelectorContext + + // IsInstanceSelectorContext differentiates from other interfaces. + IsInstanceSelectorContext() +} + +type InstanceSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyInstanceSelectorContext() *InstanceSelectorContext { + var p = new(InstanceSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_instanceSelector + return p +} + +func InitEmptyInstanceSelectorContext(p *InstanceSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_instanceSelector +} + +func (*InstanceSelectorContext) IsInstanceSelectorContext() {} + +func NewInstanceSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InstanceSelectorContext { + var p = new(InstanceSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_instanceSelector + + return p +} + +func (s *InstanceSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *InstanceSelectorContext) NamedTypeSpecifier() INamedTypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(INamedTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(INamedTypeSpecifierContext) +} + +func (s *InstanceSelectorContext) AllInstanceElementSelector() []IInstanceElementSelectorContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IInstanceElementSelectorContext); ok { + len++ + } + } + + tst := make([]IInstanceElementSelectorContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IInstanceElementSelectorContext); ok { + tst[i] = t.(IInstanceElementSelectorContext) + i++ + } + } + + return tst +} + +func (s *InstanceSelectorContext) InstanceElementSelector(i int) IInstanceElementSelectorContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IInstanceElementSelectorContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IInstanceElementSelectorContext) +} + +func (s *InstanceSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InstanceSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *InstanceSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitInstanceSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) InstanceSelector() (localctx IInstanceSelectorContext) { + localctx = NewInstanceSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 172, CqlParserRULE_instanceSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1074) + p.NamedTypeSpecifier() + } + { + p.SetState(1075) + p.Match(CqlParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1085) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__10: + { + p.SetState(1076) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__17, CqlParserT__19, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__44, CqlParserT__49, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__73, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__105, CqlParserT__106, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__118, CqlParserT__119, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__140, CqlParserT__141, CqlParserT__144, CqlParserT__148, CqlParserT__149, CqlParserT__153, CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + { + p.SetState(1077) + p.InstanceElementSelector() + } + p.SetState(1082) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(1078) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1079) + p.InstanceElementSelector() + } + + + p.SetState(1084) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + { + p.SetState(1087) + p.Match(CqlParserT__15) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IInstanceElementSelectorContext is an interface to support dynamic dispatch. +type IInstanceElementSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + Expression() IExpressionContext + + // IsInstanceElementSelectorContext differentiates from other interfaces. + IsInstanceElementSelectorContext() +} + +type InstanceElementSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyInstanceElementSelectorContext() *InstanceElementSelectorContext { + var p = new(InstanceElementSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_instanceElementSelector + return p +} + +func InitEmptyInstanceElementSelectorContext(p *InstanceElementSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_instanceElementSelector +} + +func (*InstanceElementSelectorContext) IsInstanceElementSelectorContext() {} + +func NewInstanceElementSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InstanceElementSelectorContext { + var p = new(InstanceElementSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_instanceElementSelector + + return p +} + +func (s *InstanceElementSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *InstanceElementSelectorContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *InstanceElementSelectorContext) Expression() IExpressionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *InstanceElementSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InstanceElementSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *InstanceElementSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitInstanceElementSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) InstanceElementSelector() (localctx IInstanceElementSelectorContext) { + localctx = NewInstanceElementSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 174, CqlParserRULE_instanceElementSelector) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1089) + p.ReferentialIdentifier() + } + { + p.SetState(1090) + p.Match(CqlParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1091) + p.expression(0) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IListSelectorContext is an interface to support dynamic dispatch. +type IListSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllExpression() []IExpressionContext + Expression(i int) IExpressionContext + TypeSpecifier() ITypeSpecifierContext + + // IsListSelectorContext differentiates from other interfaces. + IsListSelectorContext() +} + +type ListSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyListSelectorContext() *ListSelectorContext { + var p = new(ListSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_listSelector + return p +} + +func InitEmptyListSelectorContext(p *ListSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_listSelector +} + +func (*ListSelectorContext) IsListSelectorContext() {} + +func NewListSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ListSelectorContext { + var p = new(ListSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_listSelector + + return p +} + +func (s *ListSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *ListSelectorContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *ListSelectorContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *ListSelectorContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ListSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ListSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ListSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitListSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ListSelector() (localctx IListSelectorContext) { + localctx = NewListSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 176, CqlParserRULE_listSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(1100) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__20 { + { + p.SetState(1093) + p.Match(CqlParserT__20) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1098) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if _la == CqlParserT__21 { + { + p.SetState(1094) + p.Match(CqlParserT__21) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1095) + p.TypeSpecifier() + } + { + p.SetState(1096) + p.Match(CqlParserT__22) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + + } + { + p.SetState(1102) + p.Match(CqlParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1111) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + if ((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & -4758861967782021122) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & -1905040784319597519) != 0) || ((int64((_la - 128)) & ^0x3f) == 0 && ((int64(1) << (_la - 128)) & 549753872505) != 0) { + { + p.SetState(1103) + p.expression(0) + } + p.SetState(1108) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(1104) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1105) + p.expression(0) + } + + + p.SetState(1110) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + } + { + p.SetState(1113) + p.Match(CqlParserT__15) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IDisplayClauseContext is an interface to support dynamic dispatch. +type IDisplayClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsDisplayClauseContext differentiates from other interfaces. + IsDisplayClauseContext() +} + +type DisplayClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyDisplayClauseContext() *DisplayClauseContext { + var p = new(DisplayClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_displayClause + return p +} + +func InitEmptyDisplayClauseContext(p *DisplayClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_displayClause +} + +func (*DisplayClauseContext) IsDisplayClauseContext() {} + +func NewDisplayClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DisplayClauseContext { + var p = new(DisplayClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_displayClause + + return p +} + +func (s *DisplayClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *DisplayClauseContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *DisplayClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DisplayClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *DisplayClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitDisplayClause(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) DisplayClause() (localctx IDisplayClauseContext) { + localctx = NewDisplayClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 178, CqlParserRULE_displayClause) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1115) + p.Match(CqlParserT__153) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1116) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ICodeSelectorContext is an interface to support dynamic dispatch. +type ICodeSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + CodesystemIdentifier() ICodesystemIdentifierContext + DisplayClause() IDisplayClauseContext + + // IsCodeSelectorContext differentiates from other interfaces. + IsCodeSelectorContext() +} + +type CodeSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodeSelectorContext() *CodeSelectorContext { + var p = new(CodeSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeSelector + return p +} + +func InitEmptyCodeSelectorContext(p *CodeSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_codeSelector +} + +func (*CodeSelectorContext) IsCodeSelectorContext() {} + +func NewCodeSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodeSelectorContext { + var p = new(CodeSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_codeSelector + + return p +} + +func (s *CodeSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodeSelectorContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *CodeSelectorContext) CodesystemIdentifier() ICodesystemIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodesystemIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ICodesystemIdentifierContext) +} + +func (s *CodeSelectorContext) DisplayClause() IDisplayClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDisplayClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDisplayClauseContext) +} + +func (s *CodeSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *CodeSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitCodeSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) CodeSelector() (localctx ICodeSelectorContext) { + localctx = NewCodeSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 180, CqlParserRULE_codeSelector) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1118) + p.Match(CqlParserT__154) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1119) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1120) + p.Match(CqlParserT__18) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1121) + p.CodesystemIdentifier() + } + p.SetState(1123) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 124, p.GetParserRuleContext()) == 1 { + { + p.SetState(1122) + p.DisplayClause() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IConceptSelectorContext is an interface to support dynamic dispatch. +type IConceptSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllCodeSelector() []ICodeSelectorContext + CodeSelector(i int) ICodeSelectorContext + DisplayClause() IDisplayClauseContext + + // IsConceptSelectorContext differentiates from other interfaces. + IsConceptSelectorContext() +} + +type ConceptSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyConceptSelectorContext() *ConceptSelectorContext { + var p = new(ConceptSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_conceptSelector + return p +} + +func InitEmptyConceptSelectorContext(p *ConceptSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_conceptSelector +} + +func (*ConceptSelectorContext) IsConceptSelectorContext() {} + +func NewConceptSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ConceptSelectorContext { + var p = new(ConceptSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_conceptSelector + + return p +} + +func (s *ConceptSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *ConceptSelectorContext) AllCodeSelector() []ICodeSelectorContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ICodeSelectorContext); ok { + len++ + } + } + + tst := make([]ICodeSelectorContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ICodeSelectorContext); ok { + tst[i] = t.(ICodeSelectorContext) + i++ + } + } + + return tst +} + +func (s *ConceptSelectorContext) CodeSelector(i int) ICodeSelectorContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeSelectorContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ICodeSelectorContext) +} + +func (s *ConceptSelectorContext) DisplayClause() IDisplayClauseContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDisplayClauseContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDisplayClauseContext) +} + +func (s *ConceptSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConceptSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ConceptSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitConceptSelector(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ConceptSelector() (localctx IConceptSelectorContext) { + localctx = NewConceptSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 182, CqlParserRULE_conceptSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1125) + p.Match(CqlParserT__155) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1126) + p.Match(CqlParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1127) + p.CodeSelector() + } + p.SetState(1132) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(1128) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1129) + p.CodeSelector() + } + + + p.SetState(1134) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(1135) + p.Match(CqlParserT__15) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1137) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 126, p.GetParserRuleContext()) == 1 { + { + p.SetState(1136) + p.DisplayClause() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IKeywordContext is an interface to support dynamic dispatch. +type IKeywordContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsKeywordContext differentiates from other interfaces. + IsKeywordContext() +} + +type KeywordContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyKeywordContext() *KeywordContext { + var p = new(KeywordContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_keyword + return p +} + +func InitEmptyKeywordContext(p *KeywordContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_keyword +} + +func (*KeywordContext) IsKeywordContext() {} + +func NewKeywordContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *KeywordContext { + var p = new(KeywordContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_keyword + + return p +} + +func (s *KeywordContext) GetParser() antlr.Parser { return s.parser } +func (s *KeywordContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *KeywordContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *KeywordContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitKeyword(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Keyword() (localctx IKeywordContext) { + localctx = NewKeywordContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 184, CqlParserRULE_keyword) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1139) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & -15150577076226) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & -175643684002350017) != 0) || ((int64((_la - 128)) & ^0x3f) == 0 && ((int64(1) << (_la - 128)) & 478150271) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IReservedWordContext is an interface to support dynamic dispatch. +type IReservedWordContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsReservedWordContext differentiates from other interfaces. + IsReservedWordContext() +} + +type ReservedWordContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyReservedWordContext() *ReservedWordContext { + var p = new(ReservedWordContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_reservedWord + return p +} + +func InitEmptyReservedWordContext(p *ReservedWordContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_reservedWord +} + +func (*ReservedWordContext) IsReservedWordContext() {} + +func NewReservedWordContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ReservedWordContext { + var p = new(ReservedWordContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_reservedWord + + return p +} + +func (s *ReservedWordContext) GetParser() antlr.Parser { return s.parser } +func (s *ReservedWordContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ReservedWordContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ReservedWordContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitReservedWord(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ReservedWord() (localctx IReservedWordContext) { + localctx = NewReservedWordContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 186, CqlParserRULE_reservedWord) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1141) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & -140787825319084032) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & -283989560810272705) != 0) || ((int64((_la - 128)) & ^0x3f) == 0 && ((int64(1) << (_la - 128)) & 404594183) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IKeywordIdentifierContext is an interface to support dynamic dispatch. +type IKeywordIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsKeywordIdentifierContext differentiates from other interfaces. + IsKeywordIdentifierContext() +} + +type KeywordIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyKeywordIdentifierContext() *KeywordIdentifierContext { + var p = new(KeywordIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_keywordIdentifier + return p +} + +func InitEmptyKeywordIdentifierContext(p *KeywordIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_keywordIdentifier +} + +func (*KeywordIdentifierContext) IsKeywordIdentifierContext() {} + +func NewKeywordIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *KeywordIdentifierContext { + var p = new(KeywordIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_keywordIdentifier + + return p +} + +func (s *KeywordIdentifierContext) GetParser() antlr.Parser { return s.parser } +func (s *KeywordIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *KeywordIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *KeywordIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitKeywordIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) KeywordIdentifier() (localctx IKeywordIdentifierContext) { + localctx = NewKeywordIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 188, CqlParserRULE_keywordIdentifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1143) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & 140772674742007806) != 0) || ((int64((_la - 74)) & ^0x3f) == 0 && ((int64(1) << (_la - 74)) & 2161833627658158317) != 0) || ((int64((_la - 141)) & ^0x3f) == 0 && ((int64(1) << (_la - 141)) & 8979) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IObsoleteIdentifierContext is an interface to support dynamic dispatch. +type IObsoleteIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsObsoleteIdentifierContext differentiates from other interfaces. + IsObsoleteIdentifierContext() +} + +type ObsoleteIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyObsoleteIdentifierContext() *ObsoleteIdentifierContext { + var p = new(ObsoleteIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_obsoleteIdentifier + return p +} + +func InitEmptyObsoleteIdentifierContext(p *ObsoleteIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_obsoleteIdentifier +} + +func (*ObsoleteIdentifierContext) IsObsoleteIdentifierContext() {} + +func NewObsoleteIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ObsoleteIdentifierContext { + var p = new(ObsoleteIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_obsoleteIdentifier + + return p +} + +func (s *ObsoleteIdentifierContext) GetParser() antlr.Parser { return s.parser } +func (s *ObsoleteIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ObsoleteIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ObsoleteIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitObsoleteIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ObsoleteIdentifier() (localctx IObsoleteIdentifierContext) { + localctx = NewObsoleteIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 190, CqlParserRULE_obsoleteIdentifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1145) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & 288687772990177284) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & 13195146167297) != 0) || ((int64((_la - 154)) & ^0x3f) == 0 && ((int64(1) << (_la - 154)) & 7) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IFunctionIdentifierContext is an interface to support dynamic dispatch. +type IFunctionIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsFunctionIdentifierContext differentiates from other interfaces. + IsFunctionIdentifierContext() +} + +type FunctionIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFunctionIdentifierContext() *FunctionIdentifierContext { + var p = new(FunctionIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_functionIdentifier + return p +} + +func InitEmptyFunctionIdentifierContext(p *FunctionIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_functionIdentifier +} + +func (*FunctionIdentifierContext) IsFunctionIdentifierContext() {} + +func NewFunctionIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FunctionIdentifierContext { + var p = new(FunctionIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_functionIdentifier + + return p +} + +func (s *FunctionIdentifierContext) GetParser() antlr.Parser { return s.parser } +func (s *FunctionIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FunctionIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *FunctionIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitFunctionIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) FunctionIdentifier() (localctx IFunctionIdentifierContext) { + localctx = NewFunctionIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 192, CqlParserRULE_functionIdentifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1147) + _la = p.GetTokenStream().LA(1) + + if !(((int64(_la) & ^0x3f) == 0 && ((int64(1) << _la) & -15150577076226) != 0) || ((int64((_la - 64)) & ^0x3f) == 0 && ((int64(1) << (_la - 64)) & -175643684002350017) != 0) || ((int64((_la - 128)) & ^0x3f) == 0 && ((int64(1) << (_la - 128)) & 478145663) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// ITypeNameIdentifierContext is an interface to support dynamic dispatch. +type ITypeNameIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsTypeNameIdentifierContext differentiates from other interfaces. + IsTypeNameIdentifierContext() +} + +type TypeNameIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTypeNameIdentifierContext() *TypeNameIdentifierContext { + var p = new(TypeNameIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_typeNameIdentifier + return p +} + +func InitEmptyTypeNameIdentifierContext(p *TypeNameIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_typeNameIdentifier +} + +func (*TypeNameIdentifierContext) IsTypeNameIdentifierContext() {} + +func NewTypeNameIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeNameIdentifierContext { + var p = new(TypeNameIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_typeNameIdentifier + + return p +} + +func (s *TypeNameIdentifierContext) GetParser() antlr.Parser { return s.parser } +func (s *TypeNameIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TypeNameIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *TypeNameIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitTypeNameIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) TypeNameIdentifier() (localctx ITypeNameIdentifierContext) { + localctx = NewTypeNameIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 194, CqlParserRULE_typeNameIdentifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1149) + _la = p.GetTokenStream().LA(1) + + if !(_la == CqlParserT__89 || _la == CqlParserT__90 || _la == CqlParserT__154 || _la == CqlParserT__155) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IReferentialIdentifierContext is an interface to support dynamic dispatch. +type IReferentialIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + KeywordIdentifier() IKeywordIdentifierContext + + // IsReferentialIdentifierContext differentiates from other interfaces. + IsReferentialIdentifierContext() +} + +type ReferentialIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyReferentialIdentifierContext() *ReferentialIdentifierContext { + var p = new(ReferentialIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_referentialIdentifier + return p +} + +func InitEmptyReferentialIdentifierContext(p *ReferentialIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_referentialIdentifier +} + +func (*ReferentialIdentifierContext) IsReferentialIdentifierContext() {} + +func NewReferentialIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ReferentialIdentifierContext { + var p = new(ReferentialIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_referentialIdentifier + + return p +} + +func (s *ReferentialIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ReferentialIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ReferentialIdentifierContext) KeywordIdentifier() IKeywordIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IKeywordIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IKeywordIdentifierContext) +} + +func (s *ReferentialIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ReferentialIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ReferentialIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitReferentialIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ReferentialIdentifier() (localctx IReferentialIdentifierContext) { + localctx = NewReferentialIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 196, CqlParserRULE_referentialIdentifier) + p.SetState(1153) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1151) + p.Identifier() + } + + + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__17, CqlParserT__19, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__44, CqlParserT__49, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__73, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__105, CqlParserT__106, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__118, CqlParserT__119, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__140, CqlParserT__141, CqlParserT__144, CqlParserT__148, CqlParserT__149, CqlParserT__153: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(1152) + p.KeywordIdentifier() + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IReferentialOrTypeNameIdentifierContext is an interface to support dynamic dispatch. +type IReferentialOrTypeNameIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ReferentialIdentifier() IReferentialIdentifierContext + TypeNameIdentifier() ITypeNameIdentifierContext + + // IsReferentialOrTypeNameIdentifierContext differentiates from other interfaces. + IsReferentialOrTypeNameIdentifierContext() +} + +type ReferentialOrTypeNameIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyReferentialOrTypeNameIdentifierContext() *ReferentialOrTypeNameIdentifierContext { + var p = new(ReferentialOrTypeNameIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_referentialOrTypeNameIdentifier + return p +} + +func InitEmptyReferentialOrTypeNameIdentifierContext(p *ReferentialOrTypeNameIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_referentialOrTypeNameIdentifier +} + +func (*ReferentialOrTypeNameIdentifierContext) IsReferentialOrTypeNameIdentifierContext() {} + +func NewReferentialOrTypeNameIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ReferentialOrTypeNameIdentifierContext { + var p = new(ReferentialOrTypeNameIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_referentialOrTypeNameIdentifier + + return p +} + +func (s *ReferentialOrTypeNameIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ReferentialOrTypeNameIdentifierContext) ReferentialIdentifier() IReferentialIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReferentialIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IReferentialIdentifierContext) +} + +func (s *ReferentialOrTypeNameIdentifierContext) TypeNameIdentifier() ITypeNameIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeNameIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeNameIdentifierContext) +} + +func (s *ReferentialOrTypeNameIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ReferentialOrTypeNameIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ReferentialOrTypeNameIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitReferentialOrTypeNameIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ReferentialOrTypeNameIdentifier() (localctx IReferentialOrTypeNameIdentifierContext) { + localctx = NewReferentialOrTypeNameIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 198, CqlParserRULE_referentialOrTypeNameIdentifier) + p.SetState(1157) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 128, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1155) + p.ReferentialIdentifier() + } + + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(1156) + p.TypeNameIdentifier() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IIdentifierOrFunctionIdentifierContext is an interface to support dynamic dispatch. +type IIdentifierOrFunctionIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + FunctionIdentifier() IFunctionIdentifierContext + + // IsIdentifierOrFunctionIdentifierContext differentiates from other interfaces. + IsIdentifierOrFunctionIdentifierContext() +} + +type IdentifierOrFunctionIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIdentifierOrFunctionIdentifierContext() *IdentifierOrFunctionIdentifierContext { + var p = new(IdentifierOrFunctionIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_identifierOrFunctionIdentifier + return p +} + +func InitEmptyIdentifierOrFunctionIdentifierContext(p *IdentifierOrFunctionIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_identifierOrFunctionIdentifier +} + +func (*IdentifierOrFunctionIdentifierContext) IsIdentifierOrFunctionIdentifierContext() {} + +func NewIdentifierOrFunctionIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IdentifierOrFunctionIdentifierContext { + var p = new(IdentifierOrFunctionIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_identifierOrFunctionIdentifier + + return p +} + +func (s *IdentifierOrFunctionIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *IdentifierOrFunctionIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *IdentifierOrFunctionIdentifierContext) FunctionIdentifier() IFunctionIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFunctionIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IFunctionIdentifierContext) +} + +func (s *IdentifierOrFunctionIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IdentifierOrFunctionIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *IdentifierOrFunctionIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIdentifierOrFunctionIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) IdentifierOrFunctionIdentifier() (localctx IIdentifierOrFunctionIdentifierContext) { + localctx = NewIdentifierOrFunctionIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 200, CqlParserRULE_identifierOrFunctionIdentifier) + p.SetState(1161) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1159) + p.Identifier() + } + + + case CqlParserT__0, CqlParserT__1, CqlParserT__2, CqlParserT__3, CqlParserT__4, CqlParserT__5, CqlParserT__6, CqlParserT__7, CqlParserT__8, CqlParserT__9, CqlParserT__11, CqlParserT__12, CqlParserT__17, CqlParserT__18, CqlParserT__19, CqlParserT__20, CqlParserT__23, CqlParserT__24, CqlParserT__26, CqlParserT__27, CqlParserT__28, CqlParserT__29, CqlParserT__34, CqlParserT__35, CqlParserT__36, CqlParserT__40, CqlParserT__43, CqlParserT__44, CqlParserT__45, CqlParserT__46, CqlParserT__47, CqlParserT__48, CqlParserT__49, CqlParserT__50, CqlParserT__51, CqlParserT__52, CqlParserT__53, CqlParserT__54, CqlParserT__55, CqlParserT__56, CqlParserT__57, CqlParserT__58, CqlParserT__59, CqlParserT__60, CqlParserT__61, CqlParserT__62, CqlParserT__63, CqlParserT__64, CqlParserT__65, CqlParserT__66, CqlParserT__67, CqlParserT__68, CqlParserT__73, CqlParserT__74, CqlParserT__75, CqlParserT__76, CqlParserT__78, CqlParserT__79, CqlParserT__80, CqlParserT__81, CqlParserT__82, CqlParserT__83, CqlParserT__84, CqlParserT__85, CqlParserT__86, CqlParserT__87, CqlParserT__88, CqlParserT__89, CqlParserT__90, CqlParserT__91, CqlParserT__92, CqlParserT__93, CqlParserT__94, CqlParserT__95, CqlParserT__96, CqlParserT__97, CqlParserT__98, CqlParserT__99, CqlParserT__100, CqlParserT__101, CqlParserT__102, CqlParserT__105, CqlParserT__106, CqlParserT__107, CqlParserT__108, CqlParserT__109, CqlParserT__110, CqlParserT__111, CqlParserT__112, CqlParserT__113, CqlParserT__114, CqlParserT__118, CqlParserT__119, CqlParserT__121, CqlParserT__122, CqlParserT__123, CqlParserT__124, CqlParserT__125, CqlParserT__126, CqlParserT__127, CqlParserT__128, CqlParserT__129, CqlParserT__130, CqlParserT__131, CqlParserT__132, CqlParserT__133, CqlParserT__137, CqlParserT__138, CqlParserT__140, CqlParserT__141, CqlParserT__142, CqlParserT__143, CqlParserT__144, CqlParserT__145, CqlParserT__146, CqlParserT__147, CqlParserT__148, CqlParserT__149, CqlParserT__153, CqlParserT__154, CqlParserT__155: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(1160) + p.FunctionIdentifier() + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IIdentifierContext is an interface to support dynamic dispatch. +type IIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IDENTIFIER() antlr.TerminalNode + DELIMITEDIDENTIFIER() antlr.TerminalNode + QUOTEDIDENTIFIER() antlr.TerminalNode + + // IsIdentifierContext differentiates from other interfaces. + IsIdentifierContext() +} + +type IdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIdentifierContext() *IdentifierContext { + var p = new(IdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_identifier + return p +} + +func InitEmptyIdentifierContext(p *IdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_identifier +} + +func (*IdentifierContext) IsIdentifierContext() {} + +func NewIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IdentifierContext { + var p = new(IdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_identifier + + return p +} + +func (s *IdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *IdentifierContext) IDENTIFIER() antlr.TerminalNode { + return s.GetToken(CqlParserIDENTIFIER, 0) +} + +func (s *IdentifierContext) DELIMITEDIDENTIFIER() antlr.TerminalNode { + return s.GetToken(CqlParserDELIMITEDIDENTIFIER, 0) +} + +func (s *IdentifierContext) QUOTEDIDENTIFIER() antlr.TerminalNode { + return s.GetToken(CqlParserQUOTEDIDENTIFIER, 0) +} + +func (s *IdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *IdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Identifier() (localctx IIdentifierContext) { + localctx = NewIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 202, CqlParserRULE_identifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1163) + _la = p.GetTokenStream().LA(1) + + if !(((int64((_la - 158)) & ^0x3f) == 0 && ((int64(1) << (_la - 158)) & 97) != 0)) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IExternalConstantContext is an interface to support dynamic dispatch. +type IExternalConstantContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + STRING() antlr.TerminalNode + + // IsExternalConstantContext differentiates from other interfaces. + IsExternalConstantContext() +} + +type ExternalConstantContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyExternalConstantContext() *ExternalConstantContext { + var p = new(ExternalConstantContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_externalConstant + return p +} + +func InitEmptyExternalConstantContext(p *ExternalConstantContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_externalConstant +} + +func (*ExternalConstantContext) IsExternalConstantContext() {} + +func NewExternalConstantContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExternalConstantContext { + var p = new(ExternalConstantContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_externalConstant + + return p +} + +func (s *ExternalConstantContext) GetParser() antlr.Parser { return s.parser } + +func (s *ExternalConstantContext) Identifier() IIdentifierContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ExternalConstantContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *ExternalConstantContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExternalConstantContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ExternalConstantContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitExternalConstant(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ExternalConstant() (localctx IExternalConstantContext) { + localctx = NewExternalConstantContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 204, CqlParserRULE_externalConstant) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1165) + p.Match(CqlParserT__156) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1168) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserQUOTEDIDENTIFIER, CqlParserIDENTIFIER, CqlParserDELIMITEDIDENTIFIER: + { + p.SetState(1166) + p.Identifier() + } + + + case CqlParserSTRING: + { + p.SetState(1167) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IParamListContext is an interface to support dynamic dispatch. +type IParamListContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllExpression() []IExpressionContext + Expression(i int) IExpressionContext + + // IsParamListContext differentiates from other interfaces. + IsParamListContext() +} + +type ParamListContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyParamListContext() *ParamListContext { + var p = new(ParamListContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_paramList + return p +} + +func InitEmptyParamListContext(p *ParamListContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_paramList +} + +func (*ParamListContext) IsParamListContext() {} + +func NewParamListContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ParamListContext { + var p = new(ParamListContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_paramList + + return p +} + +func (s *ParamListContext) GetParser() antlr.Parser { return s.parser } + +func (s *ParamListContext) AllExpression() []IExpressionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IExpressionContext); ok { + len++ + } + } + + tst := make([]IExpressionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IExpressionContext); ok { + tst[i] = t.(IExpressionContext) + i++ + } + } + + return tst +} + +func (s *ParamListContext) Expression(i int) IExpressionContext { + var t antlr.RuleContext; + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExpressionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext); + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IExpressionContext) +} + +func (s *ParamListContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ParamListContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *ParamListContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitParamList(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) ParamList() (localctx IParamListContext) { + localctx = NewParamListContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 206, CqlParserRULE_paramList) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1170) + p.expression(0) + } + p.SetState(1175) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + + for _la == CqlParserT__14 { + { + p.SetState(1171) + p.Match(CqlParserT__14) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(1172) + p.expression(0) + } + + + p.SetState(1177) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IQuantityContext is an interface to support dynamic dispatch. +type IQuantityContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + NUMBER() antlr.TerminalNode + Unit() IUnitContext + + // IsQuantityContext differentiates from other interfaces. + IsQuantityContext() +} + +type QuantityContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQuantityContext() *QuantityContext { + var p = new(QuantityContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_quantity + return p +} + +func InitEmptyQuantityContext(p *QuantityContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_quantity +} + +func (*QuantityContext) IsQuantityContext() {} + +func NewQuantityContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QuantityContext { + var p = new(QuantityContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_quantity + + return p +} + +func (s *QuantityContext) GetParser() antlr.Parser { return s.parser } + +func (s *QuantityContext) NUMBER() antlr.TerminalNode { + return s.GetToken(CqlParserNUMBER, 0) +} + +func (s *QuantityContext) Unit() IUnitContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IUnitContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IUnitContext) +} + +func (s *QuantityContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QuantityContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *QuantityContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitQuantity(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Quantity() (localctx IQuantityContext) { + localctx = NewQuantityContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 208, CqlParserRULE_quantity) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1178) + p.Match(CqlParserNUMBER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(1180) + p.GetErrorHandler().Sync(p) + + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 132, p.GetParserRuleContext()) == 1 { + { + p.SetState(1179) + p.Unit() + } + + } else if p.HasError() { // JIM + goto errorExit + } + + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +// IUnitContext is an interface to support dynamic dispatch. +type IUnitContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DateTimePrecision() IDateTimePrecisionContext + PluralDateTimePrecision() IPluralDateTimePrecisionContext + STRING() antlr.TerminalNode + + // IsUnitContext differentiates from other interfaces. + IsUnitContext() +} + +type UnitContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyUnitContext() *UnitContext { + var p = new(UnitContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_unit + return p +} + +func InitEmptyUnitContext(p *UnitContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CqlParserRULE_unit +} + +func (*UnitContext) IsUnitContext() {} + +func NewUnitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *UnitContext { + var p = new(UnitContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CqlParserRULE_unit + + return p +} + +func (s *UnitContext) GetParser() antlr.Parser { return s.parser } + +func (s *UnitContext) DateTimePrecision() IDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionContext) +} + +func (s *UnitContext) PluralDateTimePrecision() IPluralDateTimePrecisionContext { + var t antlr.RuleContext; + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPluralDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext); + break + } + } + + if t == nil { + return nil + } + + return t.(IPluralDateTimePrecisionContext) +} + +func (s *UnitContext) STRING() antlr.TerminalNode { + return s.GetToken(CqlParserSTRING, 0) +} + +func (s *UnitContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *UnitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + + +func (s *UnitContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CqlVisitor: + return t.VisitUnit(s) + + default: + return t.VisitChildren(s) + } +} + + + + +func (p *CqlParser) Unit() (localctx IUnitContext) { + localctx = NewUnitContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 210, CqlParserRULE_unit) + p.SetState(1185) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CqlParserT__81, CqlParserT__82, CqlParserT__83, CqlParserT__84, CqlParserT__85, CqlParserT__86, CqlParserT__87, CqlParserT__88: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(1182) + p.DateTimePrecision() + } + + + case CqlParserT__93, CqlParserT__94, CqlParserT__95, CqlParserT__96, CqlParserT__97, CqlParserT__98, CqlParserT__99, CqlParserT__100: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(1183) + p.PluralDateTimePrecision() + } + + + case CqlParserSTRING: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(1184) + p.Match(CqlParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + + +func (p *CqlParser) Sempred(localctx antlr.RuleContext, ruleIndex, predIndex int) bool { + switch ruleIndex { + case 61: + var t *SimplePathContext = nil + if localctx != nil { t = localctx.(*SimplePathContext) } + return p.SimplePath_Sempred(t, predIndex) + + case 63: + var t *ExpressionContext = nil + if localctx != nil { t = localctx.(*ExpressionContext) } + return p.Expression_Sempred(t, predIndex) + + case 67: + var t *ExpressionTermContext = nil + if localctx != nil { t = localctx.(*ExpressionTermContext) } + return p.ExpressionTerm_Sempred(t, predIndex) + + + default: + panic("No predicate with index: " + fmt.Sprint(ruleIndex)) + } +} + +func (p *CqlParser) SimplePath_Sempred(localctx antlr.RuleContext, predIndex int) bool { + switch predIndex { + case 0: + return p.Precpred(p.GetParserRuleContext(), 2) + + case 1: + return p.Precpred(p.GetParserRuleContext(), 1) + + default: + panic("No predicate with index: " + fmt.Sprint(predIndex)) + } +} + +func (p *CqlParser) Expression_Sempred(localctx antlr.RuleContext, predIndex int) bool { + switch predIndex { + case 2: + return p.Precpred(p.GetParserRuleContext(), 8) + + case 3: + return p.Precpred(p.GetParserRuleContext(), 7) + + case 4: + return p.Precpred(p.GetParserRuleContext(), 6) + + case 5: + return p.Precpred(p.GetParserRuleContext(), 5) + + case 6: + return p.Precpred(p.GetParserRuleContext(), 4) + + case 7: + return p.Precpred(p.GetParserRuleContext(), 3) + + case 8: + return p.Precpred(p.GetParserRuleContext(), 2) + + case 9: + return p.Precpred(p.GetParserRuleContext(), 1) + + case 10: + return p.Precpred(p.GetParserRuleContext(), 16) + + case 11: + return p.Precpred(p.GetParserRuleContext(), 15) + + case 12: + return p.Precpred(p.GetParserRuleContext(), 11) + + default: + panic("No predicate with index: " + fmt.Sprint(predIndex)) + } +} + +func (p *CqlParser) ExpressionTerm_Sempred(localctx antlr.RuleContext, predIndex int) bool { + switch predIndex { + case 13: + return p.Precpred(p.GetParserRuleContext(), 7) + + case 14: + return p.Precpred(p.GetParserRuleContext(), 6) + + case 15: + return p.Precpred(p.GetParserRuleContext(), 5) + + case 16: + return p.Precpred(p.GetParserRuleContext(), 21) + + case 17: + return p.Precpred(p.GetParserRuleContext(), 20) + + default: + panic("No predicate with index: " + fmt.Sprint(predIndex)) + } +} + diff --git a/internal/embeddata/third_party/cqframework/cql/cql_visitor.go b/internal/embeddata/third_party/cqframework/cql/cql_visitor.go new file mode 100755 index 0000000..892293b --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cql/cql_visitor.go @@ -0,0 +1,548 @@ +// Code generated from Cql.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cql // Cql +import "github.com/antlr4-go/antlr/v4" + + +// A complete Visitor for a parse tree produced by CqlParser. +type CqlVisitor interface { + antlr.ParseTreeVisitor + + // Visit a parse tree produced by CqlParser#definition. + VisitDefinition(ctx *DefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#library. + VisitLibrary(ctx *LibraryContext) interface{} + + // Visit a parse tree produced by CqlParser#libraryDefinition. + VisitLibraryDefinition(ctx *LibraryDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#usingDefinition. + VisitUsingDefinition(ctx *UsingDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#includeDefinition. + VisitIncludeDefinition(ctx *IncludeDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#localIdentifier. + VisitLocalIdentifier(ctx *LocalIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#accessModifier. + VisitAccessModifier(ctx *AccessModifierContext) interface{} + + // Visit a parse tree produced by CqlParser#parameterDefinition. + VisitParameterDefinition(ctx *ParameterDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#codesystemDefinition. + VisitCodesystemDefinition(ctx *CodesystemDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#valuesetDefinition. + VisitValuesetDefinition(ctx *ValuesetDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#codesystems. + VisitCodesystems(ctx *CodesystemsContext) interface{} + + // Visit a parse tree produced by CqlParser#codesystemIdentifier. + VisitCodesystemIdentifier(ctx *CodesystemIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#libraryIdentifier. + VisitLibraryIdentifier(ctx *LibraryIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#codeDefinition. + VisitCodeDefinition(ctx *CodeDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#conceptDefinition. + VisitConceptDefinition(ctx *ConceptDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#codeIdentifier. + VisitCodeIdentifier(ctx *CodeIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#codesystemId. + VisitCodesystemId(ctx *CodesystemIdContext) interface{} + + // Visit a parse tree produced by CqlParser#valuesetId. + VisitValuesetId(ctx *ValuesetIdContext) interface{} + + // Visit a parse tree produced by CqlParser#versionSpecifier. + VisitVersionSpecifier(ctx *VersionSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#codeId. + VisitCodeId(ctx *CodeIdContext) interface{} + + // Visit a parse tree produced by CqlParser#typeSpecifier. + VisitTypeSpecifier(ctx *TypeSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#namedTypeSpecifier. + VisitNamedTypeSpecifier(ctx *NamedTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#modelIdentifier. + VisitModelIdentifier(ctx *ModelIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#listTypeSpecifier. + VisitListTypeSpecifier(ctx *ListTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#intervalTypeSpecifier. + VisitIntervalTypeSpecifier(ctx *IntervalTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#tupleTypeSpecifier. + VisitTupleTypeSpecifier(ctx *TupleTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#tupleElementDefinition. + VisitTupleElementDefinition(ctx *TupleElementDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#choiceTypeSpecifier. + VisitChoiceTypeSpecifier(ctx *ChoiceTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#statement. + VisitStatement(ctx *StatementContext) interface{} + + // Visit a parse tree produced by CqlParser#expressionDefinition. + VisitExpressionDefinition(ctx *ExpressionDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#contextDefinition. + VisitContextDefinition(ctx *ContextDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#fluentModifier. + VisitFluentModifier(ctx *FluentModifierContext) interface{} + + // Visit a parse tree produced by CqlParser#functionDefinition. + VisitFunctionDefinition(ctx *FunctionDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#operandDefinition. + VisitOperandDefinition(ctx *OperandDefinitionContext) interface{} + + // Visit a parse tree produced by CqlParser#functionBody. + VisitFunctionBody(ctx *FunctionBodyContext) interface{} + + // Visit a parse tree produced by CqlParser#querySource. + VisitQuerySource(ctx *QuerySourceContext) interface{} + + // Visit a parse tree produced by CqlParser#aliasedQuerySource. + VisitAliasedQuerySource(ctx *AliasedQuerySourceContext) interface{} + + // Visit a parse tree produced by CqlParser#alias. + VisitAlias(ctx *AliasContext) interface{} + + // Visit a parse tree produced by CqlParser#queryInclusionClause. + VisitQueryInclusionClause(ctx *QueryInclusionClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#withClause. + VisitWithClause(ctx *WithClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#withoutClause. + VisitWithoutClause(ctx *WithoutClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#retrieve. + VisitRetrieve(ctx *RetrieveContext) interface{} + + // Visit a parse tree produced by CqlParser#contextIdentifier. + VisitContextIdentifier(ctx *ContextIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#codePath. + VisitCodePath(ctx *CodePathContext) interface{} + + // Visit a parse tree produced by CqlParser#codeComparator. + VisitCodeComparator(ctx *CodeComparatorContext) interface{} + + // Visit a parse tree produced by CqlParser#terminology. + VisitTerminology(ctx *TerminologyContext) interface{} + + // Visit a parse tree produced by CqlParser#qualifier. + VisitQualifier(ctx *QualifierContext) interface{} + + // Visit a parse tree produced by CqlParser#query. + VisitQuery(ctx *QueryContext) interface{} + + // Visit a parse tree produced by CqlParser#sourceClause. + VisitSourceClause(ctx *SourceClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#letClause. + VisitLetClause(ctx *LetClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#letClauseItem. + VisitLetClauseItem(ctx *LetClauseItemContext) interface{} + + // Visit a parse tree produced by CqlParser#whereClause. + VisitWhereClause(ctx *WhereClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#returnClause. + VisitReturnClause(ctx *ReturnClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#aggregateClause. + VisitAggregateClause(ctx *AggregateClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#startingClause. + VisitStartingClause(ctx *StartingClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#sortClause. + VisitSortClause(ctx *SortClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#sortDirection. + VisitSortDirection(ctx *SortDirectionContext) interface{} + + // Visit a parse tree produced by CqlParser#sortByItem. + VisitSortByItem(ctx *SortByItemContext) interface{} + + // Visit a parse tree produced by CqlParser#qualifiedIdentifier. + VisitQualifiedIdentifier(ctx *QualifiedIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#qualifiedIdentifierExpression. + VisitQualifiedIdentifierExpression(ctx *QualifiedIdentifierExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#qualifierExpression. + VisitQualifierExpression(ctx *QualifierExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#simplePathIndexer. + VisitSimplePathIndexer(ctx *SimplePathIndexerContext) interface{} + + // Visit a parse tree produced by CqlParser#simplePathQualifiedIdentifier. + VisitSimplePathQualifiedIdentifier(ctx *SimplePathQualifiedIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#simplePathReferentialIdentifier. + VisitSimplePathReferentialIdentifier(ctx *SimplePathReferentialIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#simpleStringLiteral. + VisitSimpleStringLiteral(ctx *SimpleStringLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#simpleNumberLiteral. + VisitSimpleNumberLiteral(ctx *SimpleNumberLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#durationBetweenExpression. + VisitDurationBetweenExpression(ctx *DurationBetweenExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#inFixSetExpression. + VisitInFixSetExpression(ctx *InFixSetExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#retrieveExpression. + VisitRetrieveExpression(ctx *RetrieveExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#timingExpression. + VisitTimingExpression(ctx *TimingExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#queryExpression. + VisitQueryExpression(ctx *QueryExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#notExpression. + VisitNotExpression(ctx *NotExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#booleanExpression. + VisitBooleanExpression(ctx *BooleanExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#orExpression. + VisitOrExpression(ctx *OrExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#castExpression. + VisitCastExpression(ctx *CastExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#andExpression. + VisitAndExpression(ctx *AndExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#betweenExpression. + VisitBetweenExpression(ctx *BetweenExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#membershipExpression. + VisitMembershipExpression(ctx *MembershipExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#differenceBetweenExpression. + VisitDifferenceBetweenExpression(ctx *DifferenceBetweenExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#inequalityExpression. + VisitInequalityExpression(ctx *InequalityExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#equalityExpression. + VisitEqualityExpression(ctx *EqualityExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#existenceExpression. + VisitExistenceExpression(ctx *ExistenceExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#impliesExpression. + VisitImpliesExpression(ctx *ImpliesExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#termExpression. + VisitTermExpression(ctx *TermExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#typeExpression. + VisitTypeExpression(ctx *TypeExpressionContext) interface{} + + // Visit a parse tree produced by CqlParser#dateTimePrecision. + VisitDateTimePrecision(ctx *DateTimePrecisionContext) interface{} + + // Visit a parse tree produced by CqlParser#dateTimeComponent. + VisitDateTimeComponent(ctx *DateTimeComponentContext) interface{} + + // Visit a parse tree produced by CqlParser#pluralDateTimePrecision. + VisitPluralDateTimePrecision(ctx *PluralDateTimePrecisionContext) interface{} + + // Visit a parse tree produced by CqlParser#additionExpressionTerm. + VisitAdditionExpressionTerm(ctx *AdditionExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#indexedExpressionTerm. + VisitIndexedExpressionTerm(ctx *IndexedExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#widthExpressionTerm. + VisitWidthExpressionTerm(ctx *WidthExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#setAggregateExpressionTerm. + VisitSetAggregateExpressionTerm(ctx *SetAggregateExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#timeUnitExpressionTerm. + VisitTimeUnitExpressionTerm(ctx *TimeUnitExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#ifThenElseExpressionTerm. + VisitIfThenElseExpressionTerm(ctx *IfThenElseExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#timeBoundaryExpressionTerm. + VisitTimeBoundaryExpressionTerm(ctx *TimeBoundaryExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#elementExtractorExpressionTerm. + VisitElementExtractorExpressionTerm(ctx *ElementExtractorExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#conversionExpressionTerm. + VisitConversionExpressionTerm(ctx *ConversionExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#typeExtentExpressionTerm. + VisitTypeExtentExpressionTerm(ctx *TypeExtentExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#predecessorExpressionTerm. + VisitPredecessorExpressionTerm(ctx *PredecessorExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#pointExtractorExpressionTerm. + VisitPointExtractorExpressionTerm(ctx *PointExtractorExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#multiplicationExpressionTerm. + VisitMultiplicationExpressionTerm(ctx *MultiplicationExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#aggregateExpressionTerm. + VisitAggregateExpressionTerm(ctx *AggregateExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#durationExpressionTerm. + VisitDurationExpressionTerm(ctx *DurationExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#differenceExpressionTerm. + VisitDifferenceExpressionTerm(ctx *DifferenceExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#caseExpressionTerm. + VisitCaseExpressionTerm(ctx *CaseExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#powerExpressionTerm. + VisitPowerExpressionTerm(ctx *PowerExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#successorExpressionTerm. + VisitSuccessorExpressionTerm(ctx *SuccessorExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#polarityExpressionTerm. + VisitPolarityExpressionTerm(ctx *PolarityExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#termExpressionTerm. + VisitTermExpressionTerm(ctx *TermExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#invocationExpressionTerm. + VisitInvocationExpressionTerm(ctx *InvocationExpressionTermContext) interface{} + + // Visit a parse tree produced by CqlParser#caseExpressionItem. + VisitCaseExpressionItem(ctx *CaseExpressionItemContext) interface{} + + // Visit a parse tree produced by CqlParser#dateTimePrecisionSpecifier. + VisitDateTimePrecisionSpecifier(ctx *DateTimePrecisionSpecifierContext) interface{} + + // Visit a parse tree produced by CqlParser#relativeQualifier. + VisitRelativeQualifier(ctx *RelativeQualifierContext) interface{} + + // Visit a parse tree produced by CqlParser#offsetRelativeQualifier. + VisitOffsetRelativeQualifier(ctx *OffsetRelativeQualifierContext) interface{} + + // Visit a parse tree produced by CqlParser#exclusiveRelativeQualifier. + VisitExclusiveRelativeQualifier(ctx *ExclusiveRelativeQualifierContext) interface{} + + // Visit a parse tree produced by CqlParser#quantityOffset. + VisitQuantityOffset(ctx *QuantityOffsetContext) interface{} + + // Visit a parse tree produced by CqlParser#temporalRelationship. + VisitTemporalRelationship(ctx *TemporalRelationshipContext) interface{} + + // Visit a parse tree produced by CqlParser#concurrentWithIntervalOperatorPhrase. + VisitConcurrentWithIntervalOperatorPhrase(ctx *ConcurrentWithIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#includesIntervalOperatorPhrase. + VisitIncludesIntervalOperatorPhrase(ctx *IncludesIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#includedInIntervalOperatorPhrase. + VisitIncludedInIntervalOperatorPhrase(ctx *IncludedInIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#beforeOrAfterIntervalOperatorPhrase. + VisitBeforeOrAfterIntervalOperatorPhrase(ctx *BeforeOrAfterIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#withinIntervalOperatorPhrase. + VisitWithinIntervalOperatorPhrase(ctx *WithinIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#meetsIntervalOperatorPhrase. + VisitMeetsIntervalOperatorPhrase(ctx *MeetsIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#overlapsIntervalOperatorPhrase. + VisitOverlapsIntervalOperatorPhrase(ctx *OverlapsIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#startsIntervalOperatorPhrase. + VisitStartsIntervalOperatorPhrase(ctx *StartsIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#endsIntervalOperatorPhrase. + VisitEndsIntervalOperatorPhrase(ctx *EndsIntervalOperatorPhraseContext) interface{} + + // Visit a parse tree produced by CqlParser#invocationTerm. + VisitInvocationTerm(ctx *InvocationTermContext) interface{} + + // Visit a parse tree produced by CqlParser#literalTerm. + VisitLiteralTerm(ctx *LiteralTermContext) interface{} + + // Visit a parse tree produced by CqlParser#externalConstantTerm. + VisitExternalConstantTerm(ctx *ExternalConstantTermContext) interface{} + + // Visit a parse tree produced by CqlParser#intervalSelectorTerm. + VisitIntervalSelectorTerm(ctx *IntervalSelectorTermContext) interface{} + + // Visit a parse tree produced by CqlParser#tupleSelectorTerm. + VisitTupleSelectorTerm(ctx *TupleSelectorTermContext) interface{} + + // Visit a parse tree produced by CqlParser#instanceSelectorTerm. + VisitInstanceSelectorTerm(ctx *InstanceSelectorTermContext) interface{} + + // Visit a parse tree produced by CqlParser#listSelectorTerm. + VisitListSelectorTerm(ctx *ListSelectorTermContext) interface{} + + // Visit a parse tree produced by CqlParser#codeSelectorTerm. + VisitCodeSelectorTerm(ctx *CodeSelectorTermContext) interface{} + + // Visit a parse tree produced by CqlParser#conceptSelectorTerm. + VisitConceptSelectorTerm(ctx *ConceptSelectorTermContext) interface{} + + // Visit a parse tree produced by CqlParser#parenthesizedTerm. + VisitParenthesizedTerm(ctx *ParenthesizedTermContext) interface{} + + // Visit a parse tree produced by CqlParser#qualifiedMemberInvocation. + VisitQualifiedMemberInvocation(ctx *QualifiedMemberInvocationContext) interface{} + + // Visit a parse tree produced by CqlParser#qualifiedFunctionInvocation. + VisitQualifiedFunctionInvocation(ctx *QualifiedFunctionInvocationContext) interface{} + + // Visit a parse tree produced by CqlParser#qualifiedFunction. + VisitQualifiedFunction(ctx *QualifiedFunctionContext) interface{} + + // Visit a parse tree produced by CqlParser#memberInvocation. + VisitMemberInvocation(ctx *MemberInvocationContext) interface{} + + // Visit a parse tree produced by CqlParser#functionInvocation. + VisitFunctionInvocation(ctx *FunctionInvocationContext) interface{} + + // Visit a parse tree produced by CqlParser#thisInvocation. + VisitThisInvocation(ctx *ThisInvocationContext) interface{} + + // Visit a parse tree produced by CqlParser#indexInvocation. + VisitIndexInvocation(ctx *IndexInvocationContext) interface{} + + // Visit a parse tree produced by CqlParser#totalInvocation. + VisitTotalInvocation(ctx *TotalInvocationContext) interface{} + + // Visit a parse tree produced by CqlParser#function. + VisitFunction(ctx *FunctionContext) interface{} + + // Visit a parse tree produced by CqlParser#ratio. + VisitRatio(ctx *RatioContext) interface{} + + // Visit a parse tree produced by CqlParser#booleanLiteral. + VisitBooleanLiteral(ctx *BooleanLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#nullLiteral. + VisitNullLiteral(ctx *NullLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#stringLiteral. + VisitStringLiteral(ctx *StringLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#numberLiteral. + VisitNumberLiteral(ctx *NumberLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#longNumberLiteral. + VisitLongNumberLiteral(ctx *LongNumberLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#dateTimeLiteral. + VisitDateTimeLiteral(ctx *DateTimeLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#dateLiteral. + VisitDateLiteral(ctx *DateLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#timeLiteral. + VisitTimeLiteral(ctx *TimeLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#quantityLiteral. + VisitQuantityLiteral(ctx *QuantityLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#ratioLiteral. + VisitRatioLiteral(ctx *RatioLiteralContext) interface{} + + // Visit a parse tree produced by CqlParser#intervalSelector. + VisitIntervalSelector(ctx *IntervalSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#tupleSelector. + VisitTupleSelector(ctx *TupleSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#tupleElementSelector. + VisitTupleElementSelector(ctx *TupleElementSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#instanceSelector. + VisitInstanceSelector(ctx *InstanceSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#instanceElementSelector. + VisitInstanceElementSelector(ctx *InstanceElementSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#listSelector. + VisitListSelector(ctx *ListSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#displayClause. + VisitDisplayClause(ctx *DisplayClauseContext) interface{} + + // Visit a parse tree produced by CqlParser#codeSelector. + VisitCodeSelector(ctx *CodeSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#conceptSelector. + VisitConceptSelector(ctx *ConceptSelectorContext) interface{} + + // Visit a parse tree produced by CqlParser#keyword. + VisitKeyword(ctx *KeywordContext) interface{} + + // Visit a parse tree produced by CqlParser#reservedWord. + VisitReservedWord(ctx *ReservedWordContext) interface{} + + // Visit a parse tree produced by CqlParser#keywordIdentifier. + VisitKeywordIdentifier(ctx *KeywordIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#obsoleteIdentifier. + VisitObsoleteIdentifier(ctx *ObsoleteIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#functionIdentifier. + VisitFunctionIdentifier(ctx *FunctionIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#typeNameIdentifier. + VisitTypeNameIdentifier(ctx *TypeNameIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#referentialIdentifier. + VisitReferentialIdentifier(ctx *ReferentialIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#referentialOrTypeNameIdentifier. + VisitReferentialOrTypeNameIdentifier(ctx *ReferentialOrTypeNameIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#identifierOrFunctionIdentifier. + VisitIdentifierOrFunctionIdentifier(ctx *IdentifierOrFunctionIdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#identifier. + VisitIdentifier(ctx *IdentifierContext) interface{} + + // Visit a parse tree produced by CqlParser#externalConstant. + VisitExternalConstant(ctx *ExternalConstantContext) interface{} + + // Visit a parse tree produced by CqlParser#paramList. + VisitParamList(ctx *ParamListContext) interface{} + + // Visit a parse tree produced by CqlParser#quantity. + VisitQuantity(ctx *QuantityContext) interface{} + + // Visit a parse tree produced by CqlParser#unit. + VisitUnit(ctx *UnitContext) interface{} + +} \ No newline at end of file diff --git a/internal/embeddata/third_party/cqframework/cvlt/cvlt_base_visitor.go b/internal/embeddata/third_party/cqframework/cvlt/cvlt_base_visitor.go new file mode 100755 index 0000000..6952b14 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cvlt/cvlt_base_visitor.go @@ -0,0 +1,168 @@ +// Code generated from Cvlt.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cvlt // Cvlt +import "github.com/antlr4-go/antlr/v4" + +type BaseCvltVisitor struct { + *antlr.BaseParseTreeVisitor +} + +func (v *BaseCvltVisitor) VisitTypeSpecifier(ctx *TypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitNamedTypeSpecifier(ctx *NamedTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitModelIdentifier(ctx *ModelIdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitListTypeSpecifier(ctx *ListTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitIntervalTypeSpecifier(ctx *IntervalTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitTupleTypeSpecifier(ctx *TupleTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitTupleElementDefinition(ctx *TupleElementDefinitionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitChoiceTypeSpecifier(ctx *ChoiceTypeSpecifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitLiteralTerm(ctx *LiteralTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitIntervalSelectorTerm(ctx *IntervalSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitTupleSelectorTerm(ctx *TupleSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitInstanceSelectorTerm(ctx *InstanceSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitListSelectorTerm(ctx *ListSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitCodeSelectorTerm(ctx *CodeSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitConceptSelectorTerm(ctx *ConceptSelectorTermContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitRatio(ctx *RatioContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitBooleanLiteral(ctx *BooleanLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitNullLiteral(ctx *NullLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitStringLiteral(ctx *StringLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitNumberLiteral(ctx *NumberLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitLongNumberLiteral(ctx *LongNumberLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitDateTimeLiteral(ctx *DateTimeLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitDateLiteral(ctx *DateLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitTimeLiteral(ctx *TimeLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitQuantityLiteral(ctx *QuantityLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitRatioLiteral(ctx *RatioLiteralContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitIntervalSelector(ctx *IntervalSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitTupleSelector(ctx *TupleSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitTupleElementSelector(ctx *TupleElementSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitInstanceSelector(ctx *InstanceSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitInstanceElementSelector(ctx *InstanceElementSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitListSelector(ctx *ListSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitDisplayClause(ctx *DisplayClauseContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitCodeSelector(ctx *CodeSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitConceptSelector(ctx *ConceptSelectorContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitIdentifier(ctx *IdentifierContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitQuantity(ctx *QuantityContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitUnit(ctx *UnitContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitDateTimePrecision(ctx *DateTimePrecisionContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseCvltVisitor) VisitPluralDateTimePrecision(ctx *PluralDateTimePrecisionContext) interface{} { + return v.VisitChildren(ctx) +} diff --git a/internal/embeddata/third_party/cqframework/cvlt/cvlt_lexer.go b/internal/embeddata/third_party/cqframework/cvlt/cvlt_lexer.go new file mode 100755 index 0000000..a52ef43 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cvlt/cvlt_lexer.go @@ -0,0 +1,378 @@ +// Code generated from Cvlt.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cvlt + +import ( + "fmt" + "github.com/antlr4-go/antlr/v4" + "sync" + "unicode" +) + +// Suppress unused import error +var _ = fmt.Printf +var _ = sync.Once{} +var _ = unicode.IsLetter + +type CvltLexer struct { + *antlr.BaseLexer + channelNames []string + modeNames []string + // TODO: EOF string +} + +var CvltLexerLexerStaticData struct { + once sync.Once + serializedATN []int32 + ChannelNames []string + ModeNames []string + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func cvltlexerLexerInit() { + staticData := &CvltLexerLexerStaticData + staticData.ChannelNames = []string{ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + } + staticData.ModeNames = []string{ + "DEFAULT_MODE", + } + staticData.LiteralNames = []string{ + "", "'.'", "'List'", "'<'", "'>'", "'Interval'", "'Tuple'", "'{'", "','", + "'}'", "'Choice'", "':'", "'true'", "'false'", "'null'", "'['", "'('", + "']'", "')'", "'display'", "'Code'", "'from'", "'Concept'", "'year'", + "'month'", "'week'", "'day'", "'hour'", "'minute'", "'second'", "'millisecond'", + "'years'", "'months'", "'weeks'", "'days'", "'hours'", "'minutes'", + "'seconds'", "'milliseconds'", + } + staticData.SymbolicNames = []string{ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "DATE", "DATETIME", "TIME", "IDENTIFIER", "DELIMITEDIDENTIFIER", + "QUOTEDIDENTIFIER", "STRING", "NUMBER", "LONGNUMBER", "WS", "COMMENT", + "LINE_COMMENT", + } + staticData.RuleNames = []string{ + "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8", + "T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16", + "T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "T__23", "T__24", + "T__25", "T__26", "T__27", "T__28", "T__29", "T__30", "T__31", "T__32", + "T__33", "T__34", "T__35", "T__36", "T__37", "DATE", "DATETIME", "TIME", + "DATEFORMAT", "TIMEFORMAT", "TIMEZONEOFFSETFORMAT", "IDENTIFIER", "DELIMITEDIDENTIFIER", + "QUOTEDIDENTIFIER", "STRING", "NUMBER", "LONGNUMBER", "WS", "COMMENT", + "LINE_COMMENT", "ESC", "UNICODE", "HEX", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 0, 50, 483, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, + 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, + 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, + 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, + 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, + 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, + 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, + 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, + 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, + 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, + 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 1, 0, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, + 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, + 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, + 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, + 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, + 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, + 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, + 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, + 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, + 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, + 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, + 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, + 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, + 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, + 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, + 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, + 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, + 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, + 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, + 1, 39, 3, 39, 322, 8, 39, 1, 39, 3, 39, 325, 8, 39, 1, 40, 1, 40, 1, 40, + 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, + 41, 3, 41, 341, 8, 41, 3, 41, 343, 8, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, + 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 4, 42, 355, 8, 42, 11, 42, 12, 42, + 356, 3, 42, 359, 8, 42, 3, 42, 361, 8, 42, 3, 42, 363, 8, 42, 1, 43, 1, + 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 3, 43, 372, 8, 43, 1, 44, 3, 44, + 375, 8, 44, 1, 44, 5, 44, 378, 8, 44, 10, 44, 12, 44, 381, 9, 44, 1, 45, + 1, 45, 1, 45, 5, 45, 386, 8, 45, 10, 45, 12, 45, 389, 9, 45, 1, 45, 1, + 45, 1, 46, 1, 46, 1, 46, 5, 46, 396, 8, 46, 10, 46, 12, 46, 399, 9, 46, + 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 5, 47, 406, 8, 47, 10, 47, 12, 47, 409, + 9, 47, 1, 47, 1, 47, 1, 48, 3, 48, 414, 8, 48, 1, 48, 4, 48, 417, 8, 48, + 11, 48, 12, 48, 418, 1, 48, 1, 48, 4, 48, 423, 8, 48, 11, 48, 12, 48, 424, + 3, 48, 427, 8, 48, 1, 49, 3, 49, 430, 8, 49, 1, 49, 4, 49, 433, 8, 49, + 11, 49, 12, 49, 434, 1, 49, 1, 49, 1, 50, 4, 50, 440, 8, 50, 11, 50, 12, + 50, 441, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 450, 8, 51, 10, + 51, 12, 51, 453, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, + 1, 52, 1, 52, 5, 52, 464, 8, 52, 10, 52, 12, 52, 467, 9, 52, 1, 52, 1, + 52, 1, 53, 1, 53, 1, 53, 3, 53, 474, 8, 53, 1, 54, 1, 54, 1, 54, 1, 54, + 1, 54, 1, 54, 1, 55, 1, 55, 4, 387, 397, 407, 451, 0, 56, 1, 1, 3, 2, 5, + 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, + 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, + 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, + 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, + 40, 81, 41, 83, 0, 85, 0, 87, 0, 89, 42, 91, 43, 93, 44, 95, 45, 97, 46, + 99, 47, 101, 48, 103, 49, 105, 50, 107, 0, 109, 0, 111, 0, 1, 0, 8, 1, + 0, 48, 57, 2, 0, 43, 43, 45, 45, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, + 57, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 2, 0, 10, 10, + 13, 13, 9, 0, 34, 34, 39, 39, 47, 47, 92, 92, 96, 96, 102, 102, 110, 110, + 114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 502, 0, 1, 1, 0, 0, + 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, + 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, + 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, + 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, + 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, + 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, + 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, + 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, + 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, + 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, + 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, + 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, + 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, 1, 113, 1, + 0, 0, 0, 3, 115, 1, 0, 0, 0, 5, 120, 1, 0, 0, 0, 7, 122, 1, 0, 0, 0, 9, + 124, 1, 0, 0, 0, 11, 133, 1, 0, 0, 0, 13, 139, 1, 0, 0, 0, 15, 141, 1, + 0, 0, 0, 17, 143, 1, 0, 0, 0, 19, 145, 1, 0, 0, 0, 21, 152, 1, 0, 0, 0, + 23, 154, 1, 0, 0, 0, 25, 159, 1, 0, 0, 0, 27, 165, 1, 0, 0, 0, 29, 170, + 1, 0, 0, 0, 31, 172, 1, 0, 0, 0, 33, 174, 1, 0, 0, 0, 35, 176, 1, 0, 0, + 0, 37, 178, 1, 0, 0, 0, 39, 186, 1, 0, 0, 0, 41, 191, 1, 0, 0, 0, 43, 196, + 1, 0, 0, 0, 45, 204, 1, 0, 0, 0, 47, 209, 1, 0, 0, 0, 49, 215, 1, 0, 0, + 0, 51, 220, 1, 0, 0, 0, 53, 224, 1, 0, 0, 0, 55, 229, 1, 0, 0, 0, 57, 236, + 1, 0, 0, 0, 59, 243, 1, 0, 0, 0, 61, 255, 1, 0, 0, 0, 63, 261, 1, 0, 0, + 0, 65, 268, 1, 0, 0, 0, 67, 274, 1, 0, 0, 0, 69, 279, 1, 0, 0, 0, 71, 285, + 1, 0, 0, 0, 73, 293, 1, 0, 0, 0, 75, 301, 1, 0, 0, 0, 77, 314, 1, 0, 0, + 0, 79, 317, 1, 0, 0, 0, 81, 326, 1, 0, 0, 0, 83, 330, 1, 0, 0, 0, 85, 344, + 1, 0, 0, 0, 87, 371, 1, 0, 0, 0, 89, 374, 1, 0, 0, 0, 91, 382, 1, 0, 0, + 0, 93, 392, 1, 0, 0, 0, 95, 402, 1, 0, 0, 0, 97, 413, 1, 0, 0, 0, 99, 429, + 1, 0, 0, 0, 101, 439, 1, 0, 0, 0, 103, 445, 1, 0, 0, 0, 105, 459, 1, 0, + 0, 0, 107, 470, 1, 0, 0, 0, 109, 475, 1, 0, 0, 0, 111, 481, 1, 0, 0, 0, + 113, 114, 5, 46, 0, 0, 114, 2, 1, 0, 0, 0, 115, 116, 5, 76, 0, 0, 116, + 117, 5, 105, 0, 0, 117, 118, 5, 115, 0, 0, 118, 119, 5, 116, 0, 0, 119, + 4, 1, 0, 0, 0, 120, 121, 5, 60, 0, 0, 121, 6, 1, 0, 0, 0, 122, 123, 5, + 62, 0, 0, 123, 8, 1, 0, 0, 0, 124, 125, 5, 73, 0, 0, 125, 126, 5, 110, + 0, 0, 126, 127, 5, 116, 0, 0, 127, 128, 5, 101, 0, 0, 128, 129, 5, 114, + 0, 0, 129, 130, 5, 118, 0, 0, 130, 131, 5, 97, 0, 0, 131, 132, 5, 108, + 0, 0, 132, 10, 1, 0, 0, 0, 133, 134, 5, 84, 0, 0, 134, 135, 5, 117, 0, + 0, 135, 136, 5, 112, 0, 0, 136, 137, 5, 108, 0, 0, 137, 138, 5, 101, 0, + 0, 138, 12, 1, 0, 0, 0, 139, 140, 5, 123, 0, 0, 140, 14, 1, 0, 0, 0, 141, + 142, 5, 44, 0, 0, 142, 16, 1, 0, 0, 0, 143, 144, 5, 125, 0, 0, 144, 18, + 1, 0, 0, 0, 145, 146, 5, 67, 0, 0, 146, 147, 5, 104, 0, 0, 147, 148, 5, + 111, 0, 0, 148, 149, 5, 105, 0, 0, 149, 150, 5, 99, 0, 0, 150, 151, 5, + 101, 0, 0, 151, 20, 1, 0, 0, 0, 152, 153, 5, 58, 0, 0, 153, 22, 1, 0, 0, + 0, 154, 155, 5, 116, 0, 0, 155, 156, 5, 114, 0, 0, 156, 157, 5, 117, 0, + 0, 157, 158, 5, 101, 0, 0, 158, 24, 1, 0, 0, 0, 159, 160, 5, 102, 0, 0, + 160, 161, 5, 97, 0, 0, 161, 162, 5, 108, 0, 0, 162, 163, 5, 115, 0, 0, + 163, 164, 5, 101, 0, 0, 164, 26, 1, 0, 0, 0, 165, 166, 5, 110, 0, 0, 166, + 167, 5, 117, 0, 0, 167, 168, 5, 108, 0, 0, 168, 169, 5, 108, 0, 0, 169, + 28, 1, 0, 0, 0, 170, 171, 5, 91, 0, 0, 171, 30, 1, 0, 0, 0, 172, 173, 5, + 40, 0, 0, 173, 32, 1, 0, 0, 0, 174, 175, 5, 93, 0, 0, 175, 34, 1, 0, 0, + 0, 176, 177, 5, 41, 0, 0, 177, 36, 1, 0, 0, 0, 178, 179, 5, 100, 0, 0, + 179, 180, 5, 105, 0, 0, 180, 181, 5, 115, 0, 0, 181, 182, 5, 112, 0, 0, + 182, 183, 5, 108, 0, 0, 183, 184, 5, 97, 0, 0, 184, 185, 5, 121, 0, 0, + 185, 38, 1, 0, 0, 0, 186, 187, 5, 67, 0, 0, 187, 188, 5, 111, 0, 0, 188, + 189, 5, 100, 0, 0, 189, 190, 5, 101, 0, 0, 190, 40, 1, 0, 0, 0, 191, 192, + 5, 102, 0, 0, 192, 193, 5, 114, 0, 0, 193, 194, 5, 111, 0, 0, 194, 195, + 5, 109, 0, 0, 195, 42, 1, 0, 0, 0, 196, 197, 5, 67, 0, 0, 197, 198, 5, + 111, 0, 0, 198, 199, 5, 110, 0, 0, 199, 200, 5, 99, 0, 0, 200, 201, 5, + 101, 0, 0, 201, 202, 5, 112, 0, 0, 202, 203, 5, 116, 0, 0, 203, 44, 1, + 0, 0, 0, 204, 205, 5, 121, 0, 0, 205, 206, 5, 101, 0, 0, 206, 207, 5, 97, + 0, 0, 207, 208, 5, 114, 0, 0, 208, 46, 1, 0, 0, 0, 209, 210, 5, 109, 0, + 0, 210, 211, 5, 111, 0, 0, 211, 212, 5, 110, 0, 0, 212, 213, 5, 116, 0, + 0, 213, 214, 5, 104, 0, 0, 214, 48, 1, 0, 0, 0, 215, 216, 5, 119, 0, 0, + 216, 217, 5, 101, 0, 0, 217, 218, 5, 101, 0, 0, 218, 219, 5, 107, 0, 0, + 219, 50, 1, 0, 0, 0, 220, 221, 5, 100, 0, 0, 221, 222, 5, 97, 0, 0, 222, + 223, 5, 121, 0, 0, 223, 52, 1, 0, 0, 0, 224, 225, 5, 104, 0, 0, 225, 226, + 5, 111, 0, 0, 226, 227, 5, 117, 0, 0, 227, 228, 5, 114, 0, 0, 228, 54, + 1, 0, 0, 0, 229, 230, 5, 109, 0, 0, 230, 231, 5, 105, 0, 0, 231, 232, 5, + 110, 0, 0, 232, 233, 5, 117, 0, 0, 233, 234, 5, 116, 0, 0, 234, 235, 5, + 101, 0, 0, 235, 56, 1, 0, 0, 0, 236, 237, 5, 115, 0, 0, 237, 238, 5, 101, + 0, 0, 238, 239, 5, 99, 0, 0, 239, 240, 5, 111, 0, 0, 240, 241, 5, 110, + 0, 0, 241, 242, 5, 100, 0, 0, 242, 58, 1, 0, 0, 0, 243, 244, 5, 109, 0, + 0, 244, 245, 5, 105, 0, 0, 245, 246, 5, 108, 0, 0, 246, 247, 5, 108, 0, + 0, 247, 248, 5, 105, 0, 0, 248, 249, 5, 115, 0, 0, 249, 250, 5, 101, 0, + 0, 250, 251, 5, 99, 0, 0, 251, 252, 5, 111, 0, 0, 252, 253, 5, 110, 0, + 0, 253, 254, 5, 100, 0, 0, 254, 60, 1, 0, 0, 0, 255, 256, 5, 121, 0, 0, + 256, 257, 5, 101, 0, 0, 257, 258, 5, 97, 0, 0, 258, 259, 5, 114, 0, 0, + 259, 260, 5, 115, 0, 0, 260, 62, 1, 0, 0, 0, 261, 262, 5, 109, 0, 0, 262, + 263, 5, 111, 0, 0, 263, 264, 5, 110, 0, 0, 264, 265, 5, 116, 0, 0, 265, + 266, 5, 104, 0, 0, 266, 267, 5, 115, 0, 0, 267, 64, 1, 0, 0, 0, 268, 269, + 5, 119, 0, 0, 269, 270, 5, 101, 0, 0, 270, 271, 5, 101, 0, 0, 271, 272, + 5, 107, 0, 0, 272, 273, 5, 115, 0, 0, 273, 66, 1, 0, 0, 0, 274, 275, 5, + 100, 0, 0, 275, 276, 5, 97, 0, 0, 276, 277, 5, 121, 0, 0, 277, 278, 5, + 115, 0, 0, 278, 68, 1, 0, 0, 0, 279, 280, 5, 104, 0, 0, 280, 281, 5, 111, + 0, 0, 281, 282, 5, 117, 0, 0, 282, 283, 5, 114, 0, 0, 283, 284, 5, 115, + 0, 0, 284, 70, 1, 0, 0, 0, 285, 286, 5, 109, 0, 0, 286, 287, 5, 105, 0, + 0, 287, 288, 5, 110, 0, 0, 288, 289, 5, 117, 0, 0, 289, 290, 5, 116, 0, + 0, 290, 291, 5, 101, 0, 0, 291, 292, 5, 115, 0, 0, 292, 72, 1, 0, 0, 0, + 293, 294, 5, 115, 0, 0, 294, 295, 5, 101, 0, 0, 295, 296, 5, 99, 0, 0, + 296, 297, 5, 111, 0, 0, 297, 298, 5, 110, 0, 0, 298, 299, 5, 100, 0, 0, + 299, 300, 5, 115, 0, 0, 300, 74, 1, 0, 0, 0, 301, 302, 5, 109, 0, 0, 302, + 303, 5, 105, 0, 0, 303, 304, 5, 108, 0, 0, 304, 305, 5, 108, 0, 0, 305, + 306, 5, 105, 0, 0, 306, 307, 5, 115, 0, 0, 307, 308, 5, 101, 0, 0, 308, + 309, 5, 99, 0, 0, 309, 310, 5, 111, 0, 0, 310, 311, 5, 110, 0, 0, 311, + 312, 5, 100, 0, 0, 312, 313, 5, 115, 0, 0, 313, 76, 1, 0, 0, 0, 314, 315, + 5, 64, 0, 0, 315, 316, 3, 83, 41, 0, 316, 78, 1, 0, 0, 0, 317, 318, 5, + 64, 0, 0, 318, 319, 3, 83, 41, 0, 319, 321, 5, 84, 0, 0, 320, 322, 3, 85, + 42, 0, 321, 320, 1, 0, 0, 0, 321, 322, 1, 0, 0, 0, 322, 324, 1, 0, 0, 0, + 323, 325, 3, 87, 43, 0, 324, 323, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, + 80, 1, 0, 0, 0, 326, 327, 5, 64, 0, 0, 327, 328, 5, 84, 0, 0, 328, 329, + 3, 85, 42, 0, 329, 82, 1, 0, 0, 0, 330, 331, 7, 0, 0, 0, 331, 332, 7, 0, + 0, 0, 332, 333, 7, 0, 0, 0, 333, 342, 7, 0, 0, 0, 334, 335, 5, 45, 0, 0, + 335, 336, 7, 0, 0, 0, 336, 340, 7, 0, 0, 0, 337, 338, 5, 45, 0, 0, 338, + 339, 7, 0, 0, 0, 339, 341, 7, 0, 0, 0, 340, 337, 1, 0, 0, 0, 340, 341, + 1, 0, 0, 0, 341, 343, 1, 0, 0, 0, 342, 334, 1, 0, 0, 0, 342, 343, 1, 0, + 0, 0, 343, 84, 1, 0, 0, 0, 344, 345, 7, 0, 0, 0, 345, 362, 7, 0, 0, 0, + 346, 347, 5, 58, 0, 0, 347, 348, 7, 0, 0, 0, 348, 360, 7, 0, 0, 0, 349, + 350, 5, 58, 0, 0, 350, 351, 7, 0, 0, 0, 351, 358, 7, 0, 0, 0, 352, 354, + 5, 46, 0, 0, 353, 355, 7, 0, 0, 0, 354, 353, 1, 0, 0, 0, 355, 356, 1, 0, + 0, 0, 356, 354, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 359, 1, 0, 0, 0, + 358, 352, 1, 0, 0, 0, 358, 359, 1, 0, 0, 0, 359, 361, 1, 0, 0, 0, 360, + 349, 1, 0, 0, 0, 360, 361, 1, 0, 0, 0, 361, 363, 1, 0, 0, 0, 362, 346, + 1, 0, 0, 0, 362, 363, 1, 0, 0, 0, 363, 86, 1, 0, 0, 0, 364, 372, 5, 90, + 0, 0, 365, 366, 7, 1, 0, 0, 366, 367, 7, 0, 0, 0, 367, 368, 7, 0, 0, 0, + 368, 369, 5, 58, 0, 0, 369, 370, 7, 0, 0, 0, 370, 372, 7, 0, 0, 0, 371, + 364, 1, 0, 0, 0, 371, 365, 1, 0, 0, 0, 372, 88, 1, 0, 0, 0, 373, 375, 7, + 2, 0, 0, 374, 373, 1, 0, 0, 0, 375, 379, 1, 0, 0, 0, 376, 378, 7, 3, 0, + 0, 377, 376, 1, 0, 0, 0, 378, 381, 1, 0, 0, 0, 379, 377, 1, 0, 0, 0, 379, + 380, 1, 0, 0, 0, 380, 90, 1, 0, 0, 0, 381, 379, 1, 0, 0, 0, 382, 387, 5, + 96, 0, 0, 383, 386, 3, 107, 53, 0, 384, 386, 9, 0, 0, 0, 385, 383, 1, 0, + 0, 0, 385, 384, 1, 0, 0, 0, 386, 389, 1, 0, 0, 0, 387, 388, 1, 0, 0, 0, + 387, 385, 1, 0, 0, 0, 388, 390, 1, 0, 0, 0, 389, 387, 1, 0, 0, 0, 390, + 391, 5, 96, 0, 0, 391, 92, 1, 0, 0, 0, 392, 397, 5, 34, 0, 0, 393, 396, + 3, 107, 53, 0, 394, 396, 9, 0, 0, 0, 395, 393, 1, 0, 0, 0, 395, 394, 1, + 0, 0, 0, 396, 399, 1, 0, 0, 0, 397, 398, 1, 0, 0, 0, 397, 395, 1, 0, 0, + 0, 398, 400, 1, 0, 0, 0, 399, 397, 1, 0, 0, 0, 400, 401, 5, 34, 0, 0, 401, + 94, 1, 0, 0, 0, 402, 407, 5, 39, 0, 0, 403, 406, 3, 107, 53, 0, 404, 406, + 9, 0, 0, 0, 405, 403, 1, 0, 0, 0, 405, 404, 1, 0, 0, 0, 406, 409, 1, 0, + 0, 0, 407, 408, 1, 0, 0, 0, 407, 405, 1, 0, 0, 0, 408, 410, 1, 0, 0, 0, + 409, 407, 1, 0, 0, 0, 410, 411, 5, 39, 0, 0, 411, 96, 1, 0, 0, 0, 412, + 414, 7, 1, 0, 0, 413, 412, 1, 0, 0, 0, 413, 414, 1, 0, 0, 0, 414, 416, + 1, 0, 0, 0, 415, 417, 7, 0, 0, 0, 416, 415, 1, 0, 0, 0, 417, 418, 1, 0, + 0, 0, 418, 416, 1, 0, 0, 0, 418, 419, 1, 0, 0, 0, 419, 426, 1, 0, 0, 0, + 420, 422, 5, 46, 0, 0, 421, 423, 7, 0, 0, 0, 422, 421, 1, 0, 0, 0, 423, + 424, 1, 0, 0, 0, 424, 422, 1, 0, 0, 0, 424, 425, 1, 0, 0, 0, 425, 427, + 1, 0, 0, 0, 426, 420, 1, 0, 0, 0, 426, 427, 1, 0, 0, 0, 427, 98, 1, 0, + 0, 0, 428, 430, 7, 1, 0, 0, 429, 428, 1, 0, 0, 0, 429, 430, 1, 0, 0, 0, + 430, 432, 1, 0, 0, 0, 431, 433, 7, 0, 0, 0, 432, 431, 1, 0, 0, 0, 433, + 434, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 436, + 1, 0, 0, 0, 436, 437, 5, 76, 0, 0, 437, 100, 1, 0, 0, 0, 438, 440, 7, 4, + 0, 0, 439, 438, 1, 0, 0, 0, 440, 441, 1, 0, 0, 0, 441, 439, 1, 0, 0, 0, + 441, 442, 1, 0, 0, 0, 442, 443, 1, 0, 0, 0, 443, 444, 6, 50, 0, 0, 444, + 102, 1, 0, 0, 0, 445, 446, 5, 47, 0, 0, 446, 447, 5, 42, 0, 0, 447, 451, + 1, 0, 0, 0, 448, 450, 9, 0, 0, 0, 449, 448, 1, 0, 0, 0, 450, 453, 1, 0, + 0, 0, 451, 452, 1, 0, 0, 0, 451, 449, 1, 0, 0, 0, 452, 454, 1, 0, 0, 0, + 453, 451, 1, 0, 0, 0, 454, 455, 5, 42, 0, 0, 455, 456, 5, 47, 0, 0, 456, + 457, 1, 0, 0, 0, 457, 458, 6, 51, 0, 0, 458, 104, 1, 0, 0, 0, 459, 460, + 5, 47, 0, 0, 460, 461, 5, 47, 0, 0, 461, 465, 1, 0, 0, 0, 462, 464, 8, + 5, 0, 0, 463, 462, 1, 0, 0, 0, 464, 467, 1, 0, 0, 0, 465, 463, 1, 0, 0, + 0, 465, 466, 1, 0, 0, 0, 466, 468, 1, 0, 0, 0, 467, 465, 1, 0, 0, 0, 468, + 469, 6, 52, 0, 0, 469, 106, 1, 0, 0, 0, 470, 473, 5, 92, 0, 0, 471, 474, + 7, 6, 0, 0, 472, 474, 3, 109, 54, 0, 473, 471, 1, 0, 0, 0, 473, 472, 1, + 0, 0, 0, 474, 108, 1, 0, 0, 0, 475, 476, 5, 117, 0, 0, 476, 477, 3, 111, + 55, 0, 477, 478, 3, 111, 55, 0, 478, 479, 3, 111, 55, 0, 479, 480, 3, 111, + 55, 0, 480, 110, 1, 0, 0, 0, 481, 482, 7, 7, 0, 0, 482, 112, 1, 0, 0, 0, + 29, 0, 321, 324, 340, 342, 356, 358, 360, 362, 371, 374, 377, 379, 385, + 387, 395, 397, 405, 407, 413, 418, 424, 426, 429, 434, 441, 451, 465, 473, + 1, 0, 1, 0, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// CvltLexerInit initializes any static state used to implement CvltLexer. By default the +// static state used to implement the lexer is lazily initialized during the first call to +// NewCvltLexer(). You can call this function if you wish to initialize the static state ahead +// of time. +func CvltLexerInit() { + staticData := &CvltLexerLexerStaticData + staticData.once.Do(cvltlexerLexerInit) +} + +// NewCvltLexer produces a new lexer instance for the optional input antlr.CharStream. +func NewCvltLexer(input antlr.CharStream) *CvltLexer { + CvltLexerInit() + l := new(CvltLexer) + l.BaseLexer = antlr.NewBaseLexer(input) + staticData := &CvltLexerLexerStaticData + l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + l.channelNames = staticData.ChannelNames + l.modeNames = staticData.ModeNames + l.RuleNames = staticData.RuleNames + l.LiteralNames = staticData.LiteralNames + l.SymbolicNames = staticData.SymbolicNames + l.GrammarFileName = "Cvlt.g4" + // TODO: l.EOF = antlr.TokenEOF + + return l +} + +// CvltLexer tokens. +const ( + CvltLexerT__0 = 1 + CvltLexerT__1 = 2 + CvltLexerT__2 = 3 + CvltLexerT__3 = 4 + CvltLexerT__4 = 5 + CvltLexerT__5 = 6 + CvltLexerT__6 = 7 + CvltLexerT__7 = 8 + CvltLexerT__8 = 9 + CvltLexerT__9 = 10 + CvltLexerT__10 = 11 + CvltLexerT__11 = 12 + CvltLexerT__12 = 13 + CvltLexerT__13 = 14 + CvltLexerT__14 = 15 + CvltLexerT__15 = 16 + CvltLexerT__16 = 17 + CvltLexerT__17 = 18 + CvltLexerT__18 = 19 + CvltLexerT__19 = 20 + CvltLexerT__20 = 21 + CvltLexerT__21 = 22 + CvltLexerT__22 = 23 + CvltLexerT__23 = 24 + CvltLexerT__24 = 25 + CvltLexerT__25 = 26 + CvltLexerT__26 = 27 + CvltLexerT__27 = 28 + CvltLexerT__28 = 29 + CvltLexerT__29 = 30 + CvltLexerT__30 = 31 + CvltLexerT__31 = 32 + CvltLexerT__32 = 33 + CvltLexerT__33 = 34 + CvltLexerT__34 = 35 + CvltLexerT__35 = 36 + CvltLexerT__36 = 37 + CvltLexerT__37 = 38 + CvltLexerDATE = 39 + CvltLexerDATETIME = 40 + CvltLexerTIME = 41 + CvltLexerIDENTIFIER = 42 + CvltLexerDELIMITEDIDENTIFIER = 43 + CvltLexerQUOTEDIDENTIFIER = 44 + CvltLexerSTRING = 45 + CvltLexerNUMBER = 46 + CvltLexerLONGNUMBER = 47 + CvltLexerWS = 48 + CvltLexerCOMMENT = 49 + CvltLexerLINE_COMMENT = 50 +) diff --git a/internal/embeddata/third_party/cqframework/cvlt/cvlt_parser.go b/internal/embeddata/third_party/cqframework/cvlt/cvlt_parser.go new file mode 100755 index 0000000..017b9bc --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cvlt/cvlt_parser.go @@ -0,0 +1,4782 @@ +// Code generated from Cvlt.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cvlt // Cvlt +import ( + "fmt" + "strconv" + "sync" + + "github.com/antlr4-go/antlr/v4" +) + +// Suppress unused import errors +var _ = fmt.Printf +var _ = strconv.Itoa +var _ = sync.Once{} + +type CvltParser struct { + *antlr.BaseParser +} + +var CvltParserStaticData struct { + once sync.Once + serializedATN []int32 + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func cvltParserInit() { + staticData := &CvltParserStaticData + staticData.LiteralNames = []string{ + "", "'.'", "'List'", "'<'", "'>'", "'Interval'", "'Tuple'", "'{'", "','", + "'}'", "'Choice'", "':'", "'true'", "'false'", "'null'", "'['", "'('", + "']'", "')'", "'display'", "'Code'", "'from'", "'Concept'", "'year'", + "'month'", "'week'", "'day'", "'hour'", "'minute'", "'second'", "'millisecond'", + "'years'", "'months'", "'weeks'", "'days'", "'hours'", "'minutes'", + "'seconds'", "'milliseconds'", + } + staticData.SymbolicNames = []string{ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "DATE", "DATETIME", "TIME", "IDENTIFIER", "DELIMITEDIDENTIFIER", + "QUOTEDIDENTIFIER", "STRING", "NUMBER", "LONGNUMBER", "WS", "COMMENT", + "LINE_COMMENT", + } + staticData.RuleNames = []string{ + "typeSpecifier", "namedTypeSpecifier", "modelIdentifier", "listTypeSpecifier", + "intervalTypeSpecifier", "tupleTypeSpecifier", "tupleElementDefinition", + "choiceTypeSpecifier", "term", "ratio", "literal", "intervalSelector", + "tupleSelector", "tupleElementSelector", "instanceSelector", "instanceElementSelector", + "listSelector", "displayClause", "codeSelector", "conceptSelector", + "identifier", "quantity", "unit", "dateTimePrecision", "pluralDateTimePrecision", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 1, 50, 240, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, + 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, + 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, + 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, + 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 3, 0, 56, 8, 0, 1, 1, 1, 1, 1, 1, 5, 1, 61, 8, 1, 10, 1, 12, + 1, 64, 9, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, + 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 85, 8, 5, 10, + 5, 12, 5, 88, 9, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, + 7, 1, 7, 5, 7, 100, 8, 7, 10, 7, 12, 7, 103, 9, 7, 1, 7, 1, 7, 1, 8, 1, + 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 114, 8, 8, 1, 9, 1, 9, 1, 9, 1, + 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, + 3, 10, 130, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, + 12, 3, 12, 140, 8, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 5, 12, 147, 8, + 12, 10, 12, 12, 12, 150, 9, 12, 3, 12, 152, 8, 12, 1, 12, 1, 12, 1, 13, + 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 5, 14, 166, + 8, 14, 10, 14, 12, 14, 169, 9, 14, 3, 14, 171, 8, 14, 1, 14, 1, 14, 1, + 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 184, + 8, 16, 3, 16, 186, 8, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 192, 8, 16, + 10, 16, 12, 16, 195, 9, 16, 3, 16, 197, 8, 16, 1, 16, 1, 16, 1, 17, 1, + 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 209, 8, 18, 1, 19, + 1, 19, 1, 19, 1, 19, 1, 19, 5, 19, 216, 8, 19, 10, 19, 12, 19, 219, 9, + 19, 1, 19, 1, 19, 3, 19, 223, 8, 19, 1, 20, 1, 20, 1, 21, 1, 21, 3, 21, + 229, 8, 21, 1, 22, 1, 22, 1, 22, 3, 22, 234, 8, 22, 1, 23, 1, 23, 1, 24, + 1, 24, 1, 24, 0, 0, 25, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, + 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 6, 1, 0, 12, 13, 1, + 0, 15, 16, 1, 0, 17, 18, 1, 0, 42, 44, 1, 0, 23, 30, 1, 0, 31, 38, 251, + 0, 55, 1, 0, 0, 0, 2, 62, 1, 0, 0, 0, 4, 67, 1, 0, 0, 0, 6, 69, 1, 0, 0, + 0, 8, 74, 1, 0, 0, 0, 10, 79, 1, 0, 0, 0, 12, 91, 1, 0, 0, 0, 14, 94, 1, + 0, 0, 0, 16, 113, 1, 0, 0, 0, 18, 115, 1, 0, 0, 0, 20, 129, 1, 0, 0, 0, + 22, 131, 1, 0, 0, 0, 24, 139, 1, 0, 0, 0, 26, 155, 1, 0, 0, 0, 28, 159, + 1, 0, 0, 0, 30, 174, 1, 0, 0, 0, 32, 185, 1, 0, 0, 0, 34, 200, 1, 0, 0, + 0, 36, 203, 1, 0, 0, 0, 38, 210, 1, 0, 0, 0, 40, 224, 1, 0, 0, 0, 42, 226, + 1, 0, 0, 0, 44, 233, 1, 0, 0, 0, 46, 235, 1, 0, 0, 0, 48, 237, 1, 0, 0, + 0, 50, 56, 3, 2, 1, 0, 51, 56, 3, 6, 3, 0, 52, 56, 3, 8, 4, 0, 53, 56, + 3, 10, 5, 0, 54, 56, 3, 14, 7, 0, 55, 50, 1, 0, 0, 0, 55, 51, 1, 0, 0, + 0, 55, 52, 1, 0, 0, 0, 55, 53, 1, 0, 0, 0, 55, 54, 1, 0, 0, 0, 56, 1, 1, + 0, 0, 0, 57, 58, 3, 40, 20, 0, 58, 59, 5, 1, 0, 0, 59, 61, 1, 0, 0, 0, + 60, 57, 1, 0, 0, 0, 61, 64, 1, 0, 0, 0, 62, 60, 1, 0, 0, 0, 62, 63, 1, + 0, 0, 0, 63, 65, 1, 0, 0, 0, 64, 62, 1, 0, 0, 0, 65, 66, 3, 40, 20, 0, + 66, 3, 1, 0, 0, 0, 67, 68, 3, 40, 20, 0, 68, 5, 1, 0, 0, 0, 69, 70, 5, + 2, 0, 0, 70, 71, 5, 3, 0, 0, 71, 72, 3, 0, 0, 0, 72, 73, 5, 4, 0, 0, 73, + 7, 1, 0, 0, 0, 74, 75, 5, 5, 0, 0, 75, 76, 5, 3, 0, 0, 76, 77, 3, 0, 0, + 0, 77, 78, 5, 4, 0, 0, 78, 9, 1, 0, 0, 0, 79, 80, 5, 6, 0, 0, 80, 81, 5, + 7, 0, 0, 81, 86, 3, 12, 6, 0, 82, 83, 5, 8, 0, 0, 83, 85, 3, 12, 6, 0, + 84, 82, 1, 0, 0, 0, 85, 88, 1, 0, 0, 0, 86, 84, 1, 0, 0, 0, 86, 87, 1, + 0, 0, 0, 87, 89, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, 90, 5, 9, 0, 0, 90, + 11, 1, 0, 0, 0, 91, 92, 3, 40, 20, 0, 92, 93, 3, 0, 0, 0, 93, 13, 1, 0, + 0, 0, 94, 95, 5, 10, 0, 0, 95, 96, 5, 3, 0, 0, 96, 101, 3, 0, 0, 0, 97, + 98, 5, 8, 0, 0, 98, 100, 3, 0, 0, 0, 99, 97, 1, 0, 0, 0, 100, 103, 1, 0, + 0, 0, 101, 99, 1, 0, 0, 0, 101, 102, 1, 0, 0, 0, 102, 104, 1, 0, 0, 0, + 103, 101, 1, 0, 0, 0, 104, 105, 5, 4, 0, 0, 105, 15, 1, 0, 0, 0, 106, 114, + 3, 20, 10, 0, 107, 114, 3, 22, 11, 0, 108, 114, 3, 24, 12, 0, 109, 114, + 3, 28, 14, 0, 110, 114, 3, 32, 16, 0, 111, 114, 3, 36, 18, 0, 112, 114, + 3, 38, 19, 0, 113, 106, 1, 0, 0, 0, 113, 107, 1, 0, 0, 0, 113, 108, 1, + 0, 0, 0, 113, 109, 1, 0, 0, 0, 113, 110, 1, 0, 0, 0, 113, 111, 1, 0, 0, + 0, 113, 112, 1, 0, 0, 0, 114, 17, 1, 0, 0, 0, 115, 116, 3, 42, 21, 0, 116, + 117, 5, 11, 0, 0, 117, 118, 3, 42, 21, 0, 118, 19, 1, 0, 0, 0, 119, 130, + 7, 0, 0, 0, 120, 130, 5, 14, 0, 0, 121, 130, 5, 45, 0, 0, 122, 130, 5, + 46, 0, 0, 123, 130, 5, 47, 0, 0, 124, 130, 5, 40, 0, 0, 125, 130, 5, 39, + 0, 0, 126, 130, 5, 41, 0, 0, 127, 130, 3, 42, 21, 0, 128, 130, 3, 18, 9, + 0, 129, 119, 1, 0, 0, 0, 129, 120, 1, 0, 0, 0, 129, 121, 1, 0, 0, 0, 129, + 122, 1, 0, 0, 0, 129, 123, 1, 0, 0, 0, 129, 124, 1, 0, 0, 0, 129, 125, + 1, 0, 0, 0, 129, 126, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 129, 128, 1, 0, + 0, 0, 130, 21, 1, 0, 0, 0, 131, 132, 5, 5, 0, 0, 132, 133, 7, 1, 0, 0, + 133, 134, 3, 20, 10, 0, 134, 135, 5, 8, 0, 0, 135, 136, 3, 20, 10, 0, 136, + 137, 7, 2, 0, 0, 137, 23, 1, 0, 0, 0, 138, 140, 5, 6, 0, 0, 139, 138, 1, + 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 141, 1, 0, 0, 0, 141, 151, 5, 7, 0, + 0, 142, 152, 5, 11, 0, 0, 143, 148, 3, 26, 13, 0, 144, 145, 5, 8, 0, 0, + 145, 147, 3, 26, 13, 0, 146, 144, 1, 0, 0, 0, 147, 150, 1, 0, 0, 0, 148, + 146, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, 152, 1, 0, 0, 0, 150, 148, + 1, 0, 0, 0, 151, 142, 1, 0, 0, 0, 151, 143, 1, 0, 0, 0, 152, 153, 1, 0, + 0, 0, 153, 154, 5, 9, 0, 0, 154, 25, 1, 0, 0, 0, 155, 156, 3, 40, 20, 0, + 156, 157, 5, 11, 0, 0, 157, 158, 3, 16, 8, 0, 158, 27, 1, 0, 0, 0, 159, + 160, 3, 40, 20, 0, 160, 170, 5, 7, 0, 0, 161, 171, 5, 11, 0, 0, 162, 167, + 3, 30, 15, 0, 163, 164, 5, 8, 0, 0, 164, 166, 3, 30, 15, 0, 165, 163, 1, + 0, 0, 0, 166, 169, 1, 0, 0, 0, 167, 165, 1, 0, 0, 0, 167, 168, 1, 0, 0, + 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 170, 161, 1, 0, 0, 0, 170, + 162, 1, 0, 0, 0, 171, 172, 1, 0, 0, 0, 172, 173, 5, 9, 0, 0, 173, 29, 1, + 0, 0, 0, 174, 175, 3, 40, 20, 0, 175, 176, 5, 11, 0, 0, 176, 177, 3, 16, + 8, 0, 177, 31, 1, 0, 0, 0, 178, 183, 5, 2, 0, 0, 179, 180, 5, 3, 0, 0, + 180, 181, 3, 0, 0, 0, 181, 182, 5, 4, 0, 0, 182, 184, 1, 0, 0, 0, 183, + 179, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 186, 1, 0, 0, 0, 185, 178, + 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 196, 5, 7, + 0, 0, 188, 193, 3, 16, 8, 0, 189, 190, 5, 8, 0, 0, 190, 192, 3, 16, 8, + 0, 191, 189, 1, 0, 0, 0, 192, 195, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 193, + 194, 1, 0, 0, 0, 194, 197, 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 196, 188, + 1, 0, 0, 0, 196, 197, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 199, 5, 9, + 0, 0, 199, 33, 1, 0, 0, 0, 200, 201, 5, 19, 0, 0, 201, 202, 5, 45, 0, 0, + 202, 35, 1, 0, 0, 0, 203, 204, 5, 20, 0, 0, 204, 205, 5, 45, 0, 0, 205, + 206, 5, 21, 0, 0, 206, 208, 3, 40, 20, 0, 207, 209, 3, 34, 17, 0, 208, + 207, 1, 0, 0, 0, 208, 209, 1, 0, 0, 0, 209, 37, 1, 0, 0, 0, 210, 211, 5, + 22, 0, 0, 211, 212, 5, 7, 0, 0, 212, 217, 3, 36, 18, 0, 213, 214, 5, 8, + 0, 0, 214, 216, 3, 36, 18, 0, 215, 213, 1, 0, 0, 0, 216, 219, 1, 0, 0, + 0, 217, 215, 1, 0, 0, 0, 217, 218, 1, 0, 0, 0, 218, 220, 1, 0, 0, 0, 219, + 217, 1, 0, 0, 0, 220, 222, 5, 9, 0, 0, 221, 223, 3, 34, 17, 0, 222, 221, + 1, 0, 0, 0, 222, 223, 1, 0, 0, 0, 223, 39, 1, 0, 0, 0, 224, 225, 7, 3, + 0, 0, 225, 41, 1, 0, 0, 0, 226, 228, 5, 46, 0, 0, 227, 229, 3, 44, 22, + 0, 228, 227, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 43, 1, 0, 0, 0, 230, + 234, 3, 46, 23, 0, 231, 234, 3, 48, 24, 0, 232, 234, 5, 45, 0, 0, 233, + 230, 1, 0, 0, 0, 233, 231, 1, 0, 0, 0, 233, 232, 1, 0, 0, 0, 234, 45, 1, + 0, 0, 0, 235, 236, 7, 4, 0, 0, 236, 47, 1, 0, 0, 0, 237, 238, 7, 5, 0, + 0, 238, 49, 1, 0, 0, 0, 20, 55, 62, 86, 101, 113, 129, 139, 148, 151, 167, + 170, 183, 185, 193, 196, 208, 217, 222, 228, 233, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// CvltParserInit initializes any static state used to implement CvltParser. By default the +// static state used to implement the parser is lazily initialized during the first call to +// NewCvltParser(). You can call this function if you wish to initialize the static state ahead +// of time. +func CvltParserInit() { + staticData := &CvltParserStaticData + staticData.once.Do(cvltParserInit) +} + +// NewCvltParser produces a new parser instance for the optional input antlr.TokenStream. +func NewCvltParser(input antlr.TokenStream) *CvltParser { + CvltParserInit() + this := new(CvltParser) + this.BaseParser = antlr.NewBaseParser(input) + staticData := &CvltParserStaticData + this.Interpreter = antlr.NewParserATNSimulator(this, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + this.RuleNames = staticData.RuleNames + this.LiteralNames = staticData.LiteralNames + this.SymbolicNames = staticData.SymbolicNames + this.GrammarFileName = "Cvlt.g4" + + return this +} + +// CvltParser tokens. +const ( + CvltParserEOF = antlr.TokenEOF + CvltParserT__0 = 1 + CvltParserT__1 = 2 + CvltParserT__2 = 3 + CvltParserT__3 = 4 + CvltParserT__4 = 5 + CvltParserT__5 = 6 + CvltParserT__6 = 7 + CvltParserT__7 = 8 + CvltParserT__8 = 9 + CvltParserT__9 = 10 + CvltParserT__10 = 11 + CvltParserT__11 = 12 + CvltParserT__12 = 13 + CvltParserT__13 = 14 + CvltParserT__14 = 15 + CvltParserT__15 = 16 + CvltParserT__16 = 17 + CvltParserT__17 = 18 + CvltParserT__18 = 19 + CvltParserT__19 = 20 + CvltParserT__20 = 21 + CvltParserT__21 = 22 + CvltParserT__22 = 23 + CvltParserT__23 = 24 + CvltParserT__24 = 25 + CvltParserT__25 = 26 + CvltParserT__26 = 27 + CvltParserT__27 = 28 + CvltParserT__28 = 29 + CvltParserT__29 = 30 + CvltParserT__30 = 31 + CvltParserT__31 = 32 + CvltParserT__32 = 33 + CvltParserT__33 = 34 + CvltParserT__34 = 35 + CvltParserT__35 = 36 + CvltParserT__36 = 37 + CvltParserT__37 = 38 + CvltParserDATE = 39 + CvltParserDATETIME = 40 + CvltParserTIME = 41 + CvltParserIDENTIFIER = 42 + CvltParserDELIMITEDIDENTIFIER = 43 + CvltParserQUOTEDIDENTIFIER = 44 + CvltParserSTRING = 45 + CvltParserNUMBER = 46 + CvltParserLONGNUMBER = 47 + CvltParserWS = 48 + CvltParserCOMMENT = 49 + CvltParserLINE_COMMENT = 50 +) + +// CvltParser rules. +const ( + CvltParserRULE_typeSpecifier = 0 + CvltParserRULE_namedTypeSpecifier = 1 + CvltParserRULE_modelIdentifier = 2 + CvltParserRULE_listTypeSpecifier = 3 + CvltParserRULE_intervalTypeSpecifier = 4 + CvltParserRULE_tupleTypeSpecifier = 5 + CvltParserRULE_tupleElementDefinition = 6 + CvltParserRULE_choiceTypeSpecifier = 7 + CvltParserRULE_term = 8 + CvltParserRULE_ratio = 9 + CvltParserRULE_literal = 10 + CvltParserRULE_intervalSelector = 11 + CvltParserRULE_tupleSelector = 12 + CvltParserRULE_tupleElementSelector = 13 + CvltParserRULE_instanceSelector = 14 + CvltParserRULE_instanceElementSelector = 15 + CvltParserRULE_listSelector = 16 + CvltParserRULE_displayClause = 17 + CvltParserRULE_codeSelector = 18 + CvltParserRULE_conceptSelector = 19 + CvltParserRULE_identifier = 20 + CvltParserRULE_quantity = 21 + CvltParserRULE_unit = 22 + CvltParserRULE_dateTimePrecision = 23 + CvltParserRULE_pluralDateTimePrecision = 24 +) + +// ITypeSpecifierContext is an interface to support dynamic dispatch. +type ITypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + NamedTypeSpecifier() INamedTypeSpecifierContext + ListTypeSpecifier() IListTypeSpecifierContext + IntervalTypeSpecifier() IIntervalTypeSpecifierContext + TupleTypeSpecifier() ITupleTypeSpecifierContext + ChoiceTypeSpecifier() IChoiceTypeSpecifierContext + + // IsTypeSpecifierContext differentiates from other interfaces. + IsTypeSpecifierContext() +} + +type TypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTypeSpecifierContext() *TypeSpecifierContext { + var p = new(TypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_typeSpecifier + return p +} + +func InitEmptyTypeSpecifierContext(p *TypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_typeSpecifier +} + +func (*TypeSpecifierContext) IsTypeSpecifierContext() {} + +func NewTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeSpecifierContext { + var p = new(TypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_typeSpecifier + + return p +} + +func (s *TypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *TypeSpecifierContext) NamedTypeSpecifier() INamedTypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(INamedTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(INamedTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) ListTypeSpecifier() IListTypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IListTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IListTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) IntervalTypeSpecifier() IIntervalTypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntervalTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIntervalTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) TupleTypeSpecifier() ITupleTypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITupleTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) ChoiceTypeSpecifier() IChoiceTypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IChoiceTypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IChoiceTypeSpecifierContext) +} + +func (s *TypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *TypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) TypeSpecifier() (localctx ITypeSpecifierContext) { + localctx = NewTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 0, CvltParserRULE_typeSpecifier) + p.SetState(55) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CvltParserIDENTIFIER, CvltParserDELIMITEDIDENTIFIER, CvltParserQUOTEDIDENTIFIER: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(50) + p.NamedTypeSpecifier() + } + + case CvltParserT__1: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(51) + p.ListTypeSpecifier() + } + + case CvltParserT__4: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(52) + p.IntervalTypeSpecifier() + } + + case CvltParserT__5: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(53) + p.TupleTypeSpecifier() + } + + case CvltParserT__9: + p.EnterOuterAlt(localctx, 5) + { + p.SetState(54) + p.ChoiceTypeSpecifier() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// INamedTypeSpecifierContext is an interface to support dynamic dispatch. +type INamedTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllIdentifier() []IIdentifierContext + Identifier(i int) IIdentifierContext + + // IsNamedTypeSpecifierContext differentiates from other interfaces. + IsNamedTypeSpecifierContext() +} + +type NamedTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyNamedTypeSpecifierContext() *NamedTypeSpecifierContext { + var p = new(NamedTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_namedTypeSpecifier + return p +} + +func InitEmptyNamedTypeSpecifierContext(p *NamedTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_namedTypeSpecifier +} + +func (*NamedTypeSpecifierContext) IsNamedTypeSpecifierContext() {} + +func NewNamedTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *NamedTypeSpecifierContext { + var p = new(NamedTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_namedTypeSpecifier + + return p +} + +func (s *NamedTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *NamedTypeSpecifierContext) AllIdentifier() []IIdentifierContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IIdentifierContext); ok { + len++ + } + } + + tst := make([]IIdentifierContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IIdentifierContext); ok { + tst[i] = t.(IIdentifierContext) + i++ + } + } + + return tst +} + +func (s *NamedTypeSpecifierContext) Identifier(i int) IIdentifierContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *NamedTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *NamedTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *NamedTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitNamedTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) NamedTypeSpecifier() (localctx INamedTypeSpecifierContext) { + localctx = NewNamedTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 2, CvltParserRULE_namedTypeSpecifier) + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(62) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 1, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(57) + p.Identifier() + } + { + p.SetState(58) + p.Match(CvltParserT__0) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(64) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 1, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + { + p.SetState(65) + p.Identifier() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IModelIdentifierContext is an interface to support dynamic dispatch. +type IModelIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + + // IsModelIdentifierContext differentiates from other interfaces. + IsModelIdentifierContext() +} + +type ModelIdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyModelIdentifierContext() *ModelIdentifierContext { + var p = new(ModelIdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_modelIdentifier + return p +} + +func InitEmptyModelIdentifierContext(p *ModelIdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_modelIdentifier +} + +func (*ModelIdentifierContext) IsModelIdentifierContext() {} + +func NewModelIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ModelIdentifierContext { + var p = new(ModelIdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_modelIdentifier + + return p +} + +func (s *ModelIdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ModelIdentifierContext) Identifier() IIdentifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *ModelIdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ModelIdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ModelIdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitModelIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) ModelIdentifier() (localctx IModelIdentifierContext) { + localctx = NewModelIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, CvltParserRULE_modelIdentifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(67) + p.Identifier() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IListTypeSpecifierContext is an interface to support dynamic dispatch. +type IListTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + TypeSpecifier() ITypeSpecifierContext + + // IsListTypeSpecifierContext differentiates from other interfaces. + IsListTypeSpecifierContext() +} + +type ListTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyListTypeSpecifierContext() *ListTypeSpecifierContext { + var p = new(ListTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_listTypeSpecifier + return p +} + +func InitEmptyListTypeSpecifierContext(p *ListTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_listTypeSpecifier +} + +func (*ListTypeSpecifierContext) IsListTypeSpecifierContext() {} + +func NewListTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ListTypeSpecifierContext { + var p = new(ListTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_listTypeSpecifier + + return p +} + +func (s *ListTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ListTypeSpecifierContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ListTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ListTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ListTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitListTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) ListTypeSpecifier() (localctx IListTypeSpecifierContext) { + localctx = NewListTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 6, CvltParserRULE_listTypeSpecifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(69) + p.Match(CvltParserT__1) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(70) + p.Match(CvltParserT__2) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(71) + p.TypeSpecifier() + } + { + p.SetState(72) + p.Match(CvltParserT__3) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IIntervalTypeSpecifierContext is an interface to support dynamic dispatch. +type IIntervalTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + TypeSpecifier() ITypeSpecifierContext + + // IsIntervalTypeSpecifierContext differentiates from other interfaces. + IsIntervalTypeSpecifierContext() +} + +type IntervalTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIntervalTypeSpecifierContext() *IntervalTypeSpecifierContext { + var p = new(IntervalTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_intervalTypeSpecifier + return p +} + +func InitEmptyIntervalTypeSpecifierContext(p *IntervalTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_intervalTypeSpecifier +} + +func (*IntervalTypeSpecifierContext) IsIntervalTypeSpecifierContext() {} + +func NewIntervalTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IntervalTypeSpecifierContext { + var p = new(IntervalTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_intervalTypeSpecifier + + return p +} + +func (s *IntervalTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *IntervalTypeSpecifierContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *IntervalTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntervalTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *IntervalTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitIntervalTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) IntervalTypeSpecifier() (localctx IIntervalTypeSpecifierContext) { + localctx = NewIntervalTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 8, CvltParserRULE_intervalTypeSpecifier) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(74) + p.Match(CvltParserT__4) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(75) + p.Match(CvltParserT__2) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(76) + p.TypeSpecifier() + } + { + p.SetState(77) + p.Match(CvltParserT__3) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ITupleTypeSpecifierContext is an interface to support dynamic dispatch. +type ITupleTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllTupleElementDefinition() []ITupleElementDefinitionContext + TupleElementDefinition(i int) ITupleElementDefinitionContext + + // IsTupleTypeSpecifierContext differentiates from other interfaces. + IsTupleTypeSpecifierContext() +} + +type TupleTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleTypeSpecifierContext() *TupleTypeSpecifierContext { + var p = new(TupleTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleTypeSpecifier + return p +} + +func InitEmptyTupleTypeSpecifierContext(p *TupleTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleTypeSpecifier +} + +func (*TupleTypeSpecifierContext) IsTupleTypeSpecifierContext() {} + +func NewTupleTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleTypeSpecifierContext { + var p = new(TupleTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_tupleTypeSpecifier + + return p +} + +func (s *TupleTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleTypeSpecifierContext) AllTupleElementDefinition() []ITupleElementDefinitionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITupleElementDefinitionContext); ok { + len++ + } + } + + tst := make([]ITupleElementDefinitionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITupleElementDefinitionContext); ok { + tst[i] = t.(ITupleElementDefinitionContext) + i++ + } + } + + return tst +} + +func (s *TupleTypeSpecifierContext) TupleElementDefinition(i int) ITupleElementDefinitionContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleElementDefinitionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITupleElementDefinitionContext) +} + +func (s *TupleTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *TupleTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitTupleTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) TupleTypeSpecifier() (localctx ITupleTypeSpecifierContext) { + localctx = NewTupleTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 10, CvltParserRULE_tupleTypeSpecifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(79) + p.Match(CvltParserT__5) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(80) + p.Match(CvltParserT__6) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(81) + p.TupleElementDefinition() + } + p.SetState(86) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == CvltParserT__7 { + { + p.SetState(82) + p.Match(CvltParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(83) + p.TupleElementDefinition() + } + + p.SetState(88) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(89) + p.Match(CvltParserT__8) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ITupleElementDefinitionContext is an interface to support dynamic dispatch. +type ITupleElementDefinitionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + TypeSpecifier() ITypeSpecifierContext + + // IsTupleElementDefinitionContext differentiates from other interfaces. + IsTupleElementDefinitionContext() +} + +type TupleElementDefinitionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleElementDefinitionContext() *TupleElementDefinitionContext { + var p = new(TupleElementDefinitionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleElementDefinition + return p +} + +func InitEmptyTupleElementDefinitionContext(p *TupleElementDefinitionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleElementDefinition +} + +func (*TupleElementDefinitionContext) IsTupleElementDefinitionContext() {} + +func NewTupleElementDefinitionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleElementDefinitionContext { + var p = new(TupleElementDefinitionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_tupleElementDefinition + + return p +} + +func (s *TupleElementDefinitionContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleElementDefinitionContext) Identifier() IIdentifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *TupleElementDefinitionContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *TupleElementDefinitionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleElementDefinitionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *TupleElementDefinitionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitTupleElementDefinition(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) TupleElementDefinition() (localctx ITupleElementDefinitionContext) { + localctx = NewTupleElementDefinitionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 12, CvltParserRULE_tupleElementDefinition) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(91) + p.Identifier() + } + { + p.SetState(92) + p.TypeSpecifier() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IChoiceTypeSpecifierContext is an interface to support dynamic dispatch. +type IChoiceTypeSpecifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllTypeSpecifier() []ITypeSpecifierContext + TypeSpecifier(i int) ITypeSpecifierContext + + // IsChoiceTypeSpecifierContext differentiates from other interfaces. + IsChoiceTypeSpecifierContext() +} + +type ChoiceTypeSpecifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyChoiceTypeSpecifierContext() *ChoiceTypeSpecifierContext { + var p = new(ChoiceTypeSpecifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_choiceTypeSpecifier + return p +} + +func InitEmptyChoiceTypeSpecifierContext(p *ChoiceTypeSpecifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_choiceTypeSpecifier +} + +func (*ChoiceTypeSpecifierContext) IsChoiceTypeSpecifierContext() {} + +func NewChoiceTypeSpecifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ChoiceTypeSpecifierContext { + var p = new(ChoiceTypeSpecifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_choiceTypeSpecifier + + return p +} + +func (s *ChoiceTypeSpecifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *ChoiceTypeSpecifierContext) AllTypeSpecifier() []ITypeSpecifierContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITypeSpecifierContext); ok { + len++ + } + } + + tst := make([]ITypeSpecifierContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITypeSpecifierContext); ok { + tst[i] = t.(ITypeSpecifierContext) + i++ + } + } + + return tst +} + +func (s *ChoiceTypeSpecifierContext) TypeSpecifier(i int) ITypeSpecifierContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ChoiceTypeSpecifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ChoiceTypeSpecifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ChoiceTypeSpecifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitChoiceTypeSpecifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) ChoiceTypeSpecifier() (localctx IChoiceTypeSpecifierContext) { + localctx = NewChoiceTypeSpecifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 14, CvltParserRULE_choiceTypeSpecifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(94) + p.Match(CvltParserT__9) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(95) + p.Match(CvltParserT__2) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(96) + p.TypeSpecifier() + } + p.SetState(101) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == CvltParserT__7 { + { + p.SetState(97) + p.Match(CvltParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(98) + p.TypeSpecifier() + } + + p.SetState(103) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(104) + p.Match(CvltParserT__3) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ITermContext is an interface to support dynamic dispatch. +type ITermContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsTermContext differentiates from other interfaces. + IsTermContext() +} + +type TermContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTermContext() *TermContext { + var p = new(TermContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_term + return p +} + +func InitEmptyTermContext(p *TermContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_term +} + +func (*TermContext) IsTermContext() {} + +func NewTermContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TermContext { + var p = new(TermContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_term + + return p +} + +func (s *TermContext) GetParser() antlr.Parser { return s.parser } + +func (s *TermContext) CopyAll(ctx *TermContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *TermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TermContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +type TupleSelectorTermContext struct { + TermContext +} + +func NewTupleSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TupleSelectorTermContext { + var p = new(TupleSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *TupleSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleSelectorTermContext) TupleSelector() ITupleSelectorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleSelectorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITupleSelectorContext) +} + +func (s *TupleSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitTupleSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + +type LiteralTermContext struct { + TermContext +} + +func NewLiteralTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LiteralTermContext { + var p = new(LiteralTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *LiteralTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LiteralTermContext) Literal() ILiteralContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILiteralContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ILiteralContext) +} + +func (s *LiteralTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitLiteralTerm(s) + + default: + return t.VisitChildren(s) + } +} + +type ConceptSelectorTermContext struct { + TermContext +} + +func NewConceptSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ConceptSelectorTermContext { + var p = new(ConceptSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *ConceptSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConceptSelectorTermContext) ConceptSelector() IConceptSelectorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IConceptSelectorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IConceptSelectorContext) +} + +func (s *ConceptSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitConceptSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + +type CodeSelectorTermContext struct { + TermContext +} + +func NewCodeSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *CodeSelectorTermContext { + var p = new(CodeSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *CodeSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeSelectorTermContext) CodeSelector() ICodeSelectorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeSelectorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ICodeSelectorContext) +} + +func (s *CodeSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitCodeSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + +type InstanceSelectorTermContext struct { + TermContext +} + +func NewInstanceSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *InstanceSelectorTermContext { + var p = new(InstanceSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *InstanceSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InstanceSelectorTermContext) InstanceSelector() IInstanceSelectorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IInstanceSelectorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IInstanceSelectorContext) +} + +func (s *InstanceSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitInstanceSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + +type IntervalSelectorTermContext struct { + TermContext +} + +func NewIntervalSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *IntervalSelectorTermContext { + var p = new(IntervalSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *IntervalSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntervalSelectorTermContext) IntervalSelector() IIntervalSelectorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntervalSelectorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIntervalSelectorContext) +} + +func (s *IntervalSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitIntervalSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + +type ListSelectorTermContext struct { + TermContext +} + +func NewListSelectorTermContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ListSelectorTermContext { + var p = new(ListSelectorTermContext) + + InitEmptyTermContext(&p.TermContext) + p.parser = parser + p.CopyAll(ctx.(*TermContext)) + + return p +} + +func (s *ListSelectorTermContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ListSelectorTermContext) ListSelector() IListSelectorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IListSelectorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IListSelectorContext) +} + +func (s *ListSelectorTermContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitListSelectorTerm(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) Term() (localctx ITermContext) { + localctx = NewTermContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 16, CvltParserRULE_term) + p.SetState(113) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 4, p.GetParserRuleContext()) { + case 1: + localctx = NewLiteralTermContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(106) + p.Literal() + } + + case 2: + localctx = NewIntervalSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + { + p.SetState(107) + p.IntervalSelector() + } + + case 3: + localctx = NewTupleSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 3) + { + p.SetState(108) + p.TupleSelector() + } + + case 4: + localctx = NewInstanceSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 4) + { + p.SetState(109) + p.InstanceSelector() + } + + case 5: + localctx = NewListSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 5) + { + p.SetState(110) + p.ListSelector() + } + + case 6: + localctx = NewCodeSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 6) + { + p.SetState(111) + p.CodeSelector() + } + + case 7: + localctx = NewConceptSelectorTermContext(p, localctx) + p.EnterOuterAlt(localctx, 7) + { + p.SetState(112) + p.ConceptSelector() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IRatioContext is an interface to support dynamic dispatch. +type IRatioContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllQuantity() []IQuantityContext + Quantity(i int) IQuantityContext + + // IsRatioContext differentiates from other interfaces. + IsRatioContext() +} + +type RatioContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRatioContext() *RatioContext { + var p = new(RatioContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_ratio + return p +} + +func InitEmptyRatioContext(p *RatioContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_ratio +} + +func (*RatioContext) IsRatioContext() {} + +func NewRatioContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RatioContext { + var p = new(RatioContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_ratio + + return p +} + +func (s *RatioContext) GetParser() antlr.Parser { return s.parser } + +func (s *RatioContext) AllQuantity() []IQuantityContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IQuantityContext); ok { + len++ + } + } + + tst := make([]IQuantityContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IQuantityContext); ok { + tst[i] = t.(IQuantityContext) + i++ + } + } + + return tst +} + +func (s *RatioContext) Quantity(i int) IQuantityContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IQuantityContext) +} + +func (s *RatioContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RatioContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *RatioContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitRatio(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) Ratio() (localctx IRatioContext) { + localctx = NewRatioContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 18, CvltParserRULE_ratio) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(115) + p.Quantity() + } + { + p.SetState(116) + p.Match(CvltParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(117) + p.Quantity() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ILiteralContext is an interface to support dynamic dispatch. +type ILiteralContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsLiteralContext differentiates from other interfaces. + IsLiteralContext() +} + +type LiteralContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLiteralContext() *LiteralContext { + var p = new(LiteralContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_literal + return p +} + +func InitEmptyLiteralContext(p *LiteralContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_literal +} + +func (*LiteralContext) IsLiteralContext() {} + +func NewLiteralContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LiteralContext { + var p = new(LiteralContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_literal + + return p +} + +func (s *LiteralContext) GetParser() antlr.Parser { return s.parser } + +func (s *LiteralContext) CopyAll(ctx *LiteralContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *LiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LiteralContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +type TimeLiteralContext struct { + LiteralContext +} + +func NewTimeLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *TimeLiteralContext { + var p = new(TimeLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *TimeLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TimeLiteralContext) TIME() antlr.TerminalNode { + return s.GetToken(CvltParserTIME, 0) +} + +func (s *TimeLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitTimeLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type NullLiteralContext struct { + LiteralContext +} + +func NewNullLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *NullLiteralContext { + var p = new(NullLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *NullLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *NullLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitNullLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type RatioLiteralContext struct { + LiteralContext +} + +func NewRatioLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *RatioLiteralContext { + var p = new(RatioLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *RatioLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RatioLiteralContext) Ratio() IRatioContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRatioContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IRatioContext) +} + +func (s *RatioLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitRatioLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type DateTimeLiteralContext struct { + LiteralContext +} + +func NewDateTimeLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DateTimeLiteralContext { + var p = new(DateTimeLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *DateTimeLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateTimeLiteralContext) DATETIME() antlr.TerminalNode { + return s.GetToken(CvltParserDATETIME, 0) +} + +func (s *DateTimeLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitDateTimeLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type StringLiteralContext struct { + LiteralContext +} + +func NewStringLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *StringLiteralContext { + var p = new(StringLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *StringLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StringLiteralContext) STRING() antlr.TerminalNode { + return s.GetToken(CvltParserSTRING, 0) +} + +func (s *StringLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitStringLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type DateLiteralContext struct { + LiteralContext +} + +func NewDateLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *DateLiteralContext { + var p = new(DateLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *DateLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateLiteralContext) DATE() antlr.TerminalNode { + return s.GetToken(CvltParserDATE, 0) +} + +func (s *DateLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitDateLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type BooleanLiteralContext struct { + LiteralContext +} + +func NewBooleanLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *BooleanLiteralContext { + var p = new(BooleanLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *BooleanLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *BooleanLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitBooleanLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type NumberLiteralContext struct { + LiteralContext +} + +func NewNumberLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *NumberLiteralContext { + var p = new(NumberLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *NumberLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *NumberLiteralContext) NUMBER() antlr.TerminalNode { + return s.GetToken(CvltParserNUMBER, 0) +} + +func (s *NumberLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitNumberLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type LongNumberLiteralContext struct { + LiteralContext +} + +func NewLongNumberLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LongNumberLiteralContext { + var p = new(LongNumberLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *LongNumberLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LongNumberLiteralContext) LONGNUMBER() antlr.TerminalNode { + return s.GetToken(CvltParserLONGNUMBER, 0) +} + +func (s *LongNumberLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitLongNumberLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +type QuantityLiteralContext struct { + LiteralContext +} + +func NewQuantityLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *QuantityLiteralContext { + var p = new(QuantityLiteralContext) + + InitEmptyLiteralContext(&p.LiteralContext) + p.parser = parser + p.CopyAll(ctx.(*LiteralContext)) + + return p +} + +func (s *QuantityLiteralContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QuantityLiteralContext) Quantity() IQuantityContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQuantityContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IQuantityContext) +} + +func (s *QuantityLiteralContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitQuantityLiteral(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) Literal() (localctx ILiteralContext) { + localctx = NewLiteralContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 20, CvltParserRULE_literal) + var _la int + + p.SetState(129) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 5, p.GetParserRuleContext()) { + case 1: + localctx = NewBooleanLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(119) + _la = p.GetTokenStream().LA(1) + + if !(_la == CvltParserT__11 || _la == CvltParserT__12) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + case 2: + localctx = NewNullLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 2) + { + p.SetState(120) + p.Match(CvltParserT__13) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 3: + localctx = NewStringLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 3) + { + p.SetState(121) + p.Match(CvltParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 4: + localctx = NewNumberLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 4) + { + p.SetState(122) + p.Match(CvltParserNUMBER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 5: + localctx = NewLongNumberLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 5) + { + p.SetState(123) + p.Match(CvltParserLONGNUMBER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 6: + localctx = NewDateTimeLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 6) + { + p.SetState(124) + p.Match(CvltParserDATETIME) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 7: + localctx = NewDateLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 7) + { + p.SetState(125) + p.Match(CvltParserDATE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 8: + localctx = NewTimeLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 8) + { + p.SetState(126) + p.Match(CvltParserTIME) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 9: + localctx = NewQuantityLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 9) + { + p.SetState(127) + p.Quantity() + } + + case 10: + localctx = NewRatioLiteralContext(p, localctx) + p.EnterOuterAlt(localctx, 10) + { + p.SetState(128) + p.Ratio() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IIntervalSelectorContext is an interface to support dynamic dispatch. +type IIntervalSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllLiteral() []ILiteralContext + Literal(i int) ILiteralContext + + // IsIntervalSelectorContext differentiates from other interfaces. + IsIntervalSelectorContext() +} + +type IntervalSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIntervalSelectorContext() *IntervalSelectorContext { + var p = new(IntervalSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_intervalSelector + return p +} + +func InitEmptyIntervalSelectorContext(p *IntervalSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_intervalSelector +} + +func (*IntervalSelectorContext) IsIntervalSelectorContext() {} + +func NewIntervalSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IntervalSelectorContext { + var p = new(IntervalSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_intervalSelector + + return p +} + +func (s *IntervalSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *IntervalSelectorContext) AllLiteral() []ILiteralContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ILiteralContext); ok { + len++ + } + } + + tst := make([]ILiteralContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ILiteralContext); ok { + tst[i] = t.(ILiteralContext) + i++ + } + } + + return tst +} + +func (s *IntervalSelectorContext) Literal(i int) ILiteralContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILiteralContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ILiteralContext) +} + +func (s *IntervalSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntervalSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *IntervalSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitIntervalSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) IntervalSelector() (localctx IIntervalSelectorContext) { + localctx = NewIntervalSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 22, CvltParserRULE_intervalSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(131) + p.Match(CvltParserT__4) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(132) + _la = p.GetTokenStream().LA(1) + + if !(_la == CvltParserT__14 || _la == CvltParserT__15) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(133) + p.Literal() + } + { + p.SetState(134) + p.Match(CvltParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(135) + p.Literal() + } + { + p.SetState(136) + _la = p.GetTokenStream().LA(1) + + if !(_la == CvltParserT__16 || _la == CvltParserT__17) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ITupleSelectorContext is an interface to support dynamic dispatch. +type ITupleSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllTupleElementSelector() []ITupleElementSelectorContext + TupleElementSelector(i int) ITupleElementSelectorContext + + // IsTupleSelectorContext differentiates from other interfaces. + IsTupleSelectorContext() +} + +type TupleSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleSelectorContext() *TupleSelectorContext { + var p = new(TupleSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleSelector + return p +} + +func InitEmptyTupleSelectorContext(p *TupleSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleSelector +} + +func (*TupleSelectorContext) IsTupleSelectorContext() {} + +func NewTupleSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleSelectorContext { + var p = new(TupleSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_tupleSelector + + return p +} + +func (s *TupleSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleSelectorContext) AllTupleElementSelector() []ITupleElementSelectorContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITupleElementSelectorContext); ok { + len++ + } + } + + tst := make([]ITupleElementSelectorContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITupleElementSelectorContext); ok { + tst[i] = t.(ITupleElementSelectorContext) + i++ + } + } + + return tst +} + +func (s *TupleSelectorContext) TupleElementSelector(i int) ITupleElementSelectorContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITupleElementSelectorContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITupleElementSelectorContext) +} + +func (s *TupleSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *TupleSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitTupleSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) TupleSelector() (localctx ITupleSelectorContext) { + localctx = NewTupleSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 24, CvltParserRULE_tupleSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(139) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == CvltParserT__5 { + { + p.SetState(138) + p.Match(CvltParserT__5) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(141) + p.Match(CvltParserT__6) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(151) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CvltParserT__10: + { + p.SetState(142) + p.Match(CvltParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case CvltParserIDENTIFIER, CvltParserDELIMITEDIDENTIFIER, CvltParserQUOTEDIDENTIFIER: + { + p.SetState(143) + p.TupleElementSelector() + } + p.SetState(148) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == CvltParserT__7 { + { + p.SetState(144) + p.Match(CvltParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(145) + p.TupleElementSelector() + } + + p.SetState(150) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + { + p.SetState(153) + p.Match(CvltParserT__8) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ITupleElementSelectorContext is an interface to support dynamic dispatch. +type ITupleElementSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + Term() ITermContext + + // IsTupleElementSelectorContext differentiates from other interfaces. + IsTupleElementSelectorContext() +} + +type TupleElementSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTupleElementSelectorContext() *TupleElementSelectorContext { + var p = new(TupleElementSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleElementSelector + return p +} + +func InitEmptyTupleElementSelectorContext(p *TupleElementSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_tupleElementSelector +} + +func (*TupleElementSelectorContext) IsTupleElementSelectorContext() {} + +func NewTupleElementSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TupleElementSelectorContext { + var p = new(TupleElementSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_tupleElementSelector + + return p +} + +func (s *TupleElementSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *TupleElementSelectorContext) Identifier() IIdentifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *TupleElementSelectorContext) Term() ITermContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITermContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITermContext) +} + +func (s *TupleElementSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TupleElementSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *TupleElementSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitTupleElementSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) TupleElementSelector() (localctx ITupleElementSelectorContext) { + localctx = NewTupleElementSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 26, CvltParserRULE_tupleElementSelector) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(155) + p.Identifier() + } + { + p.SetState(156) + p.Match(CvltParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(157) + p.Term() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IInstanceSelectorContext is an interface to support dynamic dispatch. +type IInstanceSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + AllInstanceElementSelector() []IInstanceElementSelectorContext + InstanceElementSelector(i int) IInstanceElementSelectorContext + + // IsInstanceSelectorContext differentiates from other interfaces. + IsInstanceSelectorContext() +} + +type InstanceSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyInstanceSelectorContext() *InstanceSelectorContext { + var p = new(InstanceSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_instanceSelector + return p +} + +func InitEmptyInstanceSelectorContext(p *InstanceSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_instanceSelector +} + +func (*InstanceSelectorContext) IsInstanceSelectorContext() {} + +func NewInstanceSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InstanceSelectorContext { + var p = new(InstanceSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_instanceSelector + + return p +} + +func (s *InstanceSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *InstanceSelectorContext) Identifier() IIdentifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *InstanceSelectorContext) AllInstanceElementSelector() []IInstanceElementSelectorContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IInstanceElementSelectorContext); ok { + len++ + } + } + + tst := make([]IInstanceElementSelectorContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IInstanceElementSelectorContext); ok { + tst[i] = t.(IInstanceElementSelectorContext) + i++ + } + } + + return tst +} + +func (s *InstanceSelectorContext) InstanceElementSelector(i int) IInstanceElementSelectorContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IInstanceElementSelectorContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IInstanceElementSelectorContext) +} + +func (s *InstanceSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InstanceSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *InstanceSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitInstanceSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) InstanceSelector() (localctx IInstanceSelectorContext) { + localctx = NewInstanceSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 28, CvltParserRULE_instanceSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(159) + p.Identifier() + } + { + p.SetState(160) + p.Match(CvltParserT__6) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(170) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CvltParserT__10: + { + p.SetState(161) + p.Match(CvltParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case CvltParserIDENTIFIER, CvltParserDELIMITEDIDENTIFIER, CvltParserQUOTEDIDENTIFIER: + { + p.SetState(162) + p.InstanceElementSelector() + } + p.SetState(167) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == CvltParserT__7 { + { + p.SetState(163) + p.Match(CvltParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(164) + p.InstanceElementSelector() + } + + p.SetState(169) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + { + p.SetState(172) + p.Match(CvltParserT__8) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IInstanceElementSelectorContext is an interface to support dynamic dispatch. +type IInstanceElementSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Identifier() IIdentifierContext + Term() ITermContext + + // IsInstanceElementSelectorContext differentiates from other interfaces. + IsInstanceElementSelectorContext() +} + +type InstanceElementSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyInstanceElementSelectorContext() *InstanceElementSelectorContext { + var p = new(InstanceElementSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_instanceElementSelector + return p +} + +func InitEmptyInstanceElementSelectorContext(p *InstanceElementSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_instanceElementSelector +} + +func (*InstanceElementSelectorContext) IsInstanceElementSelectorContext() {} + +func NewInstanceElementSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InstanceElementSelectorContext { + var p = new(InstanceElementSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_instanceElementSelector + + return p +} + +func (s *InstanceElementSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *InstanceElementSelectorContext) Identifier() IIdentifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *InstanceElementSelectorContext) Term() ITermContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITermContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITermContext) +} + +func (s *InstanceElementSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InstanceElementSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *InstanceElementSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitInstanceElementSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) InstanceElementSelector() (localctx IInstanceElementSelectorContext) { + localctx = NewInstanceElementSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 30, CvltParserRULE_instanceElementSelector) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(174) + p.Identifier() + } + { + p.SetState(175) + p.Match(CvltParserT__10) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(176) + p.Term() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IListSelectorContext is an interface to support dynamic dispatch. +type IListSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllTerm() []ITermContext + Term(i int) ITermContext + TypeSpecifier() ITypeSpecifierContext + + // IsListSelectorContext differentiates from other interfaces. + IsListSelectorContext() +} + +type ListSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyListSelectorContext() *ListSelectorContext { + var p = new(ListSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_listSelector + return p +} + +func InitEmptyListSelectorContext(p *ListSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_listSelector +} + +func (*ListSelectorContext) IsListSelectorContext() {} + +func NewListSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ListSelectorContext { + var p = new(ListSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_listSelector + + return p +} + +func (s *ListSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *ListSelectorContext) AllTerm() []ITermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITermContext); ok { + len++ + } + } + + tst := make([]ITermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITermContext); ok { + tst[i] = t.(ITermContext) + i++ + } + } + + return tst +} + +func (s *ListSelectorContext) Term(i int) ITermContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITermContext) +} + +func (s *ListSelectorContext) TypeSpecifier() ITypeSpecifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITypeSpecifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITypeSpecifierContext) +} + +func (s *ListSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ListSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ListSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitListSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) ListSelector() (localctx IListSelectorContext) { + localctx = NewListSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 32, CvltParserRULE_listSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(185) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == CvltParserT__1 { + { + p.SetState(178) + p.Match(CvltParserT__1) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(183) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == CvltParserT__2 { + { + p.SetState(179) + p.Match(CvltParserT__2) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(180) + p.TypeSpecifier() + } + { + p.SetState(181) + p.Match(CvltParserT__3) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + + } + { + p.SetState(187) + p.Match(CvltParserT__6) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(196) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&280925226168548) != 0 { + { + p.SetState(188) + p.Term() + } + p.SetState(193) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == CvltParserT__7 { + { + p.SetState(189) + p.Match(CvltParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(190) + p.Term() + } + + p.SetState(195) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + } + { + p.SetState(198) + p.Match(CvltParserT__8) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IDisplayClauseContext is an interface to support dynamic dispatch. +type IDisplayClauseContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsDisplayClauseContext differentiates from other interfaces. + IsDisplayClauseContext() +} + +type DisplayClauseContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyDisplayClauseContext() *DisplayClauseContext { + var p = new(DisplayClauseContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_displayClause + return p +} + +func InitEmptyDisplayClauseContext(p *DisplayClauseContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_displayClause +} + +func (*DisplayClauseContext) IsDisplayClauseContext() {} + +func NewDisplayClauseContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DisplayClauseContext { + var p = new(DisplayClauseContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_displayClause + + return p +} + +func (s *DisplayClauseContext) GetParser() antlr.Parser { return s.parser } + +func (s *DisplayClauseContext) STRING() antlr.TerminalNode { + return s.GetToken(CvltParserSTRING, 0) +} + +func (s *DisplayClauseContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DisplayClauseContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *DisplayClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitDisplayClause(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) DisplayClause() (localctx IDisplayClauseContext) { + localctx = NewDisplayClauseContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 34, CvltParserRULE_displayClause) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(200) + p.Match(CvltParserT__18) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(201) + p.Match(CvltParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ICodeSelectorContext is an interface to support dynamic dispatch. +type ICodeSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + Identifier() IIdentifierContext + DisplayClause() IDisplayClauseContext + + // IsCodeSelectorContext differentiates from other interfaces. + IsCodeSelectorContext() +} + +type CodeSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCodeSelectorContext() *CodeSelectorContext { + var p = new(CodeSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_codeSelector + return p +} + +func InitEmptyCodeSelectorContext(p *CodeSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_codeSelector +} + +func (*CodeSelectorContext) IsCodeSelectorContext() {} + +func NewCodeSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CodeSelectorContext { + var p = new(CodeSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_codeSelector + + return p +} + +func (s *CodeSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *CodeSelectorContext) STRING() antlr.TerminalNode { + return s.GetToken(CvltParserSTRING, 0) +} + +func (s *CodeSelectorContext) Identifier() IIdentifierContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentifierContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentifierContext) +} + +func (s *CodeSelectorContext) DisplayClause() IDisplayClauseContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDisplayClauseContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IDisplayClauseContext) +} + +func (s *CodeSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CodeSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *CodeSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitCodeSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) CodeSelector() (localctx ICodeSelectorContext) { + localctx = NewCodeSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 36, CvltParserRULE_codeSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(203) + p.Match(CvltParserT__19) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(204) + p.Match(CvltParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(205) + p.Match(CvltParserT__20) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(206) + p.Identifier() + } + p.SetState(208) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == CvltParserT__18 { + { + p.SetState(207) + p.DisplayClause() + } + + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IConceptSelectorContext is an interface to support dynamic dispatch. +type IConceptSelectorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllCodeSelector() []ICodeSelectorContext + CodeSelector(i int) ICodeSelectorContext + DisplayClause() IDisplayClauseContext + + // IsConceptSelectorContext differentiates from other interfaces. + IsConceptSelectorContext() +} + +type ConceptSelectorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyConceptSelectorContext() *ConceptSelectorContext { + var p = new(ConceptSelectorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_conceptSelector + return p +} + +func InitEmptyConceptSelectorContext(p *ConceptSelectorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_conceptSelector +} + +func (*ConceptSelectorContext) IsConceptSelectorContext() {} + +func NewConceptSelectorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ConceptSelectorContext { + var p = new(ConceptSelectorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_conceptSelector + + return p +} + +func (s *ConceptSelectorContext) GetParser() antlr.Parser { return s.parser } + +func (s *ConceptSelectorContext) AllCodeSelector() []ICodeSelectorContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ICodeSelectorContext); ok { + len++ + } + } + + tst := make([]ICodeSelectorContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ICodeSelectorContext); ok { + tst[i] = t.(ICodeSelectorContext) + i++ + } + } + + return tst +} + +func (s *ConceptSelectorContext) CodeSelector(i int) ICodeSelectorContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICodeSelectorContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ICodeSelectorContext) +} + +func (s *ConceptSelectorContext) DisplayClause() IDisplayClauseContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDisplayClauseContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IDisplayClauseContext) +} + +func (s *ConceptSelectorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConceptSelectorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ConceptSelectorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitConceptSelector(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) ConceptSelector() (localctx IConceptSelectorContext) { + localctx = NewConceptSelectorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 38, CvltParserRULE_conceptSelector) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(210) + p.Match(CvltParserT__21) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(211) + p.Match(CvltParserT__6) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(212) + p.CodeSelector() + } + p.SetState(217) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == CvltParserT__7 { + { + p.SetState(213) + p.Match(CvltParserT__7) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(214) + p.CodeSelector() + } + + p.SetState(219) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(220) + p.Match(CvltParserT__8) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(222) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == CvltParserT__18 { + { + p.SetState(221) + p.DisplayClause() + } + + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IIdentifierContext is an interface to support dynamic dispatch. +type IIdentifierContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IDENTIFIER() antlr.TerminalNode + DELIMITEDIDENTIFIER() antlr.TerminalNode + QUOTEDIDENTIFIER() antlr.TerminalNode + + // IsIdentifierContext differentiates from other interfaces. + IsIdentifierContext() +} + +type IdentifierContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIdentifierContext() *IdentifierContext { + var p = new(IdentifierContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_identifier + return p +} + +func InitEmptyIdentifierContext(p *IdentifierContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_identifier +} + +func (*IdentifierContext) IsIdentifierContext() {} + +func NewIdentifierContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IdentifierContext { + var p = new(IdentifierContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_identifier + + return p +} + +func (s *IdentifierContext) GetParser() antlr.Parser { return s.parser } + +func (s *IdentifierContext) IDENTIFIER() antlr.TerminalNode { + return s.GetToken(CvltParserIDENTIFIER, 0) +} + +func (s *IdentifierContext) DELIMITEDIDENTIFIER() antlr.TerminalNode { + return s.GetToken(CvltParserDELIMITEDIDENTIFIER, 0) +} + +func (s *IdentifierContext) QUOTEDIDENTIFIER() antlr.TerminalNode { + return s.GetToken(CvltParserQUOTEDIDENTIFIER, 0) +} + +func (s *IdentifierContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IdentifierContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *IdentifierContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitIdentifier(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) Identifier() (localctx IIdentifierContext) { + localctx = NewIdentifierContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 40, CvltParserRULE_identifier) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(224) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&30786325577728) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IQuantityContext is an interface to support dynamic dispatch. +type IQuantityContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + NUMBER() antlr.TerminalNode + Unit() IUnitContext + + // IsQuantityContext differentiates from other interfaces. + IsQuantityContext() +} + +type QuantityContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQuantityContext() *QuantityContext { + var p = new(QuantityContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_quantity + return p +} + +func InitEmptyQuantityContext(p *QuantityContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_quantity +} + +func (*QuantityContext) IsQuantityContext() {} + +func NewQuantityContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QuantityContext { + var p = new(QuantityContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_quantity + + return p +} + +func (s *QuantityContext) GetParser() antlr.Parser { return s.parser } + +func (s *QuantityContext) NUMBER() antlr.TerminalNode { + return s.GetToken(CvltParserNUMBER, 0) +} + +func (s *QuantityContext) Unit() IUnitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IUnitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IUnitContext) +} + +func (s *QuantityContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QuantityContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *QuantityContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitQuantity(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) Quantity() (localctx IQuantityContext) { + localctx = NewQuantityContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 42, CvltParserRULE_quantity) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(226) + p.Match(CvltParserNUMBER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(228) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&35734119514112) != 0 { + { + p.SetState(227) + p.Unit() + } + + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IUnitContext is an interface to support dynamic dispatch. +type IUnitContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DateTimePrecision() IDateTimePrecisionContext + PluralDateTimePrecision() IPluralDateTimePrecisionContext + STRING() antlr.TerminalNode + + // IsUnitContext differentiates from other interfaces. + IsUnitContext() +} + +type UnitContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyUnitContext() *UnitContext { + var p = new(UnitContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_unit + return p +} + +func InitEmptyUnitContext(p *UnitContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_unit +} + +func (*UnitContext) IsUnitContext() {} + +func NewUnitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *UnitContext { + var p = new(UnitContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_unit + + return p +} + +func (s *UnitContext) GetParser() antlr.Parser { return s.parser } + +func (s *UnitContext) DateTimePrecision() IDateTimePrecisionContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IDateTimePrecisionContext) +} + +func (s *UnitContext) PluralDateTimePrecision() IPluralDateTimePrecisionContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPluralDateTimePrecisionContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IPluralDateTimePrecisionContext) +} + +func (s *UnitContext) STRING() antlr.TerminalNode { + return s.GetToken(CvltParserSTRING, 0) +} + +func (s *UnitContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *UnitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *UnitContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitUnit(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) Unit() (localctx IUnitContext) { + localctx = NewUnitContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 44, CvltParserRULE_unit) + p.SetState(233) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case CvltParserT__22, CvltParserT__23, CvltParserT__24, CvltParserT__25, CvltParserT__26, CvltParserT__27, CvltParserT__28, CvltParserT__29: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(230) + p.DateTimePrecision() + } + + case CvltParserT__30, CvltParserT__31, CvltParserT__32, CvltParserT__33, CvltParserT__34, CvltParserT__35, CvltParserT__36, CvltParserT__37: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(231) + p.PluralDateTimePrecision() + } + + case CvltParserSTRING: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(232) + p.Match(CvltParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IDateTimePrecisionContext is an interface to support dynamic dispatch. +type IDateTimePrecisionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsDateTimePrecisionContext differentiates from other interfaces. + IsDateTimePrecisionContext() +} + +type DateTimePrecisionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyDateTimePrecisionContext() *DateTimePrecisionContext { + var p = new(DateTimePrecisionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_dateTimePrecision + return p +} + +func InitEmptyDateTimePrecisionContext(p *DateTimePrecisionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_dateTimePrecision +} + +func (*DateTimePrecisionContext) IsDateTimePrecisionContext() {} + +func NewDateTimePrecisionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DateTimePrecisionContext { + var p = new(DateTimePrecisionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_dateTimePrecision + + return p +} + +func (s *DateTimePrecisionContext) GetParser() antlr.Parser { return s.parser } +func (s *DateTimePrecisionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *DateTimePrecisionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *DateTimePrecisionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitDateTimePrecision(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) DateTimePrecision() (localctx IDateTimePrecisionContext) { + localctx = NewDateTimePrecisionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 46, CvltParserRULE_dateTimePrecision) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(235) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2139095040) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IPluralDateTimePrecisionContext is an interface to support dynamic dispatch. +type IPluralDateTimePrecisionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsPluralDateTimePrecisionContext differentiates from other interfaces. + IsPluralDateTimePrecisionContext() +} + +type PluralDateTimePrecisionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyPluralDateTimePrecisionContext() *PluralDateTimePrecisionContext { + var p = new(PluralDateTimePrecisionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_pluralDateTimePrecision + return p +} + +func InitEmptyPluralDateTimePrecisionContext(p *PluralDateTimePrecisionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = CvltParserRULE_pluralDateTimePrecision +} + +func (*PluralDateTimePrecisionContext) IsPluralDateTimePrecisionContext() {} + +func NewPluralDateTimePrecisionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *PluralDateTimePrecisionContext { + var p = new(PluralDateTimePrecisionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = CvltParserRULE_pluralDateTimePrecision + + return p +} + +func (s *PluralDateTimePrecisionContext) GetParser() antlr.Parser { return s.parser } +func (s *PluralDateTimePrecisionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PluralDateTimePrecisionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *PluralDateTimePrecisionContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case CvltVisitor: + return t.VisitPluralDateTimePrecision(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *CvltParser) PluralDateTimePrecision() (localctx IPluralDateTimePrecisionContext) { + localctx = NewPluralDateTimePrecisionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 48, CvltParserRULE_pluralDateTimePrecision) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(237) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&547608330240) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} diff --git a/internal/embeddata/third_party/cqframework/cvlt/cvlt_visitor.go b/internal/embeddata/third_party/cqframework/cvlt/cvlt_visitor.go new file mode 100755 index 0000000..ac77127 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/cvlt/cvlt_visitor.go @@ -0,0 +1,129 @@ +// Code generated from Cvlt.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package cvlt // Cvlt +import "github.com/antlr4-go/antlr/v4" + +// A complete Visitor for a parse tree produced by CvltParser. +type CvltVisitor interface { + antlr.ParseTreeVisitor + + // Visit a parse tree produced by CvltParser#typeSpecifier. + VisitTypeSpecifier(ctx *TypeSpecifierContext) interface{} + + // Visit a parse tree produced by CvltParser#namedTypeSpecifier. + VisitNamedTypeSpecifier(ctx *NamedTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CvltParser#modelIdentifier. + VisitModelIdentifier(ctx *ModelIdentifierContext) interface{} + + // Visit a parse tree produced by CvltParser#listTypeSpecifier. + VisitListTypeSpecifier(ctx *ListTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CvltParser#intervalTypeSpecifier. + VisitIntervalTypeSpecifier(ctx *IntervalTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CvltParser#tupleTypeSpecifier. + VisitTupleTypeSpecifier(ctx *TupleTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CvltParser#tupleElementDefinition. + VisitTupleElementDefinition(ctx *TupleElementDefinitionContext) interface{} + + // Visit a parse tree produced by CvltParser#choiceTypeSpecifier. + VisitChoiceTypeSpecifier(ctx *ChoiceTypeSpecifierContext) interface{} + + // Visit a parse tree produced by CvltParser#literalTerm. + VisitLiteralTerm(ctx *LiteralTermContext) interface{} + + // Visit a parse tree produced by CvltParser#intervalSelectorTerm. + VisitIntervalSelectorTerm(ctx *IntervalSelectorTermContext) interface{} + + // Visit a parse tree produced by CvltParser#tupleSelectorTerm. + VisitTupleSelectorTerm(ctx *TupleSelectorTermContext) interface{} + + // Visit a parse tree produced by CvltParser#instanceSelectorTerm. + VisitInstanceSelectorTerm(ctx *InstanceSelectorTermContext) interface{} + + // Visit a parse tree produced by CvltParser#listSelectorTerm. + VisitListSelectorTerm(ctx *ListSelectorTermContext) interface{} + + // Visit a parse tree produced by CvltParser#codeSelectorTerm. + VisitCodeSelectorTerm(ctx *CodeSelectorTermContext) interface{} + + // Visit a parse tree produced by CvltParser#conceptSelectorTerm. + VisitConceptSelectorTerm(ctx *ConceptSelectorTermContext) interface{} + + // Visit a parse tree produced by CvltParser#ratio. + VisitRatio(ctx *RatioContext) interface{} + + // Visit a parse tree produced by CvltParser#booleanLiteral. + VisitBooleanLiteral(ctx *BooleanLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#nullLiteral. + VisitNullLiteral(ctx *NullLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#stringLiteral. + VisitStringLiteral(ctx *StringLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#numberLiteral. + VisitNumberLiteral(ctx *NumberLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#longNumberLiteral. + VisitLongNumberLiteral(ctx *LongNumberLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#dateTimeLiteral. + VisitDateTimeLiteral(ctx *DateTimeLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#dateLiteral. + VisitDateLiteral(ctx *DateLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#timeLiteral. + VisitTimeLiteral(ctx *TimeLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#quantityLiteral. + VisitQuantityLiteral(ctx *QuantityLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#ratioLiteral. + VisitRatioLiteral(ctx *RatioLiteralContext) interface{} + + // Visit a parse tree produced by CvltParser#intervalSelector. + VisitIntervalSelector(ctx *IntervalSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#tupleSelector. + VisitTupleSelector(ctx *TupleSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#tupleElementSelector. + VisitTupleElementSelector(ctx *TupleElementSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#instanceSelector. + VisitInstanceSelector(ctx *InstanceSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#instanceElementSelector. + VisitInstanceElementSelector(ctx *InstanceElementSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#listSelector. + VisitListSelector(ctx *ListSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#displayClause. + VisitDisplayClause(ctx *DisplayClauseContext) interface{} + + // Visit a parse tree produced by CvltParser#codeSelector. + VisitCodeSelector(ctx *CodeSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#conceptSelector. + VisitConceptSelector(ctx *ConceptSelectorContext) interface{} + + // Visit a parse tree produced by CvltParser#identifier. + VisitIdentifier(ctx *IdentifierContext) interface{} + + // Visit a parse tree produced by CvltParser#quantity. + VisitQuantity(ctx *QuantityContext) interface{} + + // Visit a parse tree produced by CvltParser#unit. + VisitUnit(ctx *UnitContext) interface{} + + // Visit a parse tree produced by CvltParser#dateTimePrecision. + VisitDateTimePrecision(ctx *DateTimePrecisionContext) interface{} + + // Visit a parse tree produced by CvltParser#pluralDateTimePrecision. + VisitPluralDateTimePrecision(ctx *PluralDateTimePrecisionContext) interface{} +} diff --git a/internal/embeddata/third_party/cqframework/fhir-modelinfo-4.0.1.xml b/internal/embeddata/third_party/cqframework/fhir-modelinfo-4.0.1.xml new file mode 100644 index 0000000..30c5b76 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/fhir-modelinfo-4.0.1.xml @@ -0,0 +1,29013 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/embeddata/third_party/cqframework/fhirpath.g4 b/internal/embeddata/third_party/cqframework/fhirpath.g4 new file mode 100644 index 0000000..8f7f780 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/fhirpath.g4 @@ -0,0 +1,175 @@ +grammar fhirpath; + +// Grammar rules +// [FHIRPath](http://hl7.org/fhirpath/N1) Normative Release + +//prog: line (line)*; +//line: ID ( '(' expr ')') ':' expr '\r'? '\n'; + +expression + : term #termExpression + | expression '.' invocation #invocationExpression + | expression '[' expression ']' #indexerExpression + | ('+' | '-') expression #polarityExpression + | expression ('*' | '/' | 'div' | 'mod') expression #multiplicativeExpression + | expression ('+' | '-' | '&') expression #additiveExpression + | expression ('is' | 'as') typeSpecifier #typeExpression + | expression '|' expression #unionExpression + | expression ('<=' | '<' | '>' | '>=') expression #inequalityExpression + | expression ('=' | '~' | '!=' | '!~') expression #equalityExpression + | expression ('in' | 'contains') expression #membershipExpression + | expression 'and' expression #andExpression + | expression ('or' | 'xor') expression #orExpression + | expression 'implies' expression #impliesExpression + //| (IDENTIFIER)? '=>' expression #lambdaExpression + ; + +term + : invocation #invocationTerm + | literal #literalTerm + | externalConstant #externalConstantTerm + | '(' expression ')' #parenthesizedTerm + ; + +literal + : '{' '}' #nullLiteral + | ('true' | 'false') #booleanLiteral + | STRING #stringLiteral + | NUMBER #numberLiteral + | DATE #dateLiteral + | DATETIME #dateTimeLiteral + | TIME #timeLiteral + | quantity #quantityLiteral + ; + +externalConstant + : '%' ( identifier | STRING ) + ; + +invocation // Terms that can be used after the function/member invocation '.' + : identifier #memberInvocation + | function #functionInvocation + | '$this' #thisInvocation + | '$index' #indexInvocation + | '$total' #totalInvocation + ; + +function + : identifier '(' paramList? ')' + ; + +paramList + : expression (',' expression)* + ; + +quantity + : NUMBER unit? + ; + +unit + : dateTimePrecision + | pluralDateTimePrecision + | STRING // UCUM syntax for units of measure + ; + +dateTimePrecision + : 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond' + ; + +pluralDateTimePrecision + : 'years' | 'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds' + ; + +typeSpecifier + : qualifiedIdentifier + ; + +qualifiedIdentifier + : identifier ('.' identifier)* + ; + +identifier + : IDENTIFIER + | DELIMITEDIDENTIFIER + | 'as' + | 'contains' + | 'in' + | 'is' + ; + + +/**************************************************************** + Lexical rules +*****************************************************************/ + +/* +NOTE: The goal of these rules in the grammar is to provide a date +token to the parser. As such it is not attempting to validate that +the date is a correct date, that task is for the parser or interpreter. +*/ + +DATE + : '@' DATEFORMAT + ; + +DATETIME + : '@' DATEFORMAT 'T' (TIMEFORMAT TIMEZONEOFFSETFORMAT?)? + ; + +TIME + : '@' 'T' TIMEFORMAT + ; + +fragment DATEFORMAT + : [0-9][0-9][0-9][0-9] ('-'[0-9][0-9] ('-'[0-9][0-9])?)? + ; + +fragment TIMEFORMAT + : [0-9][0-9] (':'[0-9][0-9] (':'[0-9][0-9] ('.'[0-9]+)?)?)? + ; + +fragment TIMEZONEOFFSETFORMAT + : ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9]) + ; + +IDENTIFIER + : ([A-Za-z] | '_')([A-Za-z0-9] | '_')* // Added _ to support CQL (FHIR could constrain it out) + ; + +DELIMITEDIDENTIFIER + : '`' (ESC | .)*? '`' + ; + +STRING + : '\'' (ESC | .)*? '\'' + ; + +// Also allows leading zeroes now (just like CQL and XSD) +NUMBER + : [0-9]+('.' [0-9]+)? + ; + +// Pipe whitespace to the HIDDEN channel to support retrieving source text through the parser. +WS + : [ \r\n\t]+ -> channel(HIDDEN) + ; + +COMMENT + : '/*' .*? '*/' -> channel(HIDDEN) + ; + +LINE_COMMENT + : '//' ~[\r\n]* -> channel(HIDDEN) + ; + +fragment ESC + : '\\' ([`'\\/fnrt] | UNICODE) // allow \`, \', \\, \/, \f, etc. and \uXXX + ; + +fragment UNICODE + : 'u' HEX HEX HEX HEX + ; + +fragment HEX + : [0-9a-fA-F] + ; diff --git a/internal/embeddata/third_party/cqframework/system-modelinfo.xml b/internal/embeddata/third_party/cqframework/system-modelinfo.xml new file mode 100644 index 0000000..d636ff8 --- /dev/null +++ b/internal/embeddata/third_party/cqframework/system-modelinfo.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/internal/iohelpers/files.go b/internal/iohelpers/files.go new file mode 100644 index 0000000..0599677 --- /dev/null +++ b/internal/iohelpers/files.go @@ -0,0 +1,185 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package iohelpers contains functions for file I/O both locally and in GCS. +package iohelpers + +import ( + "context" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + + "cloud.google.com/go/storage" + "google.golang.org/api/iterator" + "github.com/google/bulk_fhir_tools/gcs" +) + +// IOConfig contains configuration options for IO functions. +type IOConfig struct { + GCSEndpoint string +} + +// FilesWithSuffix returns all file paths in a directory that end with a given suffix. +func FilesWithSuffix(ctx context.Context, dir string, suffix string, cfg *IOConfig) ([]string, error) { + if strings.HasPrefix(dir, "gs://") { + if cfg == nil { + return nil, fmt.Errorf("FilesWithSuffix() IOConfig cannot be nil for GCS paths, but was nil. path: %s", dir) + } + return filesWithSuffixGCS(ctx, dir, suffix, *cfg) + } + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + filePaths := []string{} + for _, file := range files { + if file.IsDir() || !strings.HasSuffix(file.Name(), suffix) { + continue + } + filePaths = append(filePaths, filepath.Join(dir, file.Name())) + } + return filePaths, nil +} + +// filesWithSuffixGCS returns all file paths in a GCS bucket that end with a given suffix. +func filesWithSuffixGCS(ctx context.Context, gcsPath, suffix string, cfg IOConfig) ([]string, error) { + filePaths := []string{} + bucket, path, err := gcs.PathComponents(gcsPath) + if err != nil { + return nil, err + } + client, err := gcs.NewClient(ctx, bucket, cfg.GCSEndpoint) + if err != nil { + return nil, fmt.Errorf("could not connect to a GCS client %w", err) + } + defer func() { + if err := client.Close(); err != nil { + fmt.Printf("error closing GCS client: %v", err) + } + }() + + bucketHandle := client.Bucket(bucket) + iter := bucketHandle.Objects(ctx, &storage.Query{Prefix: path, Delimiter: "/"}) + for { + obj, err := iter.Next() + if err == iterator.Done { + break + } + if err != nil { + return nil, err + } + if strings.HasSuffix(obj.Name, suffix) { + filePaths = append(filePaths, "gs://"+bucket+"/"+obj.Name) + } + } + return filePaths, nil +} + +// ReadFile reads the contents of a file at the given path. +// If the path is a GCS it will attempt to read the file from GCS. +func ReadFile(ctx context.Context, filePath string, cfg *IOConfig) ([]byte, error) { + if strings.HasPrefix(filePath, "gs://") { + if cfg == nil { + return nil, fmt.Errorf("ReadFile() IOConfig cannot be nil for GCS paths, but was nil. path: %s", filePath) + } + return readGCSFile(ctx, filePath, *cfg) + } + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + return io.ReadAll(f) +} + +// readGCSFile reads the contents of a file at the given GCS path. +func readGCSFile(ctx context.Context, gcsPath string, cfg IOConfig) ([]byte, error) { + bucket, objPath, err := gcs.PathComponents(gcsPath) + if err != nil { + return nil, fmt.Errorf("could not parse GCS path %q: %w", gcsPath, err) + } + client, err := gcs.NewClient(ctx, bucket, cfg.GCSEndpoint) + if err != nil { + return nil, fmt.Errorf("could not connect to a GCS client %w", err) + } + defer func() { + if err := client.Close(); err != nil { + fmt.Printf("error closing GCS client: %v", err) + } + }() + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + r, err := client.GetFileReader(ctx, objPath) + if err != nil { + return nil, fmt.Errorf("could not access reader for %s/%s: %w", bucket, objPath, err) + } + defer func() { + if err := r.Close(); err != nil { + fmt.Printf("error closing GCS file reader: %v", err) + } + }() + + content, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("io.ReadAll failed to read GCS file %s/%s: %w", bucket, objPath, err) + } + return content, nil +} + +// WriteFile writes the given content to a file at the given path. +// If the path is a GCS it will attempt to write the file to GCS. +func WriteFile(ctx context.Context, filePath, fileName string, content []byte, cfg *IOConfig) error { + if strings.HasPrefix(filePath, "gs://") { + if cfg == nil { + return fmt.Errorf("WriteFile() IOConfig cannot be nil for GCS paths, but was nil. path: %s", filePath) + } + return writeGCSFile(ctx, filePath, fileName, content, *cfg) + } + return os.WriteFile(path.Join(filePath, fileName), content, 0644) +} + +func writeGCSFile(ctx context.Context, gcsPath, fileName string, content []byte, cfg IOConfig) error { + fullFilePath := gcsPath + "/" + fileName + bucket, objPath, err := gcs.PathComponents(fullFilePath) + if err != nil { + return fmt.Errorf("could not parse GCS path %q: %w", fullFilePath, err) + } + client, err := gcs.NewClient(ctx, bucket, cfg.GCSEndpoint) + if err != nil { + return fmt.Errorf("could not connect to a GCS client %w", err) + } + defer func() { + if err := client.Close(); err != nil { + fmt.Printf("error closing GCS client: %v", err) + } + }() + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + w := client.GetFileWriter(ctx, objPath) + defer func() { + if err := w.Close(); err != nil { + fmt.Printf("error closing GCS file writer: %v", err) + } + }() + + _, err = w.Write(content) + return err +} diff --git a/internal/iohelpers/files_test.go b/internal/iohelpers/files_test.go new file mode 100644 index 0000000..be5efc8 --- /dev/null +++ b/internal/iohelpers/files_test.go @@ -0,0 +1,221 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package iohelpers + +import ( + "context" + "os" + "path/filepath" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/bulk_fhir_tools/testhelpers" +) + +const testBucketName = "bucketName" + +func TestFilesWithSuffix(t *testing.T) { + tests := []struct { + name string + files []string + wantSuffix string + want []string + }{ + { + name: "only valid", + files: []string{"f2.json", "out.json", "result.json"}, + wantSuffix: "json", + want: []string{"f2.json", "out.json", "result.json"}, + }, + { + name: "no files in dir", + files: []string{}, + wantSuffix: "json", + want: []string{}, + }, + { + name: "invalid and valid", + files: []string{"result.json", "result.tmp", "result.txt"}, + wantSuffix: "json", + want: []string{"result.json"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + // Write out test files. + for i, f := range tc.files { + if i < len(tc.want) { + tc.want[i] = filepath.Join(dir, tc.want[i]) + } + filePath := filepath.Join(dir, f) + err := os.WriteFile(filePath, []byte("Hello World"), 0644) + if err != nil { + t.Fatalf("Failed to write file: %v", err) + } + } + + got, err := FilesWithSuffix(context.Background(), dir, tc.wantSuffix, nil) + if err != nil { + t.Fatalf("Failed to get files: %v", err) + } + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("FilesWithSuffix() returned an unexpected diff (-want +got): %v", diff) + } + }) + } +} + +func TestFilesWithSuffixGCS(t *testing.T) { + tests := []struct { + name string + files []string + wantSuffix string + want []string + }{ + { + name: "only valid", + files: []string{"f2.json", "out.json", "result.json"}, + wantSuffix: "json", + want: []string{ + gcsPath(t, "dir/f2.json"), + gcsPath(t, "dir/out.json"), + gcsPath(t, "dir/result.json"), + }, + }, + { + name: "no files in dir", + files: []string{}, + wantSuffix: "json", + want: []string{}, + }, + { + name: "invalid and valid", + files: []string{"result.json", "result.tmp", "result.txt"}, + wantSuffix: "json", + want: []string{gcsPath(t, "dir/result.json")}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gcsServer := testhelpers.NewGCSServer(t) + for _, f := range tc.files { + gcsServer.AddObject(testBucketName, "dir/"+f, gcsObject(t, "Hello World")) + } + + got, err := FilesWithSuffix(context.Background(), "gs://"+testBucketName+"/dir", tc.wantSuffix, &IOConfig{GCSEndpoint: gcsServer.URL()}) + if err != nil { + t.Fatalf("Failed to get files: %v", err) + } + sort.Strings(got) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("FilesWithSuffix() returned an unexpected diff (-want +got): %v", diff) + } + }) + } +} + +func TestReadFile(t *testing.T) { + d := t.TempDir() + filePath := filepath.Join(d, "result.json") + want := `Hello World` + err := os.WriteFile(filePath, []byte(want), 0644) + if err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + gotBytes, err := ReadFile(context.Background(), filePath, nil) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + if diff := cmp.Diff(want, string(gotBytes)); diff != "" { + t.Errorf("ReadFile() returned an unexpected diff (-want +got): %v", diff) + } +} + +func TestReadFileGCS(t *testing.T) { + gcsServer := testhelpers.NewGCSServer(t) + want := `Hello World` + gcsServer.AddObject(testBucketName, "result.json", gcsObject(t, want)) + + gotBytes, err := ReadFile(context.Background(), gcsPath(t, "result.json"), &IOConfig{GCSEndpoint: gcsServer.URL()}) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + if diff := cmp.Diff(want, string(gotBytes)); diff != "" { + t.Errorf("ReadFile() returned an unexpected diff (-want +got): %v", diff) + } +} + +func TestReadFileGCS_Error(t *testing.T) { + gcsServer := testhelpers.NewGCSServer(t) + + _, err := ReadFile(context.Background(), gcsPath(t, "result.json"), &IOConfig{GCSEndpoint: gcsServer.URL()}) + if err == nil { + t.Fatal("Should have failed to read non-existent file", err) + } +} + +func TestWriteFile(t *testing.T) { + want := `Hello World` + dir := t.TempDir() + fileName := "result.json" + filePath := filepath.Join(dir, fileName) + err := WriteFile(context.Background(), dir, fileName, []byte(want), nil) + if err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + gotBytes, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + if diff := cmp.Diff(want, string(gotBytes)); diff != "" { + t.Errorf("WriteFile() returned an unexpected diff (-want +got): %v", diff) + } +} + +func TestWriteFileGCS(t *testing.T) { + gcsServer := testhelpers.NewGCSServer(t) + want := `Hello World` + fileName := "result.json" + filePath := gcsPath(t, fileName) + err := WriteFile(context.Background(), "gs://"+testBucketName, fileName, []byte(want), &IOConfig{GCSEndpoint: gcsServer.URL()}) + if err != nil { + t.Fatalf("Failed to write file: %v", err) + } + + gcsObject, ok := gcsServer.GetObject(testBucketName, fileName) + if !ok { + t.Fatalf("Failed to read gcs file: %s", filePath) + } + if diff := cmp.Diff(want, string(gcsObject.Data)); diff != "" { + t.Errorf("WriteFile() returned an unexpected diff (-want +got): %v", diff) + } +} + +func gcsPath(t *testing.T, suffixPath string) string { + t.Helper() + return "gs://" + filepath.Join(testBucketName, suffixPath) +} + +func gcsObject(t *testing.T, content string) testhelpers.GCSObjectEntry { + t.Helper() + return testhelpers.GCSObjectEntry{ + Data: []byte(content), + } +} diff --git a/internal/modelinfo/modelinfo.go b/internal/modelinfo/modelinfo.go new file mode 100644 index 0000000..b540fd7 --- /dev/null +++ b/internal/modelinfo/modelinfo.go @@ -0,0 +1,710 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modelinfo + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/types" +) + +// ModelInfos provides methods for interacting with the underlying ModelInfos such as getting type +// specifiers for a property or determining implicit conversions. We currently only support the +// system and one custom ModelInfo. https://cql.hl7.org/03-developersguide.html#multiple-data-models +// is not supported. +type ModelInfos struct { + // using is the current ModelInfo and corresponds with CQL using declarations. It is nil if no + // using declaration has been set. Currently only one data model is supported. + using *Key + // models are all of the loaded model infos. + models map[Key]modelInfo +} + +// modelInfo holds the parsed data from a ModelInfo XML in an easy to use format. System model info +// is included in every custom modelInfo. +type modelInfo struct { + // typeMap maps the fully qualified name to a typeInfo. Check the types package + // ModelInfoName() method for a description of the string key. + typeMap map[string]*TypeInfo + conversionMap map[conversionKey]*conversionInfo + patientBirthDatePropertyName string + defaultContext string + url string + key Key +} + +// Key is the name and version of a ModeInfo. This is the same name and version as the CQL using +// declaration (ex using FHIR version 4.0.1). Implementation note: this struct needs to be usable as +// a key in a map. +type Key struct { + // Name represents the name of the model info (ex FHIR). + Name string + // Version represents the version of the model info (ex 4.0.1). + Version string +} + +func (k Key) String() string { + return fmt.Sprintf("%v %v", k.Name, k.Version) +} + +type conversionKey struct { + // Check the types package ModelInfoName() method for a description of fromType and toType. + fromType string + toType string +} + +// TypeInfo holds details about a NamedType from a ModelInfo. +type TypeInfo struct { + // Name is the fully qualified model info type name for this type. + Name string + // Properties is a map of property (element) name to the type specifier for it. + Properties map[string]types.IType + // BaseType is the fully qualified model info type name for the base (parent) type. + BaseType string + // The identifier specifies a unique name for the class that may be independent of the name. In + // FHIR, this corresponds to the profile identifier. + Identifier string + // Retrievable specifies whether the class can be used within a retrieve statement. + Retrievable bool + // PrimaryCodePath specifies the path that should be used to perform code filtering when a + // retrieve does not specify a code path. + PrimaryCodePath string +} + +// ErrTypeNotFound is an error that is returned when a type is not found in the modelinfo. +var ErrTypeNotFound = errors.New("not found in data model") + +var errDataModelNotFound = errors.New("data model not found") + +var errUsingNotSet = errors.New("using declaration has not been set") + +var errPropertyNotFound = errors.New("property not found in data model") + +func newErrPropertyNotFound(parentTypeName, property string) error { + return fmt.Errorf("property %q not found in Parent Type %q %w", property, parentTypeName, errPropertyNotFound) +} + +func (m *ModelInfos) typeToModelKey(t types.IType) (modelInfo, Key, error) { + var key Key + _, ok := t.(*types.Named) + if ok { + if m.using == nil { + return modelInfo{}, Key{}, fmt.Errorf("cannot use %v %w", t, errUsingNotSet) + } + // TODO: b/301606416 - When multiple data models are supported this should map the Named Type + // Qualifier --> Using Key. + key = *m.using + } else { + key = Key{Name: "System", Version: "1.0.0"} + } + model, ok := m.models[key] + if !ok { + return modelInfo{}, Key{}, fmt.Errorf("%v %w", m.using, errDataModelNotFound) + } + return model, key, nil +} + +// PropertyTypeSpecifier attempts to return the TypeSpecifier for the property on the parent type +// passed in. The passed property must be only one level deep (e.g. property1 instead of +// property1.property2). +// The input type can be a List, in which case the FHIR Path traversal rules for properties will +// be applied to determine the type: https://build.fhir.org/ig/HL7/cql/03-developersguide.html#path-traversal. +// Currently for top level resource types like FHIR.Patient, they need to be built outside of this +// helper based on knowledge of what resources were retrieved. +func (m *ModelInfos) PropertyTypeSpecifier(parentType types.IType, property string) (types.IType, error) { + model, key, err := m.typeToModelKey(parentType) + if err != nil { + return nil, err + } + if strings.Contains(property, ".") { + return nil, fmt.Errorf("internal error - property passed to PropertyTypeSpecifier should not contain \".\" only a single component of the property should be passed at a time") + } + if parentType == nil { + return nil, fmt.Errorf("internal error - cannot compute the property type of a nil parent type") + } + if parentType.Equal(types.Any) { + // The return type must be Any as well. + return types.Any, nil + } + + switch parentTypeSpecifier := parentType.(type) { + case *types.Tuple: + childTS, ok := parentTypeSpecifier.ElementTypes[property] + if !ok { + return nil, fmt.Errorf("%v does not have property %q", parentTypeSpecifier, property) + } + return childTS, nil + case *types.List: + innerSpecifier, err := m.PropertyTypeSpecifier(parentTypeSpecifier.ElementType, property) + if err != nil { + return nil, err + } + isSub, err := m.IsSubType(innerSpecifier, &types.List{ElementType: types.Any}) + if err != nil { + return nil, err + } + if isSub { + // Since the child is a list and the parent is a list, we don't wrap this in two lists to + // follow the flattening seen in + // https://build.fhir.org/ig/HL7/cql/03-developersguide.html#path-traversal. + return innerSpecifier, nil + } + return &types.List{ElementType: innerSpecifier}, nil + case *types.Named, types.System: + parentTypeName, err := parentTypeSpecifier.ModelInfoName() + if err != nil { + return nil, err + } + parent, ok := model.typeMap[parentTypeName] + if !ok { + return nil, fmt.Errorf("parentNamedType %q not found in data model %v", parentTypeName, key) + } + childTS, ok := parent.Properties[property] + if !ok { + // Try to check the property on the base type: + baseTypes, err := m.BaseTypes(parentType) + if err != nil { + return nil, err + } + for _, b := range baseTypes { + ts, err := m.PropertyTypeSpecifier(b, property) + if errors.Is(err, errPropertyNotFound) { + continue + } else if err != nil { + return nil, err + } + return ts, err + } + // Otherwise, property not found error: + return nil, newErrPropertyNotFound(parent.Name, property) + } + return childTS, nil + case *types.Interval: + // Note these are not defined in the system modelinfo, so the logic is spelled out here to + // keep it all in one place. + switch property { + case "low", "high": + return parentTypeSpecifier.PointType, nil + case "lowClosed", "highClosed": + return types.Boolean, nil + default: + return nil, fmt.Errorf("invalid property on interval. got: %v, want: low, high, lowClosed, highClosed %w", property, errPropertyNotFound) + } + case *types.Choice: + // Check the property on each choice, and keep track of the unique valid result types. + validPropertyTypes := []types.IType{} + for _, ct := range parentTypeSpecifier.ChoiceTypes { + propType, err := m.PropertyTypeSpecifier(ct, property) + if errors.Is(err, errPropertyNotFound) { + continue + } + if err != nil { + return nil, err + } + if !containsType(validPropertyTypes, propType) { + validPropertyTypes = append(validPropertyTypes, propType) + } + } + switch len(validPropertyTypes) { + case 0: + return nil, newErrPropertyNotFound(parentTypeSpecifier.String(), property) + case 1: + return validPropertyTypes[0], nil + default: + return &types.Choice{ChoiceTypes: validPropertyTypes}, nil + } + + default: + return nil, fmt.Errorf("internal error (PropertyTypeSpecifier) - parentType %v is not a NamedTypeSpecifier, ListTypeSpecifier, IntervalTypeSpecifier, or System type specifier", parentType) + } +} + +func containsType(types []types.IType, arg types.IType) bool { + for _, t := range types { + if t.Equal(arg) { + return true + } + } + return false +} + +// Convertible is the result of the IsImplicitlyConvertible function. +type Convertible struct { + IsConvertible bool + // Library and Function name of the function to call to do the conversion + // ex FHIRHelpers.ToString. + Library string + Function string +} + +// IsImplicitlyConvertible uses model info conversionInfo to determine if one type can be converted +// to another. If the `from` type is convertible to the `to` type, this function will return the +// library and function name to call to do the conversion. +func (m *ModelInfos) IsImplicitlyConvertible(from, to types.IType) (Convertible, error) { + model, _, err := m.typeToModelKey(from) + if err != nil { + return Convertible{}, err + } + fromType, err := from.ModelInfoName() + if err != nil { + return Convertible{}, err + } + toType, err := to.ModelInfoName() + if err != nil { + return Convertible{}, err + } + ci, ok := model.conversionMap[conversionKey{fromType: fromType, toType: toType}] + if !ok { + return Convertible{}, nil + } + + splitNames := strings.Split(ci.FunctionName, ".") + if len(splitNames) != 2 { + return Convertible{}, fmt.Errorf("invalid conversion function name %v", ci.FunctionName) + } + return Convertible{IsConvertible: true, Library: splitNames[0], Function: splitNames[1]}, nil +} + +// BaseTypes returns all of the BaseTypes (aka Parents) of a type excluding Any (or for nested types +// List). +func (m *ModelInfos) BaseTypes(child types.IType) ([]types.IType, error) { + model, _, err := m.typeToModelKey(child) + if err != nil { + return nil, err + } + + if child == nil { + return []types.IType{}, fmt.Errorf("internal error - child type cannot be nil, got: %v", child) + } + + if child.Equal(types.Any) { + return []types.IType{}, nil + } + + switch c := child.(type) { + case *types.Tuple: + // It is too expensive to compute all subtypes of a tuple, and the way BaseTypes is used we + // don't need to. BaseTypes are used for multi step conversions invoked --> sub --> declared, + // but Tuples will never undergo this type of multistep conversion. + return []types.IType{}, nil + case *types.List: + baseType, err := m.BaseTypes(c.ElementType) + if err != nil { + return []types.IType{}, err + } + listBaseTypes := []types.IType{} + for _, b := range baseType { + listBaseTypes = append(listBaseTypes, &types.List{ElementType: b}) + } + return listBaseTypes, nil + case *types.Interval: + baseType, err := m.BaseTypes(c.PointType) + if err != nil { + return []types.IType{}, err + } + intervalBaseTypes := []types.IType{} + for _, b := range baseType { + intervalBaseTypes = append(intervalBaseTypes, &types.Interval{PointType: b}) + } + return intervalBaseTypes, nil + case *types.Choice: + // TODO(b/301606416): Unclear when this should be true for choice types. Should we check + // each individual component type? For example is Choice a parent of + // Choice? + return []types.IType{}, nil + } + + name, err := child.ModelInfoName() + if err != nil { + return []types.IType{}, err + } + baseTypes := []types.IType{} + for depth := 0; ; depth++ { + tin, ok := model.typeMap[name] + if !ok { + return []types.IType{}, fmt.Errorf("%v not found in the data model", child) + } + if tin.BaseType == "System.Any" { + break + } + baseTypes = append(baseTypes, typeSpecifierFromElementType(tin.BaseType)) + name = tin.BaseType + + if depth > 100000 { + return []types.IType{}, fmt.Errorf("internal error - subtype depth exceeded 100000 for %v", child) + } + } + return baseTypes, nil +} + +// IsSubType returns true if the child type has the base type (parent) anywhere in it's type +// hierarchy. Returns errors on nil types.IType, since it cannot be determined what the hierarchy +// is. +func (m *ModelInfos) IsSubType(child, base types.IType) (bool, error) { + model, _, err := m.typeToModelKey(child) + if err != nil { + return false, err + } + + if child == nil || base == nil { + return false, fmt.Errorf("internal error - child or base type cannot be nil, got: %v, %v", child, base) + } + + if base.Equal(types.Any) { + return true, nil + } + + if child.Equal(types.Any) { + return false, nil + } + + switch c := child.(type) { + case *types.Tuple: + // Tuples are not defined in modelinfo, so we cannot check modelinfo for subtyping information, we + // need to calculate it. + b, isTuple := base.(*types.Tuple) + if !isTuple { + return false, nil + } + if len(c.ElementTypes) != len(b.ElementTypes) { + return false, nil + } + + for childName, childType := range c.ElementTypes { + baseType, ok := b.ElementTypes[childName] + if !ok { + return false, nil + } + isSub, err := m.IsSubType(childType, baseType) + if err != nil { + return false, err + } + if childType.Equal(baseType) || isSub { + continue + } + return false, nil + } + return true, nil + case *types.List: + baseList, ok := base.(*types.List) + if !ok { + return false, nil + } + return m.IsSubType(c.ElementType, baseList.ElementType) + case *types.Interval: + baseInt, ok := base.(*types.Interval) + if !ok { + return false, nil + } + return m.IsSubType(c.PointType, baseInt.PointType) + case *types.Choice: + // TODO(b/301606416): Unclear when this should be true for choice types. Should we check + // each individual component type? For example is Choice a parent of + // Choice? + if base.Equal(types.Any) { + return true, nil + } + return false, nil + } + + // For all other types (Named, System): + cName, err := child.ModelInfoName() + if err != nil { + return false, err + } + tin, ok := model.typeMap[cName] + if !ok { + return false, fmt.Errorf("child type %q not found in model info", child.String()) + } + + // TODO(b/301606416): Revisit what happens if a base type is a choice type. For now, return + // false since the spec is unclear on choice type hierarchies. + if _, ok := base.(*types.Choice); ok { + return false, nil + } + + pName, err := base.ModelInfoName() + if err != nil { + return false, err + } + if tin.BaseType == pName { + return true, nil + } + + anyModelName, err := types.Any.ModelInfoName() + if err != nil { + return false, err + } + if tin.BaseType == anyModelName { + return false, nil + } + return m.IsSubType(typeSpecifierFromElementType(tin.BaseType), base) +} + +// SetUsing corresponds to a CQL using declaration. +func (m *ModelInfos) SetUsing(key Key) error { + if m.using != nil && key != *m.using { + return fmt.Errorf("only one data model at a time is currently supported, but got %v and %v", m.using, key) + } + + if _, ok := m.models[key]; !ok { + return fmt.Errorf("%v %w", key, errDataModelNotFound) + } + + m.using = &key + return nil +} + +// ResetUsing resets the using declaration to the system model info key. +func (m *ModelInfos) ResetUsing() { + m.using = nil +} + +// PatientBirthDatePropertyName returns the PatientBirthDatePropertyName field from the custom +// ModelInfo. +func (m *ModelInfos) PatientBirthDatePropertyName() (string, error) { + if m.using == nil { + return "", errUsingNotSet + } + model, ok := m.models[*m.using] + if !ok { + return "", fmt.Errorf("%v %w", m.using, errDataModelNotFound) + } + + return model.patientBirthDatePropertyName, nil +} + +// URL returns the URL field from the custom ModelInfo. +func (m *ModelInfos) URL() (string, error) { + if m.using == nil { + return "", errUsingNotSet + } + model, ok := m.models[*m.using] + if !ok { + return "", fmt.Errorf("%v %w", m.using, errDataModelNotFound) + } + return model.url, nil +} + +// DefaultContext returns the default context of the custom ModelInfo. To be used if the CQL does +// not specify one. This is actually not set in the FHIR 4.0.1 ModelInfo. +func (m *ModelInfos) DefaultContext() (string, error) { + if m.using == nil { + return "", errUsingNotSet + } + model, ok := m.models[*m.using] + if !ok { + return "", fmt.Errorf("%v %w", m.using, errDataModelNotFound) + } + return model.defaultContext, nil +} + +// ToNamed converts a string into a NamedType. The string may be qualified (FHIR.Patient) or +// unqualified (Patient) and a Named type with the qualified name will be returned. ToNamed +// validates that the type is in the custom ModelInfo set by the using declaration. System types +// should not be passed to this function and will throw an error. +func (m *ModelInfos) ToNamed(str string) (*types.Named, error) { + if m.using == nil { + return nil, errUsingNotSet + } + model, ok := m.models[*m.using] + if !ok { + return nil, fmt.Errorf("%v %w", m.using, errDataModelNotFound) + } + + if str == "" { + return nil, fmt.Errorf("received an empty type, which is invalid") + } + + // If t is not qualified with key.Name (ex FHIR), then qualify it. + qualifiedStr := str + if !strings.HasPrefix(str, m.using.Name+".") { + qualifiedStr = fmt.Sprintf("%s.%s", m.using.Name, str) + } + + if _, ok := model.typeMap[qualifiedStr]; !ok { + return nil, fmt.Errorf("type %v %w %v", str, ErrTypeNotFound, m.using) + } + return &types.Named{TypeName: qualifiedStr}, nil +} + +// NamedTypeInfo returns the TypeInfo for the given Named type. +func (m *ModelInfos) NamedTypeInfo(t *types.Named) (*TypeInfo, error) { + model, _, err := m.typeToModelKey(t) + if err != nil { + return nil, err + } + + tInfo, ok := model.typeMap[t.TypeName] + if !ok { + return nil, fmt.Errorf("invalid type %v", t) + } + return tInfo, nil +} + +// New creates a new ModelInfos. The byte array of all the custom ModelInfo should be passed in. System +// ModelInfo is always loaded by default and does not need to be passed in. +func New(modelInfoBytes [][]byte) (*ModelInfos, error) { + if len(modelInfoBytes) > 1 { + return nil, fmt.Errorf("only one data model is currently supported") + } + + // Load system model info by default. + sysMIBytes, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/system-modelinfo.xml") + if err != nil { + return nil, err + } + modelInfoBytes = append(modelInfoBytes, sysMIBytes) + + modelInfos := &ModelInfos{ + models: make(map[Key]modelInfo, len(modelInfoBytes)), + using: nil, + } + + for _, miBytes := range modelInfoBytes { + miXML, err := parse(miBytes) + if err != nil { + return nil, err + } + mi, err := load(miXML) + if err != nil { + return nil, err + } + modelInfos.models[mi.key] = *mi + if mi.key != (Key{Name: "FHIR", Version: "4.0.1"}) && mi.key != (Key{Name: "System", Version: "1.0.0"}) { + return nil, fmt.Errorf("only FHIR 4.0.1 data model is supported") + } + } + + return modelInfos, nil +} + +// load parses the XML into a usable data structure. +func load(miXML *modelInfoXML) (*modelInfo, error) { + mi := &modelInfo{ + patientBirthDatePropertyName: miXML.PatientBirthDatePropertyName, + url: miXML.URL, + defaultContext: miXML.DefaultContext, + key: Key{Name: miXML.Name, Version: miXML.Version}, + typeMap: make(map[string]*TypeInfo), + conversionMap: make(map[conversionKey]*conversionInfo), + } + + for _, ti := range miXML.TypeInfos { + err := loadTypeInfo(mi, ti) + if err != nil { + return nil, err + } + } + + for _, ci := range miXML.ConversionInfos { + mi.conversionMap[conversionKey{fromType: ci.FromType, toType: ci.ToType}] = ci + } + + return mi, nil +} + +func loadTypeInfo(mi *modelInfo, ti *typeInfoXML) error { + qualifiedTypeName := ti.Name + if ti.Namespace != "" { + qualifiedTypeName = strings.Join([]string{ti.Namespace, ti.Name}, ".") + } + tin := &TypeInfo{ + Name: qualifiedTypeName, + Properties: make(map[string]types.IType), + BaseType: ti.BaseType, + Identifier: ti.Identifier, + Retrievable: ti.Retrievable, + PrimaryCodePath: ti.PrimaryCodePath, + } + + for _, e := range ti.Elements { + if e.ElementTypeSpecifier == nil && e.TypeSpecifier == nil { + // This is a simple ElementType defined elsewhere. + if e.ElementType != "" { + tin.Properties[e.Name] = typeSpecifierFromElementType(e.ElementType) + continue + } + // Otherwise use type, which is deprecated but still in use. + if e.Type != "" { + tin.Properties[e.Name] = typeSpecifierFromElementType(e.Type) + continue + } + // Unclear what to do, this is an error + return fmt.Errorf("internal error -- in model info elementTypeSpecifier is nil, and neither elementType or type is set") + } + // Otherwise, handle a more specific type specifier: + specifier := e.TypeSpecifier // deprecated field used as default + if e.ElementTypeSpecifier != nil { + specifier = e.ElementTypeSpecifier // override with non-deprecated field if set + } + sp, err := buildElementTypeSpecifier(specifier) + if err != nil { + return err + } + tin.Properties[e.Name] = sp + + } + if _, ok := mi.typeMap[qualifiedTypeName]; ok { + // There's a clash with something already in the typeMap. For now, we error. + return fmt.Errorf("duplicate model info type for type %q", qualifiedTypeName) + } + mi.typeMap[qualifiedTypeName] = tin + return nil +} + +func buildElementTypeSpecifier(e *elementTypeSpecifier) (types.IType, error) { + switch xt := e.XSIType; xt { + // The ns4 prefixes come from the System model info, for some reason. + case "NamedTypeSpecifier", "ns4:NamedTypeSpecifier": + qualifiedTypeName := strings.Join([]string{e.Namespace, e.Name}, ".") + return &types.Named{TypeName: qualifiedTypeName}, nil + case "ListTypeSpecifier", "ns4:ListTypeSpecifier": + if e.ElementTypeSpecifier != nil { + childTS, err := buildElementTypeSpecifier(e.ElementTypeSpecifier) + if err != nil { + return nil, err + } + return &types.List{ElementType: childTS}, nil + } + return &types.List{ElementType: typeSpecifierFromElementType(e.ElementType)}, nil + + case "ChoiceTypeSpecifier", "ns4:ChoiceTypeSpecifier": + t := &types.Choice{} + for _, ct := range e.Choices { + typeName := ct.Name + if ct.Namespace != "" { + typeName = strings.Join([]string{ct.Namespace, ct.Name}, ".") + } + t.ChoiceTypes = append(t.ChoiceTypes, typeSpecifierFromElementType(typeName)) + } + return t, nil + default: + return nil, fmt.Errorf("unsupported elementTypeSpecifer in modelInfo. got: %s, want: [ListTypeSpecifier, ChoiceTypeSpecifier]", xt) + } +} + +// typeSpecifierFromElementType returns the type specifier of the passed fully qualified model +// info string type name. +func typeSpecifierFromElementType(modelInfoType string) types.IType { + if strings.HasPrefix(modelInfoType, "System.") { + return types.ToSystem(modelInfoType) + + } + return &types.Named{TypeName: modelInfoType} +} diff --git a/internal/modelinfo/modelinfo_test.go b/internal/modelinfo/modelinfo_test.go new file mode 100644 index 0000000..9d679f1 --- /dev/null +++ b/internal/modelinfo/modelinfo_test.go @@ -0,0 +1,824 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modelinfo + +import ( + "errors" + "strings" + "testing" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" +) + +func TestPropertyTypeSpecifier_FHIR(t *testing.T) { + cases := []struct { + name string + parentType types.IType + property string + want types.IType + }{ + { + name: "FHIR.Account status", + parentType: &types.Named{TypeName: "FHIR.Account"}, + property: "status", + want: &types.Named{TypeName: "FHIR.AccountStatus"}, + }, + { + name: "FHIR.Account id (comes from base type FHIR.Resource)", + parentType: &types.Named{TypeName: "FHIR.Account"}, + property: "id", + want: &types.Named{TypeName: "FHIR.id"}, + }, + { + name: "List of FHIR.Account status", + parentType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Account"}}, + property: "status", + want: &types.List{ElementType: &types.Named{TypeName: "FHIR.AccountStatus"}}, + }, + { + name: "ListTypeSpecifier FHIR.Account subject", + parentType: &types.Named{TypeName: "FHIR.Account"}, + property: "subject", + want: &types.List{ElementType: &types.Named{TypeName: "FHIR.Reference"}}, + }, + { + name: "Tuple", + parentType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Integer}}, + property: "foo", + want: types.Integer, + }, + { + name: "System type, FHIR.AccountStatus value", + parentType: &types.Named{TypeName: "FHIR.AccountStatus"}, + property: "value", + want: types.String, + }, + { + name: "Bundle.Entry.link: ListTypeSpecifier with NamedTypeSpecifier", + parentType: &types.Named{TypeName: "FHIR.Bundle.Entry"}, + property: "link", + want: &types.List{ElementType: &types.Named{TypeName: "FHIR.Bundle.Link"}}, + }, + { + name: "Property on types.Any returns types.Any", + parentType: types.Any, + property: "value", + want: types.Any, + }, + { + name: "Interval.low", + parentType: &types.Interval{PointType: types.Integer}, + property: "low", + want: types.Integer, + }, + { + name: "Interval.high", + parentType: &types.Interval{PointType: types.Integer}, + property: "high", + want: types.Integer, + }, + { + name: "Interval.lowClosed", + parentType: &types.Interval{PointType: types.Integer}, + property: "lowClosed", + want: types.Boolean, + }, + { + name: "Interval.highClosed", + parentType: &types.Interval{PointType: types.Integer}, + property: "highClosed", + want: types.Boolean, + }, + { + name: "Choice Type property result (FHIR.ActivityDefinition.subject)", + parentType: &types.Named{TypeName: "FHIR.ActivityDefinition"}, + property: "subject", + want: &types.Choice{ChoiceTypes: []types.IType{ + &types.Named{TypeName: "FHIR.CodeableConcept"}, + &types.Named{TypeName: "FHIR.Reference"}, + }, + }, + }, + { + name: "Choice.id property results in FHIR.id", + parentType: &types.Choice{ChoiceTypes: []types.IType{ + &types.Named{TypeName: "FHIR.Patient"}, + &types.Named{TypeName: "FHIR.Observation"}, + }}, + property: "id", + want: &types.Named{TypeName: "FHIR.id"}, + }, + { + name: "Choice.value property results in Choice", + parentType: &types.Choice{ChoiceTypes: []types.IType{ + &types.Named{TypeName: "FHIR.boolean"}, + &types.Named{TypeName: "FHIR.string"}, + }}, + property: "value", + want: &types.Choice{ChoiceTypes: []types.IType{types.Boolean, types.String}}, + }, + { + name: "Nested Choice type: Choice, FHIR.string>.value property", + parentType: &types.Choice{ChoiceTypes: []types.IType{ + &types.Choice{ChoiceTypes: []types.IType{&types.Named{TypeName: "FHIR.integer"}, &types.Named{TypeName: "FHIR.boolean"}}}, + &types.Named{TypeName: "FHIR.string"}, + }}, + property: "value", + want: &types.Choice{ChoiceTypes: []types.IType{ + &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.Boolean}}, + types.String, + }}, + }, + // System model info properties: + { + name: "System.ValueSet.codesystems", + parentType: types.ValueSet, + property: "codesystems", + want: &types.List{ElementType: types.CodeSystem}, + }, + { + name: "System.Code.code", + parentType: types.Code, + property: "code", + want: types.String, + }, + { + name: "System.Quantity.value", + parentType: types.Quantity, + property: "value", + want: types.Decimal, + }, + { + name: "Subtype inherits properties of parent", + parentType: types.ValueSet, + property: "version", + want: types.String, + }, + } + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := modelinfo.PropertyTypeSpecifier(tc.parentType, tc.property) + if err != nil { + t.Fatalf("PropertyTypeSpecifier(%s, %s) failed unexpectedly: %v", tc.parentType, tc.property, err) + } + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("PropertyTypeSpecifier(%s, %s) diff (-want +got):\n%s", tc.parentType, tc.property, diff) + } + }) + } +} + +func TestPropertyTypeSpecifier_FHIRErrors(t *testing.T) { + cases := []struct { + name string + parentType types.IType + property string + wantErrContains string + }{ + { + name: "Missing parent type", + parentType: &types.Named{TypeName: "FHIR.MissingFakeType"}, + property: "status", + wantErrContains: "parentNamedType \"FHIR.MissingFakeType\" not found in data model", + }, + { + name: "Missing property type", + parentType: &types.Named{TypeName: "FHIR.Account"}, + property: "fake", + wantErrContains: "property \"fake\" not found in Parent Type \"FHIR.Account\"", + }, + { + name: "Deep nested property", + parentType: &types.Named{TypeName: "FHIR.Account"}, + property: "property1.property2", + wantErrContains: "internal error - property passed to PropertyTypeSpecifier should not contain \".\" only a single component of the property should be passed at a time", + }, + { + name: "unsupported property on interval", + parentType: &types.Interval{PointType: types.Integer}, + property: "fake", + wantErrContains: "invalid property on interval. got: fake, want: low, high, lowClosed, highClosed", + }, + { + name: "unsupported property on System.Quantity", + parentType: types.Quantity, + property: "fake", + wantErrContains: "property \"fake\" not found in Parent Type \"System.Quantity\"", + }, + { + name: "unsupported property on Choice", + parentType: &types.Choice{ChoiceTypes: []types.IType{types.Quantity}}, + property: "fake", + wantErrContains: "property \"fake\" not found in Parent Type \"Choice\"", + }, + { + name: "Tuple missing property", + parentType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Integer}}, + property: "bar", + wantErrContains: `Tuple does not have property "bar"`, + }, + } + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := modelinfo.PropertyTypeSpecifier(tc.parentType, tc.property) + if !strings.Contains(err.Error(), tc.wantErrContains) { + t.Errorf("PropertyTypeSpecifier(%s, %s) returned unexpected error: got: %v, want contains: %s", tc.parentType, tc.property, err, tc.wantErrContains) + } + }) + } +} + +func TestIsImplicitlyConvertible(t *testing.T) { + cases := []struct { + name string + from types.IType + to types.IType + want Convertible + }{ + { + name: "System.Integer -> System.Decimal", + from: types.Integer, + to: types.Decimal, + want: Convertible{ + IsConvertible: true, + Library: "SYSTEM", + Function: "ToDecimal", + }, + }, + { + name: "System.Date -> System.DateTime", + from: types.Date, + to: types.DateTime, + want: Convertible{ + IsConvertible: true, + Library: "SYSTEM", + Function: "ToDateTime", + }, + }, + { + name: "FHIR.Coding -> System.Code", + from: &types.Named{TypeName: "FHIR.Coding"}, + to: types.Code, + want: Convertible{ + IsConvertible: true, + Library: "FHIRHelpers", + Function: "ToCode", + }, + }, + { + name: "FHIR.Period -> Interval", + from: &types.Named{TypeName: "FHIR.Period"}, + to: &types.Interval{PointType: types.DateTime}, + want: Convertible{ + IsConvertible: true, + Library: "FHIRHelpers", + Function: "ToInterval", + }, + }, + } + + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := modelinfo.IsImplicitlyConvertible(tc.from, tc.to) + if err != nil { + t.Fatalf("IsImplicitlyConvertible(%s, %s) failed unexpectedly: %v", tc.from, tc.to, err) + } + if got != tc.want { + t.Errorf("IsImplicitlyConvertible(%s, %s) got: %v, want: %v", tc.from, tc.to, got, tc.want) + } + }) + } +} + +func TestBaseTypes(t *testing.T) { + cases := []struct { + name string + child types.IType + want []types.IType + }{ + { + name: "FHIR Modelinfo", + child: &types.Named{TypeName: "FHIR.Patient"}, + want: []types.IType{ + &types.Named{TypeName: "FHIR.DomainResource"}, + &types.Named{TypeName: "FHIR.Resource"}, + }, + }, + { + name: "Another FHIR Modelinfo", + child: &types.Named{TypeName: "FHIR.id"}, + want: []types.IType{ + &types.Named{TypeName: "FHIR.string"}, + &types.Named{TypeName: "FHIR.Element"}, + }, + }, + { + name: "Interval", + child: &types.Interval{PointType: types.ValueSet}, + want: []types.IType{&types.Interval{PointType: types.Vocabulary}}, + }, + { + name: "List", + child: &types.List{ElementType: types.ValueSet}, + want: []types.IType{&types.List{ElementType: types.Vocabulary}}, + }, + { + name: "Any", + child: types.Any, + want: []types.IType{}, + }, + { + name: "Choice", + child: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + want: []types.IType{}, + }, + { + name: "Tuple", + child: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet}}, + want: []types.IType{}, + }, + { + name: "List", + child: &types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet}}}, + want: []types.IType{}, + }, + } + + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := modelinfo.BaseTypes(tc.child) + if err != nil { + t.Fatalf("BaseTypes(%v) failed unexpectedly: %v", tc.child, err) + } + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("BaseTypes(%v) diff (-want +got):\n%s", tc.child, diff) + } + }) + } +} + +func TestBaseTypes_Errors(t *testing.T) { + cases := []struct { + name string + childType types.IType + wantErrContains string + }{ + { + name: "nil child type", + childType: nil, + wantErrContains: "cannot be nil", + }, + { + name: "Unknown child type", + childType: &types.Named{TypeName: "unknown"}, + wantErrContains: "Named not found in the data model", + }, + } + + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := modelinfo.BaseTypes(tc.childType) + if err == nil { + t.Fatalf("BaseTypes(%v) succeeded, expected (%v)", tc.childType, tc.wantErrContains) + } + if !strings.Contains(err.Error(), tc.wantErrContains) { + t.Fatalf("BaseTypes(%v) unexpected error. got: %v, want error contains: %v", tc.childType, err, tc.wantErrContains) + } + }) + } +} + +func TestIsSubType(t *testing.T) { + cases := []struct { + name string + childType types.IType + baseType types.IType + want bool + }{ + { + name: "FHIR.DomainResource is child of FHIR.Resource (FHIR.Resource -> FHIR.DomainResource)", + childType: &types.Named{TypeName: "FHIR.DomainResource"}, + baseType: &types.Named{TypeName: "FHIR.Resource"}, + want: true, + }, + { + name: "FHIR.Resource is not a child of FHIR.DomainResource (FHIR.Resource -> FHIR.DomainResource)", + childType: &types.Named{TypeName: "FHIR.Resource"}, + baseType: &types.Named{TypeName: "FHIR.DomainResource"}, + want: false, + }, + { + name: "FHIR.Patient is child of FHIR.Resource (FHIR.Resource -> FHIR.DomainResource -> FHIR.Patient)", + childType: &types.Named{TypeName: "FHIR.Patient"}, + baseType: &types.Named{TypeName: "FHIR.Resource"}, + want: true, + }, + { + name: "FHIR.id is child of FHIR.string (FHIR.id -> FHIR.string)", + childType: &types.Named{TypeName: "FHIR.id"}, + baseType: &types.Named{TypeName: "FHIR.string"}, + want: true, + }, + { + name: "Interval is child of Interval (FHIR.Resource -> FHIR.DomainResource -> FHIR.Patient)", + childType: &types.Interval{PointType: &types.Named{TypeName: "FHIR.Patient"}}, + baseType: &types.Interval{PointType: &types.Named{TypeName: "FHIR.Resource"}}, + want: true, + }, + { + name: "List is child of List (FHIR.Resource -> FHIR.DomainResource -> FHIR.Patient)", + childType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}, + baseType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Resource"}}, + want: true, + }, + { + name: "FHIR.Patient is child of System.Any (System.Any -> FHIR.Resource -> FHIR.DomainResource -> FHIR.Patient)", + childType: &types.Named{TypeName: "FHIR.Patient"}, + baseType: types.Any, + want: true, + }, + { + name: "System.Any is parent of ChoiceType", + childType: &types.Choice{ChoiceTypes: []types.IType{types.Integer}}, + baseType: types.Any, + want: true, + }, + { + name: "System.Any is parent of System.Valueset", + childType: types.ValueSet, + baseType: types.Any, + want: true, + }, + { + name: "System.Any is parent of System.Integer", + childType: types.Integer, + baseType: types.Any, + want: true, + }, + { + name: "System.Any is parent of Interval", + childType: &types.Interval{PointType: types.Integer}, + baseType: types.Any, + want: true, + }, + { + name: "System.Any is parent of Tuple", + childType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet}}, + baseType: types.Any, + want: true, + }, + { + name: "System.Any is not a parent", + childType: types.Any, + baseType: types.String, + want: false, + }, + { + name: "System.Vocabulary is a parent of System.Valueset", + childType: types.ValueSet, + baseType: types.Vocabulary, + want: true, + }, + { + name: "System.String is not a parent of ChoiceType", + childType: &types.Choice{ChoiceTypes: []types.IType{types.Integer}}, + baseType: types.String, + want: false, + }, + { + name: "Tuple is Subtype of Tuple", + childType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet, "bar": types.String}}, + baseType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Vocabulary, "bar": types.String}}, + want: true, + }, + { + name: "List is Subtype of List", + childType: &types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet, "bar": types.String}}}, + baseType: &types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Vocabulary, "bar": types.String}}}, + want: true, + }, + { + name: "Tuple is not Subtype unequal number of elements", + childType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet}}, + baseType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Vocabulary, "bar": types.String}}, + want: false, + }, + { + name: "Tuple is not Subtype non subtype elements", + childType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet, "bar": types.String}}, + baseType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Vocabulary, "bar": types.Integer}}, + want: false, + }, + { + name: "Tuple is not Subtype of other type", + childType: &types.Tuple{ElementTypes: map[string]types.IType{"foo": types.ValueSet, "bar": types.String}}, + baseType: &types.Named{TypeName: "FHIR.Patient"}, + want: false, + }, + } + + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := modelinfo.IsSubType(tc.childType, tc.baseType) + if err != nil { + t.Fatalf("IsSubType(%v, %v) failed unexpectedly: %v", tc.childType, tc.baseType, err) + } + if got != tc.want { + t.Errorf("IsSubType(%v, %v) got: %v, want: %v", tc.childType, tc.baseType, got, tc.want) + } + }) + } +} + +func TestIsSubType_FHIRErrors(t *testing.T) { + cases := []struct { + name string + childType types.IType + baseType types.IType + wantErrContains string + }{ + { + name: "nil child type", + childType: nil, + baseType: &types.Named{TypeName: "FHIR.Resource"}, + wantErrContains: "cannot be nil", + }, + { + name: "nil parent type", + childType: &types.Named{TypeName: "FHIR.Resource"}, + baseType: nil, + wantErrContains: "cannot be nil", + }, + } + + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := modelinfo.IsSubType(tc.childType, tc.baseType) + if err == nil { + t.Fatalf("IsSubType(%v, %v) succeeded, expected (%v)", tc.childType, tc.baseType, tc.wantErrContains) + } + if !strings.Contains(err.Error(), tc.wantErrContains) { + t.Fatalf("IsSubType(%v, %v) unexpected error. got: %v, want error contains: %v", tc.childType, tc.baseType, err, tc.wantErrContains) + } + }) + } +} + +func TestToNamed(t *testing.T) { + cases := []struct { + name string + input string + want *types.Named + }{ + { + name: "Unqualified", + input: "Patient", + want: &types.Named{TypeName: "FHIR.Patient"}, + }, + { + name: "Qualified", + input: "FHIR.Patient", + want: &types.Named{TypeName: "FHIR.Patient"}, + }, + { + name: "Unqualified Multi Level", + input: "Account.Coverage", + want: &types.Named{TypeName: "FHIR.Account.Coverage"}, + }, + { + name: "Qualified Multi Level", + input: "FHIR.Account.Coverage", + want: &types.Named{TypeName: "FHIR.Account.Coverage"}, + }, + } + + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := modelinfo.ToNamed(tc.input) + if err != nil { + t.Fatalf("ToNamed() failed unexpectedly: %v", err) + } + if !got.Equal(tc.want) { + t.Errorf("ToNamed() got: %v, want: %v", got, tc.want) + } + }) + } +} + +func TestToNamed_Errors(t *testing.T) { + cases := []struct { + name string + input string + wantErrContains string + }{ + { + name: "Unqualified Non Existent", + input: "Apple", + wantErrContains: "type Apple not found", + }, + { + name: "Qualified Non Existent", + input: "FHIR.Apple", + wantErrContains: "type FHIR.Apple not found", + }, + { + name: "System Type", + input: "Integer", + wantErrContains: "type Integer not found", + }, + { + name: "Empty", + input: "", + wantErrContains: "received an empty type, which is invalid", + }, + { + name: "Multi Level Non Existent", + input: "Apple.Banana", + wantErrContains: "type Apple.Banana not found", + }, + } + + modelinfo := newFHIRModelInfo(t) + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := modelinfo.ToNamed(tc.input) + if !strings.Contains(err.Error(), tc.wantErrContains) { + t.Fatalf("ToNamed() unexpected error. got: %v, want error contains: %v", err, tc.wantErrContains) + } + }) + } +} + +func TestToNamed_NotFoundError(t *testing.T) { + modelInfo := newFHIRModelInfo(t) + modelInfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + _, err := modelInfo.ToNamed("Apple") + if !errors.Is(err, ErrTypeNotFound) { + t.Errorf("ToNamed(%s) unexpected error. got: %v, want error contains: %v", "Apple", err, ErrTypeNotFound) + } +} + +func TestPatientBirthDatePropertyName(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + t.Run("PatientBirthDatePropertyName Error", func(t *testing.T) { + _, err := modelinfo.PatientBirthDatePropertyName() + if !errors.Is(err, errUsingNotSet) { + t.Errorf("PatientBirthDatePropertyName() unexpected error. got: %v, want error contains: %v", err, errUsingNotSet) + } + }) + + t.Run("PatientBirthDatePropertyName Success", func(t *testing.T) { + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + bDay, err := modelinfo.PatientBirthDatePropertyName() + wantBDay := "birthDate.value" + if err != nil { + t.Fatalf("PatientBirthDatePropertyName() failed unexpectedly: %v", err) + } + if bDay != wantBDay { + t.Errorf("PatientBirthDatePropertyName() got: %v, want: %v", bDay, wantBDay) + } + }) +} + +func TestURL(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + + t.Run("URL Error", func(t *testing.T) { + _, err := modelinfo.URL() + if !errors.Is(err, errUsingNotSet) { + t.Errorf("URL() unexpected error. got: %v, want error contains: %v", err, errUsingNotSet) + } + }) + + t.Run("URL Success", func(t *testing.T) { + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + url, err := modelinfo.URL() + wantURL := "http://hl7.org/fhir" + if err != nil { + t.Fatalf("URL() failed unexpectedly: %v", err) + } + if url != wantURL { + t.Errorf("URL() got: %v, want: %v", url, wantURL) + } + }) +} + +func TestDefaultContext(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + + t.Run("DefaultContext Error", func(t *testing.T) { + _, err := modelinfo.DefaultContext() + if !errors.Is(err, errUsingNotSet) { + t.Errorf("DefaultContext() unexpected error. got: %v, want error contains: %v", err, errUsingNotSet) + } + }) + + t.Run("DefaultContext Success", func(t *testing.T) { + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + ctx, err := modelinfo.DefaultContext() + wantCtx := "" + if err != nil { + t.Fatalf("DefaultContext() failed unexpectedly: %v", err) + } + if ctx != wantCtx { + t.Errorf("DefaultContext() got: %v, want: %v", ctx, wantCtx) + } + }) +} + +func TestNamedTypeInfo(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + + t.Run("NamedTypeInfo Error", func(t *testing.T) { + _, err := modelinfo.NamedTypeInfo(&types.Named{TypeName: "FHIR.Account.Coverage"}) + if !errors.Is(err, errUsingNotSet) { + t.Errorf("NamedTypeInfo() unexpected error. got: %v, want error contains: %v", err, errUsingNotSet) + } + }) + + t.Run("NamedTypeInfo Success", func(t *testing.T) { + modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + info, err := modelinfo.NamedTypeInfo(&types.Named{TypeName: "FHIR.Account.Coverage"}) + wantInfo := &TypeInfo{ + Name: "FHIR.Account.Coverage", + Properties: map[string]types.IType{"coverage": &types.Named{TypeName: "FHIR.Reference"}, "priority": &types.Named{TypeName: "FHIR.positiveInt"}}, + BaseType: "FHIR.BackboneElement", + } + if err != nil { + t.Fatalf("NamedTypeInfo() failed unexpectedly: %v", err) + } + if diff := cmp.Diff(info, wantInfo); diff != "" { + t.Errorf("NamedTypeInfo() returned unexpected diff (-got +want):\n%s", diff) + } + }) +} + +func TestSetUsing_Error(t *testing.T) { + t.Run("Data model does not exist", func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + err := modelinfo.SetUsing(Key{Name: "Apple", Version: "4.0.1"}) + wantErr := "Apple 4.0.1 data model not found" + if !strings.Contains(err.Error(), wantErr) { + t.Errorf("SetUsing() unexpected error. got (%v), want error contains: (%v)", err, wantErr) + } + }) + t.Run("Only one data model allowed", func(t *testing.T) { + modelinfo := newFHIRModelInfo(t) + err := modelinfo.SetUsing(Key{Name: "FHIR", Version: "4.0.1"}) + if err != nil { + t.Fatalf("SetUsing() failed unexpectedly: %v", err) + } + err = modelinfo.SetUsing(Key{Name: "Apple", Version: "4.0.1"}) + wantErr := "only one data model at a time is currently supported, but got FHIR 4.0.1 and Apple 4.0.1" + if !strings.Contains(err.Error(), wantErr) { + t.Errorf("SetUsing() unexpected error. got (%v), want error contains: (%v)", err, wantErr) + } + }) +} + +func newFHIRModelInfo(t *testing.T) *ModelInfos { + t.Helper() + fhirMIBytes, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + t.Fatalf("Reading embedded file %s failed unexpectedly: %v", "third_party/cqframework/fhir-modelinfo-4.0.1.xml", err) + } + + m, err := New([][]byte{fhirMIBytes}) + if err != nil { + t.Fatalf("New modelinfo unexpected error: %v", err) + } + return m +} diff --git a/internal/modelinfo/xml.go b/internal/modelinfo/xml.go new file mode 100644 index 0000000..a6fba41 --- /dev/null +++ b/internal/modelinfo/xml.go @@ -0,0 +1,155 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package modelinfo provides utilities for working with CQL ModelInfo XML files. +package modelinfo + +import ( + "encoding/xml" +) + +// modelInfoXML describes the underlying data model that the CQL is running over. It mainly consists +// of TypeInfo with metadata on each type, ConversionInfo which describes conversion between +// ModelInfo types and CQL system types and ContextInfos which describes the available CQL contexts. +// See the ModelInfo schema for the specification: https://cql.hl7.org/elm/schema/modelinfo.xsd +type modelInfoXML struct { + // Name that will be referenced by using statements in CQL. + Name string `xml:"name,attr"` + // Version that will be referenced by using statements in CQL if specified. + Version string `xml:"version,attr"` + URL string `xml:"url,attr"` + TargetQualifier string `xml:"targetQualifier,attr"` + PatientClassName string `xml:"patientClassName,attr"` + PatientBirthDatePropertyName string `xml:"patientBirthDatePropertyName,attr"` + // The default context to be used if the CQL does not specify one (ex Patient). + DefaultContext string `xml:"defaultContext,attr"` + TypeInfos []*typeInfoXML `xml:"typeInfo"` + ConversionInfos []*conversionInfo `xml:"conversionInfo"` + ContextInfos []*contextInfo `xml:"contextInfo"` +} + +// typeInfoXML describes the types in the data model. It describes which properties can be accessed, +// whether this type is retrievable, the name of the type as specified in CQL and more. +type typeInfoXML struct { + // BaseType creates a hierarchy of types which can be used in places like casting https://cql.hl7.org/03-developersguide.html#casting. + // System.Any -> FHIR.Resource -> FHIR.DomainResource + // System.Any -> FHIR.Element -> FHIR.BackboneElement + BaseType string `xml:"baseType,attr"` + // Namespace typically just the name of the model. In FHIR it is always set to "FHIR". + Namespace string `xml:"namespace,attr"` + // Name specifies the name of the type within the data model, in FHIR this is the name of the + // resource or for nested types could be something like Account.Coverage. + Name string `xml:"name,attr"` + // The identifier specifies a unique name for the class that may be independent of the name. In + // FHIR, this corresponds to the profile identifier. + Identifier string `xml:"identifier,attr"` + // Label specifies the name of the class as it is referenced from CQL. + Label string `xml:"label,attr"` + // Retrievable specifies whether the class can be used within a retrieve statement. + Retrievable bool `xml:"retrievable,attr"` + // PrimaryCodePath specifies the path that should be used to perform code filtering when a + // retrieve does not specify a code path. + PrimaryCodePath string `xml:"primaryCodePath,attr"` + // XSIType is always set to ClassInfo. + XSIType string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"` + // Elements holds the CQL properties that can be accessed on the type. + Elements []*element `xml:"element"` +} + +// element is a property that can be accessed on a particular typeInfo. Either the ElementType or +// ElementTypeSpecifier is set. +type element struct { + // Name is the name that can be accessed using property in CQL. + Name string `xml:"name,attr"` + // ElementType is set if this is a single instance of a type. It seems that all ElementTypes have + // conversionInfo and can be directly converted to a System type. There is no documentation + // confirming this, but it is true in FHIR. + ElementType string `xml:"elementType,attr"` + // Type is a deprecated field replaced by ElementType in newer model info versions. However, in + // the System model info this is still used. See more here: + // https://cql.hl7.org/elm/schema/modelinfo.xsd. + Type string `xml:"type,attr"` + // ElementTypeSpecifier is set for Lists or Choice types. + ElementTypeSpecifier *elementTypeSpecifier `xml:"elementTypeSpecifier"` + // TypeSpecifier a deprecated field replaced by ElementTypeSpecifier. However, it still exists in + // some model infos like the System model info. See more here: + // https://cql.hl7.org/elm/schema/modelinfo.xsd. + TypeSpecifier *elementTypeSpecifier `xml:"typeSpecifier"` +} + +// elementTypeSpecifier is used to specify list and choice types for elements. +// +// If a List then either the ElementType will be set if it is a list of types that can be converted +// to System types or ElementTypeSpecifier will be set and hold a namedTypeSpecifier pointing to +// types with their own TypeInfo. +// +// If a Choice then just Choices will be set. +type elementTypeSpecifier struct { + // Namespace is only set for NamedTypeSpecifiers. + Namespace string `xml:"namespace,attr"` + // ElementType is used for lists of types that have ConversionInfo. It includes the namespace "FHIR.CodeableConcept". + ElementType string `xml:"elementType,attr"` + // ElementTypeSpecifier is rarely set, but in FHIR when it is set it is for a List of NamedTypes. + ElementTypeSpecifier *elementTypeSpecifier `xml:"elementTypeSpecifier"` + // XSIType for FHIR is one of ListTypeSpecifier or ChoiceTypeSpecifier. If this is the + // elementTypeSpecifier of an elementTypeSpecifier aka a list of namedTypes then it will be + // NamedTypeSpecifier. + XSIType string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"` + // If the XSIType is ChoiceTypeSpecifier, the choice types are specified here. + Choices []*choice `xml:"choice"` + // Name is only set if this is a NamedTypeSpecifier, confusingly it is the type. + Name string `xml:"name,attr"` +} + +type choice struct { + Namespace string `xml:"namespace,attr"` + // Name confusingly is the type of this choice. + Name string `xml:"name,attr"` + // XSIType for FHIR is always NamedTypeSpecifier. + XSIType string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"` +} + +type conversionInfo struct { + FunctionName string `xml:"functionName,attr"` + // FromType in FHIR is always a FHIR type. + FromType string `xml:"fromType,attr"` + // ToType in FHIR is always a System type. + ToType string `xml:"toType,attr"` +} + +type contextInfo struct { + // Name that will be referenced by context statements within CQL. + Name string `xml:"name,attr"` + KeyElement string `xml:"keyElement,attr"` + BirthDateElement string `xml:"birthDateElement,attr"` + ContextType *contextType `xml:"contextType"` +} + +type contextType struct { + Namespace string `xml:"namespace,attr"` + Name string `xml:"name,attr"` + // ModelName is deprecated, but here for compatibility. + ModelName string `xml:"modelName,attr"` +} + +// parse loads a CQL data model from a model info XML byte array. +func parse(bytes []byte) (*modelInfoXML, error) { + info := &modelInfoXML{} + if err := xml.Unmarshal(bytes, &info); err != nil { + return nil, err + } + + // TODO(b/298104070): Any additional invariant checking for the model info content. + return info, nil +} diff --git a/internal/modelinfo/xml_test.go b/internal/modelinfo/xml_test.go new file mode 100644 index 0000000..b0f1610 --- /dev/null +++ b/internal/modelinfo/xml_test.go @@ -0,0 +1,221 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modelinfo + +import ( + "testing" + + "github.com/google/cql/internal/embeddata" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" +) + +func TestLoadModelInfo(t *testing.T) { + tests := []struct { + desc string + xml string + want *modelInfoXML + }{ + { + desc: "header_only", + xml: dedent.Dedent(` + + `), + want: &modelInfoXML{ + Name: "FHIR", + Version: "4.0.1", + URL: "http://hl7.org/fhir", + TargetQualifier: "fhir", + PatientClassName: "FHIR.Patient", + PatientBirthDatePropertyName: "birthDate.value", + }, + }, + { + desc: "minimal_resource_test", + xml: dedent.Dedent(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `), + want: &modelInfoXML{ + Name: "FHIR", + Version: "4.0.1", + URL: "http://hl7.org/fhir", + TargetQualifier: "fhir", + PatientClassName: "FHIR.Patient", + PatientBirthDatePropertyName: "birthDate.value", + TypeInfos: []*typeInfoXML{ + &typeInfoXML{ + BaseType: "FHIR.DomainResource", + Name: "FakeCondition", + Namespace: "FHIR", + Identifier: "http://hl7.org/fhir/StructureDefinition/FakeCondition", + PrimaryCodePath: "fakeCode", + XSIType: "ClassInfo", + Label: "FakeCondition", + Retrievable: true, + Elements: []*element{ + &element{Name: "clinicalStatus", ElementType: "FHIR.CodeableConcept"}, + &element{Name: "fakeCode", ElementType: "FHIR.CodeableConcept"}, + &element{ + Name: "event", + ElementTypeSpecifier: &elementTypeSpecifier{ + ElementType: "FHIR.CodeableConcept", + XSIType: "ListTypeSpecifier"}, + }, + &element{ + Name: "link", + ElementTypeSpecifier: &elementTypeSpecifier{ + ElementTypeSpecifier: &elementTypeSpecifier{Namespace: "FHIR", Name: "Bundle.Link", XSIType: "NamedTypeSpecifier"}, + XSIType: "ListTypeSpecifier"}, + }, + &element{ + Name: "subject", + ElementTypeSpecifier: &elementTypeSpecifier{ + Choices: []*choice{ + &choice{ + Namespace: "FHIR", + Name: "CodeableConcept", + XSIType: "NamedTypeSpecifier", + }, + &choice{ + Namespace: "FHIR", + Name: "Reference", + XSIType: "NamedTypeSpecifier", + }, + }, + XSIType: "ChoiceTypeSpecifier"}, + }, + }, + }, + &typeInfoXML{ + BaseType: "FHIR.Element", + Name: "AggregationMode", + Namespace: "FHIR", + XSIType: "ClassInfo", + Retrievable: false, + Elements: []*element{ + &element{Name: "value", ElementType: "System.String"}, + }, + }, + }, + ConversionInfos: []*conversionInfo{ + &conversionInfo{ + FunctionName: "FHIRHelpers.ToCode", + FromType: "FHIR.Coding", + ToType: "System.Code", + }, + &conversionInfo{ + FunctionName: "FHIRHelpers.ToConcept", + FromType: "FHIR.CodeableConcept", + ToType: "System.Concept", + }, + }, + ContextInfos: []*contextInfo{ + &contextInfo{ + Name: "Patient", + KeyElement: "id", + BirthDateElement: "birthDate.value", + ContextType: &contextType{Namespace: "FHIR", Name: "Patient"}, + }, + &contextInfo{ + Name: "Encounter", + KeyElement: "id", + ContextType: &contextType{Namespace: "FHIR", Name: "Encounter", ModelName: "modelName"}, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + got, err := parse([]byte(test.xml)) + if err != nil { + t.Fatalf("LoadModel failed: %v", err) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("LoadModel() diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestLoadModelInfo_SanityCheckEmbeddedModelInfos(t *testing.T) { + // This is a sanity check test, checking that loading in the embedded model info files + // work without returning an error. + cases := []struct { + name string + path string + }{ + { + name: "FHIR 4.0.1", + path: "third_party/cqframework/fhir-modelinfo-4.0.1.xml", + }, + { + name: "System ModelInfo", + path: "third_party/cqframework/system-modelinfo.xml", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + data, err := embeddata.ModelInfos.ReadFile(tc.path) + if err != nil { + t.Fatalf("Reading embedded file %s failed unexpectedly: %v", tc.path, err) + } + if _, err := parse(data); err != nil { + t.Fatalf("loadModel(%s) failed unexpectedly: %v", tc.path, err) + } + }) + } +} diff --git a/internal/reference/reference.go b/internal/reference/reference.go new file mode 100644 index 0000000..33f1567 --- /dev/null +++ b/internal/reference/reference.go @@ -0,0 +1,566 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package reference handles resolving references across CQL libraries and locally within a library +// for the CQL Engine parser and interpreter. +package reference + +import ( + "errors" + "fmt" + + "github.com/google/cql/internal/convert" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +// Resolver tracks definitions (ExpressionDefs, ParameterDefs, ValueSetDefs...) and aliases across +// CQL libraries and locally within a CQL library. When a definition is created the resolver stores +// a result (for the parser it stores a model.IExpression and for the interpreter a result.Value). +// When a reference to a definition is resolved the result is returned. Resolvers should not be +// shared between the parser and interpreter. A new empty resolver should be passed to the +// interpreter. +type Resolver[T any, F any] struct { + // defs holds all expression, value sets and parameter definitions in all CQL libraries. funcs + // holds all built-in functions and all user defined functions in all CQL libraries. The current + // CQL library does not have access to every definition in defs, or function in funcs, only + // included libraries which is mapped by includedLibs. Functions and definitions live in separate + // namespaces, a function can have the same name as an expression definition. Functions can be + // overloaded, having the same name but different operands. + defs map[defKey]exprDef[T] + funcs map[defKey][]funcDef[F] + + // builtinFuncs holds all CQL built-in functions. builtinFuncs is only used by the parser. The + // parser coverts all built-in function calls into specific structs in model.go, so the + // interpreter does not need to resolve any built-in functions. Built-in functions are different + // from user defined functions because they do not belong to a particular library or have access + // modifiers. + builtinFuncs map[string][]convert.Overload[F] + + // aliases, unlike the other maps are not persistent between CQL libraries or even across scopes. + // Aliases work like a stack and are cleared once we exit the scope in which the alias was + // defined. Aliases live in the same namespace as definitions. + aliases []map[aliasKey]T + + // libs holds the qualified identifier of all named libraries that have been parsed. + libs map[namedLibKey]struct{} + + // includedLibs maps the local identifier of included libraries to their qualified identifier. The + // following CQL would result in: + // + // library measure + // include helpers called help + // + // includedLibs[{localID: "help", includedBy: {qualified: "measure"}}]: {Qualified: "helpers", ...} + includedLibs map[includeKey]*model.LibraryIdentifier + + // currLib is the current library being parsed. + currLib libKey + + // unnamedCount is used to generate a unique unnamedLibKey for unnamed libraries. + unnamedCount int +} + +type exprDef[T any] struct { + isPublic bool + result T +} + +type funcDef[F any] struct { + isPublic bool + isFluent bool + overload convert.Overload[F] +} + +// NewResolver creates a blank resolver with zero global references. Type T is the type saved and +// resolved for definitions. Type F is the type saved and resolved for functions. +func NewResolver[T any, F any]() *Resolver[T, F] { + return &Resolver[T, F]{ + defs: make(map[defKey]exprDef[T]), + funcs: make(map[defKey][]funcDef[F]), + builtinFuncs: make(map[string][]convert.Overload[F]), + aliases: make([]map[aliasKey]T, 0), + libs: make(map[namedLibKey]struct{}), + includedLibs: make(map[includeKey]*model.LibraryIdentifier), + } +} + +// ClearDefs clears everything except for the built-in functions. +func (r *Resolver[T, F]) ClearDefs() { + r.defs = make(map[defKey]exprDef[T]) + r.funcs = make(map[defKey][]funcDef[F]) + r.aliases = make([]map[aliasKey]T, 0) + r.libs = make(map[namedLibKey]struct{}) + r.includedLibs = make(map[includeKey]*model.LibraryIdentifier) +} + +// SetCurrentLibrary sets the current library based on the library definition. Either +// SetCurrentLibrary or SetCurrentUnnamed must be called before creating and resolving references. +func (r *Resolver[T, F]) SetCurrentLibrary(m *model.LibraryIdentifier) error { + l := namedLibKey{qualified: m.Qualified, version: m.Version} + if _, ok := r.libs[l]; ok { + return fmt.Errorf("library %s %s already exists", m.Qualified, m.Version) + } + r.currLib = l + r.libs[l] = struct{}{} + return nil +} + +// SetCurrentUnnamed should be called if the CQL library does not have a library definition. All +// definitions in unnamed libraries are private. +func (r *Resolver[T, F]) SetCurrentUnnamed() { + l := unnamedLibKey{unnamedID: r.unnamedCount} + r.currLib = l + r.unnamedCount++ +} + +// IncludeLibrary should be called for each include statement in the CQL library. IncludeLibrary +// must be called before a reference to that library is resolved. ValidateIsUnique validates this +// include is unique. It is turned off by the +// interpreter to improve performance. +func (r *Resolver[T, F]) IncludeLibrary(m *model.LibraryIdentifier, validateIsUnique bool) error { + if validateIsUnique { + if err := r.isLocallyUnique(m.Local); err != nil { + return err + } + } + + lib := namedLibKey{qualified: m.Qualified, version: m.Version} + if _, ok := r.libs[lib]; !ok { + return fmt.Errorf("library %s %s was included, but does not exist", m.Qualified, m.Version) + } + + r.includedLibs[includeKey{localID: m.Local, includedBy: r.currLib}] = m + return nil +} + +// ResolveInclude takes the local name of an included library and returns the fully qualified +// identifier or nil if this local name does not exist. +func (r *Resolver[T, F]) ResolveInclude(name string) *model.LibraryIdentifier { + iKey := includeKey{localID: name, includedBy: r.currLib} + if i, ok := r.includedLibs[iKey]; ok { + return i + } + return nil +} + +// Def holds the information needed to define a definition. +type Def[T any] struct { + Name string + Result T + IsPublic bool + // ValidateIsUnique validates this definition name is unique. It is turned off by the interpreter + // to improve performance. + ValidateIsUnique bool +} + +// Define creates a new definition returning an error if the name already exists. Calling +// ResolveLocal with the same name will return the stored type t. Names must be unique within the +// CQL library. Names must be unique regardless of type, for example a ValueSet and Parameter cannot +// have the same name. +func (r *Resolver[T, F]) Define(d *Def[T]) error { + if d.ValidateIsUnique { + if err := r.isLocallyUnique(d.Name); err != nil { + return err + } + } + + _, isUnamed := r.currLib.(unnamedLibKey) + r.defs[defKey{r.currLib, d.Name}] = exprDef[T]{isPublic: d.IsPublic && !isUnamed, result: d.Result} + return nil +} + +// Func holds the information needed to define a function. +type Func[F any] struct { + Name string + Operands []types.IType + Result F + IsPublic bool + IsFluent bool + // ValidateIsUnique validates this function name + signature is unique. It is turned off by the + // interpreter to improve performance. + ValidateIsUnique bool +} + +// DefineFunc creates a new user defined function returning an error if the function signature +// already exists. Calling ResolveLocalFunc with the same name and operands will return the stored +// type f. Functions can be overloaded with the same name, but must have a unique combination of +// name and operands. +func (r *Resolver[T, F]) DefineFunc(f *Func[F]) error { + if f.ValidateIsUnique { + if err := r.isFuncLocallyUnique(f.Name, f.Operands); err != nil { + return err + } + } + + dKey := defKey{r.currLib, f.Name} + _, isUnamed := r.currLib.(unnamedLibKey) + fDef := funcDef[F]{ + isPublic: f.IsPublic && !isUnamed, + isFluent: f.IsFluent, + overload: convert.Overload[F]{Operands: f.Operands, Result: f.Result}, + } + r.funcs[dKey] = append(r.funcs[dKey], fDef) + return nil +} + +// DefineBuiltinFunc creates a new built-in function, returning an error if the function signature +// already exists. All built in functions must be defined before CQL libraries are parsed. Only the +// Parser defines built-in functions. +// TODO(b/301606416): Refactor DefineBuiltinFunc into the initialization of the reference resolver. +func (r *Resolver[T, F]) DefineBuiltinFunc(name string, operands []types.IType, f F) error { + if overloads, ok := r.builtinFuncs[name]; ok { + for _, overload := range overloads { + if exactMatch(operands, overload.Operands) { + return fmt.Errorf("internal error - built-in CQL function %v(%v) already exists", name, types.ToStrings(operands)) + } + } + } + + r.builtinFuncs[name] = append(r.builtinFuncs[name], convert.Overload[F]{Operands: operands, Result: f}) + return nil +} + +// ResolveGlobal resolves a reference to a definition in an included CQL library. +func (r *Resolver[T, F]) ResolveGlobal(libName string, defName string) (T, error) { + iKey := includeKey{localID: libName, includedBy: r.currLib} + qKey, ok := r.includedLibs[iKey] + if !ok { + return zero[T](), fmt.Errorf("could not resolve the library name %s", libName) + } + + dKey := defKey{namedLibKey{qualified: qKey.Qualified, version: qKey.Version}, defName} + a, ok := r.defs[dKey] + if !ok { + return zero[T](), fmt.Errorf("could not resolve the reference to %s.%s", libName, defName) + } + if !a.isPublic { + return zero[T](), fmt.Errorf("%s.%s is not public", libName, defName) + } + + return a.result, nil +} + +// ResolveGlobalFunc resolves a reference to a user defined function in an included CQL library. +func (r *Resolver[T, F]) ResolveGlobalFunc(libName string, defName string, operands []model.IExpression, calledFluently bool, modelInfo *modelinfo.ModelInfos) (*convert.MatchedOverload[F], error) { + iKey := includeKey{localID: libName, includedBy: r.currLib} + qKey, ok := r.includedLibs[iKey] + if !ok { + return nil, fmt.Errorf("could not resolve the library name %s", libName) + } + + dKey := defKey{namedLibKey{qualified: qKey.Qualified, version: qKey.Version}, defName} + var overloads []convert.Overload[F] + if fDefs, ok := r.funcs[dKey]; ok { + // Filter overloads that are not public or fluent before calling OverloadMatch. + for _, fDef := range fDefs { + if fDef.isPublic { + if !calledFluently || (calledFluently && fDef.isFluent) { + overloads = append(overloads, fDef.overload) + } + } + } + } + + ref, err := convert.OverloadMatch(operands, overloads, modelInfo, fmt.Sprintf("%v.%v", libName, defName)) + if err != nil { + return nil, err + } + return &ref, nil +} + +// ResolveExactGlobalFunc resolves a reference to a user defined function in an included CQL library +// without any implicit conversions. +func (r *Resolver[T, F]) ResolveExactGlobalFunc(libName string, defName string, operands []types.IType, calledFluently bool, modelInfo *modelinfo.ModelInfos) (F, error) { + iKey := includeKey{localID: libName, includedBy: r.currLib} + qKey, ok := r.includedLibs[iKey] + if !ok { + return zero[F](), fmt.Errorf("could not resolve the library name %s", libName) + } + + dKey := defKey{namedLibKey{qualified: qKey.Qualified, version: qKey.Version}, defName} + var overloads []convert.Overload[F] + if fDefs, ok := r.funcs[dKey]; ok { + // Filter overloads that are not public or fluent before calling ExactOverloadMatch. + for _, fDef := range fDefs { + if fDef.isPublic { + if !calledFluently || (calledFluently && fDef.isFluent) { + overloads = append(overloads, fDef.overload) + } + } + } + } + + ref, err := convert.ExactOverloadMatch(operands, overloads, modelInfo, fmt.Sprintf("%v.%v", libName, defName)) + if err != nil { + return zero[F](), err + } + return ref, nil +} + +// ResolveLocal resolves a reference to a definition in the current CQL library. +func (r *Resolver[T, F]) ResolveLocal(name string) (T, error) { + dKey := defKey{r.currLib, name} + if a, ok := r.defs[dKey]; ok { + return a.result, nil + } + + aKey := aliasKey{r.currLib, name} + if a, ok := r.findAlias(aKey); ok { + return a, nil + } + + return zero[T](), fmt.Errorf("could not resolve the local reference to %s", name) +} + +// ResolveLocalFunc resolves a reference to a user defined or built-in function in the current CQL +// library. +func (r *Resolver[T, F]) ResolveLocalFunc(name string, operands []model.IExpression, calledFluently bool, modelInfo *modelinfo.ModelInfos) (*convert.MatchedOverload[F], error) { + overloads := make([]convert.Overload[F], 0) + if overs, ok := r.builtinFuncs[name]; ok { + overloads = append(overloads, overs...) + } + + fDefs, ok := r.funcs[defKey{r.currLib, name}] + if ok { + // Filter overloads that are fluent before calling OverloadMatch. + for _, fDef := range fDefs { + if !calledFluently || (calledFluently && fDef.isFluent) { + overloads = append(overloads, fDef.overload) + } + } + } + + ref, err := convert.OverloadMatch(operands, overloads, modelInfo, name) + if err != nil { + return nil, err + } + return &ref, nil +} + +// ResolveExactLocalFunc resolves a reference to a user defined function in the current CQL library +// without any implicit conversions. +func (r *Resolver[T, F]) ResolveExactLocalFunc(name string, operands []types.IType, calledFluently bool, modelInfo *modelinfo.ModelInfos) (F, error) { + overloads := make([]convert.Overload[F], 0) + if overs, ok := r.builtinFuncs[name]; ok { + overloads = append(overloads, overs...) + } + + fDefs, ok := r.funcs[defKey{r.currLib, name}] + if ok { + // Filter overloads that are fluent before calling ExactOverloadMatch. + for _, fDef := range fDefs { + if !calledFluently || (calledFluently && fDef.isFluent) { + overloads = append(overloads, fDef.overload) + } + } + } + + ref, err := convert.ExactOverloadMatch(operands, overloads, modelInfo, name) + if err != nil { + return zero[F](), err + } + return ref, nil +} + +// EnterScope starts a new scope for aliases. EndScope should be called to remove all aliases in +// this scope. +func (r *Resolver[T, F]) EnterScope() { + r.aliases = append(r.aliases, make(map[aliasKey]T)) +} + +// ExitScope clears any aliases created since the last call to EnterScope. +func (r *Resolver[T, F]) ExitScope() { + if len(r.aliases) > 0 { + r.aliases = r.aliases[:len(r.aliases)-1] + } +} + +// Alias creates a new alias within the current scope. When EndScope is called all aliases in the +// scope will be removed. Calling ResolveLocal with the same name will return the stored type t. +// Names must be unique within the CQL library. +func (r *Resolver[T, F]) Alias(name string, a T) error { + if len(r.aliases) == 0 { + return errors.New("internal error - EnterScope must be called before creating an alias") + } + if err := r.isLocallyUnique(name); err != nil { + return err + } + aKey := aliasKey{r.currLib, name} + r.aliases[len(r.aliases)-1][aKey] = a + return nil +} + +// PublicDefs returns the public definitions stored in the reference resolver. +func (r *Resolver[T, F]) PublicDefs() (map[result.LibKey]map[string]T, error) { + pDefs := make(map[result.LibKey]map[string]T) + for k, v := range r.defs { + if v.isPublic { + namedK, ok := k.library.(namedLibKey) + if !ok { + return nil, fmt.Errorf("internal error - %v is not a namedLibKey", k.library) + } + lKey := result.LibKey{Name: namedK.qualified, Version: namedK.version} + if _, ok := pDefs[lKey]; !ok { + pDefs[lKey] = make(map[string]T) + } + pDefs[lKey][k.name] = v.result + } + } + return pDefs, nil +} + +// PublicAndPrivateDefs should not be used for normal engine execution. +// Returns all public and private definitions, including definitions in unnamed +// libraries. Unnamed libraries are converted to UnnamedLibrary-0 1.0, UnnamedLibrary-1 1.0 +// and so on which can clash with any named libraries that happened to be named +// UnnamedLibrary-0. Therefore this should only be used for unit tests and the CQL REPL. +func (r *Resolver[T, F]) PublicAndPrivateDefs() (map[result.LibKey]map[string]T, error) { + defs := make(map[result.LibKey]map[string]T) + for k, v := range r.defs { + var lKey result.LibKey + switch tk := k.library.(type) { + case namedLibKey: + lKey = result.LibKey{Name: tk.qualified, Version: tk.version} + case unnamedLibKey: + lKey = result.LibKey{Name: fmt.Sprintf("UnnamedLibrary-%d", tk.unnamedID), Version: "1.0"} + default: + return nil, fmt.Errorf("internal error - %v is an unexpected key type", k.library) + } + + if _, ok := defs[lKey]; !ok { + defs[lKey] = make(map[string]T) + } + defs[lKey][k.name] = v.result + } + return defs, nil +} + +// isLocallyUnique checks if the name is unique within the current CQL library. +func (r *Resolver[T, F]) isLocallyUnique(name string) error { + dKey := defKey{r.currLib, name} + if _, ok := r.defs[dKey]; ok { + return fmt.Errorf("identifier %v already exists in this CQL library", dKey.name) + } + + iKey := includeKey{localID: name, includedBy: r.currLib} + if _, ok := r.includedLibs[iKey]; ok { + return fmt.Errorf("identifier %v already exists in this CQL library", iKey.localID) + } + + aKey := aliasKey{r.currLib, name} + + if _, ok := r.findAlias(aKey); ok { + return fmt.Errorf("alias %v already exists", aKey.name) + } + + return nil +} + +func (r *Resolver[T, F]) isFuncLocallyUnique(name string, operands []types.IType) error { + if overloads, ok := r.builtinFuncs[name]; ok { + for _, overload := range overloads { + if exactMatch(operands, overload.Operands) { + return fmt.Errorf("built-in CQL function %v(%v) already exists", name, types.ToStrings(operands)) + } + } + } + + dKey := defKey{r.currLib, name} + if overloads, ok := r.funcs[dKey]; ok { + for _, overload := range overloads { + if exactMatch(operands, overload.overload.Operands) { + return fmt.Errorf("function %v(%v) already exists", dKey.name, types.ToStrings(operands)) + } + } + } + return nil +} + +func (r *Resolver[T, F]) findAlias(aKey aliasKey) (T, bool) { + for _, aMap := range r.aliases { + if t, ok := aMap[aKey]; ok { + return t, true + } + } + return zero[T](), false +} + +func exactMatch(ops1, ops2 []types.IType) bool { + if len(ops1) != len(ops2) { + return false + } + for i := range ops1 { + if !ops1[i].Equal(ops2[i]) { + return false + } + } + return true +} + +type libKey interface { + // libKey is used as the key in maps. Struct that implements it should be comparable. + isComparableLibKey() +} + +func isComparable[_ comparable]() {} + +type namedLibKey struct { + qualified string + version string // Empty if no version was specified. +} + +func (k namedLibKey) isComparableLibKey() {} + +func _[P namedLibKey]() { + // Enforces at build time that namedLibKey is comparable. + _ = isComparable[P] +} + +// An unnamed library is when there is no library definition in the CQL library ex) `library Test +// version '1'`. All definitions in unnamed libraries are private. +type unnamedLibKey struct { + unnamedID int +} + +func (k unnamedLibKey) isComparableLibKey() {} + +func _[P unnamedLibKey]() { + // Enforces at build time that unnamedLibKey is comparable. + _ = isComparable[P] +} + +type defKey struct { + library libKey + name string +} + +type includeKey struct { + localID string + // includedBy is the library that is including the library localID. + includedBy libKey +} + +type aliasKey struct { + library libKey + name string +} + +// zero is a helper function to return the Zero value of a generic type T. +func zero[T any]() T { + var zero T + return zero +} diff --git a/internal/reference/reference_test.go b/internal/reference/reference_test.go new file mode 100644 index 0000000..18896d0 --- /dev/null +++ b/internal/reference/reference_test.go @@ -0,0 +1,1067 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reference + +import ( + "strings" + "testing" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" +) + +func TestParserDefAndResolve(t *testing.T) { + tests := []struct { + name string + want model.IExpression + resolverCalls func(*Resolver[model.IExpression, model.IExpression]) model.IExpression + }{ + { + name: "Resolve Local Def", + want: &model.ExpressionRef{Name: "public def"}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + // This also tests that two expressions named "public def" in two different libraries don't + // clash. + d := &Def[model.IExpression]{ + Name: "public def", + Result: &model.ExpressionRef{Name: "public def"}, + IsPublic: true, + ValidateIsUnique: true, + } + if err := r.Define(d); err != nil { + t.Errorf("Define(public def) unexpected err: %v", err) + } + got, err := r.ResolveLocal("public def") + if err != nil { + t.Errorf("ResolveLocal(public def) unexpected err: %v", err) + } + return got + }, + }, + { + name: "Resolve Built-in Func", + want: &model.Last{}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + ops := []model.IExpression{model.NewList([]string{"4"}, types.Integer)} + opTypes := []types.IType{&types.List{ElementType: types.Any}} + if err := r.DefineBuiltinFunc("builtin func", opTypes, &model.Last{}); err != nil { + t.Errorf("DefineBuiltinFunc(builtin func) unexpected err: %v", err) + } + got, err := r.ResolveLocalFunc("builtin func", ops, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveLocalFunc(builtin func) unexpected err: %v", err) + } + wantWrappedOperands := []model.IExpression{model.NewList([]string{"4"}, types.Integer)} + if diff := cmp.Diff(wantWrappedOperands, got.WrappedOperands); diff != "" { + t.Errorf("ResolveLocalFunc() diff (-want +got):\n%s", diff) + } + return got.Result + }, + }, + { + name: "Resolve Local Func", + want: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + // This also tests that two funcs named "public func" in two different libraries don't + // clash. + f := &Func[model.IExpression]{ + Name: "public func", + Operands: []types.IType{types.Integer}, + Result: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(f); err != nil { + t.Errorf("DefineFunc(public func) unexpected err: %v", err) + } + got, err := r.ResolveLocalFunc("public func", []model.IExpression{model.NewLiteral("4", types.Integer)}, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveLocalFunc(public func) unexpected err: %v", err) + } + wantWrappedOperands := []model.IExpression{model.NewLiteral("4", types.Integer)} + if diff := cmp.Diff(wantWrappedOperands, got.WrappedOperands); diff != "" { + t.Errorf("ResolveLocalFunc() diff (-want +got):\n%s", diff) + } + return got.Result + }, + }, + { + name: "Resolve Local Func Based on Operands", + want: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + f := &Func[model.IExpression]{ + Name: "public func", + Operands: []types.IType{}, + Result: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(f); err != nil { + t.Errorf("DefineFunc(public func) unexpected err: %v", err) + } + + fSameName := &Func[model.IExpression]{ + Name: "public func", + Operands: []types.IType{types.DateTime}, + Result: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(fSameName); err != nil { + t.Errorf("DefineFunc(public func) unexpected err: %v", err) + } + + got, err := r.ResolveLocalFunc("public func", []model.IExpression{model.NewLiteral("2023-06-30", types.Date)}, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveLocalFunc(public func) unexpected err: %v", err) + } + + // This also tests that the operand is wrapped in model.ToDateTime. + wantWrappedOperands := []model.IExpression{ + &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("2023-06-30", types.Date), + Expression: model.ResultType(types.DateTime), + }, + }, + } + if diff := cmp.Diff(wantWrappedOperands, got.WrappedOperands); diff != "" { + t.Errorf("ResolveLocalFunc() diff (-want +got):\n%s", diff) + } + return got.Result + }, + }, + { + name: "Resolve Fluent Local Func", + want: &model.FunctionRef{Name: "is fluent", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + f := &Func[model.IExpression]{ + Name: "fluent func", + Operands: []types.IType{types.DateTime}, + Result: &model.FunctionRef{Name: "is fluent", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: true, + ValidateIsUnique: true, + } + if err := r.DefineFunc(f); err != nil { + t.Errorf("DefineFunc(fluent func) unexpected err: %v", err) + } + + // We don't match this one even though it is an exact match because it is not a fluent function. + fNotFluent := &Func[model.IExpression]{ + Name: "fluent func", + Operands: []types.IType{types.Date}, + Result: &model.FunctionRef{Name: "not fluent", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(fNotFluent); err != nil { + t.Errorf("DefineFunc(fluent func) unexpected err: %v", err) + } + + got, err := r.ResolveLocalFunc("fluent func", []model.IExpression{model.NewLiteral("2023-06-30", types.Date)}, true, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveLocalFunc(public func) unexpected err: %v", err) + } + return got.Result + }, + }, + { + name: "Resolve Local Built-in Func based on Operands", + want: &model.Last{}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + ops1 := []model.IExpression{model.NewLiteral("4", types.Integer)} + opTypes1 := []types.IType{types.Integer} + if err := r.DefineBuiltinFunc("builtin func", opTypes1, &model.Last{}); err != nil { + t.Errorf("DefineBuiltinFunc(builtin func) unexpected err: %v", err) + } + + f := &Func[model.IExpression]{ + Name: "builtin func", + Operands: []types.IType{types.String}, + Result: &model.FunctionRef{}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(f); err != nil { + t.Errorf("DefineFunc(builtin func) unexpected err: %v", err) + } + + got, err := r.ResolveLocalFunc("builtin func", ops1, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveLocalFunc(builtin func) unexpected err: %v", err) + } + wantWrappedOperands := []model.IExpression{model.NewLiteral("4", types.Integer)} + if diff := cmp.Diff(wantWrappedOperands, got.WrappedOperands); diff != "" { + t.Errorf("ResolveLocalFunc(builtin func) diff (-want +got):\n%s", diff) + } + return got.Result + }, + }, + { + name: "Resolve Exact Built-in Func", + want: &model.Last{}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + opTypes := []types.IType{&types.List{ElementType: types.Any}} + if err := r.DefineBuiltinFunc("builtin func", opTypes, &model.Last{}); err != nil { + t.Errorf("DefineBuiltinFunc(builtin func) unexpected err: %v", err) + } + got, err := r.ResolveExactLocalFunc("builtin func", opTypes, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveExactLocalFunc(builtin func) unexpected err: %v", err) + } + return got + }, + }, + { + name: "Resolve Exact Local Func", + want: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + // This also tests that two funcs named "public func" in two different libraries don't + // clash. + f := &Func[model.IExpression]{ + Name: "public func", + Operands: []types.IType{types.Integer}, + Result: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(f); err != nil { + t.Errorf("DefineFunc(public func) unexpected err: %v", err) + } + got, err := r.ResolveExactLocalFunc("public func", []types.IType{types.Integer}, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveExactLocalFunc(public func) unexpected err: %v", err) + } + return got + }, + }, + { + name: "Resolve Exact Fluent Local Func by Subtype", + want: &model.FunctionRef{Name: "is fluent", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + f := &Func[model.IExpression]{ + Name: "fluent func", + Operands: []types.IType{types.Vocabulary}, + Result: &model.FunctionRef{Name: "is fluent", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: true, + ValidateIsUnique: true, + } + if err := r.DefineFunc(f); err != nil { + t.Errorf("DefineFunc(fluent func) unexpected err: %v", err) + } + + // We don't match this one even though it is an exact match because it is not a fluent function. + fNotFluent := &Func[model.IExpression]{ + Name: "fluent func", + Operands: []types.IType{types.ValueSet}, + Result: &model.FunctionRef{Name: "not fluent", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(fNotFluent); err != nil { + t.Errorf("DefineFunc(fluent func) unexpected err: %v", err) + } + + got, err := r.ResolveExactLocalFunc("fluent func", []types.IType{types.ValueSet}, true, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveExactLocalFunc(public func) unexpected err: %v", err) + } + return got + }, + }, + { + name: "Resolve Global Def", + want: &model.ExpressionRef{Name: "public def"}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + got, err := r.ResolveGlobal("helpers", "public def") + if err != nil { + t.Errorf("ResolveGlobal(helpers, public def) unexpected err: %v", err) + } + return got + }, + }, + { + name: "Resolve Global Func", + want: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + ops := []model.IExpression{model.NewLiteral("Apple", types.String)} + got, err := r.ResolveGlobalFunc("helpers", "public func", ops, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveGlobalFunc() unexpected err: %v", err) + } + wantWrappedOperands := []model.IExpression{model.NewLiteral("Apple", types.String)} + if diff := cmp.Diff(wantWrappedOperands, got.WrappedOperands); diff != "" { + t.Errorf("ResolveGlobalFunc() diff (-want +got):\n%s", diff) + } + return got.Result + }, + }, + { + name: "Resolve Global Fluent Func", + want: &model.FunctionRef{Name: "public fluent func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + ops := []model.IExpression{model.NewLiteral("Apple", types.String)} + got, err := r.ResolveGlobalFunc("helpers", "public fluent func", ops, true, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveGlobalFunc() unexpected err: %v", err) + } + wantWrappedOperands := []model.IExpression{model.NewLiteral("Apple", types.String)} + if diff := cmp.Diff(wantWrappedOperands, got.WrappedOperands); diff != "" { + t.Errorf("ResolveGlobalFunc() diff (-want +got):\n%s", diff) + } + return got.Result + }, + }, + { + name: "Resolve Exact Global Func", + want: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + opTypes := []types.IType{types.String} + got, err := r.ResolveExactGlobalFunc("helpers", "public func", opTypes, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveExactGlobalFunc() unexpected err: %v", err) + } + return got + }, + }, + { + name: "Resolve Global Fluent Func by Subtype", + want: &model.FunctionRef{Name: "public fluent func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + opTypes := []types.IType{types.String} + got, err := r.ResolveExactGlobalFunc("helpers", "public fluent func", opTypes, true, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveExactGlobalFunc() unexpected err: %v", err) + } + return got + }, + }, + { + name: "Unnamed Def", + want: &model.ExpressionRef{Name: "public def"}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + r.SetCurrentUnnamed() + // Public definitions are stored as private definitions and can still be resolved locally. + d := &Def[model.IExpression]{ + Name: "public def", + Result: &model.ExpressionRef{Name: "public def"}, + IsPublic: true, + ValidateIsUnique: true, + } + if err := r.Define(d); err != nil { + t.Errorf("Define(public def) unexpected err: %v", err) + } + got, err := r.ResolveLocal("public def") + if err != nil { + t.Errorf("ResolveLocal(public def) unexpected err: %v", err) + } + return got + }, + }, + { + name: "Unnamed Func", + want: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) model.IExpression { + r.SetCurrentUnnamed() + // Public definitions are stored as private definitions and can still be resolved locally. + f := &Func[model.IExpression]{ + Name: "public func", + Operands: []types.IType{types.Integer}, + Result: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: true, + ValidateIsUnique: true, + } + if err := r.DefineFunc(f); err != nil { + t.Errorf("DefineFunc(public func) unexpected err: %v", err) + } + got, err := r.ResolveLocalFunc("public func", []model.IExpression{model.NewLiteral("4", types.Integer)}, false, newFHIRModelInfo(t)) + if err != nil { + t.Errorf("ResolveLocalFunc(public func) unexpected err: %v", err) + } + wantWrappedOperands := []model.IExpression{model.NewLiteral("4", types.Integer)} + if diff := cmp.Diff(wantWrappedOperands, got.WrappedOperands); diff != "" { + t.Errorf("ResolveLocalFunc(public func) diff (-want +got):\n%s", diff) + } + return got.Result + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // TEST SETUP - PREVIOUS PARSED LIBRARY + // + // library example.helpers version '1.0' + // define public "public def" : ... + // define private "private def": ... + // define public function "public func"(a String):... + // define public fluent function "public fluent func"(a String):... + // define private function "private func"(b String):... + r := NewResolver[model.IExpression, model.IExpression]() + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "helpers", + Qualified: "example.helpers", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + buildLibrary(t, r) + + // TEST SETUP - CURRENT LIBRARY + // + // library example.measure version '1.0' + // include example.helpers version '1.0' + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "measure", + Qualified: "example.measure", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + if err := r.IncludeLibrary(&model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}, true); err != nil { + t.Fatalf("r.IncludeLibrary() unexpected err: %v", err) + } + + got := tc.resolverCalls(r) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("resolverCalls() diff (-want +got):\n%v", diff) + } + }) + } +} + +func TestParserAliasAndResolve(t *testing.T) { + // This tests the Aliases, especially that they are appropriately deleted when a scope is exited. + r := NewResolver[model.IExpression, model.IExpression]() + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "measure", + Qualified: "example.measure", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + + // Create Alias P. + r.EnterScope() + if err := r.Alias("P", &model.AliasRef{Name: "P"}); err != nil { + t.Fatalf("Alias(P) unexpected err: %v", err) + } + + want := &model.AliasRef{Name: "P"} + got, err := r.ResolveLocal("P") + if err != nil { + t.Fatalf("ResolveLocal(P) unexpected err: %v", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ResolveLocal(P) diff (-want +got):\n%v", diff) + } + + // Create nested Alias O. + r.EnterScope() + if err := r.Alias("O", &model.AliasRef{Name: "O"}); err != nil { + t.Fatalf("Alias(O) unexpected err: %v", err) + } + // O is in the inner scope. + want = &model.AliasRef{Name: "O"} + got, err = r.ResolveLocal("O") + if err != nil { + t.Fatalf("ResolveLocal(O) unexpected err: %v", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ResolveLocal(O) diff (-want +got):\n%v", diff) + } + + // P still exists in the outer scope. + want = &model.AliasRef{Name: "P"} + got, err = r.ResolveLocal("P") + if err != nil { + t.Fatalf("ResolveLocal(P) unexpected err: %v", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ResolveLocal(P) diff (-want +got):\n%v", diff) + } + + // Inner Alias Scope Cleared. + r.ExitScope() + // O no longer exists. + wantError := "could not resolve the local reference" + _, gotError := r.ResolveLocal("O") + if gotError == nil { + t.Fatalf("ResolveLocal(O) expected error got success") + } + if !strings.Contains(gotError.Error(), wantError) { + t.Errorf("Returned error (%s) did not contain (%s)", gotError.Error(), wantError) + } + + // P still exists in the outer scope. + want = &model.AliasRef{Name: "P"} + got, gotError = r.ResolveLocal("P") + if gotError != nil { + t.Fatalf("ResolveLocal(P) unexpected err: %v", gotError) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("ResolveLocal(P) diff (-want +got):\n%v", diff) + } + + // Outer Alias Scope Cleared. + r.ExitScope() + // P no longer exists. + wantError = "could not resolve the local reference" + _, gotError = r.ResolveLocal("P") + if gotError == nil { + t.Fatalf("ResolveLocal(P) expected error got success") + } + if !strings.Contains(gotError.Error(), wantError) { + t.Errorf("Returned error (%s) did not contain (%s)", gotError.Error(), wantError) + } +} + +func TestResolveIncludedLibrary(t *testing.T) { + // TEST SETUP - PREVIOUS PARSED LIBRARY + // + // library example.helpers version '1.0' + r := NewResolver[model.IExpression, model.IExpression]() + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "helpers", + Qualified: "example.helpers", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + + // TEST SETUP - CURRENT LIBRARY + // + // library example.measure version '1.0' + // include example.helpers version '1.0' + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "measure", + Qualified: "example.measure", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + if err := r.IncludeLibrary(&model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}, true); err != nil { + t.Fatalf("r.IncludeLibrary() unexpected err: %v", err) + } + + got := r.ResolveInclude("helpers") + + want := model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"} + if *got != want { + t.Errorf("ResolveInclude(helpers) got %v, want %v", got, want) + } + +} + +func TestResolverErrors(t *testing.T) { + tests := []struct { + name string + resolverCalls func(*Resolver[model.IExpression, model.IExpression]) error + errContains string + }{ + { + name: "SetCurrentLibrary Same Name", + errContains: "already exists", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + i := &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"} + return r.SetCurrentLibrary(i) + }, + }, + { + name: "IncludeLibrary Same Name", + errContains: "already exists", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + i := &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"} + return r.IncludeLibrary(i, true) + }, + }, + { + name: "Include and Def Same Name", + errContains: "already exists", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + i := &model.LibraryIdentifier{Local: "public def", Qualified: "example.helpers", Version: "1.0"} + return r.IncludeLibrary(i, true) + }, + }, + { + name: "Alias and Def Same Name", + errContains: "already exists", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + r.EnterScope() + defer r.ExitScope() + return r.Alias("private def", &model.AliasRef{Name: "private def"}) + }, + }, + { + name: "Alias Created Without EnterScope", + errContains: "EnterScope must be called", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + return r.Alias("A", &model.AliasRef{Name: "A"}) + }, + }, + { + name: "IncludeLibrary Nonexistent Name", + errContains: "not exist", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + i := &model.LibraryIdentifier{Local: "nonexistent", Qualified: "example.nonexistent", Version: "1.0"} + return r.IncludeLibrary(i, true) + }, + }, + { + name: "Public Def Same Name", + errContains: "already exist", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + d := &Def[model.IExpression]{ + Name: "public def", + Result: &model.ExpressionRef{Name: "public def"}, + IsPublic: true, + ValidateIsUnique: true, + } + return r.Define(d) + }, + }, + { + name: "Public Func Same Name", + errContains: "already exist", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + f := &Func[model.IExpression]{ + Name: "public func", + Operands: []types.IType{types.String}, + Result: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + IsPublic: false, + IsFluent: false, + ValidateIsUnique: true, + } + return r.DefineFunc(f) + }, + }, + { + name: "Private Def Same Name", + errContains: "already exist", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + d := &Def[model.IExpression]{ + Name: "private def", + Result: &model.ExpressionRef{Name: "private def"}, + IsPublic: false, + ValidateIsUnique: true, + } + return r.Define(d) + }, + }, + { + name: "Private Func Same Name", + errContains: "already exist", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + f := &Func[model.IExpression]{ + Name: "private func", + Operands: []types.IType{types.String}, + Result: &model.FunctionRef{Name: "private func", Operands: []model.IExpression{}}, + IsPublic: false, + IsFluent: false, + ValidateIsUnique: true, + } + return r.DefineFunc(f) + }, + }, + { + name: "Public Func Same Name As Built-in", + errContains: "already exist", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + ops := []types.IType{types.String} + if err := r.DefineBuiltinFunc("public func", ops, &model.Last{}); err != nil { + t.Errorf("DefineBuiltinFunc(public func) unexpected err: %v", err) + } + + f := &Func[model.IExpression]{ + Name: "public func", + Operands: ops, + Result: &model.FunctionRef{}, + IsPublic: false, + IsFluent: false, + ValidateIsUnique: true, + } + return r.DefineFunc(f) + }, + }, + { + name: "ResolveLocal Nonexistent", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveLocal("nonexistent def") + return err + }, + }, + { + name: "ResolveLocalFunc Nonexistent", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveLocalFunc("nonexistent func", []model.IExpression{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveLocalFunc Nonexistent Operands", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveLocalFunc("public func", []model.IExpression{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveLocalFunc Not Fluent", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveLocalFunc("public func", []model.IExpression{model.NewLiteral("Apple", types.String)}, true, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveExactLocalFunc Nonexistent", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveExactLocalFunc("nonexistent func", []types.IType{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveExactLocalFunc Nonexistent Operands", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveExactLocalFunc("public func", []types.IType{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveExactLocalFunc Not Fluent", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveExactLocalFunc("public func", []types.IType{types.String}, true, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveGlobal Nonexistent Def", + errContains: "resolve the reference", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveGlobal("helpers", "nonexistent def") + return err + }, + }, + { + name: "ResolveGlobalFunc Nonexistent Def", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveGlobalFunc("helpers", "nonexistent def", []model.IExpression{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveGlobal Nonexistent Library", + errContains: "resolve the library", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveGlobal("nonexistent lib", "public def") + return err + }, + }, + { + name: "ResolveGlobalFunc Nonexistent Library", + errContains: "resolve the library", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveGlobalFunc("nonexistent lib", "public func", []model.IExpression{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveGlobalFunc Nonexistent Operands", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveGlobalFunc("helpers", "public func", []model.IExpression{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveGlobalFunc Not Fluent", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveGlobalFunc("helpers", "public func", []model.IExpression{model.NewLiteral("Apple", types.String)}, true, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveExactGlobalFunc Nonexistent Library", + errContains: "resolve the library", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveExactGlobalFunc("nonexistent lib", "public func", []types.IType{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveExactGlobalFunc Nonexistent Operands", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveExactGlobalFunc("helpers", "public func", []types.IType{}, false, newFHIRModelInfo(t)) + return err + }, + }, + { + name: "ResolveExactGlobalFunc Not Fluent", + errContains: "could not resolve", + resolverCalls: func(r *Resolver[model.IExpression, model.IExpression]) error { + _, err := r.ResolveExactGlobalFunc("helpers", "public func", []types.IType{types.String}, true, newFHIRModelInfo(t)) + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // TEST SETUP - PREVIOUS PARSED LIBRARY + // + // library example.helpers version '1.0' + // define public "public def" : ... + // define private "private def": ... + // define public function "public func"(a String):... + // define private function "private func"(b String):... + + r := NewResolver[model.IExpression, model.IExpression]() + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "helpers", + Qualified: "example.helpers", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + buildLibrary(t, r) + + // TEST SETUP - CURRENT LIBRARY + // + // library example.measure version '1.0' + // include example.helpers version '1.0' + // define public "public def" : ... + // define private "private def": ... + // define public function "public func"(a String):... + // define private function "private func"(b String):... + + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "measure", + Qualified: "example.measure", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + if err := r.IncludeLibrary(&model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}, true); err != nil { + t.Fatalf("r.IncludeLibrary() unexpected err: %v", err) + } + buildLibrary(t, r) + + gotErr := tc.resolverCalls(r) + if gotErr == nil { + t.Fatalf("Resolver did not return an error") + } + if !strings.Contains(gotErr.Error(), tc.errContains) { + t.Errorf("Returned error (%s) did not contain (%s)", gotErr.Error(), tc.errContains) + } + }) + } +} + +func TestDefs(t *testing.T) { + // TEST SETUP - PREVIOUS PARSED LIBRARY + // + // define public "public def" : ... + // define private "private def": ... + r := NewResolver[result.Value, *model.FunctionDef]() + r.SetCurrentUnnamed() + dUnnamed := &Def[result.Value]{ + Name: "public def", + Result: newOrFatal(4, t), + IsPublic: true, + ValidateIsUnique: true, + } + if err := r.Define(dUnnamed); err != nil { + t.Fatalf("r.Define(public def) unexpected err: %v", err) + } + + dUnnamedPrivate := &Def[result.Value]{ + Name: "private def", + Result: newOrFatal(5, t), + IsPublic: false, + ValidateIsUnique: true, + } + if err := r.Define(dUnnamedPrivate); err != nil { + t.Fatalf("r.Define(private def) unexpected err: %v", err) + } + + // TEST SETUP - CURRENT LIBRARY + // + // library example.measure version '1.0' + // define public "public def": ... + // define private "private def": ... + if err := r.SetCurrentLibrary(&model.LibraryIdentifier{ + Local: "measure", + Qualified: "example.measure", + Version: "1.0", + }); err != nil { + t.Fatalf("r.SetCurrentLibrary() unexpected err: %v", err) + } + dPublic := &Def[result.Value]{ + Name: "public def", + Result: newOrFatal(4, t), + IsPublic: true, + ValidateIsUnique: true, + } + if err := r.Define(dPublic); err != nil { + t.Fatalf("r.Define(public def) unexpected err: %v", err) + } + dPrivate := &Def[result.Value]{ + Name: "private def", + Result: newOrFatal(5, t), + IsPublic: false, + ValidateIsUnique: true, + } + if err := r.Define(dPrivate); err != nil { + t.Fatalf("r.Define(private def) unexpected err: %v", err) + } + + t.Run("Public Defs", func(t *testing.T) { + got, err := r.PublicDefs() + if err != nil { + t.Fatalf("r.PublicDefs() unexpected err: %v", err) + } + want := map[result.LibKey]map[string]result.Value{ + result.LibKey{Name: "example.measure", Version: "1.0"}: map[string]result.Value{"public def": newOrFatal(4, t)}, + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("r.PublicDefs() returned unexpected diff (-got +want):\n%s", diff) + } + }) + + t.Run("All Defs", func(t *testing.T) { + got, err := r.PublicAndPrivateDefs() + if err != nil { + t.Fatalf("r.Defs() unexpected err: %v", err) + } + want := map[result.LibKey]map[string]result.Value{ + result.LibKey{Name: "example.measure", Version: "1.0"}: map[string]result.Value{ + "public def": newOrFatal(4, t), + "private def": newOrFatal(5, t), + }, + result.LibKey{Name: "UnnamedLibrary-0", Version: "1.0"}: map[string]result.Value{ + "public def": newOrFatal(4, t), + "private def": newOrFatal(5, t), + }, + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("r.Defs() returned unexpected diff (-got +want):\n%s", diff) + } + }) +} + +func newOrFatal(a any, t *testing.T) result.Value { + o, err := result.New(a) + if err != nil { + t.Fatalf("New(%v) returned unexpected error: %v", a, err) + } + return o +} + +func buildLibrary(t *testing.T, r *Resolver[model.IExpression, model.IExpression]) { + // define public "public def" : ... + // define private "private def": ... + // define public function "public func"(a String):... + // define private function "private func"(b String):... + + t.Helper() + dPublic := &Def[model.IExpression]{ + Name: "public def", + Result: &model.ExpressionRef{Name: "public def"}, + IsPublic: true, + ValidateIsUnique: true, + } + if err := r.Define(dPublic); err != nil { + t.Fatalf("r.Define(public def) unexpected err: %v", err) + } + dPrivate := &Def[model.IExpression]{ + Name: "private def", + Result: &model.ExpressionRef{Name: "private def"}, + IsPublic: false, + ValidateIsUnique: true, + } + if err := r.Define(dPrivate); err != nil { + t.Fatalf("r.Define(private def) unexpected err: %v", err) + } + ops := []types.IType{types.String} + fPublic := &Func[model.IExpression]{ + Name: "public func", + Operands: ops, + Result: &model.FunctionRef{Name: "public func", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(fPublic); err != nil { + t.Fatalf("r.Define(public def) unexpected err: %v", err) + } + fPublicFluent := &Func[model.IExpression]{ + Name: "public fluent func", + Operands: ops, + Result: &model.FunctionRef{Name: "public fluent func", Operands: []model.IExpression{}}, + IsPublic: true, + IsFluent: true, + ValidateIsUnique: true, + } + if err := r.DefineFunc(fPublicFluent); err != nil { + t.Fatalf("r.Define(public fluent def) unexpected err: %v", err) + } + fPrivate := &Func[model.IExpression]{ + Name: "private func", + Operands: ops, + Result: &model.FunctionRef{Name: "private func", Operands: []model.IExpression{}}, + IsPublic: false, + IsFluent: false, + ValidateIsUnique: true, + } + if err := r.DefineFunc(fPrivate); err != nil { + t.Fatalf("r.Define(private def) unexpected err: %v", err) + } +} + +func newFHIRModelInfo(t *testing.T) *modelinfo.ModelInfos { + t.Helper() + fhirMIBytes, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + t.Fatalf("Reading embedded file %s failed unexpectedly: %v", "third_party/cqframework/fhir-modelinfo-4.0.1.xml", err) + } + + m, err := modelinfo.New([][]byte{fhirMIBytes}) + if err != nil { + t.Fatalf("New modelinfo unexpected error: %v", err) + } + m.SetUsing(modelinfo.Key{Name: "FHIR", Version: "4.0.1"}) + return m +} diff --git a/internal/resourcewrapper/resourcewrapper.go b/internal/resourcewrapper/resourcewrapper.go new file mode 100644 index 0000000..9e49c72 --- /dev/null +++ b/internal/resourcewrapper/resourcewrapper.go @@ -0,0 +1,81 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package resourcewrapper provides helper methods to work with R4 FHIR Resources. +package resourcewrapper + +import ( + "fmt" + + "github.com/google/fhir/go/protopath" + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// ResourceWrapper holds helper methods to work with R4 FHIR Contained Resources. +type ResourceWrapper struct { + Resource *r4pb.ContainedResource +} + +// New returns a ResourceWrapper that wraps the R4 ContainedResource. +func New(in *r4pb.ContainedResource) *ResourceWrapper { + return &ResourceWrapper{ + Resource: in, + } +} + +// ResourceType gets the type of the underlying resource or an error. +func (m *ResourceWrapper) ResourceType() (string, error) { + if m.Resource == nil { + return "", fmt.Errorf("resource is nil") + } + rMsg, err := m.ResourceMessageField() + if err != nil { + return "", err + } + return string(rMsg.ProtoReflect().Descriptor().Name()), nil +} + +// ResourceID gets the ID of the underlying resource or an error. +func (m *ResourceWrapper) ResourceID() (string, error) { + msg, err := m.ResourceMessageField() + if err != nil { + return "", err + } + return protopath.Get[string](msg, protopath.NewPath("id.value")) +} + +// ResourceMessageField returns the resource from within the ContainedResource. +func (m *ResourceWrapper) ResourceMessageField() (proto.Message, error) { + if m.Resource == nil { + return nil, fmt.Errorf("resource is nil") + } + + rpb := m.Resource.ProtoReflect() + oneof := rpb.Descriptor().Oneofs().ByName("oneof_resource") + if oneof == nil { + return nil, fmt.Errorf("failed to extract oneof") + } + fd := rpb.WhichOneof(oneof) + if fd == nil { + return nil, fmt.Errorf("no resource type was populated") + } + f := rpb.Get(fd) + innerMsg, ok := f.Interface().(protoreflect.Message) + if !ok { + return nil, fmt.Errorf("inner resource is not a message") + } + return innerMsg.Interface(), nil +} diff --git a/internal/resourcewrapper/resourcewrapper_test.go b/internal/resourcewrapper/resourcewrapper_test.go new file mode 100644 index 0000000..7dcfabc --- /dev/null +++ b/internal/resourcewrapper/resourcewrapper_test.go @@ -0,0 +1,61 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resourcewrapper + +import ( + "testing" + + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + r4patientpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/patient_go_proto" +) + +func TestGetType(t *testing.T) { + tests := []struct { + name string + resource *ResourceWrapper + wantType string + wantError bool + }{ + { + name: "R4 Patient", + resource: New(&r4pb.ContainedResource{OneofResource: &r4pb.ContainedResource_Patient{Patient: &r4patientpb.Patient{}}}), + wantType: "Patient", + wantError: false, + }, + { + name: "empty resource", + resource: New(&r4pb.ContainedResource{}), + wantType: "", + wantError: true, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotType, gotError := tc.resource.ResourceType() + if gotType != tc.wantType { + t.Errorf("GetType() returned unexpected type = %q, want %q", gotType, tc.wantType) + } + if tc.wantError { + if gotError == nil { + t.Errorf("GetType() expected an error but got %v", gotError) + } + } else { + if gotError != nil { + t.Errorf("GetType() returned unexpected error = %v", gotError) + } + } + }) + } +} diff --git a/internal/testhelpers/json.go b/internal/testhelpers/json.go new file mode 100644 index 0000000..f886fdd --- /dev/null +++ b/internal/testhelpers/json.go @@ -0,0 +1,37 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package testhelpers is an internal package providing useful test helpers for the CQL engine +// project. +package testhelpers + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +// WriteJSONs writes each string in jsons to a JSON file in a temporary test directory, which +// is returned. +func WriteJSONs(t testing.TB, jsons []string) (dir string) { + t.Helper() + dir = t.TempDir() + for i, json := range jsons { + if err := os.WriteFile(filepath.Join(dir, fmt.Sprintf("file_%d.json", i)), []byte(json), 0644); err != nil { + t.Fatalf("Unable to write test json: %v", err) + } + } + return dir +} diff --git a/internal/testhelpers/json_test.go b/internal/testhelpers/json_test.go new file mode 100644 index 0000000..5c3ab0d --- /dev/null +++ b/internal/testhelpers/json_test.go @@ -0,0 +1,57 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testhelpers_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/google/cql/internal/testhelpers" + "github.com/google/go-cmp/cmp" +) + +func TestWriteJSONs(t *testing.T) { + // Sanity check for WriteJSONs. + jsons := []string{ + `{"key": "value"}`, + `random bytes`, + `{"one": {"two": 3}}`, + } + + dir := testhelpers.WriteJSONs(t, jsons) + + for i, json := range jsons { + file := filepath.Join(dir, fmt.Sprintf("file_%d.json", i)) + data, err := os.ReadFile(file) + if err != nil { + t.Fatalf("os.ReadFile(%v) unexpected err: %v", file, err) + } + if !cmp.Equal(string(data), json) { + t.Errorf("WriteJSONs incorrect file contents for index %d. got: %v, want: %v", i, data, json) + } + } + + // Check number of files in dir: + files, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("os.ReadDir(%v) unexpected err: %v", dir, err) + } + if len(files) != len(jsons) { + t.Fatalf("len of files in output directory = %v, want %v", len(files), len(jsons)) + } + +} diff --git a/interpreter/TEST_README.md b/interpreter/TEST_README.md new file mode 100644 index 0000000..6a1542e --- /dev/null +++ b/interpreter/TEST_README.md @@ -0,0 +1,3 @@ +# CQL Engine Interpreter Tests + +Most of the "unit tests" for the CQL Engine Interpreter are actually integration tests located in internal/enginetests. \ No newline at end of file diff --git a/interpreter/conditional.go b/interpreter/conditional.go new file mode 100644 index 0000000..5351eeb --- /dev/null +++ b/interpreter/conditional.go @@ -0,0 +1,105 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + + "github.com/google/cql/model" + "github.com/google/cql/result" +) + +// evalIfThenElse handles evaluation and overload matching for the if then else statement. +// When the `if` condition is or evaluates to null, the else condition should be returned. +// https://cql.hl7.org/03-developersguide.html#conditional-expressions +func (i *interpreter) evalIfThenElse(ite *model.IfThenElse) (result.Value, error) { + cndObj, err := i.evalExpression(ite.Condition) + if err != nil { + return result.Value{}, err + } + + if result.IsNull(cndObj) { + return i.evalExpression(ite.Else) + } + condResult, err := result.ToBool(cndObj) + if err != nil { + return result.Value{}, err + } + + if condResult { + return i.evalExpression(ite.Then) + } + return i.evalExpression(ite.Else) +} + +// evalCase handles case expressions. +// https://cql.hl7.org/03-developersguide.html#conditional-expressions +func (i *interpreter) evalCase(c *model.Case) (result.Value, error) { + if c.Comparand == nil { + // No Comparand - CaseItem.Whens are booleans. + for _, caseItem := range c.CaseItem { + whenObj, err := i.evalExpression(caseItem.When) + if err != nil { + return result.Value{}, err + } + if result.IsNull(whenObj) { + // Null is treated as false. + continue + } + when, err := result.ToBool(whenObj) + if err != nil { + return result.Value{}, err + } + if when { + return i.evalExpression(caseItem.Then) + } + } + return i.evalExpression(c.Else) + } + + // Comparand - evaluate Equal(Comparand, CaseItem.When). + comparandObj, err := i.evalExpression(c.Comparand) + if err != nil { + return result.Value{}, err + } + if result.IsNull(comparandObj) { + return i.evalExpression(c.Else) + } + + for _, caseItem := range c.CaseItem { + whenObj, err := i.evalExpression(caseItem.When) + if err != nil { + return result.Value{}, err + } + if result.IsNull(whenObj) { + continue + } + if !comparandObj.RuntimeType().Equal(whenObj.RuntimeType()) { + return result.Value{}, fmt.Errorf("internal error - in case expressions the comparand and case must be the same type, got comparand: %v case: %v", comparandObj.RuntimeType(), whenObj.RuntimeType()) + } + eqObj, err := i.evalEqual(nil, comparandObj, whenObj) + if err != nil { + return result.Value{}, err + } + eq, err := result.ToBool(eqObj) + if err != nil { + return result.Value{}, err + } + if eq { + return i.evalExpression(caseItem.Then) + } + } + return i.evalExpression(c.Else) +} diff --git a/interpreter/conditional_test.go b/interpreter/conditional_test.go new file mode 100644 index 0000000..1dc1f83 --- /dev/null +++ b/interpreter/conditional_test.go @@ -0,0 +1,105 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" +) + +func TestEvalIfConditional_Error(t *testing.T) { + tests := []struct { + name string + model model.IExpression + wantErr string + }{ + { + name: "non boolean condition", + model: &model.IfThenElse{ + Condition: model.NewLiteral("2", types.Integer), + Then: model.NewLiteral("2", types.Integer), + Else: model.NewLiteral("3", types.Integer), + Expression: model.ResultType(types.Integer), + }, + wantErr: "cannot convert System.Integer to a boolean", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := Eval(context.Background(), []*model.Library{wrapInLib(t, tc.model)}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("Eval succeeded, wanted error") + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err, tc.wantErr) + } + }) + } +} + +func TestCase_Error(t *testing.T) { + tests := []struct { + name string + expr model.IExpression + wantErr string + }{ + { + name: "No Comparand When Not Boolean", + expr: &model.Case{ + Comparand: nil, + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: model.NewLiteral("5", types.Integer), + Then: model.NewLiteral("6", types.Integer), + }, + }, + Else: model.NewLiteral("7", types.Integer), + Expression: model.ResultType(types.Integer), + }, + wantErr: "internal error - cannot convert System.Integer to a boolean", + }, + { + name: "Comparand Different Type Then When", + expr: &model.Case{ + Comparand: model.NewLiteral("Apple", types.String), + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: model.NewLiteral("5", types.Integer), + Then: model.NewLiteral("6", types.Integer), + }, + }, + Else: model.NewLiteral("7", types.Integer), + Expression: model.ResultType(types.Integer), + }, + wantErr: "internal error - in case expressions the comparand", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := Eval(context.Background(), []*model.Library{wrapInLib(t, test.expr)}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("Eval succeeded, wanted error") + } + if !strings.Contains(err.Error(), test.wantErr) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err, test.wantErr) + } + }) + } +} diff --git a/interpreter/expressions.go b/interpreter/expressions.go new file mode 100644 index 0000000..f8cc222 --- /dev/null +++ b/interpreter/expressions.go @@ -0,0 +1,381 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "errors" + "fmt" + "reflect" + "strings" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/terminology" + "github.com/google/cql/types" + dtpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func (i *interpreter) evalExpression(elem model.IExpression) (result.Value, error) { + switch elem := elem.(type) { + case *model.Literal: + return i.evalLiteral(elem) + case *model.Quantity: + return i.evalQuantity(elem) + case *model.Ratio: + return i.evalRatio(elem) + case *model.List: + return i.evalList(elem) + case *model.Code: + return i.evalCode(elem) + case model.IUnaryExpression: + return i.evalUnaryExpression(elem) + case model.IBinaryExpression: + return i.evalBinaryExpression(elem) + case model.INaryExpression: + return i.evalNaryExpression(elem) + case *model.Retrieve: + return i.evalRetrieve(elem) + case *model.Property: + return i.evalProperty(elem) + case *model.Query: + return i.evalQuery(elem) + case *model.QueryLetRef: + return i.evalQueryLetRef(elem) + case *model.AliasRef: + return i.evalAliasRef(elem) + case *model.CodeSystemRef: + return i.evalCodeSystemRef(elem) + case *model.ValuesetRef: + return i.evalValuesetRef(elem) + case *model.ParameterRef: + return i.evalParameterRef(elem) + case *model.CodeRef: + return i.evalCodeRef(elem) + case *model.ConceptRef: + return i.evalConceptRef(elem) + case *model.ExpressionRef: + return i.evalExpressionRef(elem) + case *model.Interval: + return i.evalInterval(elem) + case *model.FunctionRef: + return i.evalFunctionRef(elem) + case *model.OperandRef: + return i.evalOperandRef(elem) + case *model.Tuple: + return i.evalTuple(elem) + case *model.Instance: + return i.evalInstance(elem) + case *model.IfThenElse: + return i.evalIfThenElse(elem) + case *model.Case: + return i.evalCase(elem) + case *model.MaxValue: + return i.evalMaxValue(elem) + case *model.MinValue: + return i.evalMinValue(elem) + case *model.Message: + return i.evalMessage(elem) + default: + // TODO(b/297089208): Add support for line/col error report. + return result.Value{}, fmt.Errorf("internal error - unsupported expression") + } +} + +// TODO b/324637028 - Implement all message types +func (i *interpreter) evalMessage(m *model.Message) (result.Value, error) { + source, err := i.evalExpression(m.Source) + if err != nil { + return result.Value{}, err + } + + cond, err := i.evalExpression(m.Condition) + if err != nil { + return result.Value{}, err + } + + // Whether or not to emit the message value + condVal, err := result.ToBool(cond) + if err != nil { + return result.Value{}, err + } + if !condVal { + return source, nil + } + + // Emit the message. + t, err := i.evalExpression(m.Severity) + if err != nil { + return result.Value{}, err + } + messageType, err := result.ToString(t) + if err != nil { + return result.Value{}, err + } + severity, err := messageSeverity(messageType) + if err != nil { + return result.Value{}, err + } + + code, err := i.evalExpression(m.Code) + if err != nil { + return result.Value{}, err + } + codeVal, err := result.ToString(code) + if err != nil { + return result.Value{}, err + } + + message, err := i.evalExpression(m.Message) + if err != nil { + return result.Value{}, err + } + messageVal, err := result.ToString(message) + if err != nil { + return result.Value{}, err + } + + // TODO b/301606416: Add support for model.TRACE. + + outString := fmt.Sprintf("%s %s: %s", severity, codeVal, messageVal) + fmt.Println(outString) + if severity == model.ERROR { + errMsg := fmt.Sprintf("log error - Message with severity of type `Error` was called with message: %s", outString) + return source, errors.New(errMsg) + } + return source, nil +} + +func (i *interpreter) evalRetrieve(expr *model.Retrieve) (result.Value, error) { + if i.retriever == nil { + return result.Value{}, fmt.Errorf("retriever was not set") + } + url, err := i.modelInfo.URL() + if err != nil { + return result.Value{}, err + } + name := strings.Split(expr.DataType, fmt.Sprintf("{%s}", url)) + if len(name) != 2 { + return result.Value{}, fmt.Errorf("Resource datatype (%s) did not contain the library uri (%s)", expr.DataType, url) + } + got, err := i.retriever.Retrieve(context.Background(), name[1]) + if err != nil { + return result.Value{}, err + } + + // Assume the retrieve result type should be a list: + listResultType, ok := expr.ResultType.(*types.List) + if !ok { + return result.Value{}, fmt.Errorf("internal error - retrieve result type should be a list, got %v", expr.ResultType) + } + elemResultType, ok := listResultType.ElementType.(*types.Named) + if !ok { + return result.Value{}, fmt.Errorf("internal error - retrieve result type should be a list of named types, got %v", listResultType) + } + + l := []result.Value{} + for _, c := range got { + r, err := unwrapContained(c) + if err != nil { + return result.Value{}, err + } + msg, err := result.New(result.Named{Value: r, RuntimeType: elemResultType}) + if err != nil { + return result.Value{}, err + } + + if expr.Codes != nil { + // We must try to filter on the codes provided. + if expr.CodeProperty == "" { + return result.Value{}, fmt.Errorf("code property must be populated when filtering on codes") + } + propertyType, err := i.modelInfo.PropertyTypeSpecifier(msg.RuntimeType(), expr.CodeProperty) + if err != nil { + return result.Value{}, err + } + cc, err := i.valueProperty(msg, expr.CodeProperty, propertyType) + if err != nil { + return result.Value{}, err + } + // If this isn't a codeableConcept, this will result in an error. + in, err := i.inValueSet(cc, expr.Codes) + if err != nil { + return result.Value{}, err + } + if in { + l = append(l, msg) + } + } else { + // If no code filtering, always add to the result set. + l = append(l, msg) + } + + } + // TODO(b/311222838): Currently only adding matched items as support, + // but should confirm this meets use case needs. + return result.NewWithSources(result.List{Value: l, StaticType: listResultType}, expr, l...) +} + +func (i *interpreter) inValueSet(codeableConcept result.Value, codes model.IExpression) (bool, error) { + if result.IsNull(codeableConcept) { + return false, nil + } + + vr, ok := codes.(*model.ValuesetRef) + if !ok { + return false, fmt.Errorf("only ValueSet references are currently supported for valueset filtering") + } + + protoVal, ok := codeableConcept.GolangValue().(result.Named) + if !ok { + return false, fmt.Errorf("internal error -- inValueSet: the input Value must be a result.Named. got: %s", reflect.ValueOf(codeableConcept).Type()) + } + ccPB, ok := protoVal.Value.(*dtpb.CodeableConcept) + if !ok { + return false, fmt.Errorf("internal error -- the input proto Value must be a *dtpb.CodeableConcept type. got: %s", reflect.ValueOf(codeableConcept).Type()) + } + + vs, err := i.evalValuesetRef(vr) + if err != nil { + return false, err + } + + vsv, ok := vs.GolangValue().(result.ValueSet) + if !ok { + return false, fmt.Errorf("internal error - expected a ValueSetValue instead got %v", reflect.ValueOf(vs.GolangValue()).Type()) + } + + for _, coding := range ccPB.GetCoding() { + // TODO: b/331447080 - Convert to using system operators for evaluating valueset membership. + in, err := i.terminologyProvider.AnyInValueSet([]terminology.Code{{System: coding.GetSystem().Value, Code: coding.GetCode().Value}}, vsv.ID, vsv.Version) + if err != nil { + return false, err + } + if in { + return true, nil + } + } + + return false, nil +} + +// unwrapContained returns the FHIR resource from within the ContainedResource. +func unwrapContained(r *r4pb.ContainedResource) (proto.Message, error) { + if r == nil { + return nil, fmt.Errorf("resource is nil") + } + + rpb := r.ProtoReflect() + oneof := rpb.Descriptor().Oneofs().ByName("oneof_resource") + if oneof == nil { + return nil, fmt.Errorf("failed to extract oneof") + } + fd := rpb.WhichOneof(oneof) + if fd == nil { + return nil, fmt.Errorf("no resource type was populated") + } + f := rpb.Get(fd) + innerMsg, ok := f.Interface().(protoreflect.Message) + if !ok { + return nil, fmt.Errorf("inner resource is not a message") + } + return innerMsg.Interface(), nil +} + +func (i *interpreter) evalQueryLetRef(a *model.QueryLetRef) (result.Value, error) { + return i.refs.ResolveLocal(a.Name) +} + +func (i *interpreter) evalAliasRef(a *model.AliasRef) (result.Value, error) { + return i.refs.ResolveLocal(a.Name) +} + +func (i *interpreter) evalOperandRef(a *model.OperandRef) (result.Value, error) { + return i.refs.ResolveLocal(a.Name) +} + +func (i *interpreter) evalCodeSystemRef(expr *model.CodeSystemRef) (result.Value, error) { + if expr.LibraryName != "" { + return i.refs.ResolveGlobal(expr.LibraryName, expr.Name) + } + return i.refs.ResolveLocal(expr.Name) +} + +func (i *interpreter) evalValuesetRef(expr *model.ValuesetRef) (result.Value, error) { + if expr.LibraryName != "" { + return i.refs.ResolveGlobal(expr.LibraryName, expr.Name) + } + return i.refs.ResolveLocal(expr.Name) +} + +func (i *interpreter) evalParameterRef(expr *model.ParameterRef) (result.Value, error) { + if expr.LibraryName != "" { + return i.refs.ResolveGlobal(expr.LibraryName, expr.Name) + } + return i.refs.ResolveLocal(expr.Name) +} + +func (i *interpreter) evalCodeRef(expr *model.CodeRef) (result.Value, error) { + if expr.LibraryName != "" { + return i.refs.ResolveGlobal(expr.LibraryName, expr.Name) + } + return i.refs.ResolveLocal(expr.Name) +} + +func (i *interpreter) evalConceptRef(expr *model.ConceptRef) (result.Value, error) { + if expr.LibraryName != "" { + return i.refs.ResolveGlobal(expr.LibraryName, expr.Name) + } + return i.refs.ResolveLocal(expr.Name) +} + +func (i *interpreter) evalExpressionRef(expr *model.ExpressionRef) (result.Value, error) { + if expr.LibraryName != "" { + return i.refs.ResolveGlobal(expr.LibraryName, expr.Name) + } + return i.refs.ResolveLocal(expr.Name) +} + +// applyToValues is a convenience wrapper that invokes fn on both Values. If an error is returned +// for either invocation, it is returned, otherwise the results are returned. +func applyToValues[T any](l, r result.Value, fn func(result.Value) (T, error)) (T, T, error) { + lObj, err := fn(l) + if err != nil { + return *new(T), *new(T), err + } + rObj, err := fn(r) + if err != nil { + return *new(T), *new(T), err + } + return lObj, rObj, nil +} + +func messageSeverity(s string) (model.MessageSeverity, error) { + switch s { + case "Error": + return model.ERROR, nil + case "Message": + return model.MESSAGE, nil + case "Trace": + return model.TRACE, nil + case "Warning": + return model.WARNING, nil + default: + return model.UNSETMESSAGESEVERITY, fmt.Errorf("invalid message severity %s", s) + } +} diff --git a/interpreter/functions.go b/interpreter/functions.go new file mode 100644 index 0000000..11765d1 --- /dev/null +++ b/interpreter/functions.go @@ -0,0 +1,71 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +func (i *interpreter) evalFunctionRef(f *model.FunctionRef) (result.Value, error) { + // Evaluate the operands + ops := []result.Value{} + opTypes := []types.IType{} + for _, exp := range f.Operands { + op, err := i.evalExpression(exp) + if err != nil { + return result.Value{}, err + } + ops = append(ops, op) + opTypes = append(opTypes, exp.GetResultType()) + } + + // Resolve the function + var resolved *model.FunctionDef + var err error + if f.LibraryName != "" { + resolved, err = i.refs.ResolveExactGlobalFunc(f.LibraryName, f.Name, opTypes, false, i.modelInfo) + } else { + resolved, err = i.refs.ResolveExactLocalFunc(f.Name, opTypes, false, i.modelInfo) + } + if err != nil { + return result.Value{}, err + } + + // Evaluate the function + if resolved.External { + return result.Value{}, fmt.Errorf("function %v is external, but external functions are not supported", f.Name) + } + i.refs.EnterScope() + defer i.refs.ExitScope() + for j, op := range ops { + if err := i.refs.Alias(resolved.Operands[j].Name, op); err != nil { + return result.Value{}, err + } + } + // TODO(b/301606416): Verify that the type of the result of the evaluated function matches the + // return type in model.FunctionDef. + r, err := i.evalExpression(resolved.Expression) + if err != nil { + return result.Value{}, err + } + // TODO(b/311222838): This currently add only the function expression to the resulting expression, + // since function parameters would be attached as operands in sub-expressions. We should + // determine whether this is sufficiently for real explainability workloads. + return r.WithSources(f), nil +} diff --git a/interpreter/functions_test.go b/interpreter/functions_test.go new file mode 100644 index 0000000..a267cd1 --- /dev/null +++ b/interpreter/functions_test.go @@ -0,0 +1,124 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" +) + +// Keep: These functions produce parse errors if ran via enginetests. +func TestFailingMultipleLibraries(t *testing.T) { + tests := []struct { + name string + tree *model.Library + errContains string + }{ + { + name: "Local FunctionRef Not Found", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.FunctionRef{Name: "Non existent", Operands: []model.IExpression{}}, + }, + }, + }, + }, + errContains: "could not resolve", + }, + { + name: "OperandRef Not Found", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "FuncName", + Context: "Patient", + Expression: &model.OperandRef{ + Name: "B", + Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}, + }, + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{model.OperandDef{Name: "A", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}}, + }, + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.FunctionRef{ + Name: "FuncName", + Operands: []model.IExpression{&model.Literal{Value: "4", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}}, + }, + }, + }, + }, + }, + errContains: "could not resolve", + }, + { + name: "Global FunctionRef Private Not Found", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.FunctionRef{ + Name: "private func", + LibraryName: "helpers", + Operands: []model.IExpression{&model.Literal{Value: "4", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}}}, + }, + }, + }, + }, + errContains: "could not resolve", + }, + { + name: "Global FunctionRef Library Name Not Found", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.FunctionRef{Name: "private func", LibraryName: "Non existent", Operands: []model.IExpression{}}, + }, + }, + }, + }, + errContains: "could not resolve", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := Eval(context.Background(), []*model.Library{test.tree, helperLib(t)}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("Eval Library(%s) = nil, want error", test.name) + } + if !strings.Contains(err.Error(), test.errContains) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err, test.errContains) + } + }) + } +} diff --git a/interpreter/helpers_test.go b/interpreter/helpers_test.go new file mode 100644 index 0000000..b033370 --- /dev/null +++ b/interpreter/helpers_test.go @@ -0,0 +1,71 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "testing" + "time" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" +) + +var defaultEvalTimestamp = time.Date(2024, 1, 1, 0, 0, 0, 0, time.FixedZone("Fixed", 4*60*60)) + +func defaultInterpreterConfig(t testing.TB) Config { + t.Helper() + fhirMI, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + t.Fatalf("internal error - could not read fhir-modelinfo-4.0.1.xml: %v", err) + } + p, err := parser.New(context.Background(), [][]byte{fhirMI}) + if err != nil { + t.Fatal("Could not create Parser: ", err) + } + return Config{ + DataModels: p.DataModel(), + Retriever: buildRetriever(t), + Terminology: getTerminologyProvider(t), + EvaluationTimestamp: defaultEvalTimestamp, + ReturnPrivateDefs: true} +} + +func wrapInLib(t *testing.T, expr model.IExpression) *model.Library { + return &model.Library{ + Identifier: &model.LibraryIdentifier{Qualified: "TESTLIB", Version: "1.0.0"}, + Usings: []*model.Using{&model.Using{LocalIdentifier: "FHIR", Version: "4.0.1", URI: "http://hl7.org/fhir"}}, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "TESTRESULT", + Context: "Patient", + Expression: expr, + }, + }, + }, + } +} + +func newOrFatal(t *testing.T, a any) result.Value { + t.Helper() + o, err := result.New(a) + if err != nil { + t.Fatalf("New(%v) returned unexpected error: %v", a, err) + } + return o +} diff --git a/interpreter/interpreter.go b/interpreter/interpreter.go new file mode 100644 index 0000000..e246e54 --- /dev/null +++ b/interpreter/interpreter.go @@ -0,0 +1,295 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package interpreter interprets and evaluates the data model produced by the CQL parser. +package interpreter + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/internal/reference" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/retriever" + "github.com/google/cql/terminology" + "github.com/google/cql/types" +) + +// Config configures the evaluation of the CQL. +type Config struct { + DataModels *modelinfo.ModelInfos + Parameters map[result.DefKey]model.IExpression + Retriever retriever.Retriever + Terminology terminology.Provider + EvaluationTimestamp time.Time + ReturnPrivateDefs bool +} + +// Eval evaluates the intermediate ELM like data structure from our parser. +func Eval(ctx context.Context, libs []*model.Library, config Config) (result.Libraries, error) { + i := &interpreter{ + refs: reference.NewResolver[result.Value, *model.FunctionDef](), + terminologyProvider: config.Terminology, + retriever: config.Retriever, + modelInfo: config.DataModels, + evaluationTimestamp: config.EvaluationTimestamp, + } + + for _, lib := range libs { + if err := i.evalLibrary(lib, config.Parameters); err != nil { + return nil, result.NewEngineError(result.LibKeyFromModel(lib.Identifier).String(), result.ErrEvaluationError, err) + } + } + + if config.ReturnPrivateDefs { + return i.refs.PublicAndPrivateDefs() + } + return i.refs.PublicDefs() +} + +// interpreter takes the intermediate ELM like data structure from the parser and executes it. +type interpreter struct { + refs *reference.Resolver[result.Value, *model.FunctionDef] + retriever retriever.Retriever + terminologyProvider terminology.Provider + modelInfo *modelinfo.ModelInfos + evaluationTimestamp time.Time +} + +// evalLibrary takes a library and evaluates all the expressions that it contains. +func (i *interpreter) evalLibrary(lib *model.Library, passedParams map[result.DefKey]model.IExpression) error { + for _, using := range lib.Usings { + if err := i.modelInfo.SetUsing(modelinfo.Key{Name: using.LocalIdentifier, Version: using.Version}); err != nil { + return err + } + } + + if passedParams == nil { + passedParams = make(map[result.DefKey]model.IExpression) + } + + if lib.Identifier != nil { + i.refs.SetCurrentLibrary(lib.Identifier) + } else { + i.refs.SetCurrentUnnamed() + } + + err := i.evalParameters(lib.Parameters, lib.Identifier, passedParams) + if err != nil { + return err + } + + // CodeSystems must be evaluated before ValueSets. + // TODO b/325631219 - Add a test to validate CodeSystems are evaluated before Valuesets. + for _, cs := range lib.CodeSystems { + csObj, err := result.New(result.CodeSystem{ID: cs.ID, Version: cs.Version}) + if err != nil { + return err + } + d := &reference.Def[result.Value]{ + Name: cs.Name, + Result: csObj, + IsPublic: cs.AccessLevel == model.Public, + ValidateIsUnique: false, + } + if err := i.refs.Define(d); err != nil { + return err + } + } + + for _, vs := range lib.Valuesets { + var codeSystems []result.CodeSystem + for _, cs := range vs.CodeSystems { + csr, err := i.evalCodeSystemRef(cs) + if err != nil { + return err + } + csVal, err := result.ToCodeSystem(csr) + if err != nil { + return err + } + codeSystems = append(codeSystems, csVal) + } + vObj, err := result.New(result.ValueSet{ID: vs.ID, Version: vs.Version, CodeSystems: codeSystems}) + if err != nil { + return err + } + d := &reference.Def[result.Value]{ + Name: vs.Name, + Result: vObj, + IsPublic: vs.AccessLevel == model.Public, + ValidateIsUnique: false, + } + if err := i.refs.Define(d); err != nil { + return err + } + } + for _, c := range lib.Codes { + // TODO: b/326332640 - Investigate whether CodeSystem for codes is optional. + if c.CodeSystem == nil { + return fmt.Errorf("The CodeSystem for a Code cannot be null, got code: %v", c) + } + cs, err := i.evalCodeSystemRef(c.CodeSystem) + if err != nil { + return err + } + + csVal, err := result.ToCodeSystem(cs) + if err != nil { + return err + } + cv := result.Code{ + Code: c.Code, + System: csVal.ID, + Version: csVal.Version, + Display: c.Display, + } + cObj, err := result.New(cv) + if err != nil { + return err + } + d := &reference.Def[result.Value]{ + Name: c.Name, + Result: cObj, + IsPublic: c.AccessLevel == model.Public, + ValidateIsUnique: false, + } + if err := i.refs.Define(d); err != nil { + return err + } + } + + for _, c := range lib.Concepts { + var codes []result.Code + for _, code := range c.Codes { + codeRef, err := i.evalCodeRef(code) + if err != nil { + return err + } + codeVal, err := result.ToCode(codeRef) + if err != nil { + return err + } + codes = append(codes, codeVal) + } + cObj, err := result.New(result.Concept{Codes: codes, Display: c.Display}) + if err != nil { + return err + } + d := &reference.Def[result.Value]{ + Name: c.Name, + Result: cObj, + IsPublic: c.AccessLevel == model.Public, + ValidateIsUnique: false, + } + if err := i.refs.Define(d); err != nil { + return err + } + } + + for _, inc := range lib.Includes { + if err := i.refs.IncludeLibrary(inc.Identifier, false); err != nil { + return err + } + } + + if lib.Statements != nil { + for _, s := range lib.Statements.Defs { + switch t := s.(type) { + case *model.ExpressionDef: + res, err := i.evalExpression(s.GetExpression()) + if err != nil { + return err + } + d := &reference.Def[result.Value]{ + Name: s.GetName(), + Result: res, + IsPublic: s.GetAccessLevel() == model.Public, + ValidateIsUnique: false, + } + if err = i.refs.Define(d); err != nil { + return err + } + case *model.FunctionDef: + opTypes := []types.IType{} + for _, op := range t.Operands { + opTypes = append(opTypes, op.GetResultType()) + } + f := &reference.Func[*model.FunctionDef]{ + Name: s.GetName(), + Operands: opTypes, + Result: t, + IsPublic: s.GetAccessLevel() == model.Public, + IsFluent: t.Fluent, + ValidateIsUnique: false, + } + if err = i.refs.DefineFunc(f); err != nil { + return err + } + default: + return errors.New("internal error - unsupported statement type") + } + } + } + + return nil +} + +func (i *interpreter) evalParameters(paramDefs []*model.ParameterDef, id *model.LibraryIdentifier, passedParams map[result.DefKey]model.IExpression) error { + if id == nil && len(paramDefs) > 0 { + return fmt.Errorf("unnamed libraries cannot have parameters, got %v", paramDefs[0].Name) + } else if id == nil { + return nil + } + + lKey := result.LibKeyFromModel(id) + for _, param := range paramDefs { + var err error + var pObj result.Value + pModel, ok := passedParams[result.DefKey{Name: param.Name, Library: lKey}] + if ok { + // TODO(b/301606416): We are not supporting arbitrary expressions as passed parameters. We + // should verify that this is a list, interval or literal. + pObj, err = i.evalExpression(pModel) + if err != nil { + return err + } + } else if param.Default != nil { + // TODO(b/301606416): Parameter defaults should be computable at compile time. We should + // verify and move this computation to parse time. + pObj, err = i.evalExpression(param.Default) + if err != nil { + return err + } + } else { + // TODO(b/301606416): Send a warning to the user that the param was not provided and is therefore null. + pObj, err = result.New(nil) + if err != nil { + return err + } + } + d := &reference.Def[result.Value]{ + Name: param.Name, + Result: pObj, + IsPublic: param.AccessLevel == model.Public, + ValidateIsUnique: false, + } + i.refs.Define(d) + } + return nil +} diff --git a/interpreter/interpreter_test.go b/interpreter/interpreter_test.go new file mode 100644 index 0000000..46ac7cb --- /dev/null +++ b/interpreter/interpreter_test.go @@ -0,0 +1,775 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/google/cql/internal/testhelpers" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/google/cql/terminology" + "github.com/google/cql/types" + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" +) + +func buildRetriever(t testing.TB) *local.Retriever { + // TODO(b/300653289): Getting this to parse can be finicky with new lines. Find a more robust way. + bundle := `{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1", + "active": true, + "name": [{"given":["John", "Smith"], "family":"Doe"}]} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1", + "code" : { + "coding" : [{ + "system" : "http://example.com", + "code" : "15074-8", + "display" : "Glucose [Moles/volume] in Blood" + }] + } + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "2"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Encounter", + "id": "1"} + } + ] + }` + r, err := local.NewRetrieverFromR4Bundle([]byte(bundle)) + if err != nil { + t.Fatalf("Failed to create retriever: %v", err) + } + return r +} + +func getTerminologyProvider(t testing.TB) terminology.Provider { + t.Helper() + terminologies := []string{ + `{ + "resourceType": "ValueSet", + "id": "https://example.com/glucose", + "url": "https://example.com/glucose", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "http://example.com", "code": "15074-8" } + ] + } + }`, + } + dir := testhelpers.WriteJSONs(t, terminologies) + tp, err := terminology.NewLocalFHIRProvider(dir) + if err != nil { + t.Fatalf("Failed to create terminology provider: %v", err) + } + return tp +} + +func helperLib(t *testing.T) *model.Library { + t.Helper() + // library example.helpers version '1.0' + // define public "public def" : 2 + // define private "private def": 3 + return &model.Library{ + Identifier: &model.LibraryIdentifier{ + Local: "helpers", + Qualified: "example.helpers", + Version: "1.0", + }, + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Parameters: []*model.ParameterDef{ + &model.ParameterDef{Name: "param true", AccessLevel: model.Public, Element: &model.Element{ResultType: types.Boolean}}, + &model.ParameterDef{Name: "private param false", AccessLevel: model.Private, Element: &model.Element{ResultType: types.Boolean}}, + &model.ParameterDef{Name: "param interval", AccessLevel: model.Public, Element: &model.Element{ResultType: &types.Interval{PointType: types.DateTime}}}, + &model.ParameterDef{ + Name: "param default", + AccessLevel: model.Public, + Default: &model.Literal{Value: "2", Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}}, + }, + Valuesets: []*model.ValuesetDef{ + &model.ValuesetDef{ + Name: "public valueset", + ID: "PublicValueset", + Version: "1.0", + AccessLevel: "PUBLIC", + }, + &model.ValuesetDef{ + Name: "private valueset", + ID: "PrivateValueset", + Version: "1.0", + AccessLevel: "PRIVATE", + }, + }, + CodeSystems: []*model.CodeSystemDef{ + &model.CodeSystemDef{ + Name: "public codesystem", + ID: "PublicCodeSystem", + Version: "1.0", + AccessLevel: "PUBLIC", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "public func", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.OperandRef{ + Name: "A", + Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}, + }, + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{model.OperandDef{Name: "A", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}}, + }, + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "private func", + Context: "Patient", + AccessLevel: "PRIVATE", + Expression: &model.OperandRef{ + Name: "A", + Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}, + }, + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{model.OperandDef{Name: "A", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}}, + }, + &model.ExpressionDef{ + Name: "public def", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Literal{Value: "2", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}, + Element: &model.Element{ResultType: types.Integer}, + }, + &model.ExpressionDef{ + Name: "private def", + Context: "Patient", + AccessLevel: "PRIVATE", + Expression: &model.Literal{Value: "3", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + } +} + +func TestFailingEvalSingleLibrary(t *testing.T) { + tests := []struct { + name string + tree *model.Library + errContains string + }{ + { + name: "ExpressionRef not found local", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.ExpressionRef{Name: "Non existent"}, + }, + }, + }, + }, + errContains: "could not resolve", + }, + { + name: "property no scope or source", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Property{ + Path: "active", + }, + }, + }, + }, + }, + errContains: "source must be populated", + }, + { + name: "Message returns log error", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Message{ + Source: model.NewLiteral("1.2", types.Decimal), + Condition: model.NewLiteral("true", types.Boolean), + Code: model.NewLiteral("100", types.String), + Severity: model.NewLiteral("Error", types.String), + Message: model.NewLiteral("Test Message", types.String), + Expression: model.ResultType(types.Decimal), + }, + }, + }, + }, + }, + errContains: "log error", + }, + { + name: "Query without source", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Query{ + Source: []*model.AliasedSource{}, + }, + }, + }, + }, + }, + errContains: "query must have", + }, + { + name: "Where is not boolean", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Query{ + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "O", + Source: &model.List{ + List: []model.IExpression{ + &model.Literal{Value: "true", Expression: &model.Expression{Element: &model.Element{ResultType: types.Boolean}}}, + }, + Expression: model.ResultType(&types.List{ElementType: types.Boolean}), + }, + }, + }, + Where: &model.Literal{Value: "3", Expression: &model.Expression{Element: &model.Element{ResultType: types.Integer}}}, + }, + }, + }, + }, + }, + errContains: "where clause of a query", + }, + { + name: "Failed retrieve with mismatched URI", + tree: &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.1", LocalIdentifier: "FHIR"}}, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Retrieve{ + DataType: "{http://random}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + }, + }, + }, + }, + errContains: "Resource datatype ({http://random}Patient) did not contain the library uri (http://hl7.org/fhir)", + }, + { + name: "Incorrect Using Local Identifier", + tree: &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.1", LocalIdentifier: "FIRE"}}, + }, + errContains: "FIRE 4.0.1 data model not found", + }, + { + name: "Incorrect Using Version", + tree: &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.2", LocalIdentifier: "FHIR"}}, + }, + errContains: "FHIR 4.0.2 data model not found", + }, + { + name: "First unsupported literal", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.UnaryExpression{ + Operand: &model.Literal{ + Value: "false", + Expression: &model.Expression{Element: &model.Element{ResultType: types.Boolean}}, + }, + }, + }, + }, + }, + }, + errContains: "internal error - unsupported expression", + }, + { + name: "Retrieve Observations without ValuesetRef", + tree: &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.1", LocalIdentifier: "FHIR"}}, + Valuesets: []*model.ValuesetDef{&model.ValuesetDef{Name: "Test Glucose", ID: "https://example.com/glucose", Version: "1.0.0"}}, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Retrieve{ + CodeProperty: "code", + Codes: &model.UnaryExpression{}, // something that's not a ValuesetRef. + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + }, + }, + }, + errContains: "only ValueSet references are currently supported for valueset filtering", + }, + { + name: "Retrieve Observations with incorrect CodeProperty", + tree: &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.1", LocalIdentifier: "FHIR"}}, + Valuesets: []*model.ValuesetDef{&model.ValuesetDef{Name: "Test Glucose", ID: "https://example.com/glucose", Version: "1.0.0"}}, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Retrieve{ + CodeProperty: "id", // this isn't the code property. + Codes: &model.ValuesetRef{Name: "Test Glucose", Expression: model.ResultType(types.ValueSet)}, + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + }, + }, + }, + }, + errContains: "input proto Value must be a *dtpb.CodeableConcept type", + }, + { + name: "Retrieve Observations with missing CodeProperty", + tree: &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.1", LocalIdentifier: "FHIR"}}, + Valuesets: []*model.ValuesetDef{&model.ValuesetDef{Name: "Test Glucose", ID: "https://example.com/glucose", Version: "1.0.0"}}, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Retrieve{ + CodeProperty: "", // empty + Codes: &model.ValuesetRef{Name: "Test Glucose", Expression: model.ResultType(types.ValueSet)}, + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + }, + }, + }, + errContains: "code property must be populated when filtering on codes", + }, + { + name: "Retrieve Observations with missing ValueSet", + tree: &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.1", LocalIdentifier: "FHIR"}}, + Valuesets: []*model.ValuesetDef{&model.ValuesetDef{Name: "Test Glucose", ID: "https://example.com/glucose", Version: "1.0.0"}}, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Retrieve{ + CodeProperty: "code", + Codes: &model.ValuesetRef{Name: "Something Missing!", Expression: model.ResultType(types.ValueSet)}, + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + }, + }, + }, + errContains: "could not resolve the local reference", + }, + { + name: "List with invalid element", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.List{ + Expression: &model.Expression{Element: &model.Element{ResultType: &types.List{ElementType: types.Integer}}}, + List: []model.IExpression{ + // Property is missing scope and source + &model.Property{ + Path: "active", + }, + }, + }, + }, + }, + }, + }, + errContains: "at index 0", + }, + { + name: "Invalid Date Literal", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Literal{ + Value: "@2024-03-3", + Expression: model.ResultType(types.Date), + }, + }, + }, + }, + }, + errContains: "got System.Date @2024-03-3 but want a layout like @YYYY-MM-DD", + }, + { + name: "Invalid DateTime Literal", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Literal{ + Value: "@20288", + Expression: model.ResultType(types.DateTime), + }, + }, + }, + }, + }, + errContains: "got System.DateTime @20288 but want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm)", + }, + { + name: "Invalid Time Literal", + tree: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Literal{ + Value: "@T30", + Expression: model.ResultType(types.Time), + }, + }, + }, + }, + }, + errContains: "got System.Time @T30 but want a layout like @Thh:mm:ss.fff: hour out of range", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.tree.Identifier = &model.LibraryIdentifier{Qualified: "Highly.Qualified", Version: "1.0"} + _, err := Eval(context.Background(), []*model.Library{test.tree}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("Eval Library(%s) = nil, want error", test.name) + } + if err != nil && !strings.Contains(err.Error(), test.errContains) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err, test.errContains) + } + }) + } +} + +func TestFailingEvalMultipleLibraries(t *testing.T) { + tests := []struct { + name string + tree *model.Library + errContains string + }{ + { + name: "ExpressionRef global private", + tree: &model.Library{ + Identifier: &model.LibraryIdentifier{Qualified: "example.measure", Version: "1.0"}, + Includes: []*model.Include{ + &model.Include{Identifier: &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}}, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.ExpressionRef{Name: "private def", LibraryName: "helpers", Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + errContains: "helpers.private def is not public", + }, + { + name: "ExpressionRef library does not exist", + tree: &model.Library{ + Identifier: &model.LibraryIdentifier{Qualified: "example.measure", Version: "1.0"}, + Includes: []*model.Include{ + &model.Include{Identifier: &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}}, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.ExpressionRef{Name: "public def", LibraryName: "non existent"}, + }, + }, + }, + }, + errContains: "resolve the library name", + }, + { + name: "ParameterRef global private", + tree: &model.Library{ + Identifier: &model.LibraryIdentifier{Qualified: "example.measure", Version: "1.0"}, + Includes: []*model.Include{ + &model.Include{Identifier: &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}}, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.ParameterRef{Name: "private param false", LibraryName: "helpers", Expression: model.ResultType(types.Boolean)}, + }, + }, + }, + }, + errContains: "helpers.private param false is not public", + }, + { + name: "ParameterRef library does not exist", + tree: &model.Library{ + Identifier: &model.LibraryIdentifier{Qualified: "example.measure", Version: "1.0"}, + Includes: []*model.Include{ + &model.Include{Identifier: &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}}, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.ParameterRef{Name: "param true", LibraryName: "non existent"}, + }, + }, + }, + }, + errContains: "resolve the library name", + }, + { + name: "ValuesetRef global private", + tree: &model.Library{ + Identifier: &model.LibraryIdentifier{Qualified: "example.measure", Version: "1.0"}, + Includes: []*model.Include{ + &model.Include{Identifier: &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}}, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.ValuesetRef{Name: "private valueset", LibraryName: "helpers", Expression: model.ResultType(types.ValueSet)}, + }, + }, + }, + }, + errContains: "helpers.private valueset is not public", + }, + { + name: "ValuesetRef library does not exist", + tree: &model.Library{ + Identifier: &model.LibraryIdentifier{Qualified: "example.measure", Version: "1.0"}, + Includes: []*model.Include{ + &model.Include{Identifier: &model.LibraryIdentifier{Local: "helpers", Qualified: "example.helpers", Version: "1.0"}}, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.ValuesetRef{Name: "public valuset", LibraryName: "non existent", Expression: model.ResultType(types.ValueSet)}, + }, + }, + }, + }, + errContains: "resolve the library name", + }, + { + name: "Unnamed lib with parameter defs", + tree: &model.Library{ + Parameters: []*model.ParameterDef{ + &model.ParameterDef{ + Name: "param in unnamed", + AccessLevel: model.Public, + }, + }, + }, + errContains: "unnamed libraries cannot have parameters", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := Eval(context.Background(), []*model.Library{helperLib(t), test.tree}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("Eval Library(%s) = nil, want error", test.name) + } + if err != nil && !strings.Contains(err.Error(), test.errContains) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err, test.errContains) + } + }) + } +} + +type InvalidRetriever struct{} + +func (i *InvalidRetriever) Retrieve(_ context.Context, _ string) ([]*r4pb.ContainedResource, error) { + return []*r4pb.ContainedResource{&r4pb.ContainedResource{}}, nil +} + +func TestInvalidContainedResource(t *testing.T) { + tree := &model.Library{ + Usings: []*model.Using{&model.Using{URI: "http://hl7.org/fhir", Version: "4.0.1", LocalIdentifier: "FHIR"}}, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Param", + Context: "Patient", + Expression: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + }, + }, + }, + } + config := defaultInterpreterConfig(t) + config.Retriever = &InvalidRetriever{} + _, err := Eval(context.Background(), []*model.Library{tree}, config) + if err == nil { + t.Errorf("Eval Library() = nil, want error") + } + if !strings.Contains(err.Error(), "no resource type was populated") { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err, "no resource type was populated") + } +} + +func TestConvertValuesWith(t *testing.T) { + tests := []struct { + name string + left result.Value + right result.Value + fn func(result.Value) (int32, error) + wantErr string + }{ + { + name: "left value throws error on error", + left: newOrFatal(t, 1), + right: newOrFatal(t, 2), + fn: func(o result.Value) (int32, error) { + if o.GolangValue().(int32) == 1 { + return 0, fmt.Errorf("error invalid value") + } + return o.GolangValue().(int32), nil + }, + wantErr: "error invalid value", + }, + { + name: "left value throws error on error", + left: newOrFatal(t, 2), + right: newOrFatal(t, 1), + fn: func(o result.Value) (int32, error) { + if o.GolangValue().(int32) == 1 { + return 0, fmt.Errorf("error invalid value") + } + return o.GolangValue().(int32), nil + }, + wantErr: "error invalid value", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, _, err := applyToValues(tc.left, tc.right, tc.fn) + if err == nil { + t.Errorf("applyToValue() expected error but got none with args: %v %v, wanted: %s, got: %v", tc.left, tc.right, tc.wantErr, err) + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("applyToValues() returned unexpected error: %v, wanted: %s", err, tc.wantErr) + } + }) + } +} diff --git a/interpreter/literal.go b/interpreter/literal.go new file mode 100644 index 0000000..1a2774e --- /dev/null +++ b/interpreter/literal.go @@ -0,0 +1,388 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + "strconv" + "strings" + + "github.com/google/cql/internal/datehelpers" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +func (i *interpreter) evalLiteral(l *model.Literal) (result.Value, error) { + t, ok := l.GetResultType().(types.System) + if !ok { + return result.Value{}, fmt.Errorf("Literal type must be a CQL base type, instead got %v", l.GetResultType()) + } + // TODO(b/301606416): Many of these strconv are not quite correct. + switch t { + case types.Integer: + a, err := strconv.ParseInt(l.Value, 10, 32) + if err != nil { + return result.Value{}, err + } + return result.NewWithSources(int32(a), l) + case types.Long: + value := l.Value + if strings.HasSuffix(value, "L") { + value = l.Value[:len(l.Value)-1] + } + a, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return result.Value{}, err + } + return result.NewWithSources(a, l) + case types.Decimal: + d, err := strconv.ParseFloat(l.Value, 64) + if err != nil { + return result.Value{}, err + } + return result.NewWithSources(d, l) + case types.Boolean: + b, err := strconv.ParseBool(l.Value) + if err != nil { + return result.Value{}, err + } + return result.NewWithSources(b, l) + case types.String: + return result.NewWithSources(l.Value, l) + case types.Date: + t, p, err := datehelpers.ParseDate(l.Value, i.evaluationTimestamp.Location()) + if err != nil { + return result.Value{}, err + } + return result.NewWithSources(result.Date{Date: t, Precision: p}, l) + case types.DateTime: + t, p, err := datehelpers.ParseDateTime(l.Value, i.evaluationTimestamp.Location()) + if err != nil { + return result.Value{}, err + } + return result.NewWithSources(result.DateTime{Date: t, Precision: p}, l) + case types.Time: + t, p, err := datehelpers.ParseTime(l.Value, i.evaluationTimestamp.Location()) + if err != nil { + return result.Value{}, err + } + return result.NewWithSources(result.Time{Date: t, Precision: p}, l) + case types.Any: + if l.Value == "null" { + return result.NewWithSources(nil, l) + } + } + + // Support other literals. + return result.Value{}, fmt.Errorf("unsupported literal type %s %v", l.Value, t) +} + +func (i *interpreter) evalInterval(l *model.Interval) (result.Value, error) { + lowObj, err := i.evalExpression(l.Low) + if err != nil { + return result.Value{}, err + } + highObj, err := i.evalExpression(l.High) + if err != nil { + return result.Value{}, err + } + + iType, ok := l.GetResultType().(*types.Interval) + if !ok { + return result.Value{}, fmt.Errorf("internal error -- interval result type should be an interval, got %v", l.GetResultType()) + } + + return result.NewWithSources(result.Interval{ + Low: lowObj, + High: highObj, + LowInclusive: l.LowInclusive, + HighInclusive: l.HighInclusive, + StaticType: iType, + }, l, lowObj, highObj) +} + +func (i *interpreter) evalList(l *model.List) (result.Value, error) { + objs := []result.Value{} + for index, e := range l.List { + obj, err := i.evalExpression(e) + if err != nil { + return result.Value{}, fmt.Errorf("at index %d: %w", index, err) + } + objs = append(objs, obj) + } + listResultType, ok := l.GetResultType().(*types.List) + if !ok { + return result.Value{}, fmt.Errorf("internal error -- list result type should be a list, got %v", l.GetResultType()) + } + return result.NewWithSources(result.List{Value: objs, StaticType: listResultType}, l, objs...) +} + +func (i *interpreter) evalQuantity(q *model.Quantity) (result.Value, error) { + qv := result.Quantity{Value: q.Value, Unit: q.Unit} + return result.NewWithSources(qv, q) +} + +func (i *interpreter) evalRatio(r *model.Ratio) (result.Value, error) { + numerator, err := i.evalQuantity(&r.Numerator) + if err != nil { + return result.Value{}, err + } + denominator, err := i.evalQuantity(&r.Denominator) + if err != nil { + return result.Value{}, err + } + rv := result.Ratio{ + Numerator: numerator.GolangValue().(result.Quantity), + Denominator: denominator.GolangValue().(result.Quantity), + } + return result.NewWithSources(rv, r) +} + +func (i *interpreter) evalCode(c *model.Code) (result.Value, error) { + if c.System == nil { + return result.Value{}, fmt.Errorf("The CodeSystem for a Code cannot be null, got code: %v", c) + } + cs, err := i.evalCodeSystemRef(c.System) + if err != nil { + return result.Value{}, err + } + + csVal, err := result.ToCodeSystem(cs) + if err != nil { + return result.Value{}, err + } + cv := result.Code{ + Code: c.Code, + System: csVal.ID, + Version: csVal.Version, + Display: c.Display, + } + + return result.NewWithSources(cv, c) +} + +func (i *interpreter) evalTuple(in *model.Tuple) (result.Value, error) { + tuple := result.Tuple{ + Value: make(map[string]result.Value), + RuntimeType: in.GetResultType(), + } + + for _, elem := range in.Elements { + obj, err := i.evalExpression(elem.Value) + if err != nil { + return result.Value{}, err + } + tuple.Value[elem.Name] = obj + } + + return result.NewWithSources(tuple, in) +} + +func (i *interpreter) evalInstance(in *model.Instance) (result.Value, error) { + elems := make(map[string]result.Value) + for _, elem := range in.Elements { + obj, err := i.evalExpression(elem.Value) + if err != nil { + return result.Value{}, err + } + elems[elem.Name] = obj + } + + switch in.ClassType { + case types.Quantity: + qv := result.Quantity{} + // Value + valueObj, ok := elems["value"] + if ok { + value, err := result.ToFloat64(valueObj) + if err != nil { + return result.Value{}, err + } + qv.Value = value + } + + // Unit + unitObj, ok := elems["unit"] + if ok { + unit, err := result.ToString(unitObj) + if err != nil { + return result.Value{}, err + } + qv.Unit = model.Unit(unit) + } + + return result.New(qv) + case types.Code: + cv := result.Code{} + // Code + codeObj, ok := elems["code"] + if ok { + code, err := result.ToString(codeObj) + if err != nil { + return result.Value{}, err + } + cv.Code = code + } + + // System + systemObj, ok := elems["system"] + if ok { + system, err := result.ToString(systemObj) + if err != nil { + return result.Value{}, err + } + cv.System = system + } + + // Version + versionObj, ok := elems["version"] + if ok { + version, err := result.ToString(versionObj) + if err != nil { + return result.Value{}, err + } + cv.Version = version + } + + // Display + displayObj, ok := elems["display"] + if ok { + display, err := result.ToString(displayObj) + if err != nil { + return result.Value{}, err + } + cv.Display = display + } + + return result.New(cv) + case types.CodeSystem: + csv := result.CodeSystem{} + + // ID + idObj, ok := elems["id"] + if ok { + id, err := result.ToString(idObj) + if err != nil { + return result.Value{}, err + } + csv.ID = id + } + + // Version + versionObj, ok := elems["version"] + if ok { + version, err := result.ToString(versionObj) + if err != nil { + return result.Value{}, err + } + csv.Version = version + } + + return result.New(csv) + case types.Concept: + cv := result.Concept{} + + // Codes + codeObj, ok := elems["codes"] + if ok { + codeObjs, err := result.ToSlice(codeObj) + if err != nil { + return result.Value{}, err + } + var codes []result.Code + for _, codeObj := range codeObjs { + code, err := result.ToCode(codeObj) + if err != nil { + return result.Value{}, err + } + codes = append(codes, code) + } + cv.Codes = codes + } + + // Display + displayObj, ok := elems["display"] + if ok { + display, err := result.ToString(displayObj) + if err != nil { + return result.Value{}, err + } + cv.Display = display + } + + return result.New(cv) + case types.ValueSet: + vsv := result.ValueSet{} + + // ID + idObj, ok := elems["id"] + if ok { + id, err := result.ToString(idObj) + if err != nil { + return result.Value{}, err + } + vsv.ID = id + } + + // Version + versionObj, ok := elems["version"] + if ok { + version, err := result.ToString(versionObj) + if err != nil { + return result.Value{}, err + } + vsv.Version = version + } + + // CodeSystem + codeSysObj, ok := elems["codesystems"] + if ok { + codeSysObjs, err := result.ToSlice(codeSysObj) + if err != nil { + return result.Value{}, err + } + var codeSystems []result.CodeSystem + for _, codeSysObj := range codeSysObjs { + codeSys, err := result.ToCodeSystem(codeSysObj) + if err != nil { + return result.Value{}, err + } + codeSystems = append(codeSystems, codeSys) + } + vsv.CodeSystems = codeSystems + } + + return result.New(vsv) + } + + // This is a Named type not a System type. + tuple := result.Tuple{ + Value: make(map[string]result.Value), + RuntimeType: in.ClassType, + } + + for _, elem := range in.Elements { + obj, err := i.evalExpression(elem.Value) + if err != nil { + return result.Value{}, err + } + // The parser should have already validated that the name and value of each element matches + // model info for the class type. + tuple.Value[elem.Name] = obj + } + + return result.NewWithSources(tuple, in) +} diff --git a/interpreter/operator_arithmetic.go b/interpreter/operator_arithmetic.go new file mode 100644 index 0000000..32ee91b --- /dev/null +++ b/interpreter/operator_arithmetic.go @@ -0,0 +1,682 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + "math" + "reflect" + "time" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +// ARITHMETIC OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#arithmetic-operators-4 + +const ( + // Max/Min values for CQL decimals. + // In the future we may switch to using math.MaxFloat64. + maxDecimal = float64(99999999999999999999.99999999) + minDecimal = float64(-99999999999999999999.99999999) +) + +// op(left Integer, right Integer) Integer +// https://cql.hl7.org/09-b-cqlreference.html#add +// https://cql.hl7.org/09-b-cqlreference.html#subtract +// https://cql.hl7.org/09-b-cqlreference.html#multiply +// https://cql.hl7.org/09-b-cqlreference.html#truncated-divide +// https://cql.hl7.org/09-b-cqlreference.html#modulo +func evalArithmeticInteger(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToInt32) + if err != nil { + return result.Value{}, err + } + return arithmetic(m, l, r) +} + +// op(left Long, right Long) Long +// https://cql.hl7.org/09-b-cqlreference.html#add +// https://cql.hl7.org/09-b-cqlreference.html#subtract +// https://cql.hl7.org/09-b-cqlreference.html#multiply +// https://cql.hl7.org/09-b-cqlreference.html#truncated-divide +// https://cql.hl7.org/09-b-cqlreference.html#modulo +func evalArithmeticLong(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToInt64) + if err != nil { + return result.Value{}, err + } + return arithmetic(m, l, r) +} + +// op(left Decimal, right Decimal) Decimal +// https://cql.hl7.org/09-b-cqlreference.html#add +// https://cql.hl7.org/09-b-cqlreference.html#subtract +// https://cql.hl7.org/09-b-cqlreference.html#multiply +// https://cql.hl7.org/09-b-cqlreference.html#truncated-divide +// https://cql.hl7.org/09-b-cqlreference.html#modulo +func evalArithmeticDecimal(m model.IBinaryExpression, lObj result.Value, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToFloat64) + if err != nil { + return result.Value{}, err + } + return arithmetic(m, l, r) +} + +// op(left Quantity, right Quantity) Quantity +// https://cql.hl7.org/09-b-cqlreference.html#add +// https://cql.hl7.org/09-b-cqlreference.html#subtract +// While the docs for these functions are ambiguous on this topic, performing +// arithmetic on Quantities with a unit of Day or less to a Date/DateTime with +// month or year precision is undefined and should return null. This is because +// of the variability of what the definition of a month is. If a user needs this +// functionality they should use UCUM duration values. +// See: https://cql.hl7.org/09-b-cqlreference.html#equal +func evalArithmeticQuantity(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToQuantity) + if err != nil { + return result.Value{}, err + } + return arithmeticQuantity(m, l, r) +} + +// op(left Date, right Quantity) Date +// https://cql.hl7.org/09-b-cqlreference.html#add-1 +// https://cql.hl7.org/09-b-cqlreference.html#subtract-1 +func evalArithmeticDate(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + r, err := result.ToQuantity(rObj) + if err != nil { + return result.Value{}, err + } + + d, err := result.ToDateTime(lObj) + if err != nil { + return result.Value{}, err + } + allowUnsetPrec := false // Dates must have a precision + err = validateDatePrecision(d.Precision, allowUnsetPrec) + if err != nil { + return result.Value{}, err + } + dtv, err := arithmeticDateTime(m, d, r) + if err != nil { + return result.Value{}, err + } + return result.New(result.Date{Date: dtv.Date, Precision: dtv.Precision}) +} + +// op(left DateTime, right Quantity) DateTime +// https://cql.hl7.org/09-b-cqlreference.html#add-1 +// https://cql.hl7.org/09-b-cqlreference.html#subtract-1 +func evalArithmeticDateTime(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + r, err := result.ToQuantity(rObj) + if err != nil { + return result.Value{}, err + } + + d, err := result.ToDateTime(lObj) + if err != nil { + return result.Value{}, err + } + allowUnsetPrec := false // DateTimes must have a precision. + err = validateDateTimePrecision(d.Precision, allowUnsetPrec) + if err != nil { + return result.Value{}, err + } + dtv, err := arithmeticDateTime(m, d, r) + if err != nil { + return result.Value{}, err + } + return result.New(dtv) +} + +func arithmetic[t float64 | int64 | int32](m model.IBinaryExpression, l, r t) (result.Value, error) { + switch m.(type) { + case *model.Add: + return result.New(l + r) + case *model.Subtract: + return result.New(l - r) + case *model.Multiply: + return result.New(l * r) + case *model.TruncatedDivide: + if r == 0 { + return result.New(nil) + } + // The first int64() truncates any decimal, then t() converts it back to the original type. + return result.New(t(int64(l / r))) + case *model.Divide: + if r == 0 { + return result.New(nil) + } + return result.New(l / r) + case *model.Modulo: + return mod(l, r) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Arithmetic Expression %v", m) +} + +// TODO(b/319156186): Add support for converting quantities between different units. +// TODO(b/319333058): Add support for Date + Quantity arithmetic. +// TODO(b/319525986): Add support for additional arithmetic for Quantities. +func arithmeticQuantity(m model.IBinaryExpression, l, r result.Quantity) (result.Value, error) { + if l.Unit != r.Unit { + return result.Value{}, fmt.Errorf("internal error - quantity unit conversion unsupported, got units: %s and %s", l.Unit, r.Unit) + } + switch m.(type) { + case *model.Add: + return result.New(result.Quantity{Value: l.Value + r.Value, Unit: l.Unit}) + case *model.Subtract: + return result.New(result.Quantity{Value: l.Value - r.Value, Unit: l.Unit}) + case *model.Multiply: + return result.Value{}, fmt.Errorf("internal error - quantity multiplication unsupported, got: %v and %v", l, r) + case *model.TruncatedDivide: + return result.New(result.Quantity{Value: float64(int64(l.Value / r.Value)), Unit: model.ONEUNIT}) + case *model.Divide: + return result.New(result.Quantity{Value: l.Value / r.Value, Unit: model.ONEUNIT}) + case *model.Modulo: + if l.Unit != r.Unit { + return result.Value{}, fmt.Errorf("internal error - quantity modulo with different units unsupported, got units: %s and %s", l.Unit, r.Unit) + } + if r.Value == 0 { + return result.New(nil) + } + return result.New(result.Quantity{Value: math.Mod(l.Value, r.Value), Unit: l.Unit}) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Arithmetic Expression %v", m) +} + +// arithmeticDateTime performs arithmetic operations for Date, Quantity values. +// When performing arithmetic over differing precisions, only whole values up to +// the given Date or DateTime's precision should be added. +func arithmeticDateTime(m model.IBinaryExpression, l result.DateTime, r result.Quantity) (result.DateTime, error) { + var sign int64 + switch m.(type) { + case *model.Add: + sign = 1 + case *model.Subtract: + sign = -1 + default: + return result.DateTime{}, fmt.Errorf("internal error - unsupported Binary Arithmetic Expression %v", m) + } + cq, err := convertQuantityUpToPrecision(r, l.Precision) + if err != nil { + return result.DateTime{}, err + } + + switch cq.Unit { + case model.YEARUNIT: + return result.DateTime{Date: l.Date.AddDate(int(sign)*int(cq.Value), 0, 0), Precision: l.Precision}, nil + case model.MONTHUNIT: + return result.DateTime{Date: l.Date.AddDate(0, int(sign)*int(cq.Value), 0), Precision: l.Precision}, nil + case model.WEEKUNIT: + // Weeks need to be converted to days before they can be operated on. + return result.DateTime{Date: l.Date.AddDate(0, 0, int(sign)*int(cq.Value*7)), Precision: l.Precision}, nil + case model.DAYUNIT: + return result.DateTime{Date: l.Date.AddDate(0, 0, int(sign)*int(cq.Value)), Precision: l.Precision}, nil + case model.HOURUNIT: + d := time.Hour * time.Duration(sign*int64(cq.Value)) + return result.DateTime{Date: l.Date.Add(d), Precision: l.Precision}, nil + case model.MINUTEUNIT: + d := time.Minute * time.Duration(sign*int64(cq.Value)) + return result.DateTime{Date: l.Date.Add(d), Precision: l.Precision}, nil + // Seconds and Milliseconds shouldn't be truncated so we have to convert them to + // nanoseconds manually. + case model.SECONDUNIT: + d := time.Duration(sign * int64(cq.Value*1000*1000*1000)) + return result.DateTime{Date: l.Date.Add(d), Precision: l.Precision}, nil + case model.MILLISECONDUNIT: + d := time.Duration(sign * int64(cq.Value*1000*1000)) + return result.DateTime{Date: l.Date.Add(d), Precision: l.Precision}, nil + } + return result.DateTime{}, fmt.Errorf("internal error - unsupported quantity unit %v in arithmetic operation", cq.Unit) +} + +// -(argument Integer) Integer +// https://cql.hl7.org/09-b-cqlreference.html#negate +func evalNegateInteger(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + val, err := result.ToInt32(obj) + if err != nil { + return result.Value{}, err + } + min, err := minValue(types.Integer, nil) + if err != nil { + return result.Value{}, err + } + if obj.Equal(min) { + return result.New(nil) + } + return result.New(-val) +} + +// -(argument Long) Long +// https://cql.hl7.org/09-b-cqlreference.html#negate +func evalNegateLong(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + val, err := result.ToInt64(obj) + if err != nil { + return result.Value{}, err + } + min, err := minValue(types.Long, nil) + if err != nil { + return result.Value{}, err + } + if obj.Equal(min) { + return result.New(nil) + } + return result.New(-val) +} + +// -(argument Decimal) Decimal +// https://cql.hl7.org/09-b-cqlreference.html#negate +func evalNegateDecimal(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + val, err := result.ToFloat64(obj) + if err != nil { + return result.Value{}, err + } + min, err := minValue(types.Decimal, nil) + if err != nil { + return result.Value{}, err + } + if obj.Equal(min) { + return result.New(nil) + } + return result.New(-val) +} + +// -(argument Quantity) Quantity +// https://cql.hl7.org/09-b-cqlreference.html#negate +func evalNegateQuantity(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + val, err := result.ToQuantity(obj) + if err != nil { + return result.Value{}, err + } + min, err := minValue(types.Quantity, nil) + if err != nil { + return result.Value{}, err + } + if obj.Equal(min) { + return result.New(nil) + } + val.Value = -val.Value + return result.New(val) +} + +// predecessor of(obj T) T +// https://cql.hl7.org/09-b-cqlreference.html#predecessor +func (i *interpreter) evalPredecessor(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + return predecessor(obj, &i.evaluationTimestamp) +} + +// successor of(obj T) T +// https://cql.hl7.org/09-b-cqlreference.html#successor +func (i *interpreter) evalSuccessor(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + return successor(obj, &i.evaluationTimestamp) +} + +func predecessor(obj result.Value, evaluationTimestamp *time.Time) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + minVal, err := minValue(obj.RuntimeType(), evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + if obj.Equal(minVal) { + return result.Value{}, fmt.Errorf("tried to compute predecessor for value that is already a min value, %v", obj.GolangValue()) + } + + switch t := obj.RuntimeType(); t { + case types.Integer: + i, err := result.ToInt32(obj) + if err != nil { + return result.Value{}, err + } + return result.New(i - 1) + case types.Long: + l, err := result.ToInt64(obj) + if err != nil { + return result.Value{}, err + } + return result.New(l - 1) + case types.Decimal: + d, err := result.ToFloat64(obj) + if err != nil { + return result.Value{}, err + } + return result.New(d - 0.00000001) + case types.Quantity: + // TODO: b/329707836 - Determine under what cases quantities should be incremented by whole numbers in stead of decimals. + q, err := result.ToQuantity(obj) + if err != nil { + return result.Value{}, err + } + return result.New(result.Quantity{Value: q.Value - 0.00000001, Unit: q.Unit}) + case types.Date, types.DateTime, types.Time: + return dateTimePredecessor(obj, evaluationTimestamp) + default: + return result.Value{}, fmt.Errorf("internal error - unsupported type %v", t) + } +} + +func successor(obj result.Value, evaluationTimestamp *time.Time) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + maxVal, err := maxValue(obj.RuntimeType(), evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + if obj.Equal(maxVal) { + return result.Value{}, fmt.Errorf("tried to compute successor for value that is already a max value, %v", obj.GolangValue()) + } + + switch t := obj.RuntimeType(); t { + case types.Integer: + i, err := result.ToInt32(obj) + if err != nil { + return result.Value{}, err + } + return result.New(i + 1) + case types.Long: + l, err := result.ToInt64(obj) + if err != nil { + return result.Value{}, err + } + return result.New(l + 1) + case types.Decimal: + d, err := result.ToFloat64(obj) + if err != nil { + return result.Value{}, err + } + return result.New(d + 0.00000001) + case types.Quantity: + // TODO: b/329707836 - Determine under what cases quantities should be incremented by whole numbers in stead of decimals. + q, err := result.ToQuantity(obj) + if err != nil { + return result.Value{}, err + } + return result.New(result.Quantity{Value: q.Value + 0.00000001, Unit: q.Unit}) + case types.Date, types.DateTime, types.Time: + return dateTimeSuccessor(obj, evaluationTimestamp) + default: + return result.Value{}, fmt.Errorf("internal error - unsupported type %v", t) + } +} + +// dateTimePredecessor computes the predecessor for a date time value. +// Returns error for unsupported types, precisions or values out of range. +func dateTimePredecessor(dt result.Value, evaluationTimestamp *time.Time) (result.Value, error) { + t := dt.RuntimeType() + switch t { + case types.Date, types.DateTime, types.Time: + // Valid types + default: + return result.Value{}, fmt.Errorf("internal error - unsupported type %v for date time predecessor", t) + } + + minVal, err := minValue(dt.RuntimeType(), evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + d, err := result.ToDateTime(dt) + if err != nil { + return result.Value{}, err + } + minDt, err := result.ToDateTime(minVal) + if err != nil { + return result.Value{}, err + } + + // Check if the Value is the minimum at its precision. + minDt.Precision = d.Precision + if cmpResult, err := compareDateTime(d, minDt); err != nil { + return result.Value{}, err + } else if cmpResult == leftEqualRight || cmpResult == leftBeforeRight { + return result.Value{}, fmt.Errorf("tried to compute predecessor for %s that is already a min value for it's precision, %v", t, dt.GolangValue()) + } + + var predecessorVal result.DateTime + switch d.Precision { + case model.YEAR: + predecessorVal = result.DateTime{Date: d.Date.AddDate(-1, 0, 0), Precision: d.Precision} + case model.MONTH: + predecessorVal = result.DateTime{Date: d.Date.AddDate(0, -1, 0), Precision: d.Precision} + case model.DAY: + predecessorVal = result.DateTime{Date: d.Date.AddDate(0, 0, -1), Precision: d.Precision} + case model.HOUR: + predecessorVal = result.DateTime{Date: d.Date.Add(-time.Hour), Precision: d.Precision} + case model.MINUTE: + predecessorVal = result.DateTime{Date: d.Date.Add(-time.Minute), Precision: d.Precision} + case model.SECOND: + predecessorVal = result.DateTime{Date: d.Date.Add(-time.Second), Precision: d.Precision} + case model.MILLISECOND: + predecessorVal = result.DateTime{Date: d.Date.Add(-time.Millisecond), Precision: d.Precision} + default: + return result.Value{}, fmt.Errorf("internal error - unsupported precision %v in %s predecessor", d.Precision, t) + } + + switch dt.RuntimeType() { + case types.Date: + return result.New(result.Date{Date: predecessorVal.Date, Precision: predecessorVal.Precision}) + case types.DateTime: + return result.New(result.DateTime{Date: predecessorVal.Date, Precision: predecessorVal.Precision}) + case types.Time: + return result.New(result.Time{Date: predecessorVal.Date, Precision: predecessorVal.Precision}) + default: + return result.Value{}, fmt.Errorf("internal error - unsupported type %v for date time predecessor", t) + } +} + +// dateTimeSuccessor computes the successor for a date time value. +// Returns error for unsupported types, precisions or values out of range. +func dateTimeSuccessor(dt result.Value, evaluationTimestamp *time.Time) (result.Value, error) { + t := dt.RuntimeType() + switch t { + case types.Date, types.DateTime, types.Time: + // Valid types + default: + return result.Value{}, fmt.Errorf("internal error - unsupported type %v for date time successor", t) + } + + maxVal, err := maxValue(dt.RuntimeType(), evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + d, err := result.ToDateTime(dt) + if err != nil { + return result.Value{}, err + } + maxDt, err := result.ToDateTime(maxVal) + if err != nil { + return result.Value{}, err + } + + // Check if the Value is the maximum at its precision. + maxDt.Precision = d.Precision + if cmpResult, err := compareDateTime(d, maxDt); err != nil { + return result.Value{}, err + } else if cmpResult == leftEqualRight || cmpResult == leftAfterRight { + return result.Value{}, fmt.Errorf("tried to compute successor for %s that is already a max value for it's precision, %v", t, dt.GolangValue()) + } + + var successorVal result.DateTime + switch d.Precision { + case model.YEAR: + successorVal = result.DateTime{Date: d.Date.AddDate(1, 0, 0), Precision: d.Precision} + case model.MONTH: + successorVal = result.DateTime{Date: d.Date.AddDate(0, 1, 0), Precision: d.Precision} + case model.DAY: + successorVal = result.DateTime{Date: d.Date.AddDate(0, 0, 1), Precision: d.Precision} + case model.HOUR: + successorVal = result.DateTime{Date: d.Date.Add(time.Hour), Precision: d.Precision} + case model.MINUTE: + successorVal = result.DateTime{Date: d.Date.Add(time.Minute), Precision: d.Precision} + case model.SECOND: + successorVal = result.DateTime{Date: d.Date.Add(time.Second), Precision: d.Precision} + case model.MILLISECOND: + successorVal = result.DateTime{Date: d.Date.Add(time.Millisecond), Precision: d.Precision} + default: + return result.Value{}, fmt.Errorf("internal error - unsupported precision %v in date time successor", d.Precision) + } + + switch dt.RuntimeType() { + case types.Date: + return result.New(result.Date{Date: successorVal.Date, Precision: successorVal.Precision}) + case types.DateTime: + return result.New(result.DateTime{Date: successorVal.Date, Precision: successorVal.Precision}) + case types.Time: + return result.New(result.Time{Date: successorVal.Date, Precision: successorVal.Precision}) + default: + return result.Value{}, fmt.Errorf("internal error - unsupported type %v for date time successor", t) + } +} + +// maximum() T +// https://cql.hl7.org/09-b-cqlreference.html#maximum +func (i *interpreter) evalMaxValue(m *model.MaxValue) (result.Value, error) { + return maxValue(m.ValueType, &i.evaluationTimestamp) +} + +// minimum() T +// https://cql.hl7.org/09-b-cqlreference.html#minimum +func (i *interpreter) evalMinValue(m *model.MinValue) (result.Value, error) { + return minValue(m.ValueType, &i.evaluationTimestamp) +} + +// Note: For Date/Time based values, the spec states that an engine can choose to set the timezone +// to UTC for min/max values, we use the evaluation timestamp's timezone. +// We do this because when creating a literal it's also in the evaluation timestamp's timezone, and +// some external tests will fail when these are different. +func maxValue(t types.IType, evaluationTimestamp *time.Time) (result.Value, error) { + switch t { + case types.Integer: + return result.New(int32(math.MaxInt32)) + case types.Long: + return result.New(int64(math.MaxInt64)) + case types.Decimal: + return result.New(maxDecimal) + case types.Quantity: + return result.New(result.Quantity{Value: maxDecimal, Unit: "1"}) + case types.Date: + if evaluationTimestamp == nil { + return result.Value{}, fmt.Errorf("internal error - evaluation timestamp cannot be nil for Date max value") + } + return result.New(result.Date{Date: time.Date(9999, 12, 31, 0, 0, 0, 0, evaluationTimestamp.Location()), Precision: model.DAY}) + case types.DateTime: + if evaluationTimestamp == nil { + return result.Value{}, fmt.Errorf("internal error - evaluation timestamp cannot be nil for DateTime max value") + } + return result.New(result.DateTime{Date: time.Date(9999, 12, 31, 23, 59, 59, 999, evaluationTimestamp.Location()), Precision: model.MILLISECOND}) + case types.Time: + if evaluationTimestamp == nil { + return result.Value{}, fmt.Errorf("internal error - evaluation timestamp cannot be nil for Time max value") + } + return result.New(result.Time{Date: time.Date(0, time.January, 1, 23, 59, 59, 999000000, evaluationTimestamp.Location()), Precision: model.MILLISECOND}) + default: + return result.Value{}, fmt.Errorf("unsupported type, cannot compute max value for: %v", t) + } +} + +// Note: For Date/Time based values, the spec states that an engine can choose to set the timezone +// to UTC for min/max values, we use the evaluation timestamp's timezone. +// We do this because when creating a literal it's also in the evaluation timestamp's timezone, and +// some external tests will fail when these are different. +func minValue(t types.IType, evaluationTimestamp *time.Time) (result.Value, error) { + switch t { + case types.Integer: + return result.New(int32(math.MinInt32)) + case types.Long: + return result.New(int64(math.MinInt64)) + case types.Decimal: + return result.New(minDecimal) + case types.Quantity: + return result.New(result.Quantity{Value: minDecimal, Unit: "1"}) + case types.Date: + if evaluationTimestamp == nil { + return result.Value{}, fmt.Errorf("internal error - evaluation timestamp cannot be nil for Date min value") + } + return result.New(result.Date{Date: time.Date(1, 1, 1, 0, 0, 0, 0, evaluationTimestamp.Location()), Precision: model.DAY}) + case types.DateTime: + if evaluationTimestamp == nil { + return result.Value{}, fmt.Errorf("internal error - evaluation timestamp cannot be nil for DateTime min value") + } + return result.New(result.DateTime{Date: time.Date(1, 1, 1, 0, 0, 0, 0, evaluationTimestamp.Location()), Precision: model.MILLISECOND}) + case types.Time: + if evaluationTimestamp == nil { + return result.Value{}, fmt.Errorf("internal error - evaluation timestamp cannot be nil for Time min value") + } + return result.New(result.Time{Date: time.Date(0, time.January, 1, 0, 0, 0, 0, evaluationTimestamp.Location()), Precision: model.MILLISECOND}) + default: + return result.Value{}, fmt.Errorf("unsupported type, cannot compute min value for: %v", t) + } +} + +// https://cql.hl7.org/09-b-cqlreference.html#modulo +// According to the spec, "If the result of the modulo cannot be represented, or the right argument +// is 0, the result is null.". +func mod(l, r any) (result.Value, error) { + // The modulo operator doesn't support floats so we need to call math.Mod. + switch l.(type) { + case int32, int: + rVal := r.(int32) + if rVal == 0 { + return result.New(nil) + } + return result.New(l.(int32) % r.(int32)) + case int64: + rVal := r.(int64) + if rVal == 0 { + return result.New(nil) + } + return result.New(l.(int64) % r.(int64)) + case float64: + rVal := r.(float64) + if rVal == 0 { + return result.New(nil) + } + return result.New(math.Mod(l.(float64), r.(float64))) + } + return result.Value{}, fmt.Errorf("internal error - mod does not support %v", reflect.TypeOf(l)) +} diff --git a/interpreter/operator_clinical.go b/interpreter/operator_clinical.go new file mode 100644 index 0000000..74fecff --- /dev/null +++ b/interpreter/operator_clinical.go @@ -0,0 +1,220 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/terminology" + "github.com/google/cql/types" +) + +// CLINICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#clinical-operators-3 + +// CalculateAgeIn[Years|Months|Weeks|Days]At(birthDate Date, asOf Date) Integer +// https://cql.hl7.org/09-b-cqlreference.html#calculateageat +func evalCalculateAgeAtDate(b model.IBinaryExpression, birthObj, asOfObj result.Value) (result.Value, error) { + m := b.(*model.CalculateAgeAt) + p := model.DateTimePrecision(m.Precision) + if err := validatePrecision(p, []model.DateTimePrecision{model.YEAR, model.MONTH, model.WEEK, model.DAY}); err != nil { + return result.Value{}, err + } + if result.IsNull(birthObj) || result.IsNull(asOfObj) { + return result.New(nil) + } + + birth, asOf, err := applyToValues(birthObj, asOfObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return calculateAgeAt(birth, asOf, p) +} + +// CalculateAgeIn[Years|Months|Weeks|Days|Hours|Minutes|Seconds]At(birthDate DateTime, asOf DateTime) Integer +// https://cql.hl7.org/09-b-cqlreference.html#calculateageat +func evalCalculateAgeAtDateTime(b model.IBinaryExpression, birthObj, asOfObj result.Value) (result.Value, error) { + m := b.(*model.CalculateAgeAt) + p := model.DateTimePrecision(m.Precision) + if err := validatePrecision(p, []model.DateTimePrecision{model.YEAR, model.MONTH, model.WEEK, model.DAY, model.HOUR, model.MINUTE, model.SECOND}); err != nil { + return result.Value{}, err + } + if result.IsNull(birthObj) || result.IsNull(asOfObj) { + return result.New(nil) + } + + birth, asOf, err := applyToValues(birthObj, asOfObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return calculateAgeAt(birth, asOf, p) +} + +// calculateAgeAt is the helper to calculate age for Date and DateTime overloads. +func calculateAgeAt(birth, asOf result.DateTime, p model.DateTimePrecision) (result.Value, error) { + // TODO(b/304349114): CalculateAgeAt is just syntactic sugar over the duration system operator. We + // should fully implement duration and call the duration helper function from calculateAge. + + if p == model.YEAR { + if asOf.Date.Month() > birth.Date.Month() || + (asOf.Date.Month() == birth.Date.Month() && asOf.Date.Day() >= birth.Date.Day()) { + return result.New(asOf.Date.Year() - birth.Date.Year()) + } + + return result.New(asOf.Date.Year() - birth.Date.Year() - 1) + } + + if p == model.MONTH { + months := 12*(asOf.Date.Year()-birth.Date.Year()) + int((asOf.Date.Month())) - int(birth.Date.Month()) + if asOf.Date.Day() < birth.Date.Day() { + months-- + } + + return result.New(months) + } + + if p == model.WEEK { + return result.New(int(asOf.Date.Sub(birth.Date).Hours() / 24 / 7)) + } + + if p == model.DAY { + return result.New(int(asOf.Date.Sub(birth.Date).Hours() / 24)) + } + + // TODO(b/304349114): Per https://cql.hl7.org/09-b-cqlreference.html#ageat and + // the external tests mentioned in b/304349114#comment3, these date-related + // functions should propagate "uncertainty" ranges if precision is less than + // a day. + return result.Value{}, fmt.Errorf("Unsupported CalculateAgeAt precision %v", p) +} + +// in(code Code, codesystem CodeSystemRef) Boolean +// in(codes List, codesystem CodeSystemRef) Boolean +// in(concept Concept, codesystem CodeSystemRef) Boolean +// in(concepts List, codesystem CodeSystemRef) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#in-codesystem +// The In operator for list overloads checks if any value is in the CodeSystem. +// TODO: b/327282181 - add support for other In CodeSystem Operators +func (i *interpreter) evalInCodeSystem(b model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) { + return result.New(false) + } + + if result.IsNull(rObj) { + return result.Value{}, fmt.Errorf("in operator for CodeSystems should always resolve to a valid codesystem got: %v", rObj) + } + csv, err := result.ToCodeSystem(rObj) + if err != nil { + return result.Value{}, err + } + + termCodes, err := valueToCodes(lObj) + if err != nil { + return result.Value{}, err + } + + in, err := i.terminologyProvider.AnyInCodeSystem(termCodes, csv.ID, csv.Version) + if err != nil { + return result.Value{}, err + } + return result.New(in) +} + +// in(code Code, valueset ValueSetRef) Boolean +// in(codes List, valueset ValueSetRef) Boolean +// in(concept Concept, valueset ValueSetRef) Boolean +// in(concepts List, valueset ValueSetRef) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#in-valueset +// The In operator for list overloads checks if any value is in the ValueSet. +// TODO: b/327281742 - add support for other In ValueSet Operators +func (i *interpreter) evalInValueSet(b model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) { + return result.New(false) + } + + if result.IsNull(rObj) { + return result.Value{}, fmt.Errorf("in operator for Valuesets should always resolve to a valid valueset got: %v", rObj) + } + vsv, err := result.ToValueSet(rObj) + if err != nil { + return result.Value{}, err + } + + termCodes, err := valueToCodes(lObj) + if err != nil { + return result.Value{}, err + } + + in, err := i.terminologyProvider.AnyInValueSet(termCodes, vsv.ID, vsv.Version) + if err != nil { + return result.Value{}, err + } + return result.New(in) +} + +// valueToCodes is the helper to convert a value to a list of terminology.Code. Returns an error for +// value types that are not valid clinical values. Currently only supports Code, Concept, +// List, List. +func valueToCodes(o result.Value) ([]terminology.Code, error) { + var termCodes []terminology.Code + if rt := o.RuntimeType(); rt.Equal(types.Code) { + lv, err := result.ToCode(o) + if err != nil { + return nil, err + } + return []terminology.Code{{System: lv.System, Code: lv.Code}}, nil + } else if rt.Equal(types.Concept) { + concept, err := result.ToConcept(o) + if err != nil { + return nil, err + } + for _, c := range concept.Codes { + termCodes = append(termCodes, terminology.Code{System: c.System, Code: c.Code}) + } + return termCodes, nil + } else if rt.Equal(&types.List{ElementType: types.Code}) { + list, err := result.ToSlice(o) + if err != nil { + return nil, err + } + + for _, c := range list { + code, err := result.ToCode(c) + if err != nil { + return nil, err + } + termCodes = append(termCodes, terminology.Code{System: code.System, Code: code.Code}) + } + return termCodes, nil + } else if rt.Equal(&types.List{ElementType: types.Concept}) { + list, err := result.ToSlice(o) + if err != nil { + return nil, err + } + + for _, c := range list { + concept, err := result.ToConcept(c) + if err != nil { + return nil, err + } + for _, c := range concept.Codes { + termCodes = append(termCodes, terminology.Code{System: c.System, Code: c.Code}) + } + } + return termCodes, nil + } + return nil, fmt.Errorf("unsupported runtime type for clinical in operator, got: %v", o.RuntimeType()) +} diff --git a/interpreter/operator_clinical_test.go b/interpreter/operator_clinical_test.go new file mode 100644 index 0000000..3c98c60 --- /dev/null +++ b/interpreter/operator_clinical_test.go @@ -0,0 +1,72 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" +) + +func TestClinicalOperatorCalculateAgeAt_Error(t *testing.T) { + ageTests := []struct { + name string + expr model.IExpression + wantErr string + }{ + { + name: "Invalid Precision Date", + expr: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Date), + Operands: []model.IExpression{ + model.NewLiteral("@1981", types.Date), + model.NewLiteral("@2023-06-14", types.Date), + }, + }, + Precision: model.MINUTE, + }, + wantErr: "precision must be one of [year month week day]", + }, + { + name: "Invalid Precision DateTime", + expr: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.DateTime), + Operands: []model.IExpression{ + model.NewLiteral("@1981-06-15T10:01:01.000Z", types.DateTime), + model.NewLiteral("@2023-06-14T10:01:01.000Z", types.DateTime), + }, + }, + Precision: model.MILLISECOND, + }, + wantErr: "precision must be one of [year month week day hour minute second]", + }, + } + for _, tc := range ageTests { + t.Run(tc.name, func(t *testing.T) { + _, err := Eval(context.Background(), []*model.Library{wrapInLib(t, tc.expr)}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("TestClinicalOperatorCalculateAgeAt_Error() call to evalLibrary() succeeded, wanted error") + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("TestClinicalOperatorCalculateAgeAt_Error() returned unexpected error: %v want: %s", err, tc.wantErr) + } + }) + } +} diff --git a/interpreter/operator_comparison.go b/interpreter/operator_comparison.go new file mode 100644 index 0000000..cb0e8cc --- /dev/null +++ b/interpreter/operator_comparison.go @@ -0,0 +1,379 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "cmp" + "errors" + "fmt" + "strings" + "unicode" + + "github.com/google/cql/internal/convert" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +// COMPARISON OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#comparison-operators-4 + +// =(left T, right T) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#equal +func (i *interpreter) evalEqual(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + // TODO: b/327612471 - Revisit Equal to make sure it is correctly implemented for all types. + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + return result.New(lObj.Equal(rObj)) +} + +// =(left DateTime, right DateTime) Boolean +// =(left Date, right Date) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#equal +func evalEqualDateTime(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + lVal, rVal, err := applyToValues(lObj, rObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + comp, err := compareDateTime(lVal, rVal) + if err != nil { + return result.Value{}, err + } + switch comp { + case leftEqualRight: + return result.New(true) + case insufficientPrecision: + return result.New(nil) + default: + return result.New(false) + } +} + +// ~(left Boolean, right Boolean) Boolean +// ~(left Integer, right Integer) Boolean +// ~(left Long, right Long) Boolean +// All equivalent overloads should be resilient to a nil model. +// https://cql.hl7.org/09-b-cqlreference.html#equivalent +func evalEquivalentSimpleType(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + // TODO(b/301606416): Revisit Equivalent to make sure it is correctly implemented for all types. + if result.IsNull(lObj) && result.IsNull(rObj) { + return result.New(true) + } + return result.New(lObj.Equal(rObj)) +} + +// evalEquivalentValue applies the CQL equivalent operator to the passed Values. +func (i *interpreter) evalEquivalentValue(lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) && result.IsNull(rObj) { + return result.New(true) + } + if result.IsNull(lObj) != result.IsNull(rObj) { + return result.New(false) + } + overloads, err := i.binaryOverloads(&model.Equivalent{}) + if err != nil { + return result.Value{}, err + } + // We are able to use RuntimeType instead of static type since all null cases are handled above, + // and there are no Choice type overloads defined. + opTypes := []types.IType{lObj.RuntimeType(), rObj.RuntimeType()} + innerEquivalentFunc, err := convert.ExactOverloadMatch(opTypes, overloads, i.modelInfo, "Equivalent") + if err != nil { + return result.Value{}, err + } + // All equivalent overloads should be resilient to a nil model. + return innerEquivalentFunc(nil, lObj, rObj) +} + +// ~(left List, right List) Boolean +// All equivalent overloads should be resilient to a nil model. +// https://cql.hl7.org/09-b-cqlreference.html#equivalent-2 +func (i *interpreter) evalEquivalentList(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) && result.IsNull(rObj) { + return result.New(true) + } + if result.IsNull(lObj) != result.IsNull(rObj) { + return result.New(false) + } + lList, err := result.ToSlice(lObj) + if err != nil { + return result.Value{}, err + } + rList, err := result.ToSlice(rObj) + if err != nil { + return result.Value{}, err + } + + if len(lList) != len(rList) { + return result.New(false) + } + + // TODO: b/301606416 - For a non-mixed list, one optimization could be to compute the equivalent + // overload to call once instead of computing it every time inside evalEquivalentValue. + for idx := range lList { + // TODO: b/326277425 - Properly support mixed lists. For mixed lists (including List with + // mixed element types), the parser may not be able to apply all valid implicit conversions so + // we may need to consider applying them in the interpreter. For now, an element comparison with + // types that are not exact matches or subtypes will result in an Equivalent overload matching + // error. In the future we may consider checking if elements are convertible and returning false + // instead of an error, but we can address this in the future. + equi, err := i.evalEquivalentValue(lList[idx], rList[idx]) + if errors.Is(err, convert.ErrNoMatch) { + return result.Value{}, fmt.Errorf("unable to match Equivalent overload for elements in a list, this is likely because our engine does not fully support mixed type lists yet: %w", err) + } + if err != nil { + return result.Value{}, err + } + // All equivalent overloads should return true or false, so we expect a non-null boolean here. + isEquivalent, err := result.ToBool(equi) + if err != nil { + return result.Value{}, err + } + if !isEquivalent { + return result.New(false) + } + } + return result.New(true) +} + +// ~(left String, right String) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#equivalent +func evalEquivalentString(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) && result.IsNull(rObj) { + return result.New(true) + } + if result.IsNull(lObj) != result.IsNull(rObj) { + return result.New(false) + } + lStr, rStr, err := applyToValues(lObj, rObj, result.ToString) + if err != nil { + return result.Value{}, err + } + return result.New(equivalentString(lStr) == equivalentString(rStr)) +} + +// equivalentString converts all characters to lowercase, and normalizes all whitespace to a single +// space for equivalent string comparison. +func equivalentString(input string) string { + var out strings.Builder + for _, elem := range input { + if unicode.IsSpace(elem) { + out.WriteString(" ") + } else { + out.WriteRune(unicode.ToLower(elem)) + } + } + return out.String() +} + +// ~(left Interval, right Interval) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#equivalent-1 +func (i *interpreter) evalEquivalentInterval(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) && result.IsNull(rObj) { + return result.New(true) + } + if result.IsNull(lObj) != result.IsNull(rObj) { + return result.New(false) + } + + // Check to see if start and end points of the interval are equivalent. + startL, err := start(lObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + startR, err := start(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + endL, err := end(lObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + endR, err := end(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + + startEqui, err := i.evalEquivalentValue(startL, startR) + if err != nil { + return result.Value{}, err + } + endEqui, err := i.evalEquivalentValue(endL, endR) + if err != nil { + return result.Value{}, err + } + + // All equivalent overloads should return true or false, so we expect non-null booleans here. + startEquiBool, endEquiBool, err := applyToValues(startEqui, endEqui, result.ToBool) + if err != nil { + return result.Value{}, err + } + if startEquiBool && endEquiBool { + return result.New(true) + } + return result.New(false) +} + +// ~(left DateTime, right DateTime) Boolean +// ~(left Date, right Date) Boolean +// All equivalent overloads should be resilient to a nil model. +// https://cql.hl7.org/09-b-cqlreference.html#equivalent +func evalEquivalentDateTime(_ model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) && result.IsNull(rObj) { + return result.New(true) + } + if result.IsNull(lObj) != result.IsNull(rObj) { + return result.New(false) + } + lVal, rVal, err := applyToValues(lObj, rObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + comp, err := compareDateTime(lVal, rVal) + if err != nil { + return result.Value{}, err + } + switch comp { + case leftEqualRight: + return result.New(true) + case insufficientPrecision: + return result.New(false) + default: + return result.New(false) + } +} + +// op(left Integer, right Integer) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#less +// https://cql.hl7.org/09-b-cqlreference.html#less-or-equal +// https://cql.hl7.org/09-b-cqlreference.html#greater +// https://cql.hl7.org/09-b-cqlreference.html#greater-or-equal +func evalCompareInteger(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToInt32) + if err != nil { + return result.Value{}, err + } + return compare(m, l, r) +} + +// op(left Long, right Long) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#less +// https://cql.hl7.org/09-b-cqlreference.html#less-or-equal +// https://cql.hl7.org/09-b-cqlreference.html#greater +// https://cql.hl7.org/09-b-cqlreference.html#greater-or-equal +func evalCompareLong(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToInt64) + if err != nil { + return result.Value{}, err + } + return compare(m, l, r) +} + +// op(left Decimal, right Decimal) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#less +// https://cql.hl7.org/09-b-cqlreference.html#less-or-equal +// https://cql.hl7.org/09-b-cqlreference.html#greater +// https://cql.hl7.org/09-b-cqlreference.html#greater-or-equal +func evalCompareDecimal(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToFloat64) + if err != nil { + return result.Value{}, err + } + return compare(m, l, r) +} + +// op(left String, right String) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#less +// https://cql.hl7.org/09-b-cqlreference.html#less-or-equal +// https://cql.hl7.org/09-b-cqlreference.html#greater +// https://cql.hl7.org/09-b-cqlreference.html#greater-or-equal +func evalCompareString(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToString) + if err != nil { + return result.Value{}, err + } + return compare(m, l, r) +} + +// op(left DateTime, right DateTime) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#less +// https://cql.hl7.org/09-b-cqlreference.html#less-or-equal +// https://cql.hl7.org/09-b-cqlreference.html#greater +// https://cql.hl7.org/09-b-cqlreference.html#greater-or-equal +func evalCompareDateTime(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, r, err := applyToValues(lObj, rObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + switch m.(type) { + case *model.Less: + return beforeDateTime(l, r) + case *model.LessOrEqual: + return beforeOrEqualDateTime(l, r) + case *model.Greater: + return afterDateTime(l, r) + case *model.GreaterOrEqual: + return afterOrEqualDateTime(l, r) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Comparison Expression %v", m) +} + +func compare[n cmp.Ordered](m model.IBinaryExpression, l, r n) (result.Value, error) { + switch m.(type) { + case *model.Less: + return result.New(l < r) + case *model.LessOrEqual: + return result.New(l <= r) + case *model.Greater: + return result.New(l > r) + case *model.GreaterOrEqual: + return result.New(l >= r) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Comparison Expression %v", m) +} + +// ~(left Code, right Code) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#equivalent +func evalEquivalentCode(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) && result.IsNull(rObj) { + return result.New(true) + } + if result.IsNull(lObj) != result.IsNull(rObj) { + return result.New(false) + } + + lc := lObj.GolangValue().(result.Code) + rc := rObj.GolangValue().(result.Code) + eq := lc.Code == rc.Code && lc.System == rc.System + return result.New(eq) +} diff --git a/interpreter/operator_datetime.go b/interpreter/operator_datetime.go new file mode 100644 index 0000000..2fa98fb --- /dev/null +++ b/interpreter/operator_datetime.go @@ -0,0 +1,911 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "cmp" + "errors" + "fmt" + "time" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +// DATETIME OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#datetime-operators-2 + +// op(left Date, right Date) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#after +// https://cql.hl7.org/09-b-cqlreference.html#before +// https://cql.hl7.org/09-b-cqlreference.html#same-or-after-1 +// https://cql.hl7.org/09-b-cqlreference.html#same-or-before-1 +func evalCompareDateWithPrecision(b model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + + p, err := precisionFromBinaryExpression(b) + if err != nil { + return result.Value{}, err + } + allowUnsetPrec := true + if err := validateDatePrecision(p, allowUnsetPrec); err != nil { + return result.Value{}, err + } + l, r, err := applyToValues(lObj, rObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + + switch b.(type) { + case *model.After: + return afterDateTimeWithPrecision(l, r, p) + case *model.Before: + return beforeDateTimeWithPrecision(l, r, p) + case *model.SameOrAfter: + return afterOrEqualDateTimeWithPrecision(l, r, p) + case *model.SameOrBefore: + return beforeOrEqualDateTimeWithPrecision(l, r, p) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Comparison Expression %v", b) +} + +// op(left DateTime, right DateTime) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#after +// https://cql.hl7.org/09-b-cqlreference.html#before +// https://cql.hl7.org/09-b-cqlreference.html#same-or-after-1 +// https://cql.hl7.org/09-b-cqlreference.html#same-or-before-1 +func evalCompareDateTimeWithPrecision(b model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + p, err := precisionFromBinaryExpression(b) + if err != nil { + return result.Value{}, err + } + allowUnsetPrec := true + if err := validateDateTimePrecision(p, allowUnsetPrec); err != nil { + return result.Value{}, err + } + l, r, err := applyToValues(lObj, rObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + + switch b.(type) { + case *model.After: + return afterDateTimeWithPrecision(l, r, p) + case *model.Before: + return beforeDateTimeWithPrecision(l, r, p) + case *model.SameOrAfter: + return afterOrEqualDateTimeWithPrecision(l, r, p) + case *model.SameOrBefore: + return beforeOrEqualDateTimeWithPrecision(l, r, p) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Comparison Expression %v", b) +} + +func precisionFromBinaryExpression(b model.IBinaryExpression) (model.DateTimePrecision, error) { + var p model.DateTimePrecision + switch t := b.(type) { + case *model.After: + p = t.Precision + case *model.Before: + p = t.Precision + case *model.SameOrAfter: + p = t.Precision + case *model.SameOrBefore: + p = t.Precision + default: + return model.DateTimePrecision(""), fmt.Errorf("internal error - unsupported Binary Comparison Expression %v", b) + } + return p, nil +} + +// afterDateTime returns whether or not the given DateTimeValue comes after the right DateTimeValue. +// Returns null in cases where values cannot be compared such as right precision being less than +// left precision. +func afterDateTime(l, r result.DateTime) (result.Value, error) { + compareResult, err := compareDateTime(l, r) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(false) + case leftEqualRight: + return result.New(false) + case leftAfterRight: + return result.New(true) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateAfter") +} + +// afterDateTimeWithPrecision returns whether or not the given DateTimeValue comes after the right +// DateTimeValue up to the given precision. Returns null in cases where values cannot be compared +// such as right precision being less than left precision. +func afterDateTimeWithPrecision(l, r result.DateTime, p model.DateTimePrecision) (result.Value, error) { + compareResult, err := compareDateTimeWithPrecision(l, r, p) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(false) + case leftEqualRight: + return result.New(false) + case leftAfterRight: + return result.New(true) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateAfter") +} + +func afterOrEqualDateTime(l, r result.DateTime) (result.Value, error) { + compareResult, err := compareDateTime(l, r) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(false) + case leftEqualRight: + return result.New(true) + case leftAfterRight: + return result.New(true) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateAfter") +} + +// afterOrEqualDateTimeWithPrecision returns whether or not the given DateTimeValue is on or after +// the right DateTimeValue up to the given precision. Returns null in cases where values cannot be +// compared such as right precision being less than left precision. +func afterOrEqualDateTimeWithPrecision(l, r result.DateTime, p model.DateTimePrecision) (result.Value, error) { + compareResult, err := compareDateTimeWithPrecision(l, r, p) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(false) + case leftEqualRight: + return result.New(true) + case leftAfterRight: + return result.New(true) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateAfter") +} + +// beforeDateTime returns whether or not the given DateTimeValue comes before the right +// DateTimeValue. Returns null in cases where values cannot be compared such as right precision +// being less than left precision. +func beforeDateTime(l, r result.DateTime) (result.Value, error) { + compareResult, err := compareDateTime(l, r) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(true) + case leftEqualRight: + return result.New(false) + case leftAfterRight: + return result.New(false) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateTimeBefore") +} + +func beforeOrEqualDateTime(l, r result.DateTime) (result.Value, error) { + compareResult, err := compareDateTime(l, r) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(true) + case leftEqualRight: + return result.New(true) + case leftAfterRight: + return result.New(false) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateTimeBefore") +} + +// beforeDateTimeWithPrecision returns whether or not the given DateTimeValue comes before the right +// DateTimeValue up to the given precision. Returns null in cases where values cannot be compared +// such as right precision being less than left precision. +func beforeDateTimeWithPrecision(l, r result.DateTime, p model.DateTimePrecision) (result.Value, error) { + compareResult, err := compareDateTimeWithPrecision(l, r, p) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(true) + case leftEqualRight: + return result.New(false) + case leftAfterRight: + return result.New(false) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateTimeBefore") +} + +// beforeOrEqualDateTimeWithPrecision returns whether or not the given DateTimeValue is on or before +// the right DateTimeValue up to the given precision. Returns null in cases where values cannot be +// compared such as right precision being less than left precision. +func beforeOrEqualDateTimeWithPrecision(l, r result.DateTime, p model.DateTimePrecision) (result.Value, error) { + compareResult, err := compareDateTimeWithPrecision(l, r, p) + if err != nil { + return result.Value{}, err + } + switch compareResult { + case leftBeforeRight: + return result.New(true) + case leftEqualRight: + return result.New(true) + case leftAfterRight: + return result.New(false) + case insufficientPrecision: + return result.New(nil) + } + return result.Value{}, errors.New("internal error - reached the end of timeComparison enum in dateTimeBefore") +} + +// CanConvertQuantity(left Quantity, right String) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#canconvertquantity +// Returns whether or not a Quantity can be converted into the given unit string. +// This is not a required function to implement, and for the time being we are +// choosing to not implement unit conversion so this function always returns false. +func evalCanConvertQuantity(b model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + return result.New(false) +} + +// difference in _precision_ between(left Date, right Date) Integer +// https://cql.hl7.org/09-b-cqlreference.html#difference +// Returns the number of boundaries crossed between two dates. +func evalDifferenceBetweenDate(b model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + m := b.(*model.DifferenceBetween) + p := model.DateTimePrecision(m.Precision) + if err := validatePrecision(p, []model.DateTimePrecision{model.YEAR, model.MONTH, model.WEEK, model.DAY}); err != nil { + return result.Value{}, err + } + + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + + l, r, err := applyToValues(lObj, rObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return dateTimeDifference(l, r, p) +} + +// difference in _precision_ between(left DateTime, right DateTime) Integer +// https://cql.hl7.org/09-b-cqlreference.html#difference +// Returns the number of boundaries crossed between two datetimes. +func evalDifferenceBetweenDateTime(b model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + m := b.(*model.DifferenceBetween) + p := model.DateTimePrecision(m.Precision) + if err := validatePrecision(p, []model.DateTimePrecision{model.YEAR, model.MONTH, model.WEEK, model.DAY, model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND}); err != nil { + return result.Value{}, err + } + + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + + l, r, err := applyToValues(lObj, rObj, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return dateTimeDifference(l, r, p) +} + +// Now() DateTime +// https://cql.hl7.org/09-b-cqlreference.html#now +// Returns the evaluation timestamp value in DateTime format. +func (i *interpreter) evalNow(n model.INaryExpression, _ []result.Value) (result.Value, error) { + return result.New(result.DateTime{Date: i.evaluationTimestamp, Precision: model.MILLISECOND}) +} + +// Date(year Integer) Date +// Date(year Integer, month Integer) Date +// Date(year Integer, month Integer, day Integer) Date +// https://cql.hl7.org/09-b-cqlreference.html#date-1 +func (i *interpreter) evalDate(n model.INaryExpression, objs []result.Value) (result.Value, error) { + if result.IsNull(objs[0]) { + return result.Value{}, fmt.Errorf("in Date %v cannot be null", model.YEAR) + } + + var dateVals []int + foundNull := false + precisions := []model.DateTimePrecision{model.YEAR, model.MONTH, model.DAY} + for i := range objs { + if result.IsNull(objs[i]) { + foundNull = true + continue + } + if foundNull { + return result.Value{}, fmt.Errorf("when constructing Date precision %v had value %v, even though a higher precision was null", precisions[i], objs[i].GolangValue()) + } + v, err := result.ToInt32(objs[i]) + if err != nil { + return result.Value{}, err + } + dateVals = append(dateVals, int(v)) + } + + switch len(dateVals) { + case 1: + t := time.Date(dateVals[0], 1, 1, 0, 0, 0, 0, time.UTC) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.Date{Date: t, Precision: model.YEAR}) + case 2: + t := time.Date(dateVals[0], time.Month(dateVals[1]), 1, 0, 0, 0, 0, time.UTC) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.Date{Date: t, Precision: model.MONTH}) + case 3: + t := time.Date(dateVals[0], time.Month(dateVals[1]), dateVals[2], 0, 0, 0, 0, time.UTC) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.Date{Date: t, Precision: model.DAY}) + default: + return result.Value{}, errors.New("internal error - should never receive Date with more than 3 arguments") + } +} + +// DateTime(year Integer) DateTime +// DateTime(year Integer, month Integer) DateTime +// DateTime(year Integer, month Integer, day Integer) DateTime +// DateTime(year Integer, month Integer, day Integer, hour Integer) DateTime +// DateTime(year Integer, month Integer, day Integer, hour Integer, minute Integer) DateTime +// DateTime(year Integer, month Integer, day Integer, hour Integer, minute Integer, second Integer) DateTime +// DateTime(year Integer, month Integer, day Integer, hour Integer, minute Integer, second Integer, millisecond Integer) DateTime +// DateTime(year Integer, month Integer, day Integer, hour Integer, minute Integer, second Integer, millisecond Integer, timezoneOffset Decimal) DateTime +// https://cql.hl7.org/09-b-cqlreference.html#datetime-1 +func (i *interpreter) evalDateTime(n model.INaryExpression, objs []result.Value) (result.Value, error) { + allNull := true + for _, obj := range objs { + if !result.IsNull(obj) { + allNull = false + break + } + } + if allNull { + return result.New(nil) + } + + loc := i.evaluationTimestamp.Location() + if len(objs) == 8 { + v, err := result.ToFloat64(objs[7]) + if err != nil { + return result.Value{}, err + } + if v > 14 || v < -14 { + return result.Value{}, fmt.Errorf("timezone offset %v is out of range", v) + } + // int() will truncate timezones with greater than second precision. + loc = time.FixedZone(fmt.Sprintf("%v", v), int(v*60.0*60.0)) + objs = objs[:7] + } + + var dateVals []int + isNull := false + precisions := []model.DateTimePrecision{model.YEAR, model.MONTH, model.DAY, model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND} + for i := range objs { + if result.IsNull(objs[i]) { + isNull = true + continue + } + if isNull { + return result.Value{}, fmt.Errorf("when constructing DateTime precision %v had value %v, even though a higher precision was null", precisions[i], objs[i]) + } + v, err := result.ToInt32(objs[i]) + if err != nil { + return result.Value{}, err + } + dateVals = append(dateVals, int(v)) + } + + switch len(dateVals) { + case 1: + t := time.Date(dateVals[0], 1, 1, 0, 0, 0, 0, loc) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.DateTime{Date: t, Precision: model.YEAR}) + case 2: + t := time.Date(dateVals[0], time.Month(dateVals[1]), 1, 0, 0, 0, 0, loc) + return result.New(result.DateTime{Date: t, Precision: model.MONTH}) + case 3: + t := time.Date(dateVals[0], time.Month(dateVals[1]), dateVals[2], 0, 0, 0, 0, loc) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.DateTime{Date: t, Precision: model.DAY}) + case 4: + t := time.Date(dateVals[0], time.Month(dateVals[1]), dateVals[2], dateVals[3], 0, 0, 0, loc) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.DateTime{Date: t, Precision: model.HOUR}) + case 5: + t := time.Date(dateVals[0], time.Month(dateVals[1]), dateVals[2], dateVals[3], dateVals[4], 0, 0, loc) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.DateTime{Date: t, Precision: model.MINUTE}) + case 6: + t := time.Date(dateVals[0], time.Month(dateVals[1]), dateVals[2], dateVals[3], dateVals[4], dateVals[5], 0, loc) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.DateTime{Date: t, Precision: model.SECOND}) + case 7: + t := time.Date(dateVals[0], time.Month(dateVals[1]), dateVals[2], dateVals[3], dateVals[4], dateVals[5], dateVals[6]*int(time.Millisecond/time.Nanosecond), loc) + if err := validateDateTime(dateVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.DateTime{Date: t, Precision: model.MILLISECOND}) + default: + return result.Value{}, errors.New("internal error - should never receive DateTime with more than 8 arguments") + } +} + +// Time(hour Integer) Time +// Time(hour Integer, minute Integer) Time +// Time(hour Integer, minute Integer, second Integer) Time +// Time(hour Integer, minute Integer, second Integer, millisecond Integer) Time +// https://cql.hl7.org/09-b-cqlreference.html#time-1 +func (i *interpreter) evalTime(n model.INaryExpression, objs []result.Value) (result.Value, error) { + if result.IsNull(objs[0]) { + return result.Value{}, fmt.Errorf("in Time %v cannot be null", model.HOUR) + } + + var timeVals []int + foundNull := false + precisions := []model.DateTimePrecision{model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND} + for i := range objs { + if result.IsNull(objs[i]) { + foundNull = true + continue + } + if foundNull { + return result.Value{}, fmt.Errorf("when constructing Time precision %v had value %v, even though a higher precision was null", precisions[i], objs[i].GolangValue()) + } + v, err := result.ToInt32(objs[i]) + if err != nil { + return result.Value{}, err + } + timeVals = append(timeVals, int(v)) + } + + switch len(timeVals) { + case 1: + t := time.Date(0, 1, 1, timeVals[0], 0, 0, 0, i.evaluationTimestamp.Location()) + if err := validateTime(timeVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.Time{Date: t, Precision: model.HOUR}) + case 2: + t := time.Date(0, 1, 1, timeVals[0], timeVals[1], 0, 0, i.evaluationTimestamp.Location()) + if err := validateTime(timeVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.Time{Date: t, Precision: model.MINUTE}) + case 3: + t := time.Date(0, 1, 1, timeVals[0], timeVals[1], timeVals[2], 0, i.evaluationTimestamp.Location()) + if err := validateTime(timeVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.Time{Date: t, Precision: model.SECOND}) + case 4: + t := time.Date(0, 1, 1, timeVals[0], timeVals[1], timeVals[2], timeVals[3]*int(time.Millisecond/time.Nanosecond), i.evaluationTimestamp.Location()) + if err := validateTime(timeVals, t); err != nil { + return result.Value{}, err + } + return result.New(result.Time{Date: t, Precision: model.MILLISECOND}) + default: + return result.Value{}, errors.New("internal error - should never receive Time with more than 4 arguments") + } +} + +// Golang time.Date() values may be outside their usual ranges and will be normalized. This is not +// the desired CQL behaviour so we check if they have been normalized and return an error. +func validateDateTime(dateVals []int, t time.Time) error { + if dateVals[0] < 1 || dateVals[0] > 9999 { + return fmt.Errorf("%v %v is out of range", model.YEAR, dateVals[0]) + } + + for i := range dateVals { + switch i { + case 0: + if t.Year() != dateVals[0] { + return fmt.Errorf("%v %v is out of range", model.MONTH, dateVals[1]) + } + case 1: + if int(t.Month()) != dateVals[1] { + return fmt.Errorf("%v %v is out of range", model.DAY, dateVals[2]) + } + case 2: + if t.Day() != dateVals[2] { + return fmt.Errorf("%v %v is out of range", model.HOUR, dateVals[3]) + } + case 3: + if t.Hour() != dateVals[3] { + return fmt.Errorf("%v %v is out of range", model.MINUTE, dateVals[4]) + } + case 4: + if t.Minute() != dateVals[4] { + return fmt.Errorf("%v %v is out of range", model.SECOND, dateVals[5]) + } + case 5: + if t.Second() != dateVals[5] { + return fmt.Errorf("%v %v is out of range", model.MILLISECOND, dateVals[6]) + } + } + } + return nil +} + +// Golang time.Date() values may be outside their usual ranges and will be normalized. This is not +// the desired CQL behaviour so we check if they have been normalized and return an error. +func validateTime(dateVals []int, t time.Time) error { + for i := range dateVals { + switch i { + case 0: + if t.Day() != 1 { + return fmt.Errorf("%v %v is out of range", model.HOUR, dateVals[0]) + } + case 1: + if t.Hour() != dateVals[0] { + return fmt.Errorf("%v %v is out of range", model.MINUTE, dateVals[1]) + } + case 2: + if t.Minute() != dateVals[1] { + return fmt.Errorf("%v %v is out of range", model.SECOND, dateVals[2]) + } + case 3: + if t.Second() != dateVals[2] { + return fmt.Errorf("%v %v is out of range", model.MILLISECOND, dateVals[3]) + } + } + } + return nil +} + +// TimeOfDay() Time +// https://cql.hl7.org/09-b-cqlreference.html#timeofday +// Returns the time of the evaluation timestamp value as a Time value. +// TODO: b/346805860 - Enforce execution timestamp have millisecond precision. +func (i *interpreter) evalTimeOfDay(_ model.INaryExpression, _ []result.Value) (result.Value, error) { + t := time.Date(0, time.January, 1, i.evaluationTimestamp.Hour(), i.evaluationTimestamp.Minute(), i.evaluationTimestamp.Second(), i.evaluationTimestamp.Nanosecond(), i.evaluationTimestamp.Location()) + return result.New(result.Time{Date: t, Precision: model.MILLISECOND}) +} + +// Today() Date +// https://cql.hl7.org/09-b-cqlreference.html#today +// Returns the evaluation timestamp value in Date format. +func (i *interpreter) evalToday(n model.INaryExpression, _ []result.Value) (result.Value, error) { + year, month, day := i.evaluationTimestamp.Date() + return result.New(result.Date{ + Date: time.Date(year, month, day, 0, 0, 0, 0, time.UTC), + Precision: model.DAY, + }) +} + +// dateTimeDifference returns the difference at the desired precision for two Go time values. +// Left value can be greater than right value, in such cases a negative value should be returned. +// TODO b/318386749 - Add uncertainty logic once uncertainties are implemented. +func dateTimeDifference(l, r result.DateTime, opPrecision model.DateTimePrecision) (result.Value, error) { + if !precisionGreaterOrEqual(opPrecision, l.Precision) || !precisionGreaterOrEqual(opPrecision, r.Precision) { + // TODO b/318386749 - precisionGreaterOrEqual is a temporary check to ensure the precision of + // "difference in _precision_ between" is greater than the precision of the + // l, r dateTimeValues. In the future we need to support all cases by returning + // an uncertainty. + return result.Value{}, fmt.Errorf("difference between specified a precision greater than argument precision got, %s, and %s, wanted %v", l.Precision, r.Precision, opPrecision) + } + left, right := l.Date, r.Date + + switch opPrecision { + case model.YEAR: + return result.New(right.Year() - left.Year()) + case model.MONTH: + return result.New(12*(right.Year()-left.Year()) + int((right.Month())) - int(left.Month())) + case model.WEEK: + // Weekly borders crossed are number of times a Sunday boundary has been crossed. + // TODO(b/301606416): Weeks do not correctly support negative values. + diffInDays := int(right.Sub(left).Hours() / 24) + leftDaysSinceSunday, rightDaysSinceSunday := int(left.Weekday()), int(right.Weekday()) + if diffInDays < 7 && rightDaysSinceSunday < leftDaysSinceSunday { + return result.New(1) + } else if diffInDays < 7 { + return result.New(0) + } + // There is at least one week here. Remove the left side days until Sunday and add a week to account for that. + // From there the number of remaining weeks are only whole seven day weeks. + return result.New(int((diffInDays-(7-leftDaysSinceSunday))/7) + 1) + case model.DAY: + // This logic is to ensure we are only counting by day boundaries crossed. + epoch := time.UnixMilli(0) + leftDaysSinceEpoch := int(left.Sub(epoch).Hours() / 24) + rightDaysSinceEpoch := int(right.Sub(epoch).Hours() / 24) + return result.New(rightDaysSinceEpoch - leftDaysSinceEpoch) + case model.HOUR: + return result.New(int(right.Sub(left).Hours())) + case model.MINUTE: + return result.New(int(right.Sub(left).Minutes())) + case model.SECOND: + // TODO(b/301606416): According to the spec seconds and milliseconds should be combined and + // compared as a decimal. It is not clear what this means, but this implementation may be + // incorrect. + return result.New(int(right.Sub(left).Seconds())) + case model.MILLISECOND: + return result.New(int(right.Sub(left).Milliseconds())) + default: + return result.Value{}, fmt.Errorf("unsupported precision for dateTimeDifference: %v", opPrecision) + } +} + +type comparison int + +const ( + unsetComparison comparison = iota + leftBeforeRight + leftEqualRight + leftAfterRight + insufficientPrecision + comparedToNull +) + +// orderedPrecisions are DateTimePrecisions ordered from least precise to most precise. +var orderedPrecisions = []model.DateTimePrecision{model.YEAR, model.MONTH, model.DAY, model.WEEK, model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND} + +// getFinestPrecision returns the finest (most precise) precision between two DateTimePrecisions. An +// error is returned if any of the precisions are unset. +func getFinestPrecision(l, r model.DateTimePrecision) (model.DateTimePrecision, error) { + if l == model.UNSETDATETIMEPRECISION || r == model.UNSETDATETIMEPRECISION { + return model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error -- input to getFinestPrecision must not be unset. got: %v, %v", l, r) + } + + // Iterating over precisions from least precise to most precise. + for _, currPrec := range orderedPrecisions { + // If one precision matches, return the _other_ one, which must be equally or more precise. + if l == currPrec { + return r, nil + } + if r == currPrec { + return l, nil + } + } + // We should not get here: + return model.UNSETDATETIMEPRECISION, fmt.Errorf("internal error -- unable to get finest precision for: %v, %v", l, r) +} + +// compareDateTimeWithPrecision returns a comparison of DateTimeValues with the given maximum +// TimePrecision. If either left or right has insufficient precision to determine which is greater +// before reaching maxPrecision then insufficientPrecision is returned. +func compareDateTimeWithPrecision(left, right result.DateTime, maxPrecision model.DateTimePrecision) (comparison, error) { + if maxPrecision == model.UNSETDATETIMEPRECISION { + // If precision is unset, proceed until the finest precision specified by either input. + finestPrecision, err := getFinestPrecision(left.Precision, right.Precision) + if err != nil { + return unsetComparison, err + } + maxPrecision = finestPrecision + } + + left = normalizeDateTime(left) + right = normalizeDateTime(right) + + for _, p := range orderedPrecisions { + switch p { + case model.YEAR: + if r := cmp.Compare(left.Date.Year(), right.Date.Year()); r != 0 { + return toComparison(r), nil + } + case model.MONTH: + if r := cmp.Compare(left.Date.Month(), right.Date.Month()); r != 0 { + return toComparison(r), nil + } + // Note that week is intentionally skipped because it is not valid for DateTime/Dates. + case model.DAY: + if r := cmp.Compare(left.Date.Day(), right.Date.Day()); r != 0 { + return toComparison(r), nil + } + case model.HOUR: + if r := cmp.Compare(left.Date.Hour(), right.Date.Hour()); r != 0 { + return toComparison(r), nil + } + case model.MINUTE: + if r := cmp.Compare(left.Date.Minute(), right.Date.Minute()); r != 0 { + return toComparison(r), nil + } + // TODO: b/329321570 - According to the spec, we may need to combine seconds and milliseconds + // into a decimal, and do the comparison at the seconds precision. + case model.SECOND: + if r := cmp.Compare(left.Date.Second(), right.Date.Second()); r != 0 { + return toComparison(r), nil + } + case model.MILLISECOND: + r := cmp.Compare(left.Date.UnixMilli(), right.Date.UnixMilli()) + return toComparison(r), nil + } + if p == maxPrecision { + // Reached the max required precision, so they are equal. + return leftEqualRight, nil + } + if p == left.Precision || p == right.Precision { + return insufficientPrecision, nil + } + } + return leftEqualRight, nil +} + +// compareDateTime returns a pure comparison of DateTimeValues. If left and right are equal up +// to the precision of only one of the two values insufficientPrecision is returned. +func compareDateTime(left, right result.DateTime) (comparison, error) { + return compareDateTimeWithPrecision(left, right, model.UNSETDATETIMEPRECISION) +} + +func normalizeDateTime(d result.DateTime) result.DateTime { + if precisionGreaterOrEqual(d.Precision, model.DAY) { + return d + } + d.Date = d.Date.In(time.UTC) + return d +} + +// toComparison converts the result of cmp.Compare() to comparison. +func toComparison(a int) comparison { + switch a { + case -1: + return leftBeforeRight + case 0: + return leftEqualRight + case 1: + return leftAfterRight + } + return unsetComparison +} + +func validateDateTimePrecision(precision model.DateTimePrecision, allowUnset bool) error { + allowed := []model.DateTimePrecision{model.YEAR, model.MONTH, model.DAY, model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND} + if allowUnset { + allowed = append(allowed, model.UNSETDATETIMEPRECISION) + } + return validatePrecision(precision, allowed) +} + +func validateDatePrecision(precision model.DateTimePrecision, allowUnset bool) error { + allowed := []model.DateTimePrecision{model.YEAR, model.MONTH, model.DAY} + if allowUnset { + allowed = append(allowed, model.UNSETDATETIMEPRECISION) + } + return validatePrecision(precision, allowed) +} + +// validatePrecision returns an error if p is not in validPs. +func validatePrecision(p model.DateTimePrecision, validPs []model.DateTimePrecision) error { + for _, v := range validPs { + if p == v { + return nil + } + } + return fmt.Errorf("precision must be one of %v, got %v", validPs, p) +} + +func validatePrecisionByType(precision model.DateTimePrecision, allowUnset bool, dateType types.IType) error { + switch dateType { + case types.Date: + return validateDatePrecision(precision, allowUnset) + case types.DateTime: + return validateDateTimePrecision(precision, allowUnset) + default: + return fmt.Errorf("unsupported type for validatePrecisionByType got: %v, expected: types.Date, types.DateTime", dateType) + } +} + +// precisionGreaterOrEqual returns true if l is of greater or equal precision than r. +func precisionGreaterOrEqual(l, r model.DateTimePrecision) bool { + if l == r { + return true + } + precisions := []model.DateTimePrecision{model.YEAR, model.MONTH, model.WEEK, model.DAY, model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND} + for _, p := range precisions { + if p == l { + return true + } + if p == r { + return false + } + } + return false +} + +// convertQuantityUpToPrecision converts a QuantityValue to a given precision. +// Returns error for cases where a conversion cannot be performed. +// This function only converts upwards. +func convertQuantityUpToPrecision(q result.Quantity, wantPrecision model.DateTimePrecision) (result.Quantity, error) { + qp := model.DateTimePrecision(q.Unit) + qv := q.Value + // validateDateTimePrecision explicitly does not check for 'week' values so we need to + // do that manually. + err := validatePrecision(qp, []model.DateTimePrecision{model.YEAR, model.MONTH, model.WEEK, model.DAY, model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND}) + if err != nil { + return result.Quantity{}, err + } + if precisionGreaterOrEqual(qp, wantPrecision) { + return result.Quantity{Value: qv, Unit: model.Unit(qp)}, nil + } + if qp == model.WEEK { + return result.Quantity{}, fmt.Errorf("error: cannot convert from week to a higher precision for Date/DateTime values. want: %v, got: %v", wantPrecision, q.Unit) + } + + // It's considered an error to convert from days/weeks up to month. + precisions := []model.DateTimePrecision{model.MILLISECOND, model.SECOND, model.MINUTE, model.HOUR, model.DAY, model.MONTH, model.YEAR} + foundStartPrecision := false + // iterate up to the precision of the quantity then start converting. + for _, p := range precisions { + if p == qp { + foundStartPrecision = true + continue + } + if !foundStartPrecision { + continue + } + + switch p { + case model.SECOND: + qv = qv / 1000 + case model.MINUTE, + model.HOUR: + qv = qv / 60 + case model.DAY: + qv = qv / 24 + case model.MONTH: + return result.Quantity{}, fmt.Errorf("error: invalid unit conversion, starting precision cannot be converted to be more precise than days. want: %v, got: %v", wantPrecision, q.Unit) + case model.YEAR: + qv = qv / 12 + } + if p == wantPrecision { + return result.Quantity{Value: qv, Unit: model.Unit(wantPrecision)}, nil + } + } + return result.Quantity{}, fmt.Errorf("error: failed to reach desired precision when adding Date/DateTime to Quantity with precisions want: %v, got: %v", wantPrecision, q.Unit) +} diff --git a/interpreter/operator_datetime_test.go b/interpreter/operator_datetime_test.go new file mode 100644 index 0000000..a8d668b --- /dev/null +++ b/interpreter/operator_datetime_test.go @@ -0,0 +1,235 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "strings" + "testing" + "time" + + "github.com/google/cql/model" + "github.com/google/cql/result" +) + +func TestCompareWithPrecision(t *testing.T) { + tests := []struct { + name string + l result.DateTime + r result.DateTime + precision model.DateTimePrecision + want comparison + }{ + { + name: "same dates with year precision returns 0", + l: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + precision: model.YEAR, + want: leftEqualRight, + }, + { + name: "two different dates with same year and year precision returns 0", + l: result.DateTime{ + Date: time.Date(2024, time.February, 5, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 22, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + precision: model.YEAR, + want: leftEqualRight, + }, + { + name: "same dates with month precision returns 0", + precision: model.MONTH, + l: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + want: leftEqualRight, + }, + { + name: "left month is lower with month precision returns -1", + precision: model.MONTH, + l: result.DateTime{ + Date: time.Date(2024, time.February, 22, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 22, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + want: leftBeforeRight, + }, + { + name: "left month is higher with month precision returns 1", + precision: model.MONTH, + l: result.DateTime{ + Date: time.Date(2024, time.September, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + want: leftAfterRight, + }, + { + name: "left year is greater but same month at month precision returns 1", + precision: model.MONTH, + l: result.DateTime{ + Date: time.Date(2030, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + want: leftAfterRight, + }, + { + name: "dates are the same, l has higher precision and should return precision error", + precision: model.DAY, + l: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.MONTH, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + want: insufficientPrecision, + }, + { + name: "leftAfterRight: unset precision uses finest precision of l and r (model.DAY)", + precision: model.UNSETDATETIMEPRECISION, + l: result.DateTime{ + Date: time.Date(2024, time.April, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.MONTH, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.DAY, + }, + want: leftAfterRight, + }, + { + name: "leftEqualRight: unset precision uses finest precision of l and r (model.DAY)", + precision: model.UNSETDATETIMEPRECISION, + l: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.DAY, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.DAY, + }, + want: leftEqualRight, + }, + { + name: "leftBeforeRight: unset precision uses finest precision of l and r (model.DAY)", + precision: model.UNSETDATETIMEPRECISION, + l: result.DateTime{ + Date: time.Date(2024, time.January, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.DAY, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.MONTH, + }, + want: leftBeforeRight, + }, + { + name: "insufficientPrecision: unset precision uses finest precision of l and r (model.DAY)", + precision: model.UNSETDATETIMEPRECISION, + l: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.MONTH, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.DAY, + }, + want: insufficientPrecision, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := compareDateTimeWithPrecision(test.l, test.r, test.precision) + if err != nil { + t.Errorf("compareDateTimeWithPrecision(%v, %v, %v) returned unexpected error: %v", test.l, test.r, test.precision, err) + } + if got != test.want { + t.Errorf("compareDateTimeWithPrecision(%v, %v, %v) = %v, want %v", test.l, test.r, test.precision, got, test.want) + } + }) + } +} + +func TestCompareDateTimeWithPrecision_Errors(t *testing.T) { + tests := []struct { + name string + l result.DateTime + r result.DateTime + precision model.DateTimePrecision + wantErrContains string + }{ + { + name: "unset precision on l DateTimeValue", + l: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.UNSETDATETIMEPRECISION, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + precision: model.UNSETDATETIMEPRECISION, + wantErrContains: "internal error -- input to getFinestPrecision must not be unset.", + }, + { + name: "unset precision on r DateTimeValue", + l: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + r: result.DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.UNSETDATETIMEPRECISION, + }, + precision: model.UNSETDATETIMEPRECISION, + wantErrContains: "internal error -- input to getFinestPrecision must not be unset.", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := compareDateTimeWithPrecision(test.l, test.r, test.precision) + if !strings.Contains(err.Error(), test.wantErrContains) { + t.Errorf("compareDateTimeWithPrecision(%v, %v, %v) returned unexpected error: got: %v, want contains: %v", test.l, test.r, test.precision, err, test.wantErrContains) + } + }) + } +} diff --git a/interpreter/operator_dispatcher.go b/interpreter/operator_dispatcher.go new file mode 100644 index 0000000..804bd82 --- /dev/null +++ b/interpreter/operator_dispatcher.go @@ -0,0 +1,796 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + + "github.com/google/cql/internal/convert" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +func (i *interpreter) evalUnaryExpression(m model.IUnaryExpression) (result.Value, error) { + // Evaluate Operand + operand, err := i.evalExpression(m.GetOperand()) + if err != nil { + return result.Value{}, err + } + + // Match Overload + overloads, err := i.unaryOverloads(m) + if err != nil { + return result.Value{}, err + } + + evalFunc, err := convert.ExactOverloadMatch[evalUnarySignature]([]types.IType{m.GetOperand().GetResultType()}, overloads, i.modelInfo, m.GetName()) + if err != nil { + return result.Value{}, err + } + + // Evaluate the Overload + res, err := evalFunc(m, operand) + if err != nil { + return result.Value{}, err + } + return res.WithSources(m, operand), nil +} + +func (i *interpreter) evalBinaryExpression(m model.IBinaryExpression) (result.Value, error) { + // Evaluate Operands + l, err := i.evalExpression(m.Left()) + if err != nil { + return result.Value{}, err + } + r, err := i.evalExpression(m.Right()) + if err != nil { + return result.Value{}, err + } + + // Match Overload + overloads, err := i.binaryOverloads(m) + if err != nil { + return result.Value{}, err + } + + evalFunc, err := convert.ExactOverloadMatch[evalBinarySignature]([]types.IType{m.Left().GetResultType(), m.Right().GetResultType()}, overloads, i.modelInfo, m.GetName()) + if err != nil { + return result.Value{}, err + } + + // Evaluate the Overload + res, err := evalFunc(m, l, r) + if err != nil { + return result.Value{}, err + } + + return res.WithSources(m, l, r), nil +} + +func (i *interpreter) evalNaryExpression(m model.INaryExpression) (result.Value, error) { + // Evaluate Operands + evalOps := make([]result.Value, len(m.GetOperands())) + for idx, operand := range m.GetOperands() { + operand, err := i.evalExpression(operand) + if err != nil { + return result.Value{}, err + } + evalOps[idx] = operand + } + + // Match Overloads + overloads, err := i.naryOverloads(m) + if err != nil { + return result.Value{}, err + } + + evalFunc, err := convert.ExactOverloadMatch(convert.OperandsToTypes(m.GetOperands()), overloads, i.modelInfo, m.GetName()) + if err != nil { + return result.Value{}, err + } + + // Evaluate the Overload + res, err := evalFunc(m, evalOps) + if err != nil { + return result.Value{}, err + } + + return res.WithSources(m, evalOps...), nil +} + +type evalUnarySignature func(model.IUnaryExpression, result.Value) (result.Value, error) +type evalBinarySignature func(model.IBinaryExpression, result.Value, result.Value) (result.Value, error) +type evalNarySignature func(model.INaryExpression, []result.Value) (result.Value, error) + +func (i *interpreter) unaryOverloads(m model.IUnaryExpression) ([]convert.Overload[evalUnarySignature], error) { + switch m.(type) { + case *model.Exists: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{&types.List{ElementType: types.Any}}, + Result: evalExists, + }, + }, nil + case *model.First: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{&types.List{ElementType: types.Any}}, + Result: evalFirst, + }, + }, nil + case *model.Last: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{&types.List{ElementType: types.Any}}, + Result: evalLast, + }, + }, nil + case *model.As: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Any}, + Result: i.evalAs, + }, + }, nil + case *model.ToDateTime: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.DateTime}, + Result: evalToDateTimeDate, + }, + { + Operands: []types.IType{types.Date}, + Result: evalToDateTimeDate, + }, + { + Operands: []types.IType{types.String}, + Result: i.evalToDateTimeString, + }, + }, nil + case *model.ToDate: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Date}, + Result: evalToDateDateTime, + }, + { + Operands: []types.IType{types.DateTime}, + Result: evalToDateDateTime, + }, + { + Operands: []types.IType{types.String}, + Result: i.evalToDateString, + }, + }, nil + case *model.ToDecimal: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Decimal}, + Result: evalToDecimal, + }, + { + Operands: []types.IType{types.Long}, + Result: evalToDecimal, + }, + { + Operands: []types.IType{types.Integer}, + Result: evalToDecimal, + }, + { + Operands: []types.IType{types.String}, + Result: evalToDecimalString, + }, + { + Operands: []types.IType{types.Boolean}, + Result: evalToDecimalBoolean, + }, + }, nil + case *model.ToLong: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Long}, + Result: evalToLong, + }, + { + Operands: []types.IType{types.Integer}, + Result: evalToLong, + }, + { + Operands: []types.IType{types.String}, + Result: evalToLongString, + }, + { + Operands: []types.IType{types.Boolean}, + Result: evalToLongBoolean, + }, + }, nil + case *model.ToQuantity: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Decimal}, + Result: evalToQuantity, + }, + { + Operands: []types.IType{types.Integer}, + Result: evalToQuantity, + }, + { + Operands: []types.IType{types.String}, + Result: evalToQuantityString, + }, + }, nil + case *model.ToConcept: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Code}, + Result: evalToConceptCode, + }, + { + Operands: []types.IType{&types.List{ElementType: types.Code}}, + Result: evalToConceptList, + }, + }, nil + case *model.End: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{&types.Interval{PointType: types.Any}}, + Result: i.evalEnd, + }, + }, nil + case *model.Start: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{&types.Interval{PointType: types.Any}}, + Result: i.evalStart, + }, + }, nil + case *model.SingletonFrom: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{&types.List{ElementType: types.Any}}, + Result: evalSingletonFrom, + }, + }, nil + case *model.Is: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Any}, + Result: i.evalIs, + }, + }, nil + case *model.IsNull: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Any}, + Result: evalIsNull, + }, + }, nil + case *model.IsTrue: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Any}, + Result: evalIsTrue, + }, + }, nil + case *model.IsFalse: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Any}, + Result: evalIsFalse, + }, + }, nil + case *model.Not: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Boolean}, + Result: evalNot, + }, + }, nil + case *model.Negate: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Integer}, + Result: evalNegateInteger, + }, + { + Operands: []types.IType{types.Long}, + Result: evalNegateLong, + }, + { + Operands: []types.IType{types.Decimal}, + Result: evalNegateDecimal, + }, + { + Operands: []types.IType{types.Quantity}, + Result: evalNegateQuantity, + }, + }, nil + case *model.Predecessor: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Integer}, + Result: i.evalPredecessor, + }, + { + Operands: []types.IType{types.Long}, + Result: i.evalPredecessor, + }, + { + Operands: []types.IType{types.Decimal}, + Result: i.evalPredecessor, + }, + { + Operands: []types.IType{types.Quantity}, + Result: i.evalPredecessor, + }, + { + Operands: []types.IType{types.Date}, + Result: i.evalPredecessor, + }, + { + Operands: []types.IType{types.DateTime}, + Result: i.evalPredecessor, + }, + { + Operands: []types.IType{types.Time}, + Result: i.evalPredecessor, + }, + }, nil + case *model.Successor: + return []convert.Overload[evalUnarySignature]{ + { + Operands: []types.IType{types.Integer}, + Result: i.evalSuccessor, + }, + { + Operands: []types.IType{types.Long}, + Result: i.evalSuccessor, + }, + { + Operands: []types.IType{types.Decimal}, + Result: i.evalSuccessor, + }, + { + Operands: []types.IType{types.Quantity}, + Result: i.evalSuccessor, + }, + { + Operands: []types.IType{types.Date}, + Result: i.evalSuccessor, + }, + { + Operands: []types.IType{types.DateTime}, + Result: i.evalSuccessor, + }, + { + Operands: []types.IType{types.Time}, + Result: i.evalSuccessor, + }, + }, nil + default: + return nil, fmt.Errorf("unsupported Unary Expression %v", m.GetName()) + } +} + +// TODO(b/312172420): Move BinaryOverloads and UnaryOverloads to their own files. +func (i *interpreter) binaryOverloads(m model.IBinaryExpression) ([]convert.Overload[evalBinarySignature], error) { + switch m.(type) { + case *model.Add, *model.Subtract: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: evalArithmeticInteger, + }, + { + Operands: []types.IType{types.Long, types.Long}, + Result: evalArithmeticLong, + }, + { + Operands: []types.IType{types.Decimal, types.Decimal}, + Result: evalArithmeticDecimal, + }, + { + Operands: []types.IType{types.Quantity, types.Quantity}, + Result: evalArithmeticQuantity, + }, + { + Operands: []types.IType{types.Date, types.Quantity}, + Result: evalArithmeticDate, + }, + { + Operands: []types.IType{types.DateTime, types.Quantity}, + Result: evalArithmeticDateTime, + }, + }, nil + case *model.Multiply, *model.TruncatedDivide, *model.Modulo: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: evalArithmeticInteger, + }, + { + Operands: []types.IType{types.Long, types.Long}, + Result: evalArithmeticLong, + }, + { + Operands: []types.IType{types.Decimal, types.Decimal}, + Result: evalArithmeticDecimal, + }, + { + Operands: []types.IType{types.Quantity, types.Quantity}, + Result: evalArithmeticQuantity, + }, + }, nil + case *model.Divide: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Decimal, types.Decimal}, + Result: evalArithmeticDecimal, + }, + { + Operands: []types.IType{types.Quantity, types.Quantity}, + Result: evalArithmeticQuantity, + }, + }, nil + case *model.And, *model.Or, *model.XOr, *model.Implies: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Boolean, types.Boolean}, + Result: evalLogic, + }, + }, nil + case *model.Equal: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Any, types.Any}, + Result: i.evalEqual, + }, + { + Operands: []types.IType{types.DateTime, types.DateTime}, + Result: evalEqualDateTime, + }, + { + Operands: []types.IType{types.Date, types.Date}, + Result: evalEqualDateTime, + }, + }, nil + case *model.Equivalent: + // TODO(b/301606416): Expand equivalent support to all types. + // All equivalent overloads must be resilient to a nil model input. + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Boolean, types.Boolean}, + Result: evalEquivalentSimpleType, + }, + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: evalEquivalentSimpleType, + }, + { + Operands: []types.IType{types.Long, types.Long}, + Result: evalEquivalentSimpleType, + }, + { + Operands: []types.IType{types.String, types.String}, + Result: evalEquivalentString, + }, + { + Operands: []types.IType{types.Code, types.Code}, + Result: evalEquivalentCode, + }, + { + Operands: []types.IType{types.DateTime, types.DateTime}, + Result: evalEquivalentDateTime, + }, + { + Operands: []types.IType{types.Date, types.Date}, + Result: evalEquivalentDateTime, + }, + // The parser will make sure the List, List have correctly matching or converted T. + { + Operands: []types.IType{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}}, + Result: i.evalEquivalentList, + }, + // The parser will make sure the Interval, Interval have correctly matching or converted T. + { + Operands: []types.IType{&types.Interval{PointType: types.Any}, &types.Interval{PointType: types.Any}}, + Result: i.evalEquivalentInterval, + }, + }, nil + case *model.Less, *model.LessOrEqual, *model.Greater, *model.GreaterOrEqual: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: evalCompareInteger, + }, + { + Operands: []types.IType{types.Long, types.Long}, + Result: evalCompareLong, + }, + { + Operands: []types.IType{types.Decimal, types.Decimal}, + Result: evalCompareDecimal, + }, + { + Operands: []types.IType{types.String, types.String}, + Result: evalCompareString, + }, + { + Operands: []types.IType{types.Date, types.Date}, + Result: evalCompareDateTime, + }, + { + Operands: []types.IType{types.DateTime, types.DateTime}, + Result: evalCompareDateTime, + }, + }, nil + case *model.After, *model.Before, *model.SameOrAfter, *model.SameOrBefore: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Date, types.Date}, + Result: evalCompareDateWithPrecision, + }, + { + Operands: []types.IType{types.DateTime, types.DateTime}, + Result: evalCompareDateTimeWithPrecision, + }, + { + Operands: []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + Result: i.evalCompareDateTimeInterval, + }, + { + Operands: []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + Result: i.evalCompareDateTimeInterval, + }, + { + Operands: []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}}, + Result: i.evalCompareIntervalDateTimeInterval, + }, + { + Operands: []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + Result: i.evalCompareIntervalDateTimeInterval, + }, + }, nil + case *model.CanConvertQuantity: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Quantity, types.String}, + Result: evalCanConvertQuantity, + }, + }, nil + case *model.DifferenceBetween: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Date, types.Date}, + Result: evalDifferenceBetweenDate, + }, + { + Operands: []types.IType{types.DateTime, types.DateTime}, + Result: evalDifferenceBetweenDateTime, + }, + }, nil + case *model.In: + // TODO(b/301606416): Support all other In operator overloads. + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Any, &types.List{ElementType: types.Any}}, + Result: evalInList, + }, + { + Operands: []types.IType{types.Decimal, &types.Interval{PointType: types.Decimal}}, + Result: evalInIntervalNumeral, + }, + { + Operands: []types.IType{types.Long, &types.Interval{PointType: types.Long}}, + Result: evalInIntervalNumeral, + }, + { + Operands: []types.IType{types.Integer, &types.Interval{PointType: types.Integer}}, + Result: evalInIntervalNumeral, + }, + { + Operands: []types.IType{types.Quantity, &types.Interval{PointType: types.Quantity}}, + Result: evalInIntervalNumeral, + }, + { + Operands: []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + Result: i.evalInIntervalDateTime, + }, + { + Operands: []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + Result: i.evalInIntervalDateTime, + }, + }, nil + case *model.InCodeSystem: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Code, types.CodeSystem}, + Result: i.evalInCodeSystem, + }, + { + Operands: []types.IType{&types.List{ElementType: types.Code}, types.CodeSystem}, + Result: i.evalInCodeSystem, + }, + { + Operands: []types.IType{types.Concept, types.CodeSystem}, + Result: i.evalInCodeSystem, + }, + { + Operands: []types.IType{&types.List{ElementType: types.Concept}, types.CodeSystem}, + Result: i.evalInCodeSystem, + }, + }, nil + case *model.InValueSet: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Code, types.ValueSet}, + Result: i.evalInValueSet, + }, + { + Operands: []types.IType{&types.List{ElementType: types.Code}, types.ValueSet}, + Result: i.evalInValueSet, + }, + { + Operands: []types.IType{types.Concept, types.ValueSet}, + Result: i.evalInValueSet, + }, + { + Operands: []types.IType{&types.List{ElementType: types.Concept}, types.ValueSet}, + Result: i.evalInValueSet, + }, + }, nil + case *model.CalculateAgeAt: + return []convert.Overload[evalBinarySignature]{ + { + Operands: []types.IType{types.Date, types.Date}, + Result: evalCalculateAgeAtDate, + }, + { + Operands: []types.IType{types.DateTime, types.DateTime}, + Result: evalCalculateAgeAtDateTime, + }, + }, nil + default: + return nil, fmt.Errorf("unsupported Binary Expression %v", m.GetName()) + } +} + +func (i *interpreter) naryOverloads(m model.INaryExpression) ([]convert.Overload[evalNarySignature], error) { + switch m.(type) { + case *model.Date: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{types.Integer}, + Result: i.evalDate, + }, + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: i.evalDate, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer}, + Result: i.evalDate, + }, + }, nil + case *model.DateTime: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{types.Integer}, + Result: i.evalDateTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: i.evalDateTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer}, + Result: i.evalDateTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer, types.Integer}, + Result: i.evalDateTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer}, + Result: i.evalDateTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer}, + Result: i.evalDateTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer}, + Result: i.evalDateTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Decimal}, + Result: i.evalDateTime, + }, + }, nil + case *model.Time: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{types.Integer}, + Result: i.evalTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer}, + Result: i.evalTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer}, + Result: i.evalTime, + }, + { + Operands: []types.IType{types.Integer, types.Integer, types.Integer, types.Integer}, + Result: i.evalTime, + }, + }, nil + case *model.Now: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{}, + Result: i.evalNow, + }, + }, nil + case *model.TimeOfDay: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{}, + Result: i.evalTimeOfDay, + }, + }, nil + case *model.Today: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{}, + Result: i.evalToday, + }, + }, nil + case *model.Coalesce: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{types.Any, types.Any}, + Result: evalCoalesce, + }, + { + Operands: []types.IType{types.Any, types.Any, types.Any}, + Result: evalCoalesce, + }, + { + Operands: []types.IType{types.Any, types.Any, types.Any, types.Any}, + Result: evalCoalesce, + }, + { + Operands: []types.IType{types.Any, types.Any, types.Any, types.Any, types.Any}, + Result: evalCoalesce, + }, + { + Operands: []types.IType{&types.List{ElementType: types.Any}}, + Result: evalCoalesceList, + }, + }, nil + case *model.Concatenate: + return []convert.Overload[evalNarySignature]{ + { + Operands: []types.IType{types.String, types.String}, + Result: evalConcatenate, + }, + }, nil + default: + return nil, fmt.Errorf("unsupported Nary Expression %v", m.GetName()) + } +} diff --git a/interpreter/operator_dispatcher_test.go b/interpreter/operator_dispatcher_test.go new file mode 100644 index 0000000..ce2b388 --- /dev/null +++ b/interpreter/operator_dispatcher_test.go @@ -0,0 +1,79 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" +) + +// The dispatcher non error cases are tested by all operator files. This test covers top level +// errors. +func TestDispatcherError(t *testing.T) { + tests := []struct { + name string + expr model.IExpression + wantErr string + }{ + { + name: "Unary Expression Unsupported Overload", + expr: &model.Last{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("4", types.Integer), + }, + }, + wantErr: "could not resolve Last(System.Integer)", + }, + { + name: "Unary Expression Unsupported Overload", + expr: &model.First{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("false", types.Boolean), + }, + }, + wantErr: "could not resolve First(System.Boolean)", + }, + { + name: "Binary Expression Unsupported Overload", + expr: &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("4", types.Integer), model.NewLiteral("Hello", types.String), + }, + }, + }, + wantErr: "could not resolve Subtract(System.Integer, System.String)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := Eval(context.Background(), []*model.Library{wrapInLib(t, test.expr)}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("TestDispatcherError() call to evalLibrary() succeeded, wanted error") + } + if !strings.Contains(err.Error(), test.wantErr) { + t.Errorf("TestDispatcherError() returned unexpected error: %v want: %s", err, test.wantErr) + } + }) + } +} diff --git a/interpreter/operator_interval.go b/interpreter/operator_interval.go new file mode 100644 index 0000000..bb834a4 --- /dev/null +++ b/interpreter/operator_interval.go @@ -0,0 +1,454 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + "time" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +// INTERVAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#interval-operators-3 + +// end of(argument Interval) T +// https://cql.hl7.org/09-b-cqlreference.html#end +func (i *interpreter) evalEnd(m model.IUnaryExpression, intervalObj result.Value) (result.Value, error) { + return end(intervalObj, &i.evaluationTimestamp) +} + +// end returns the upper value of the interval. +// This function wraps the complexities of null inclusive bounds as well as non-inclusive boundary +// calculation via value predecessor functionality. +func end(intervalObj result.Value, evaluationTimestamp *time.Time) (result.Value, error) { + if result.IsNull(intervalObj) { + return result.New(nil) + } + interval, err := result.ToInterval(intervalObj) + if err != nil { + return result.Value{}, err + } + + if interval.HighInclusive { + if result.IsNull(interval.High) { + iType, ok := intervalObj.RuntimeType().(*types.Interval) + if !ok { + return result.Value{}, fmt.Errorf("internal error - end got Value that is not an interval type") + } + return maxValue(iType.PointType, evaluationTimestamp) + } + return interval.High, nil + + } + if result.IsNull(interval.High) { + return interval.High, nil + } + return predecessor(interval.High, evaluationTimestamp) +} + +// start of(argument Interval) T +// https://cql.hl7.org/09-b-cqlreference.html#start +func (i *interpreter) evalStart(m model.IUnaryExpression, intervalObj result.Value) (result.Value, error) { + return start(intervalObj, &i.evaluationTimestamp) +} + +// start returns the lower value of the interval. +// This function wraps the complexities of null inclusive bounds as well as non-inclusive boundary +// calculation via value successor functionality. +func start(intervalObj result.Value, evaluationTimestamp *time.Time) (result.Value, error) { + if result.IsNull(intervalObj) { + return result.New(nil) + } + interval, err := result.ToInterval(intervalObj) + if err != nil { + return result.Value{}, err + } + if interval.LowInclusive { + if result.IsNull(interval.Low) { + iType, ok := intervalObj.RuntimeType().(*types.Interval) + if !ok { + return result.Value{}, fmt.Errorf("internal error - start got Value that is not an interval type") + } + return minValue(iType.PointType, evaluationTimestamp) + } + return interval.Low, nil + } + if result.IsNull(interval.Low) { + return interval.Low, nil + } + return successor(interval.Low, evaluationTimestamp) +} + +// op(left DateTime, right Interval) Boolean +// op(left Date, right Interval) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#after-1 +// https://cql.hl7.org/09-b-cqlreference.html#before-1 +// https://cql.hl7.org/09-b-cqlreference.html#on-or-after-2 +// https://cql.hl7.org/09-b-cqlreference.html#on-or-before-2 +// TODO(b/308016038): Once Start and End are properly supported, handle low/high inclusivity. +func (i *interpreter) evalCompareDateTimeInterval(be model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + l, err := result.ToDateTime(lObj) + if err != nil { + return result.Value{}, err + } + + p, err := precisionFromBinaryExpression(be) + if err != nil { + return result.Value{}, err + } + + allowUnsetPrec := true + if err := validatePrecisionByType(p, allowUnsetPrec, be.Left().GetResultType()); err != nil { + return result.Value{}, err + } + + switch be.(type) { + case *model.After: + e, err := end(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rHigh, err := result.ToDateTime(e) + if err != nil { + return result.Value{}, err + } + return afterDateTimeWithPrecision(l, rHigh, p) + case *model.Before: + s, err := start(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rLow, err := result.ToDateTime(s) + if err != nil { + return result.Value{}, err + } + return beforeDateTimeWithPrecision(l, rLow, p) + case *model.SameOrAfter: + e, err := end(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rHigh, err := result.ToDateTime(e) + if err != nil { + return result.Value{}, err + } + return afterOrEqualDateTimeWithPrecision(l, rHigh, p) + case *model.SameOrBefore: + s, err := start(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rLow, err := result.ToDateTime(s) + if err != nil { + return result.Value{}, err + } + return beforeOrEqualDateTimeWithPrecision(l, rLow, p) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Comparison Expression %v", be) +} + +// op(left Interval, right Interval) Boolean +// op(left Interval, right Interval) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#after-1 +// https://cql.hl7.org/09-b-cqlreference.html#before-1 +// https://cql.hl7.org/09-b-cqlreference.html#on-or-after-2 +// https://cql.hl7.org/09-b-cqlreference.html#on-or-before-2 +// TODO(b/308016038): Once Start and End are properly supported, handle low/high inclusivity. +func (i *interpreter) evalCompareIntervalDateTimeInterval(be model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + if result.IsNull(lObj) || result.IsNull(rObj) { + return result.New(nil) + } + p, err := precisionFromBinaryExpression(be) + if err != nil { + return result.Value{}, err + } + + iType, ok := be.Left().GetResultType().(*types.Interval) + if !ok { + return result.Value{}, fmt.Errorf("internal error - evalCompareIntervalDateTimeInterval got Value that is not an interval type") + } + pointType := iType.PointType + allowUnsetPrec := true + if err := validatePrecisionByType(p, allowUnsetPrec, pointType); err != nil { + return result.Value{}, err + } + + switch be.(type) { + case *model.After: + // lObj starts after the rObj ends + lObjStart, err := start(lObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rObjEnd, err := end(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + lStart, rEnd, err := applyToValues(lObjStart, rObjEnd, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return afterDateTimeWithPrecision(lStart, rEnd, p) + case *model.Before: + // lObj ends before rObj ond one starts + lObjEnd, err := end(lObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rObjStart, err := start(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + lEnd, rStart, err := applyToValues(lObjEnd, rObjStart, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return beforeDateTimeWithPrecision(lEnd, rStart, p) + case *model.SameOrAfter: + // lObj starts on or after the rObj ends + lObjStart, err := start(lObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rObjEnd, err := end(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + lStart, rEnd, err := applyToValues(lObjStart, rObjEnd, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return afterOrEqualDateTimeWithPrecision(lStart, rEnd, p) + case *model.SameOrBefore: + // lObj ends on or before rObj one starts + lObjEnd, err := end(lObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + rObjStart, err := start(rObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + lEnd, rStart, err := applyToValues(lObjEnd, rObjStart, result.ToDateTime) + if err != nil { + return result.Value{}, err + } + return beforeOrEqualDateTimeWithPrecision(lEnd, rStart, p) + } + return result.Value{}, fmt.Errorf("internal error - unsupported Binary Comparison Expression in evalCompareIntervalDateTimeInterval: %v", be) +} + +// in _precision_ (point Decimal, argument Interval) Boolean +// in _precision_ (point Long, argument Interval) Boolean +// in _precision_ (point Integer, argument Interval) Boolean +// in _precision_ (point Quantity, argument Interval) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#in +func evalInIntervalNumeral(b model.IBinaryExpression, pointObj, intervalObj result.Value) (result.Value, error) { + if result.IsNull(pointObj) { + return result.New(nil) + } + if result.IsNull(intervalObj) { + return result.New(false) + } + + // start and end handles null inclusivity as well as non-inclusive logic. + s, err := start(intervalObj, nil) + if err != nil { + return result.Value{}, err + } + e, err := end(intervalObj, nil) + if err != nil { + return result.Value{}, err + } + // This will only happen for null exclusive bounds. + // TODO b/335910011 - fix not inclusive but outside of the opposite bounds. + if result.IsNull(s) || result.IsNull(e) { + return result.New(nil) + } + + switch pointObj.RuntimeType() { + case types.Decimal: + point, err := result.ToFloat64(pointObj) + if err != nil { + return result.Value{}, err + } + startVal, endVal, err := applyToValues(s, e, result.ToFloat64) + if err != nil { + return result.Value{}, err + } + return numeralInInterval(point, startVal, endVal) + case types.Integer: + point, err := result.ToInt32(pointObj) + if err != nil { + return result.Value{}, err + } + startVal, endVal, err := applyToValues(s, e, result.ToInt32) + if err != nil { + return result.Value{}, err + } + return numeralInInterval(point, startVal, endVal) + case types.Long: + point, err := result.ToInt64(pointObj) + if err != nil { + return result.Value{}, err + } + startVal, endVal, err := applyToValues(s, e, result.ToInt64) + if err != nil { + return result.Value{}, err + } + return numeralInInterval(point, startVal, endVal) + case types.Quantity: + point, err := result.ToQuantity(pointObj) + if err != nil { + return result.Value{}, err + } + startVal, endVal, err := applyToValues(s, e, result.ToQuantity) + if err != nil { + return result.Value{}, err + } + if point.Unit != startVal.Unit { + return result.Value{}, fmt.Errorf("in operator recieved Quantities with differing unit values, unit conversion is not currently supported, got: %v, %v", point.Unit, startVal.Unit) + } + if point.Unit != endVal.Unit { + return result.Value{}, fmt.Errorf("in operator recieved Quantities with differing unit values, unit conversion is not currently supported, got: %v, %v", point.Unit, endVal.Unit) + } + return numeralInInterval(point.Value, startVal.Value, endVal.Value) + default: + return result.Value{}, fmt.Errorf("internal error - unsupported point type in evalInIntervalNumeral: %v", pointObj.RuntimeType()) + } +} + +func numeralInInterval[t float64 | int64 | int32](point, startVal, endVal t) (result.Value, error) { + // This should only happen in cases such as Interval[1, 1). + if compareNumeral(startVal, endVal) == leftAfterRight { + return result.New(false) + } + lowCompare := compareNumeral(point, startVal) + highCompare := compareNumeral(point, endVal) + return inInterval(lowCompare, highCompare, true, true) +} + +func compareNumeral[t float64 | int64 | int32](left, right t) comparison { + if left == right { + return leftEqualRight + } else if left < right { + return leftBeforeRight + } + return leftAfterRight +} + +// in _precision_ (point DateTime, argument Interval) Boolean +// in _precision_ (point Date, argument Interval) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#in +// 'IncludedIn' with left arg of point type is forwarded here. +func (i *interpreter) evalInIntervalDateTime(b model.IBinaryExpression, pointObj, intervalObj result.Value) (result.Value, error) { + m := b.(*model.In) + precision := model.DateTimePrecision(m.Precision) + allowUnsetPrec := true + if err := validatePrecisionByType(precision, allowUnsetPrec, m.Left().GetResultType()); err != nil { + return result.Value{}, err + } + + if result.IsNull(pointObj) { + return result.New(nil) + } + if result.IsNull(intervalObj) { + return result.New(false) + } + + point, err := result.ToDateTime(pointObj) + if err != nil { + return result.Value{}, err + } + interval, err := result.ToInterval(intervalObj) + if err != nil { + return result.Value{}, err + } + + var lowCompare, highCompare comparison + s, err := start(intervalObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + if result.IsNull(s) { + lowCompare = comparedToNull + } else { + low, err := result.ToDateTime(s) + if err != nil { + return result.Value{}, err + } + lowCompare, err = compareDateTimeWithPrecision(point, low, precision) + if err != nil { + return result.Value{}, err + } + } + + e, err := end(intervalObj, &i.evaluationTimestamp) + if err != nil { + return result.Value{}, err + } + if result.IsNull(e) { + highCompare = comparedToNull + } else { + high, err := result.ToDateTime(e) + if err != nil { + return result.Value{}, err + } + highCompare, err = compareDateTimeWithPrecision(point, high, precision) + if err != nil { + return result.Value{}, err + } + } + + return inInterval(lowCompare, highCompare, interval.LowInclusive, interval.HighInclusive) +} + +func inInterval(lowCompare, highCompare comparison, lowInclusive, highInclusive bool) (result.Value, error) { + // This includes cases where we know the point is for sure outside the interval such as: + // 5 in Interval[0, 2] - point is outside the interval + if lowCompare == leftBeforeRight || highCompare == leftAfterRight { + return result.New(false) + } + + // Handles Cases: + // 3 in Interval[0, 3) - point is on the exclusive bound + // 3 in Interval[3, 3) - ignores cases like this, the will fall through to null + if (lowCompare == leftEqualRight && !lowInclusive) && !(highCompare == leftEqualRight && highInclusive) { + return result.New(false) + } + if (highCompare == leftEqualRight && !highInclusive) && !(lowCompare == leftEqualRight && lowInclusive) { + return result.New(false) + } + + // This handles three cases: + // 3 in Interval[0, 5] - point is within the interval + // 3 in Interval[0, 3] - point is on the boundary but the boundary is inclusive + if lowCompare == leftAfterRight || (lowInclusive && lowCompare == leftEqualRight) { + if highCompare == leftBeforeRight || (highInclusive && highCompare == leftEqualRight) { + return result.New(true) + } + } + + // All other cases return null, this includes: + // * Cases where a start/end bound was null: 3 in Interval(null, 5] + // * Cases where Dates/DateTimes have insufficient precision for the comparison: + // Date(2020) in Interval[Date(2020, 3), Date(2020, 4)] + return result.New(nil) +} diff --git a/interpreter/operator_interval_test.go b/interpreter/operator_interval_test.go new file mode 100644 index 0000000..9bc8a99 --- /dev/null +++ b/interpreter/operator_interval_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "context" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" +) + +func TestIntervalOperatorIn_Error(t *testing.T) { + tests := []struct { + name string + expr model.IExpression + wantErr string + }{ + { + name: "Invalid Precision Date", + expr: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("@2020-03", types.Date), + model.NewInclusiveInterval("@2020-03-25", "@2020-04", types.Date), + }, + }, + Precision: model.SECOND, + }, + wantErr: "precision must be one of", + }, + { + name: "Invalid Precision DateTime", + expr: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("@2024-02-29T01:20:30.101-07:00", types.DateTime), + model.NewInclusiveInterval("@2024-02-29T01:20:30.101-07:00", "@2024-04-29T01:20:30.101-07:00", types.DateTime), + }, + }, + Precision: model.WEEK, + }, + wantErr: "precision must be one of", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := Eval(context.Background(), []*model.Library{wrapInLib(t, test.expr)}, defaultInterpreterConfig(t)) + if err == nil { + t.Errorf("TestIntervalOperatorIn_Error() call to evalLibrary() succeeded, wanted error") + } + if !strings.Contains(err.Error(), test.wantErr) { + t.Errorf("TestIntervalOperatorIn_Error() returned unexpected error: %v want: %s", err, test.wantErr) + } + }) + } +} diff --git a/interpreter/operator_list.go b/interpreter/operator_list.go new file mode 100644 index 0000000..f6f9b82 --- /dev/null +++ b/interpreter/operator_list.go @@ -0,0 +1,123 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + + "github.com/google/cql/model" + "github.com/google/cql/result" +) + +// LIST OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#list-operators-2 + +// exists(argument List) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#exists +func evalExists(m model.IUnaryExpression, listObj result.Value) (result.Value, error) { + if result.IsNull(listObj) { + return result.New(false) + } + list, err := result.ToSlice(listObj) + if err != nil { + return result.Value{}, err + } + + if len(list) == 0 { + return result.New(false) + } + + for _, elemObj := range list { + if result.IsNull(elemObj) { + return result.New(false) + } + } + + return result.New(true) +} + +// in(element T, argument List) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#in-1 +func evalInList(m model.IBinaryExpression, lObj, listObj result.Value) (result.Value, error) { + if result.IsNull(listObj) { + return result.New(nil) + } + r, err := result.ToSlice(listObj) + if err != nil { + return result.Value{}, err + } + + for _, elemObj := range r { + if lObj.Equal(elemObj) { + return result.New(true) + } + } + return result.New(false) +} + +// First(argument List) T +// https://cql.hl7.org/09-b-cqlreference.html#first +func evalFirst(m model.IUnaryExpression, listObj result.Value) (result.Value, error) { + if result.IsNull(listObj) { + return result.New(nil) + } + list, err := result.ToSlice(listObj) + if err != nil { + return result.Value{}, err + } + + if len(list) == 0 { + return result.New(nil) + } + return list[0], nil +} + +// Last(argument List) T +// https://cql.hl7.org/09-b-cqlreference.html#last +func evalLast(m model.IUnaryExpression, listObj result.Value) (result.Value, error) { + if result.IsNull(listObj) { + return result.New(nil) + } + list, err := result.ToSlice(listObj) + if err != nil { + return result.Value{}, err + } + + if len(list) == 0 { + return result.New(nil) + } + return list[len(list)-1], nil +} + +// singleton from(argument List) T +// https://cql.hl7.org/09-b-cqlreference.html#singleton-from +func evalSingletonFrom(m model.IUnaryExpression, listObj result.Value) (result.Value, error) { + if result.IsNull(listObj) { + return result.New(nil) + } + + list, err := result.ToSlice(listObj) + if err != nil { + return result.Value{}, err + } + + switch len(list) { + case 0: + return result.New(nil) + case 1: + return list[0], nil + default: + return result.Value{}, fmt.Errorf("singleton from requires a list of length 0 or 1, but got length %d", len(list)) + } +} diff --git a/interpreter/operator_logic.go b/interpreter/operator_logic.go new file mode 100644 index 0000000..5a91845 --- /dev/null +++ b/interpreter/operator_logic.go @@ -0,0 +1,195 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + + "github.com/google/cql/model" + "github.com/google/cql/result" +) + +// LOGICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#logical-operators-3 + +// and (left Boolean, right Boolean) Boolean +// or (left Boolean, right Boolean) Boolean +// xor (left Boolean, right Boolean) Boolean +// implies (left Boolean, right Boolean) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#and +// https://cql.hl7.org/09-b-cqlreference.html#or +// https://cql.hl7.org/09-b-cqlreference.html#xor +// https://cql.hl7.org/09-b-cqlreference.html#implies +func evalLogic(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) { + l := new(bool) + r := new(bool) + var err error + if result.IsNull(lObj) { + l = nil + } else { + *l, err = result.ToBool(lObj) + if err != nil { + return result.Value{}, err + } + } + + if result.IsNull(rObj) { + r = nil + } else { + *r, err = result.ToBool(rObj) + if err != nil { + return result.Value{}, err + } + } + + switch m.(type) { + case *model.And: + return and(l, r) + case *model.Or: + return or(l, r) + case *model.XOr: + return xor(l, r) + case *model.Implies: + return implies(l, r) + } + return result.Value{}, fmt.Errorf("Unsupported BinaryLogicExpression %v", m) +} + +/* + right + +-------+------+-------+------+ + | | TRUE | FALSE | NULL | + +l +-------+------+-------+------+ +e | TRUE | TRUE | TRUE | TRUE | +f +-------+------+-------+------+ +t | FALSE | TRUE | FALSE | NULL | + + +-------+------+-------+------+ + | NULL | TRUE | NULL | NULL | + +-------+------+-------+------+ +*/ +func or(l, r *bool) (result.Value, error) { + if l != nil && *l { + return result.New(true) + } + if r != nil && *r { + return result.New(true) + } + if l == nil || r == nil { + return result.New(nil) + } + return result.New(false) +} + +/* + right + +-------+-------+-------+-------+ + | | TRUE | FALSE | NULL | + +l +-------+-------+-------+-------+ +e | TRUE | TRUE | FALSE | NULL | +f +-------+-------+-------+-------+ +t | FALSE | FALSE | FALSE | FALSE | + + +-------+-------+-------+-------+ + | NULL | NULL | FALSE | NULL | + +-------+-------+-------+-------+ +*/ +func and(l, r *bool) (result.Value, error) { + if l != nil && r != nil { + return result.New(*l && *r) + } + if l != nil && !*l { + return result.New(false) + } + if r != nil && !*r { + return result.New(false) + } + return result.New(nil) +} + +/* + right + +-------+-------+-------+------+ + | | TRUE | FALSE | NULL | + +l +-------+-------+-------+------+ +e | TRUE | FALSE | TRUE | NULL | +f +-------+-------+-------+------+ +t | FALSE | TRUE | FALSE | NULL | + + +-------+-------+-------+------+ + | NULL | NULL | NULL | NULL | + +-------+-------+-------+------+ +*/ +func xor(l, r *bool) (result.Value, error) { + if l == nil || r == nil { + return result.New(nil) + } + return result.New(*l != *r) +} + +/* + right + +-------+------+-------+-------+ + | | TRUE | FALSE | NULL | + +l +-------+------+-------+-------+ +e | TRUE | TRUE | FALSE | NULL | +f +-------+------+-------+-------+ +t | FALSE | TRUE | TRUE | TRUE | + + +-------+------+-------+-------+ + | NULL | TRUE | NULL | NULL | + +-------+------+-------+-------+ +*/ +func implies(l, r *bool) (result.Value, error) { + if l == nil { + if r != nil && *r { + return result.New(true) + } + return result.New(nil) + } + + if r == nil { + if !*l { + return result.New(true) + } + return result.New(nil) + } + + if *r { + return result.New(true) + } + if !*l { + return result.New(true) + } + + return result.New(*r) +} + +// not (argument Boolean) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#not +func evalNot(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + + boolObj, err := result.ToBool(obj) + if err != nil { + return result.Value{}, err + } + return result.New(!boolObj) +} diff --git a/interpreter/operator_nullological.go b/interpreter/operator_nullological.go new file mode 100644 index 0000000..ad61108 --- /dev/null +++ b/interpreter/operator_nullological.go @@ -0,0 +1,87 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + + "github.com/google/cql/model" + "github.com/google/cql/result" +) + +// Coalesce(argument1 T, argument2 T) T +// Coalesce(argument1 T, argument2 T, argument3 T) T +// Coalesce(argument1 T, argument2 T, argument3 T, argument4 T) T +// Coalesce(argument1 T, argument2 T, argument3 T, argument4 T, argument5 T) T +// https://cql.hl7.org/09-b-cqlreference.html#coalesce +func evalCoalesce(m model.INaryExpression, objs []result.Value) (result.Value, error) { + for _, obj := range objs { + if !result.IsNull(obj) { + return obj, nil + } + } + return result.New(nil) +} + +// Coalesce(arguments List) T +// https://cql.hl7.org/09-b-cqlreference.html#coalesce +func evalCoalesceList(m model.INaryExpression, objs []result.Value) (result.Value, error) { + if len(objs) != 1 { + return result.Value{}, fmt.Errorf("coalesce list overload expected exactly 1 list argument, got: %v", len(objs)) + } + list, err := result.ToSlice(objs[0]) + if err != nil { + return result.Value{}, fmt.Errorf("coalesce list overload unable to convert argument to List, err: %w", err) + } + + for _, obj := range list { + if !result.IsNull(obj) { + return obj, nil + } + } + return result.New(nil) +} + +// is null(argument Any) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#isnull +func evalIsNull(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + return result.New(result.IsNull(obj)) +} + +// is true(argument Boolean) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#istrue +func evalIsTrue(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(false) + } + objVal, err := result.ToBool(obj) + if err != nil { + return result.Value{}, err + } + return result.New(objVal == true) +} + +// is false(argument Boolean) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#isfalse +func evalIsFalse(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(false) + } + objVal, err := result.ToBool(obj) + if err != nil { + return result.Value{}, err + } + return result.New(objVal == false) +} diff --git a/interpreter/operator_string.go b/interpreter/operator_string.go new file mode 100644 index 0000000..224f9ef --- /dev/null +++ b/interpreter/operator_string.go @@ -0,0 +1,43 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "strings" + + "github.com/google/cql/model" + "github.com/google/cql/result" +) + +// +(left String, right String) String +// &(left String, right String) String +// https://cql.hl7.org/09-b-cqlreference.html#concatenate +func evalConcatenate(m model.INaryExpression, operands []result.Value) (result.Value, error) { + retStr := strings.Builder{} + for _, operand := range operands { + if result.IsNull(operand) { + // Return null if any operand is null. + // If the & operator is used, nulls must be treated as empty strings. The parser handles this + // by inserting a Coalesce operation with an empty string over the operands. + return result.New(nil) + } + opStr, err := result.ToString(operand) + if err != nil { + return result.Value{}, err + } + retStr.WriteString(opStr) + } + return result.New(retStr.String()) +} diff --git a/interpreter/operator_type.go b/interpreter/operator_type.go new file mode 100644 index 0000000..7438ff4 --- /dev/null +++ b/interpreter/operator_type.go @@ -0,0 +1,451 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +var decimalStringRegex = regexp.MustCompile(`(\+|\-)?\d+(\.\d+)?`) +var longStringRegex = regexp.MustCompile(`(\+|\-)?\d+`) + +// The string should start with a decimal value that may have a prefix of + or -. +// It optionally may also include a unit designation. +// Currently assumes the that inner string has not been escaped. +var quantityStringRegex = regexp.MustCompile(`^([\+|\-]?\d+(?:\.\d+)?){1}\s*('{1}[A-Za-z0-9-]+'{1})?$`) + +// TYPE OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#type-operators-1 + +// as(argument Any) T +// cast as(argument Any) T +// https://cql.hl7.org/09-b-cqlreference.html#as +func (i *interpreter) evalAs(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + a := m.(*model.As) + + // Null can be cast to anything https://cql.hl7.org/03-developersguide.html#implicit-casting + if result.IsNull(obj) { + return result.New(nil) + } + + // This is a special case, anything can be cast to Any. + if a.AsTypeSpecifier.Equal(types.Any) { + return obj, nil + } + + // At runtime a Choice will be either Integer or String. So for Choice As String if the runtime type is a String that will be handled here. + if obj.RuntimeType().Equal(a.AsTypeSpecifier) { + return obj, nil + } + isSub, err := i.modelInfo.IsSubType(a.AsTypeSpecifier, obj.RuntimeType()) + if err != nil { + return result.Value{}, err + } + if isSub { + // TODO(b/301606416): The type should probably be changed to AsTypeSpecifier. + return obj, nil + } + + // This covers casts to choice types such as Decimal --> Choice. For cases that require a + // conversion such as Integer --> Choice the parser should have already inserted any + // necessary conversions so that obj is equal or a subtype of one of the choices + // As(ToDecimal(operand), Choice). + aChoice, ok := a.AsTypeSpecifier.(*types.Choice) + if ok { + for _, choice := range aChoice.ChoiceTypes { + if obj.RuntimeType().Equal(choice) { + return obj, nil + } + + isSub, err := i.modelInfo.IsSubType(choice, obj.RuntimeType()) + if err != nil { + return result.Value{}, err + } + if isSub { + return obj, nil + } + } + } + + if a.Strict { + return result.Value{}, fmt.Errorf("cannot strict cast type %v to type %v", obj.RuntimeType().String(), a.AsTypeSpecifier.String()) + } + + return result.New(nil) +} + +// is(argument Any) Boolean +// https://cql.hl7.org/09-b-cqlreference.html#is +// TODO: b/326499348 - We may need to make some updates here or in the subtype resolution for choice +// types, based on how external spec items are clarified. +func (i *interpreter) evalIs(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + isExpr := m.(*model.Is) + + if obj.RuntimeType().Equal(isExpr.IsTypeSpecifier) { + return result.New(true) + } + + isSub, err := i.modelInfo.IsSubType(obj.RuntimeType(), isExpr.IsTypeSpecifier) + if err != nil { + return result.Value{}, err + } + fmt.Println(isSub) + + return result.New(isSub) +} + +// ToDate(argument DateTime) Date +// ToDate(argument Date) Date +// https://cql.hl7.org/09-b-cqlreference.html#todate +func evalToDateDateTime(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToDateTime(opObj) + if err != nil { + return result.Value{}, err + } + + newDate := result.Date(op) + if newDate.Precision != model.DAY && newDate.Precision != model.MONTH && newDate.Precision != model.YEAR { + // Precision must be more precise than DAY, but we're converting to Date, so set the precision + // to DAY, which is the max precision for Dates. + newDate.Precision = model.DAY + } + return result.New(newDate) +} + +// ToDate(argument String) Date +// https://cql.hl7.org/09-b-cqlreference.html#todate +// +// Converts a ISO-8601 date formatted string to a CQL Date. +func (i *interpreter) evalToDateString(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToString(opObj) + if err != nil { + return result.Value{}, err + } + + obj, err := i.stringToDate(op, types.Date) + if err != nil { + return result.Value{}, err + } + if obj.RuntimeType() != types.Date { + return result.Value{}, fmt.Errorf("interal error - ToDateString, expected Date got: %v", obj.RuntimeType()) + } + + newDate := obj.GolangValue().(result.Date) + if newDate.Precision != model.DAY && newDate.Precision != model.MONTH && newDate.Precision != model.YEAR { + // Precision must be more precise than DAY, but we're converting to Date, so set the precision + // to DAY, which is the max precision for Dates. + newDate.Precision = model.DAY + } + return result.New(newDate) +} + +// ToDateTime(argument Date) DateTime +// ToDateTime(argument DateTime) DateTime +// https://cql.hl7.org/09-b-cqlreference.html#todatetime +// +// Currently timezone will default to UTC, we need to change this when execution timestamp is added. +// Even though the result is a DateTime, time values are considered unspecified, and precision value is not changed. +// TODO(b/303836614) make this function execution timestamp context aware. +func evalToDateTimeDate(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + + op, err := result.ToDateTime(opObj) + if err != nil { + return result.Value{}, err + } + + return result.New(op) +} + +// ToDateTime(argument String) DateTime +// https://cql.hl7.org/09-b-cqlreference.html#todatetime +// +// Converts a ISO-8601 datetime formatted string to a CQL DateTime. +// Currently timezone will default to UTC, we need to change this when execution timestamp is added. +// Even though the result is a DateTime, time values are considered unspecified, and precision value is not changed. +// TODO(b/303836614) make this function execution timestamp context aware. +func (i *interpreter) evalToDateTimeString(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToString(opObj) + if err != nil { + return result.Value{}, err + } + + dt, err := i.stringToDate(op, types.DateTime) + if err == nil { + return dt, nil + } + // date formatted strings may also be parsed into a datetime. + d, err := i.stringToDate(op, types.Date) + if err != nil { + return result.Value{}, err + } + dtv, ok := d.GolangValue().(result.Date) + if !ok { + return result.Value{}, fmt.Errorf("internal error - ToDateTimeString, failed to convert string in date format to datetime, %v", op) + } + return result.New(result.DateTime(dtv)) +} + +// ToDecimal(argument Decimal) Decimal +// ToDecimal(argument Long) Decimal +// ToDecimal(argument Integer) Decimal +// https://cql.hl7.org/09-b-cqlreference.html#todecimal +func evalToDecimal(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + + switch v := opObj.GolangValue().(type) { + case int32: + return result.New(float64(v)) + case int64: + return result.New(float64(v)) + default: + op, err := result.ToFloat64(opObj) + if err != nil { + return result.Value{}, err + } + return result.New(op) + } +} + +// ToDecimal(argument String) Decimal +// https://cql.hl7.org/09-b-cqlreference.html#todecimal +func evalToDecimalString(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToString(opObj) + if err != nil { + return result.Value{}, err + } + + // Check that the string meets the CQL decimal spec requirements. + found := decimalStringRegex.FindString(op) + if found == "" || found != op { + return result.New(nil) + } + + // ParseFloat works for every string that meets the CQL spec. + f, err := strconv.ParseFloat(op, 64) + if err != nil { + return result.Value{}, err + } + return result.New(f) +} + +// ToDecimal(argument Boolean) Decimal +// https://cql.hl7.org/09-b-cqlreference.html#todecimal +func evalToDecimalBoolean(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToBool(opObj) + if err != nil { + return result.Value{}, err + } + if op { + return result.New(1.0) + } + return result.New(0.0) +} + +// ToLong(argument Long) Long +// ToLong(argument Integer) Long +// https://cql.hl7.org/09-b-cqlreference.html#tolong +func evalToLong(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + switch v := opObj.GolangValue().(type) { + case int32: + return result.New(int64(v)) + default: + op, err := result.ToInt64(opObj) + if err != nil { + return result.Value{}, err + } + return result.New(op) + } +} + +// ToLong(argument String) Long +// https://cql.hl7.org/09-b-cqlreference.html#tolong +func evalToLongString(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToString(opObj) + if err != nil { + return result.Value{}, err + } + + // Check that the string meets the CQL long spec requirements. + found := longStringRegex.FindString(op) + if found == "" || found != op { + return result.New(nil) + } + + // ParseInt works for every string that meets the CQL spec. + f, err := strconv.ParseInt(op, 10, 64) + if err != nil { + return result.Value{}, err + } + return result.New(f) +} + +// ToLong(argument Boolean) Long +// https://cql.hl7.org/09-b-cqlreference.html#tolong +func evalToLongBoolean(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToBool(opObj) + if err != nil { + return result.Value{}, err + } + if op { + return result.New(int64(1)) + } + return result.New(int64(0)) +} + +// ToConcept(argument Code) Concept +// https://cql.hl7.org/09-b-cqlreference.html#toconcept +func evalToConceptCode(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + code, err := result.ToCode(obj) + if err != nil { + return result.Value{}, err + } + return result.New(result.Concept{Codes: []result.Code{code}, Display: code.Display}) +} + +// ToConcept(argument List) Concept +// https://cql.hl7.org/09-b-cqlreference.html#toconcept +func evalToConceptList(m model.IUnaryExpression, obj result.Value) (result.Value, error) { + if result.IsNull(obj) { + return result.New(nil) + } + codes, err := result.ToSlice(obj) + if err != nil { + return result.Value{}, err + } + + cv := result.Concept{} + for _, code := range codes { + if result.IsNull(code) { + continue + } + c, err := result.ToCode(code) + if err != nil { + return result.Value{}, err + } + cv.Codes = append(cv.Codes, c) + } + + return result.New(cv) +} + +// ToQuantity(argument Decimal) Quantity +// ToQuantity(argument Integer) Quantity +// https://cql.hl7.org/09-b-cqlreference.html#toquantity +// TODO: b/323978857 - Implement ToQuantity for Ratio. +// TODO: b/324131050 - Implement ToQuantity for strings. +func evalToQuantity(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + switch t := opObj.GolangValue().(type) { + case float64: + return result.New(result.Quantity{Value: t, Unit: model.ONEUNIT}) + case int32: + return result.New(result.Quantity{Value: float64(t), Unit: model.ONEUNIT}) + default: + op, err := result.ToQuantity(opObj) + if err != nil { + return result.Value{}, err + } + return result.New(op) + } +} + +// ToQuantity(argument String) Quantity +// https://cql.hl7.org/09-b-cqlreference.html#toquantity +func evalToQuantityString(m model.IUnaryExpression, opObj result.Value) (result.Value, error) { + if result.IsNull(opObj) { + return result.New(nil) + } + op, err := result.ToString(opObj) + if err != nil { + return result.Value{}, err + } + + // On valid match FindStringSubmatch returns a list containing: + // the whole matched text, the captured number, the captured unit text. + found := quantityStringRegex.FindStringSubmatch(op) + if len(found) != 3 { + return result.New(nil) + } + + // ParseFloat works for every string that meets the CQL spec. + f, err := strconv.ParseFloat(found[1], 64) + if err != nil { + return result.Value{}, err + } + unit := "1" + if len(found[2]) != 0 { + // trim off quotations + unit = found[2][1 : len(found[2])-1] + } + // TODO(b/319156186): When UCUM values are supported we should validate the unit value as well. + return result.New(result.Quantity{Value: f, Unit: model.Unit(unit)}) +} + +// Add an @ symbol to the string so we can use the same parsing logic as engine literals. +func (i *interpreter) stringToDate(input string, inputType types.System) (result.Value, error) { + return i.evalLiteral(&model.Literal{ + Value: "@" + unqoteSingle(input), + Expression: &model.Expression{Element: &model.Element{ResultType: inputType}}, + }) +} + +// unqoteSingle returns the unquoted version of the string, if it's quoted with single quotes, +// otherwise returns the input string. +func unqoteSingle(str string) string { + if string(str[0]) == "'" && string(str[len(str)-1]) == "'" { + return str[1 : len(str)-1] + } + return str +} diff --git a/interpreter/property.go b/interpreter/property.go new file mode 100644 index 0000000..31283b7 --- /dev/null +++ b/interpreter/property.go @@ -0,0 +1,471 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "errors" + "fmt" + "reflect" + "strings" + "time" + "unicode" + + d4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" + + "github.com/google/cql/internal/datehelpers" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/fhir/go/protopath" + annotations_pb "github.com/google/fhir/go/proto/google/fhir/proto/annotations_go_proto" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// evalProperty evaluates the ELM property expression passed in. +func (i *interpreter) evalProperty(expr *model.Property) (result.Value, error) { + if expr.Source == nil { + return result.Value{}, fmt.Errorf("internal error - source must be populated when accessing property %s", expr.Path) + } + obj, err := i.evalExpression(expr.Source) + if err != nil { + return result.Value{}, err + } + if result.IsNull(obj) { + return result.NewWithSources(nil, expr, obj) + } + // TODO(b/315503615): if Element or result type is unset, error in the future. + subObj, err := i.valueProperty(obj, expr.Path, expr.GetResultType()) + if err != nil { + return result.Value{}, err + } + return subObj.WithSources(expr, obj), nil +} + +// valueProperty computes the specified property on the given result.Value. +func (i *interpreter) valueProperty(v result.Value, property string, staticResultType types.IType) (result.Value, error) { + if property == "" { + return v, nil + } + + switch ot := v.GolangValue().(type) { + case result.Tuple: + elem, ok := ot.Value[property] + if !ok { + // The parser should have already validated that this is a valid property for the Tuple or + // Class type. If is not set in map return null. + return result.New(nil) + } + return elem, nil + case result.Named: + return i.protoProperty(ot, property, staticResultType) + case result.List: + return i.listProperty(ot, property, staticResultType) + case result.Interval: + switch property { + case "low": + return ot.Low, nil + case "high": + return ot.High, nil + case "lowClosed": + return result.New(ot.LowInclusive) + case "highClosed": + return result.New(ot.HighInclusive) + default: + return result.Value{}, fmt.Errorf("property %s is not supported on Intervals", property) + } + case result.Quantity: + switch property { + case "value": + return result.New(ot.Value) + case "unit": + return result.New(string(ot.Unit)) + default: + return result.Value{}, fmt.Errorf("property %s is not supported on %v", property, types.Quantity) + } + case result.Code: + switch property { + case "code": + return result.New(ot.Code) + case "system": + return result.New(ot.System) + case "version": + return result.New(ot.Version) + case "display": + return result.New(ot.Display) + default: + return result.Value{}, fmt.Errorf("property %s is not supported on %v", property, types.Code) + } + case result.Concept: + switch property { + case "codes": + return result.New(ot.Codes) + case "display": + return result.New(ot.Display) + default: + return result.Value{}, fmt.Errorf("property %s is not supported on %v", property, types.Concept) + } + case result.ValueSet: + switch property { + case "id": + return result.New(ot.ID) + case "version": + return result.New(ot.Version) + default: + return result.Value{}, fmt.Errorf("property %s is not supported on %v", property, types.ValueSet) + } + case result.CodeSystem: + switch property { + case "id": + return result.New(ot.ID) + case "version": + return result.New(ot.Version) + default: + return result.Value{}, fmt.Errorf("property %s is not supported on %v", property, types.CodeSystem) + } + // TODO(b/301606416): Support Ratio and Vocabulary properties. + default: + return result.Value{}, fmt.Errorf("unable to eval property %s on unsupported type %v", property, ot) + } +} + +func protoFieldFromJSONName(p proto.Message, jsonProperty string) (string, error) { + fd := p.ProtoReflect().Descriptor().Fields().ByJSONName(jsonProperty) + if fd != nil { + return fd.TextName(), nil + } + return "", fmt.Errorf("proto json field name %s not found", jsonProperty) +} + +func (i *interpreter) protoProperty(source result.Named, property string, staticResultType types.IType) (result.Value, error) { + // For .value properties on FHIR.dateTime, FHIR.time, FHIR.date, the result type is expected to be + // a System.DateTime, System.Time, System.Date. This is not how the data is represented in the + // FHIR proto data model, so we must catch this case and apply manual conversion. + + if property == "value" && (source.RuntimeType.Equal(&types.Named{TypeName: "FHIR.dateTime"}) || + source.RuntimeType.Equal(&types.Named{TypeName: "FHIR.time"}) || + source.RuntimeType.Equal(&types.Named{TypeName: "FHIR.date"})) { + return handleDateTimeValueProperty(source.Value, property, i.evaluationTimestamp.Location()) + } + + protoProperty, err := protoFieldFromJSONName(source.Value, property) + if err != nil { + return result.Value{}, err + } + subAny, err := protopath.Get[any](source.Value, protopath.NewPath(protoProperty)) + if err != nil { + return result.Value{}, err + } + + if reflect.ValueOf(subAny).Kind() == reflect.Slice { + return sliceToValue(reflect.ValueOf(subAny), staticResultType) + } + + switch s := subAny.(type) { + case proto.Message: + return handleProtoValue(s, property, staticResultType, i.modelInfo) + case protoreflect.Enum: + return handleEnumValue(s) + } + + obj, err := result.New(subAny) + if err != nil { + return result.Value{}, fmt.Errorf("error at property %s: %w", property, err) + } + return obj, nil +} + +func handleEnumValue(e protoreflect.Enum) (result.Value, error) { + // We need to transform this into a System.String. + // Roughly, we follow the pattern here: + // https://github.com/google/fhir/blob/358f39a5fae0faa006c9a630ca113f03d191e929/go/jsonformat/marshaller.go#L797-L811 + ed := e.Descriptor() + ev := ed.Values().ByNumber(e.Number()) + origCode := proto.GetExtension(ev.Options(), annotations_pb.E_FhirOriginalCode).(string) + if origCode != "" { + return result.New(origCode) + } + rawEnumName := string(ev.Name()) + return result.New(strings.Replace(strings.ToLower(rawEnumName), "_", "-", -1)) +} + +func handleProtoValue(msg proto.Message, property string, staticResultType types.IType, mi *modelinfo.ModelInfos) (result.Value, error) { + if !msg.ProtoReflect().IsValid() { + return result.New(nil) + } + + // runtimeResultType is the runtime result of the property result. We default to the static type, + // but in cases (like if the property is on a oneof) this might be updated to a more specific type + // (e.g. for a property on a oneof, the staticResultType will be a Choice, but the + // runtimeResultType will be updated to be a specific type based on the set oneof). + runtimeResultType := staticResultType + + // Check to see if this is a oneof wrapper message, and if so, extract the set oneof type. This is + // an artifact of how the FHIR protos are generated, that a choice type field like + // "Observation.value" will have a wrapper message called ValueX and ValueX.choice will contain + // the actual proto oneof. + // The protopath library will do this extraction for us, if we access the 'choice' property, which + // is the field name of all oneof wrapper types in the R4 generated protos. + // Note if the extension does not exist, this will be false: + msgReflect := msg.ProtoReflect() + if proto.GetExtension(msgReflect.Descriptor().Options(), annotations_pb.E_IsChoiceType).(bool) { + // TODO(b/316960208): When choice types are supported, we can know in advance if a property + // evaluation will result in a choice type wrapper, and could create a single call + // to protopath.Get by appending to the protopath.Path. + oneofValue, err := protopath.Get[any](msg, protopath.NewPath("choice")) + if err != nil { + return result.Value{}, err + } + + // Oneofs should not have repeated fields in FHIR, and we don't know how to compute the runtime + // type for them. https://hl7.org/fhir/R4/formats.html#choice + // TODO(b/324240909): support computing the runtime type correctly if needed in the future for + // other data models or future versions of FHIR. + if reflect.ValueOf(oneofValue).Kind() == reflect.Slice { + return result.Value{}, fmt.Errorf("cannot access this oneof proto submessage at property %v because the result %T is a slice, and no repeated elements were expected inside a choice type", property, oneofValue) + } + + // Since this is a oneof, use the set oneof field to determine the specific named type of the + // result. + runtimeResultType, err = fhirOneofRuntimeType(msgReflect, property, mi) + if err != nil { + return result.Value{}, err + } + + var ok bool + msg, ok = oneofValue.(proto.Message) + if !ok { + // TODO(b/316960208): We still expect oneof values to be proto.Message for now. Consider + // circling back. + return result.Value{}, fmt.Errorf("cannot access this oneof proto submessage at property %v because %T is not a proto.Message", property, oneofValue) + } + } + namedResultType, ok := runtimeResultType.(*types.Named) + if !ok { + return result.Value{}, fmt.Errorf("internal error - handleProtoValue expected runtimeResultType to be a Named type, got: %v", runtimeResultType) + } + return result.New(result.Named{Value: msg, RuntimeType: namedResultType}) +} + +// handleDateTimeValueProperty computes the value property for FHIR.date and FHIR.dateTime. It will +// eventually support FHIR.time when support for it is added. +func handleDateTimeValueProperty(sourceProto proto.Message, property string, evaluationLoc *time.Location) (result.Value, error) { + if property != "value" { + return result.Value{}, fmt.Errorf("internal error - handleTimeProperty expected a value property, got: %v", property) + } + switch v := sourceProto.(type) { + case *d4pb.Date: + t, prec, err := datehelpers.ParseFHIRDate(v, evaluationLoc) + if err != nil { + return result.Value{}, err + } + return result.New(result.Date{ + Date: t, + Precision: prec, + }) + case *d4pb.DateTime: + t, prec, err := datehelpers.ParseFHIRDateTime(v, evaluationLoc) + if err != nil { + return result.Value{}, err + } + return result.New(result.DateTime{ + Date: t, + Precision: prec, + }) + } + // TODO: b/301606416 - add support for FHIR.time. + return result.Value{}, fmt.Errorf("internal error - handleTimeValueProperty got an unexpected source value type: %T", sourceProto) +} + +// fhirOneofRuntimeType computes the runtime type of the property result on the oneofWrapperMsg, by +// inspecting the set oneof field of sourceMsg. This convention may apply to other data models, +// but for now is FHIR specific. +func fhirOneofRuntimeType(oneofWrapperMsg protoreflect.Message, property string, mi *modelinfo.ModelInfos) (*types.Named, error) { + if oneofWrapperMsg.Descriptor().Oneofs().Len() != 1 { + // All oneof wrapper types should have only one contained oneof type. + return nil, fmt.Errorf("internal error - cannot access this proto submessage at property %v because the wrapper oneof type has multiple oneof fields", property) + } + oneofField := oneofWrapperMsg.WhichOneof(oneofWrapperMsg.Descriptor().Oneofs().Get(0)) + // fieldName should be similar to the FHIR convention, but without the "nnn" prefix and without + // the title case described here: + // https://hl7.org/fhir/R4/formats.html#choice. + fieldName := oneofField.JSONName() + // Try to use the JSON name as is, and if it doesn't validate, we may need to capitalize the first + // letter since some FHIR types are lowercase (FHIR.dateTime) but others are title cased. + t, err := mi.ToNamed(fieldName) + if errors.Is(err, modelinfo.ErrTypeNotFound) { + // Try with title case. + titledFieldName := []rune(fieldName) + titledFieldName[0] = unicode.ToUpper(titledFieldName[0]) + return mi.ToNamed(string(titledFieldName)) + } + return t, err +} + +func (i *interpreter) listProperty(l result.List, property string, staticResultType types.IType) (result.Value, error) { + // The result type should be a list, so let's check that and grab the element type. + resultListType, ok := staticResultType.(*types.List) + if !ok { + return result.Value{}, fmt.Errorf("internal error -- evalPropertyList expects a staticResultType of list, got :%v", staticResultType.String()) + } + var subList []result.Value + for idx, elem := range l.Value { + // To compute a property on a list, we compute the property on each element (elem) in the list + // and return the combined result list. In cases where the output is a list of lists, the inner + // lists are later flattened. Because of this, it is possible that the property evaluation for a + // given element will result in a runtime list but the parser resultListType.ElementType will + // _not_ be a list for properties that were nested, because the flattening happens after the + // element property computation. This flattening is defined + // in https://build.fhir.org/ig/HL7/cql/03-developersguide.html#path-traversal and implemented + // in the parser static type computation in the internal/modelinfo package. + // + // For evalPropertyValue(elem, elemResultType), we want to ensure the passed elemResultType + // will match the interim runtime type, even in cases where flattening may be later applied. To + // ensure this, we recompute the property result type using the type helper directly on the list + // element (elem.property) instead of relying on the parser resultType, where flattening may + // have been applied. + // + // For example, consider the property name.given, where both name and given are repeated: + // name: [{given: ["a", "b"]}, {given: ["c", "d"]}] + // Evaluating ".given" should result in ["a", "b", "c", "d"], a flattened list in CQL for the + // completed computation of evalPropertyList. The parser result type would be List, with + // the element type being String. However, inside this loop we compute the property + // ".given" for each input name in the list. When computing a property on a single name element + // inside this loop (e.g. {given: ["a", "b"]}) the property should result in a runtime slice + // (["a", "b"]) for each element, so we must ensure we actually pass List for this + // element result type instead of just String, which would be the parser result type's element + // type. + elemResultType, err := i.modelInfo.PropertyTypeSpecifier(elem.RuntimeType(), property) + if err != nil { + return result.Value{}, err + } + subObj, err := i.valueProperty(elem, property, elemResultType) + if err != nil { + return result.Value{}, fmt.Errorf("at index %d: %w", idx, err) + } + + isSub, err := i.modelInfo.IsSubType(subObj.RuntimeType(), &types.List{ElementType: types.Any}) + if err != nil { + return result.Value{}, err + } + if isSub { + // When accessing repeated fields such as Patient.name.given we want to return a list of all + // given's in all names. This flattens the givens into a single list. + subList = append(subList, subObj.GolangValue().(result.List).Value...) + } else { + subList = append(subList, subObj) + } + } + return result.New(result.List{Value: subList, StaticType: resultListType}) +} + +// sliceToValue takes a slice of arbitrary Golang values and converts it to a properly typed +// *result.List Value. This means that it will ensure that the Golang elements of the input slice +// are properly converted to result.Values with the right type, based on the expected result type +// of the overall slice (listType). For example, a Golang slice of []*r4pb.HumanName with listType +// of *types.List{ElementType: "FHIR.HumanName"} would be properly converted to a result.List +// with the right type, and all the List elements would be correctly added as +// result.Named{..., Type: types.Named{"FHIR.HumanName"}} with the correct static result type +// annotated. +// TODO(b/315503615): would it be helpful to other callers to have this be in result package and +// exported? +func sliceToValue(v reflect.Value, staticResultType types.IType) (result.Value, error) { + if v.Kind() != reflect.Slice { + return result.Value{}, fmt.Errorf("%T: not a slice", v) + } + + listType, ok := staticResultType.(*types.List) + if !ok { + return result.Value{}, fmt.Errorf("internal error -- sliceToValue expects a staticResultType of list, got :%v", staticResultType.String()) + } + elementType, ok := listType.ElementType.(*types.Named) + if !ok { + return result.Value{}, fmt.Errorf("internal error -- sliceToValue expects a staticResultType of list with named elements, got :%v", staticResultType.String()) + } + + l := make([]result.Value, v.Len()) + for i := 0; i < v.Len(); i++ { + val := v.Index(i).Interface() + switch typedVal := val.(type) { + case proto.Message: + // TODO(b/315503615): apply proto reflection based type determination when that is added, + // to resolve choice types to their specific runtime type. + o, err := result.New(result.Named{Value: typedVal, RuntimeType: elementType}) + if err != nil { + return result.Value{}, fmt.Errorf("unable to create Value at index %d: %w", i, err) + } + l[i] = o + continue + // TODO: add interval + } + if reflect.ValueOf(val).Kind() == reflect.Slice { + // This means we have a nested list. This would happen when computing "value" property on + // something like {"value": [[1,2], [3,4]]}. + // This should be exceedingly rare in FHIR land, but could happen in CQL. + // We don't support mixed lists yet, so assume the elemType is itself a list + // and not a choice. + innerList, ok := listType.ElementType.(*types.List) + if !ok { + return result.Value{}, fmt.Errorf("internal error -- sliceToValue got element value of type Slice, so expected it to be a list but got :%v", listType.ElementType) + } + o, err := sliceToValue(reflect.ValueOf(val), innerList) + if err != nil { + return result.Value{}, fmt.Errorf("unable to create Value at index %d: %w", i, err) + } + l[i] = o + continue + } + + // Other primitive types: + o, err := result.New(val) + if err != nil { + return result.Value{}, fmt.Errorf("unable to create Value at index %d: %w", i, err) + } + l[i] = o + } + return result.New(result.List{Value: l, StaticType: listType}) +} + +func datePrecisionFromProto(p d4pb.Date_Precision) model.DateTimePrecision { + switch p { + case d4pb.Date_YEAR: + return model.YEAR + case d4pb.Date_MONTH: + return model.MONTH + case d4pb.Date_DAY: + return model.DAY + } + return model.UNSETDATETIMEPRECISION +} + +func dateTimePrecisionFromProto(p d4pb.DateTime_Precision) model.DateTimePrecision { + switch p { + case d4pb.DateTime_YEAR: + return model.YEAR + case d4pb.DateTime_MONTH: + return model.MONTH + case d4pb.DateTime_DAY: + return model.DAY + case d4pb.DateTime_SECOND: + return model.SECOND + case d4pb.DateTime_MILLISECOND: + return model.MILLISECOND + // FHIR datetimes can have microsecond precision, since CQL doesn't support this we map it to millisecond. + case d4pb.DateTime_MICROSECOND: + return model.MILLISECOND + } + return model.UNSETDATETIMEPRECISION +} diff --git a/interpreter/property_test.go b/interpreter/property_test.go new file mode 100644 index 0000000..9de03f5 --- /dev/null +++ b/interpreter/property_test.go @@ -0,0 +1,135 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "strings" + "testing" + "time" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/internal/reference" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" + r4patientpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/patient_go_proto" +) + +func TestEvalPropertyValue_Errors(t *testing.T) { + tests := []struct { + name string + property string + value result.Value + resultType types.IType + wantErrContains string + }{ + { + name: "protomessage invalid property", + property: "apple", + value: newOrFatal(t, result.Named{Value: &r4patientpb.Patient{}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + { + name: "integer", + property: "name", + value: newOrFatal(t, 4), + }, + { + name: "boolean", + property: "name", + value: newOrFatal(t, true), + }, + { + name: "string", + property: "name", + value: newOrFatal(t, "hello"), + }, + { + name: "long", + property: "name", + value: newOrFatal(t, 10), + }, + { + name: "decimal", + property: "name", + value: newOrFatal(t, 10.001), + }, + { + name: "valueset", + property: "name", + value: newOrFatal(t, result.ValueSet{ID: "ID", Version: "Version"}), + }, + { + name: "list of integers", + property: "name", + value: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 4), + newOrFatal(t, 5), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + resultType: &types.List{ElementType: types.Integer}, + }, + { + name: "null", + property: "name", + value: newOrFatal(t, nil), + }, + { + name: "interval invalid property", + property: "invalid", + value: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, 4), + High: newOrFatal(t, 5), + LowInclusive: false, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + wantErrContains: "property invalid is not supported on Intervals", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + i := &interpreter{ + refs: reference.NewResolver[result.Value, *model.FunctionDef](), + modelInfo: newFHIRModelInfo(t), + evaluationTimestamp: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + } + _, err := i.valueProperty(tc.value, tc.property, tc.resultType) + if err == nil { + t.Errorf("evalPropertyValue(%q) succeeded, want error", tc.property) + } + if tc.wantErrContains != "" && !strings.Contains(err.Error(), tc.wantErrContains) { + t.Errorf("evalPropertyValue(%s) error did not contain expected string. got: %v, want: %v", tc.property, err.Error(), tc.wantErrContains) + } + }) + } +} + +func newFHIRModelInfo(t *testing.T) *modelinfo.ModelInfos { + t.Helper() + fhirMIBytes, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + t.Fatalf("Reading embedded file %s failed unexpectedly: %v", "third_party/cqframework/fhir-modelinfo-4.0.1.xml", err) + } + + m, err := modelinfo.New([][]byte{fhirMIBytes}) + if err != nil { + t.Fatalf("New modelinfo unexpected error: %v", err) + } + m.SetUsing(modelinfo.Key{Name: "FHIR", Version: "4.0.1"}) + return m +} diff --git a/interpreter/query.go b/interpreter/query.go new file mode 100644 index 0000000..fc223a0 --- /dev/null +++ b/interpreter/query.go @@ -0,0 +1,556 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interpreter + +import ( + "errors" + "fmt" + "slices" + "strings" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" +) + +// iteration is the aliases for a single iteration of the query. For a CQL query like define Foo: +// from (4) A, ({1, 2, 3}) B iteration may be [A: {A, 4}, B: {B, 1}]. Map is from the alias's name +// to the alias. +type iteration map[string]alias + +func (i iteration) Equal(a iteration) bool { + if len(i) != len(a) { + return false + } + for _, aAlias := range a { + iAlias, ok := i[aAlias.alias] + if !ok { + return false + } + // TODO: b/301606416 - when equal is implemented, call that logic from here. + if !iAlias.obj.Equal(aAlias.obj) { + return false + } + } + return true +} + +type alias struct { + alias string + obj result.Value +} + +func (i *interpreter) evalQuery(q *model.Query) (result.Value, error) { + // The top level scope holds let clauses. Each query iteration defines a nested scope for the + // source and relationship aliases. + i.refs.EnterScope() + defer i.refs.ExitScope() + + iters, sourceObjs, err := i.sourceClause(q.Source) + if err != nil { + return result.Value{}, err + } + + sourceObj, err := i.letClause(q.Let) + if err != nil { + return result.Value{}, err + } + sourceObjs = append(sourceObjs, sourceObj...) + + for _, relationship := range q.Relationship { + var err error + var sourceObj result.Value + iters, sourceObj, err = i.relationshipClause(iters, relationship) + if err != nil { + return result.Value{}, err + } + sourceObjs = append(sourceObjs, sourceObj) + } + + iters, err = i.whereClause(iters, q.Where) + if err != nil { + return result.Value{}, err + } + + // finalVals is the list of values that will be returned by the query. + finalVals := make([]result.Value, 0, len(iters)) + + if q.Aggregate != nil { + finalVals, err = i.aggregateClause(iters, q.Aggregate) + if err != nil { + return result.Value{}, err + } + } + + if q.Return != nil { + finalVals, err = i.returnClause(iters, q.Return) + if err != nil { + return result.Value{}, err + } + } + + if q.Return == nil && q.Aggregate == nil { + if len(q.Source) == 1 { + // If there is no return clause and this was a single source query, unpack the alias. + for _, iter := range iters { + for _, alias := range iter { + finalVals = append(finalVals, alias.obj) + } + } + } else { + return result.Value{}, errors.New("internal error - multi-source queries must have a return clause, the parser should insert a default one if the user did not write one") + } + } + + if q.Sort != nil && len(finalVals) > 0 { + if sbd, ok := q.Sort.ByItems[0].(*model.SortByDirection); ok { + err := sortByDirection(finalVals, sbd) + if err != nil { + return result.Value{}, err + } + } else { + i.sortByColumn(finalVals, q.Sort.ByItems) + if err != nil { + return result.Value{}, err + } + } + } + + // Right now, we expect the query result type to be a List and forward that along. + returnStaticType, ok := q.GetResultType().(*types.List) + if ok { + return result.NewWithSources(result.List{Value: finalVals, StaticType: returnStaticType}, q, sourceObjs...) + } + + if len(finalVals) == 0 { + return result.NewWithSources(nil, q, sourceObjs...) + } else if len(finalVals) == 1 { + return result.NewWithSources(finalVals[0].GolangValue(), q, sourceObjs...) + } + + return result.Value{}, fmt.Errorf("internal error - query static result type is %v, but resulted in a list of values", returnStaticType) +} + +// sourceClause returns the iterations the query should be executed on and the source values. For +// multi-source queries the iterations are the cartesian product. For example, +// define Foo: from (4) A, ({1, 2, 3}) B will return +// [{A: {A, 4}, B: {B, 1}}, {A: {A, 4}, B: {B, 2}}, {A: {A, 4}, B: {B, 3}}]. +func (i *interpreter) sourceClause(s []*model.AliasedSource) ([]iteration, []result.Value, error) { + if s == nil || len(s) == 0 { + return nil, nil, fmt.Errorf("internal error - query must have at least one source") + } + + sourceObjs := make([]result.Value, 0, len(s)) + + // For a multi-source query like (4) A, ({1, 2, 3}) B aliases will be + // [[{A, 4}], [{B, 1}, {B, 2}, {B, 3}]]. + aliases := [][]alias{} + for _, source := range s { + a := []alias{} + obj, err := i.evalExpression(source.Source) + if err != nil { + return nil, nil, err + } + sourceObjs = append(sourceObjs, obj) + l, err := result.ToSlice(obj) + if err == nil { + // The source is a list so unpack. + for _, obj := range l { + a = append(a, alias{alias: source.Alias, obj: obj}) + } + } else if errors.Is(err, result.ErrCannotConvert) { + // The source is not a list, append directly. + a = append(a, alias{alias: source.Alias, obj: obj}) + } else { + return nil, nil, err + } + aliases = append(aliases, a) + } + return cartesianProduct(aliases), sourceObjs, nil +} + +// cartesianProduct converts [[{A, 4}], [{B, 1}, {B, 2}, {B, 3}]] into the cartesian product +// [{A: {A, 4}, B: {B, 1}}, {A: {A, 4}, B: {B, 2}}, {A: {A, 4}, B: {B, 3}}]. +func cartesianProduct(aliases [][]alias) []iteration { + if len(aliases) == 1 { + cartIters := []iteration{} + for _, a := range aliases[0] { + + cartIters = append(cartIters, iteration{a.alias: a}) + } + return cartIters + } + + subCartIters := cartesianProduct(aliases[1:]) + cartIters := []iteration{} + for _, a := range aliases[0] { + for _, b := range subCartIters { + cartIter := iteration{a.alias: a} + for k, v := range b { + cartIter[k] = v + } + cartIters = append(cartIters, cartIter) + } + } + return cartIters +} + +func (i *interpreter) letClause(m []*model.LetClause) ([]result.Value, error) { + sourceObjs := make([]result.Value, 0, len(m)) + for _, letClause := range m { + obj, err := i.evalExpression(letClause.Expression) + if err != nil { + return nil, err + } + sourceObjs = append(sourceObjs, obj) + + if err := i.refs.Alias(letClause.Identifier, obj); err != nil { + return nil, err + } + } + + return sourceObjs, nil +} + +func (i *interpreter) relationshipClause(iters []iteration, m model.IRelationshipClause) ([]iteration, result.Value, error) { + var relAliasName string + var relAliasSource model.IExpression + var suchThat model.IExpression + var with bool + + switch t := m.(type) { + case *model.With: + relAliasName = t.Alias + relAliasSource = t.Expression + suchThat = t.SuchThat + with = true + case *model.Without: + relAliasName = t.Alias + relAliasSource = t.Expression + suchThat = t.SuchThat + with = false + default: + return nil, result.Value{}, fmt.Errorf("internal error - there should only be a with or without relationship clause, got: %T", m) + } + + relSourceObj, err := i.evalExpression(relAliasSource) + if err != nil { + return nil, result.Value{}, err + } + + var relIters []result.Value + l, err := result.ToSlice(relSourceObj) + if err == nil { + relIters = l + } else if errors.Is(err, result.ErrCannotConvert) { + relIters = []result.Value{relSourceObj} + } else { + return nil, result.Value{}, err + } + + filteredIters := []iteration{} + for _, iter := range iters { + for _, relIter := range relIters { + i.refs.EnterScope() + // Define the alias for the relationship. + if err := i.refs.Alias(relAliasName, relIter); err != nil { + return nil, result.Value{}, err + } + // Define the aliases for the query iterations. + for _, alias := range iter { + if err := i.refs.Alias(alias.alias, alias.obj); err != nil { + return nil, result.Value{}, err + } + } + + filter, err := i.evalExpression(suchThat) + if err != nil { + return nil, result.Value{}, err + } + + if !result.IsNull(filter) && !filter.RuntimeType().Equal(types.Boolean) { + return nil, result.Value{}, fmt.Errorf("internal error - such that clause of a query must evaluate to a boolean or null, instead got %v", filter.RuntimeType()) + } + if (with && filter.GolangValue() == true) || (!with && filter.GolangValue() == false) { + // We found a relationship where such that expression evaluated to true. Save this iter in + // filteredIters and break. + i.refs.ExitScope() + filteredIters = append(filteredIters, iter) + break + } + i.refs.ExitScope() + } + } + return filteredIters, relSourceObj, nil +} + +func (i *interpreter) whereClause(iters []iteration, where model.IExpression) ([]iteration, error) { + if where == nil { + return iters, nil + } + + var filteredIters []iteration + for _, iter := range iters { + i.refs.EnterScope() + for _, alias := range iter { + if err := i.refs.Alias(alias.alias, alias.obj); err != nil { + return nil, err + } + } + filter, err := i.evalExpression(where) + if err != nil { + return nil, err + } + + if !result.IsNull(filter) && !filter.RuntimeType().Equal(types.Boolean) { + return nil, fmt.Errorf("internal error - where clause of a query must evaluate to a boolean or null, instead got %v", filter.RuntimeType()) + } + if filter.GolangValue() == true { + filteredIters = append(filteredIters, iter) + } + i.refs.ExitScope() + } + return filteredIters, nil +} + +func (i *interpreter) aggregateClause(iters []iteration, aggregateClause *model.AggregateClause) ([]result.Value, error) { + var filteredIters []iteration + if aggregateClause.Distinct { + for _, iter := range iters { + filteredIters = appendIfIterDistinct(filteredIters, iter) + } + } else { + filteredIters = iters + } + + aggregateObj, err := i.evalExpression(aggregateClause.Starting) + if err != nil { + return nil, err + } + + for _, iter := range filteredIters { + i.refs.EnterScope() + + if err := i.refs.Alias(aggregateClause.Identifier, aggregateObj); err != nil { + i.refs.ExitScope() + return nil, err + } + + for _, alias := range iter { + if err := i.refs.Alias(alias.alias, alias.obj); err != nil { + i.refs.ExitScope() + return nil, err + } + } + + aggregateObj, err = i.evalExpression(aggregateClause.Expression) + if err != nil { + i.refs.ExitScope() + return nil, err + } + + i.refs.ExitScope() + } + return []result.Value{aggregateObj}, nil +} + +func (i *interpreter) returnClause(iters []iteration, returnClause *model.ReturnClause) ([]result.Value, error) { + returnObjs := make([]result.Value, 0, len(iters)) + for _, iter := range iters { + i.refs.EnterScope() + for _, alias := range iter { + if err := i.refs.Alias(alias.alias, alias.obj); err != nil { + return nil, err + } + } + retObj, err := i.evalExpression(returnClause.Expression) + if err != nil { + return nil, err + } + if returnClause.Distinct { + returnObjs = appendIfDistinct(returnObjs, retObj) + } else { + returnObjs = append(returnObjs, retObj) + } + i.refs.ExitScope() + } + return returnObjs, nil +} + +func appendIfIterDistinct(distinctIters []iteration, maybeDistinct iteration) []iteration { + for _, distinct := range distinctIters { + if distinct.Equal(maybeDistinct) { + return distinctIters + } + } + return append(distinctIters, maybeDistinct) +} + +func appendIfDistinct(objs []result.Value, obj result.Value) []result.Value { + for _, o := range objs { + // TODO: b/327612359 - when distinct is implemented, call that logic from here. + if o.Equal(obj) { + return objs + } + } + return append(objs, obj) +} + +func sortByDirection(objs []result.Value, sbd *model.SortByDirection) error { + // Only allow Dates, DateTimes, Integers, Decimals, Longs and Strings for now. + // TODO(b/316984809): add sorting support for other types and nulls. + var sortFunc func(a, b result.Value) int + rt := objs[0].RuntimeType() + switch rt { + case types.Integer: + sortFunc = func(a, b result.Value) int { + av := a.GolangValue().(int32) + bv := b.GolangValue().(int32) + if sbd.SortByItem.Direction == model.DESCENDING { + return compareNumeralInt(bv, av) + } + return compareNumeralInt(av, bv) + } + case types.Decimal: + sortFunc = func(a, b result.Value) int { + av := a.GolangValue().(float64) + bv := b.GolangValue().(float64) + if sbd.SortByItem.Direction == model.DESCENDING { + return compareNumeralInt(bv, av) + } + return compareNumeralInt(av, bv) + } + case types.Long: + sortFunc = func(a, b result.Value) int { + av := a.GolangValue().(int64) + bv := b.GolangValue().(int64) + if sbd.SortByItem.Direction == model.DESCENDING { + return compareNumeralInt(bv, av) + } + return compareNumeralInt(av, bv) + } + case types.String: + sortFunc = func(a, b result.Value) int { + av := a.GolangValue().(string) + bv := b.GolangValue().(string) + if sbd.SortByItem.Direction == model.DESCENDING { + return strings.Compare(bv, av) + } + return strings.Compare(av, bv) + } + case types.Date: + sortFunc = func(a, b result.Value) int { + av := a.GolangValue().(result.Date).Date + bv := b.GolangValue().(result.Date).Date + if sbd.SortByItem.Direction == model.DESCENDING { + return bv.Compare(av) + } + return av.Compare(bv) + } + case types.DateTime: + // TODO: b/301606416 - we should use a precision aware comparison here. + sortFunc = func(a, b result.Value) int { + av := a.GolangValue().(result.DateTime).Date + bv := b.GolangValue().(result.DateTime).Date + if sbd.SortByItem.Direction == model.DESCENDING { + return bv.Compare(av) + } + return av.Compare(bv) + } + default: + return fmt.Errorf("sort column of a query by direction must evaluate to a Date, DateTime, Integer, Decimal, or Long, instead got %v", objs[0].RuntimeType()) + } + slices.SortFunc(objs[:], sortFunc) + return nil +} + +// compareNumeralInt returns the integer comparison value of two numeric values. +func compareNumeralInt[t float64 | int64 | int32](left, right t) int { + switch compareNumeral(left, right) { + case leftBeforeRight: + return -1 + case leftEqualRight: + return 0 + case leftAfterRight: + return 1 + default: + panic("internal error - unsupported comparison, this case should never happen") + } +} + +func (i *interpreter) sortByColumn(objs []result.Value, sbis []model.ISortByItem) error { + // Validate sort column types. + for _, sortItems := range sbis { + // TODO(b/316984809): Is this validation in advance necessary? What if other values (beyond + // objs[0]) have a different runtime type for the property (e.g. if they're a choice type)? + // Consider validating types inline during the sort instead. + path := sortItems.(*model.SortByColumn).Path + propertyType, err := i.modelInfo.PropertyTypeSpecifier(objs[0].RuntimeType(), path) + if err != nil { + return err + } + columnVal, err := i.valueProperty(objs[0], path, propertyType) + if err != nil { + return err + } + // Strictly only allow DateTimes for now. + // TODO(b/316984809): add sorting support for other types. + if !columnVal.RuntimeType().Equal(types.DateTime) { + return fmt.Errorf("sort column of a query must evaluate to a date time, instead got %v", columnVal.RuntimeType()) + } + } + + var sortErr error = nil + slices.SortFunc(objs[:], func(a, b result.Value) int { + for _, sortItems := range sbis { + sortCol := sortItems.(*model.SortByColumn) + // Passing the static types here is likely unimportant, but we compute it for completeness. + aType, err := i.modelInfo.PropertyTypeSpecifier(a.RuntimeType(), sortCol.Path) + if err != nil { + sortErr = err + continue + } + ap, err := i.valueProperty(a, sortCol.Path, aType) + if err != nil { + sortErr = err + continue + } + bType, err := i.modelInfo.PropertyTypeSpecifier(b.RuntimeType(), sortCol.Path) + if err != nil { + sortErr = err + continue + } + bp, err := i.valueProperty(b, sortCol.Path, bType) + if err != nil { + sortErr = err + continue + } + av := ap.GolangValue().(result.DateTime).Date + bv := bp.GolangValue().(result.DateTime).Date + + // In the future when we have an implementation of dateTime comparison without precision we should swap to using that. + // TODO(b/308012659): Implement dateTime comparison that doesn't take a precision. + if av.Equal(bv) { + continue + } else if sortCol.SortByItem.Direction == model.DESCENDING { + return bv.Compare(av) + } + return av.Compare(bv) + } + // All columns evaluated to equal so this sort is undefined. + return 0 + }) + return sortErr +} diff --git a/model/builder.go b/model/builder.go new file mode 100644 index 0000000..ab789c6 --- /dev/null +++ b/model/builder.go @@ -0,0 +1,56 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "github.com/google/cql/types" +) + +// NewLiteral is a helper function to build a model.Literal. +func NewLiteral(value string, t types.IType) *Literal { + return &Literal{Value: value, Expression: ResultType(t)} +} + +// NewInclusiveInterval returns an Interval[NewLiteral(low), NewLiteral(high)] where the low and high are +// literals of type t. +func NewInclusiveInterval(low, high string, t types.IType) *Interval { + return &Interval{ + Low: NewLiteral(low, t), + High: NewLiteral(high, t), + LowInclusive: true, + HighInclusive: true, + Expression: ResultType(&types.Interval{PointType: t}), + } +} + +// NewList returns a List{NewLiteral(elems[0]), NewLiteral(elems[1]), ...} where the elements of the +// list are literals of type t constructed from elems. +func NewList(elems []string, t types.IType) *List { + l := &List{ + List: []IExpression{}, + Expression: ResultType(&types.List{ElementType: t}), + } + for _, elem := range elems { + l.List = append(l.List, NewLiteral(elem, t)) + } + return l +} + +// ResultType is a helper function to set the resultType in a model.Element. +func ResultType(t types.IType) *Expression { + return &Expression{ + Element: &Element{ResultType: t}, + } +} diff --git a/model/model.go b/model/model.go new file mode 100644 index 0000000..4773cf6 --- /dev/null +++ b/model/model.go @@ -0,0 +1,1262 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package model provides an ELM-like data structure for an intermediate representation of CQL. +package model + +import ( + "github.com/google/cql/types" + "github.com/kylelemons/godebug/pretty" +) + +// TODO(b/302631773): We should make this model 1:1 with ELM, utilize the language and terms +// used by ELM's spec and XSD even if we do not automatically generate go code from the XSD. +// As long as this is hand-rolled though, we should comply with go conventions, some of which +// being violated here such as Functions should not have a "get" prefix, and interface names +// should not be `I` prefixed. + +// Library represents a base level CQL library, typically from one CQL file. +type Library struct { + Identifier *LibraryIdentifier + Usings []*Using + Includes []*Include + Parameters []*ParameterDef + CodeSystems []*CodeSystemDef + Concepts []*ConceptDef + Valuesets []*ValuesetDef + Codes []*CodeDef + Statements *Statements +} + +func (l *Library) String() string { + return pretty.Sprint(l) +} + +// IElement is an interface implemented by all CQL Element structs. +type IElement interface { + Row() int + Col() int + GetResultType() types.IType +} + +// Element is the base for all CQL nodes. +type Element struct { + // TODO(b/298104167): Add common row, column. + ResultType types.IType +} + +// Row returns the element's row in the source file. +func (t *Element) Row() int { + // TODO(b/298104167): Add row information. + return 0 +} + +// Col returns the element's column in the source file. +func (t *Element) Col() int { + // TODO(b/298104167): Add column information. + return 0 +} + +// GetResultType returns the type of the result which may be nil if unknown or not yet implemented. +func (t *Element) GetResultType() types.IType { + if t == nil { + return types.Unset + } + return t.ResultType +} + +// DateTimePrecision represents the precision of a DateTimeValue (or soon, TimeValue). +// This is a string and not an integer value so that the default JSON marshaled value is readable +// and useful. +type DateTimePrecision string + +const ( + // UNSETDATETIMEPRECISION represents unknown precision. + UNSETDATETIMEPRECISION DateTimePrecision = "" + // YEAR represents year precision. + YEAR DateTimePrecision = "year" + // MONTH represents month precision. + MONTH DateTimePrecision = "month" + // WEEK represents a week precision. Not valid for Date / DateTime values. + WEEK DateTimePrecision = "week" + // DAY represents day precision. + DAY DateTimePrecision = "day" + // HOUR represents an hour precision. + HOUR DateTimePrecision = "hour" + // MINUTE represents a minute precision. + MINUTE DateTimePrecision = "minute" + // SECOND represents second precision. + SECOND DateTimePrecision = "second" + // MILLISECOND represents millisecond precision. + MILLISECOND DateTimePrecision = "millisecond" +) + +// Unit represents the unit for a QuantityValue. +type Unit string + +// TODO(b/319155752) Add support for UCUM values. +const ( + // UNSETUNIT represents unknown unit. + UNSETUNIT Unit = "" + // ONEUNIT defines a base unit. + // This is often the result of dividing quantities with the same unit, canceling the unit out. + ONEUNIT Unit = "1" + // YEARUNIT represents year unit. + YEARUNIT Unit = "year" + // MONTHUNIT represents month unit. + MONTHUNIT Unit = "month" + // WEEKUNIT represents a week unit. + WEEKUNIT Unit = "week" + // DAYUNIT represents day unit. + DAYUNIT Unit = "day" + // HOURUNIT represents an hour unit. + HOURUNIT Unit = "hour" + // MINUTEUNIT represents a minute unit. + MINUTEUNIT Unit = "minute" + // SECONDUNIT represents second unit. + SECONDUNIT Unit = "second" + // MILLISECONDUNIT represents millisecond unit. + MILLISECONDUNIT Unit = "millisecond" +) + +// AccessLevel defines the access modifier for a definition (ExpressionDef, ParameterDef, +// ValueSetDef). If the user does not specify an access modifier the default is public. If the +// library is unnamed then the interpreter should treat all definitions as private even if the +// access modifier is public. +type AccessLevel string + +const ( + // Public means other CQL libraries can access the definition. + Public AccessLevel = "PUBLIC" + // Private means only the local CQL libraries can access the definition. + Private AccessLevel = "PRIVATE" +) + +// ValuesetDef is a named valueset definition that references a value set by ID. +type ValuesetDef struct { + *Element + Name string + ID string // 1..1 + Version string // 0..1 + CodeSystems []*CodeSystemRef // 0..* + AccessLevel AccessLevel +} + +// CodeSystemDef is a named definition that references an external code system by ID and version. +type CodeSystemDef struct { + *Element + Name string + ID string // 1..1 + Version string // 0..1 + AccessLevel AccessLevel +} + +// ConceptDef is a named definition that represents a terminology concept. It is made up of code(s) +// from one or more CodeSystems. At least one code is required. +type ConceptDef struct { + *Element + Name string + Codes []*CodeRef // 1..* + Display string // 0..1 + AccessLevel AccessLevel +} + +// CodeDef is a named definition that references an external code from a CodeSystem by ID. +type CodeDef struct { + *Element + Name string + Code string // 1..1 + CodeSystem *CodeSystemRef // 0..1 + Display string // 0..1 + AccessLevel AccessLevel +} + +// ParameterDef is a top-level statement that defines a named CQL parameter. +type ParameterDef struct { + *Element + Name string + Default IExpression + AccessLevel AccessLevel +} + +// LibraryIdentifier for the library definition. This matches up with the ELM VersionedIdentifier +// (https://cql.hl7.org/04-logicalspecification.html#versionedidentifier). If nil then this is an +// unnamed library. +type LibraryIdentifier struct { + *Element + Local string + Qualified string // The full identifier of the library. + Version string +} + +// Using defines a Using directive in CQL. +type Using struct { + *Element + LocalIdentifier string + // URI is the URL specified at the top of the modelinfo, for FHIR "http://hl7.org/fhir". + URI string + Version string +} + +// Include defines an Include library statement in CQL. +type Include struct { + *Element + Identifier *LibraryIdentifier +} + +// Statements is a collection of expression and function definitions, similar to the ELM structure. +type Statements struct { + Defs []IExpressionDef +} + +// IExpressionDef is implemented by both ExpressionDef and FunctionDef. +type IExpressionDef interface { + IElement + GetName() string + GetContext() string + GetExpression() IExpression + GetAccessLevel() AccessLevel +} + +// ExpressionDef is a top-level named definition of a CQL expression. +type ExpressionDef struct { + *Element + Name string + Context string + Expression IExpression + AccessLevel AccessLevel +} + +// GetName returns the name of the definition. +func (e *ExpressionDef) GetName() string { return e.Name } + +// GetContext returns the context of the definition. +func (e *ExpressionDef) GetContext() string { return e.Context } + +// GetExpression returns the expression of the definition. +func (e *ExpressionDef) GetExpression() IExpression { return e.Expression } + +// GetAccessLevel returns the access level of the definition. +func (e *ExpressionDef) GetAccessLevel() AccessLevel { return e.AccessLevel } + +// FunctionDef represents a user defined function. All CQL built-in functions have their own struct +// defined below. +type FunctionDef struct { + // The body of the function is represented by the Expression field in the ExpressionDef. The + // return type is the ResultType set in the Element. + *ExpressionDef + Operands []OperandDef + Fluent bool + // External functions do not have a function body. + External bool +} + +// OperandDef defines an operand for a user defined function. +type OperandDef struct { + // The type of the operand is the ResultType set in the Element. + *Expression + Name string +} + +// All items below are for CQL expressions. All CQL expressions embed the base Expression struct +// and implement the IExpression interface. This allows for dynamic trees and sequences of +// expression types, matching CQL's structure. + +// IExpression is an interface implemented by all CQL Expression structs +type IExpression interface { + IElement + isExpression() +} + +// Expression is a base type containing common metadata for all CQL expression types. +type Expression struct { + *Element +} + +func (e *Expression) isExpression() {} + +// GetResultType returns the type of the result which may be nil if unknown or not yet implemented. +func (e *Expression) GetResultType() types.IType { + if e == nil { + return types.Unset + } + return e.Element.GetResultType() +} + +// Literal represents a CQL literal. +type Literal struct { + *Expression + Value string +} + +// An Interval expression. +type Interval struct { + *Expression + Low IExpression + High IExpression + + // Either LowClosedExpression or LowInclusive should be set. + LowClosedExpression IExpression + LowInclusive bool + + // Either HighClosedExpression or HighInclusive should be set. + HighClosedExpression IExpression + HighInclusive bool +} + +// Quantity is an expression representation of a clinical quantity. +// https://cql.hl7.org/04-logicalspecification.html#quantity +type Quantity struct { + *Expression + Value float64 + Unit Unit +} + +// A Ratio is an expression that expresses a ratio between two Quantities. +// https://cql.hl7.org/04-logicalspecification.html#ratio +type Ratio struct { + *Expression + Numerator Quantity + Denominator Quantity +} + +// A List expression. +type List struct { + *Expression + List []IExpression +} + +// Code is a literal code selector. +type Code struct { + *Expression + System *CodeSystemRef + Code string + Display string +} + +// Tuple represents a tuple (aka Structured Value), see +// https://cql.hl7.org/04-logicalspecification.html#tuple +type Tuple struct { + *Expression + Elements []*TupleElement +} + +// TupleElement is an element in a CQL Tuple. +type TupleElement struct { + Name string + Value IExpression +} + +// Instance represents an instance of a Class (aka Named Structured Value), see +// https://cql.hl7.org/04-logicalspecification.html#instance +type Instance struct { + *Expression + ClassType types.IType + Elements []*InstanceElement +} + +// InstanceElement is an element in a CQL structure Instance. +type InstanceElement struct { + Name string + Value IExpression +} + +// A MessageSeverity determines the type of the message and how it will be processed. +type MessageSeverity string + +const ( + // UNSETMESSAGESEVERITY denotes a message severity that shouldn't be allowed. + UNSETMESSAGESEVERITY MessageSeverity = "" + // TRACE denotes a message that should be printed with trace information. + TRACE MessageSeverity = "Trace" + // MESSAGE denotes a simple message that should be printed. + MESSAGE MessageSeverity = "Message" + // WARNING denotes a message that should log a warning to users. + WARNING MessageSeverity = "Warning" + // ERROR denotes an error message that should also halt execution. + ERROR MessageSeverity = "Error" +) + +// Message is a CQL expression that represents a message, which is the equivalent of print in most +// other languages. +// https://cql.hl7.org/04-logicalspecification.html#message +type Message struct { + *Expression + Source IExpression + Condition IExpression + Code IExpression + Severity IExpression + Message IExpression +} + +// A SortDirection determines what ordering to use for a query if sorting is enabled. +type SortDirection string + +const ( + // UNSETSORTDIRECTION denotes a sort direction that shouldn't be allowed. + UNSETSORTDIRECTION SortDirection = "" + // ASCENDING denotes query sorting from smallest to largest values. + ASCENDING SortDirection = "ASCENDING" + // DESCENDING denotes query sorting from largest to smallest values. + DESCENDING SortDirection = "DESCENDING" +) + +// A Query expression. +type Query struct { + *Expression + Source []*AliasedSource + Let []*LetClause + Relationship []IRelationshipClause + Where IExpression + Sort *SortClause + Aggregate *AggregateClause // Only aggregate or Return can be populated, not both. + Return *ReturnClause +} + +// LetClause is https://cql.hl7.org/04-logicalspecification.html#letclause. +type LetClause struct { + *Element + Expression IExpression + Identifier string +} + +// IRelationshipClause is an interface that all With and Without meet. +type IRelationshipClause interface { + IElement + isRelationshipClause() +} + +// RelationshipClause for a Query expression. +type RelationshipClause struct { + *Element + // Expression is the source of the inclusion clause. + Expression IExpression + Alias string + SuchThat IExpression +} + +func (c *RelationshipClause) isRelationshipClause() {} + +// With is https://cql.hl7.org/04-logicalspecification.html#with. +type With struct{ *RelationshipClause } + +// Without is https://cql.hl7.org/04-logicalspecification.html#without. +type Without struct{ *RelationshipClause } + +// SortClause for a Query expression. +type SortClause struct { + *Element + ByItems []ISortByItem +} + +// AggregateClause for a Query expression. +type AggregateClause struct { + *Element + Expression IExpression + // Starting is the starting value of the aggregate variable. It is always set. If the user does + // not set it the parser will insert a null literal. + Starting IExpression + // Identifier is the alias for the aggregate variable. + Identifier string + Distinct bool +} + +// ReturnClause for a Query expression. +type ReturnClause struct { + *Element + Expression IExpression + Distinct bool +} + +// ISortByItem defines one or more items that a query can be sorted by. +// Follows format outlined in https://cql.hl7.org/elm/schema/expression.xsd. +type ISortByItem interface { + IElement + isSortByItem() +} + +// SortByItem is the base abstract type for all query types. +type SortByItem struct { + *Element + Direction SortDirection +} + +// SortByDirection enables sorting non-tuple values by direction +type SortByDirection struct { + *SortByItem +} + +func (c *SortByDirection) isSortByItem() {} + +// SortByColumn enables sorting by a given column and direction. +type SortByColumn struct { + *SortByItem + Path string +} + +func (c *SortByColumn) isSortByItem() {} + +// AliasedSource is a query source with an alias. +type AliasedSource struct { + *Expression + Alias string + Source IExpression +} + +// Property gets a property from an expression. In ELM if the expression is an AliasRef then Scope +// is set instead of Source. In our model Source is set to AliasRef; there is no Scope. +type Property struct { + *Expression + Source IExpression + Path string +} + +// A Retrieve expression. +type Retrieve struct { + *Expression + // TODO(b/312172420): Changing DataType to a named type would make life much easier. + DataType string + TemplateID string + CodeProperty string + // Codes is an expression that returns a list of code values. + Codes IExpression +} + +// Case is a conditional case expression https://cql.hl7.org/04-logicalspecification.html#case. +type Case struct { + *Expression + // If comparand is provided it is compared against each When in the CaseItems. The CaseItems are + // expected to be of the same type or implicitly convertible to the same type as the Comparand. If + // the comparand is not provided then each When must have resultType boolean. + Comparand IExpression + CaseItem []*CaseItem + // Else must always be provided. + Else IExpression +} + +// CaseItem is a single case item in a Case expression. +type CaseItem struct { + *Element + When IExpression + Then IExpression +} + +// IfThenElse Elm expression from https://cql.hl7.org/04-logicalspecification.html#if +type IfThenElse struct { + *Expression + Condition IExpression + Then IExpression + Else IExpression +} + +// MaxValue ELM expression from https://cql.hl7.org/04-logicalspecification.html#maxvalue +type MaxValue struct { + *Expression + ValueType types.IType +} + +// MinValue ELM expression from https://cql.hl7.org/04-logicalspecification.html#minvalue +type MinValue struct { + *Expression + ValueType types.IType +} + +// IUnaryExpression is an interface that all Unary expressions meet. +type IUnaryExpression interface { + IExpression + GetName() string + GetOperand() IExpression + SetOperand(IExpression) + // To differentiate IUnaryExpression from other interfaces like IBinaryExpression. + isUnaryExpression() +} + +// UnaryExpression is a CQL expression that has one operand. The ELM representation may have +// additional operands. +type UnaryExpression struct { + *Expression + Operand IExpression +} + +// GetOperand returns the unary expression's operand. +func (a *UnaryExpression) GetOperand() IExpression { return a.Operand } + +// SetOperand sets the unary expression's operand. +func (a *UnaryExpression) SetOperand(operand IExpression) { a.Operand = operand } + +func (a *UnaryExpression) isUnaryExpression() {} + +// TODO(b/297089208): eventually consider moving all UnaryExpressions into their own file for +// organization. + +// As is https://cql.hl7.org/09-b-cqlreference.html#as. +type As struct { + *UnaryExpression + AsTypeSpecifier types.IType + Strict bool +} + +var _ IUnaryExpression = &As{} + +// Is is https://cql.hl7.org/04-logicalspecification.html#is. +type Is struct { + *UnaryExpression + IsTypeSpecifier types.IType +} + +var _ IUnaryExpression = &Is{} + +// Negate is https://cql.hl7.org/04-logicalspecification.html#negate. +type Negate struct{ *UnaryExpression } + +var _ IUnaryExpression = &Negate{} + +// Exists is https://cql.hl7.org/04-logicalspecification.html#exists. +type Exists struct{ *UnaryExpression } + +var _ IUnaryExpression = &Exists{} + +// Not is https://cql.hl7.org/04-logicalspecification.html#not. +type Not struct{ *UnaryExpression } + +var _ IUnaryExpression = &Not{} + +// First ELM expression from https://cql.hl7.org/04-logicalspecification.html#first +type First struct { + *UnaryExpression + // TODO(b/301606416): Support the orderBy parameter. +} + +// Last ELM expression from https://cql.hl7.org/04-logicalspecification.html#last. +type Last struct { + *UnaryExpression + // TODO(b/301606416): Support the orderBy parameter. +} + +var _ IUnaryExpression = &Last{} + +// SingletonFrom is https://cql.hl7.org/04-logicalspecification.html#singletonfrom. +type SingletonFrom struct{ *UnaryExpression } + +var _ IUnaryExpression = &SingletonFrom{} + +// Start is https://cql.hl7.org/04-logicalspecification.html#start. +type Start struct{ *UnaryExpression } + +var _ IUnaryExpression = &Start{} + +// End is https://cql.hl7.org/04-logicalspecification.html#end. +type End struct{ *UnaryExpression } + +var _ IUnaryExpression = &End{} + +// Predecessor ELM expression from https://cql.hl7.org/04-logicalspecification.html#predecessor. +type Predecessor struct{ *UnaryExpression } + +var _ IUnaryExpression = &Predecessor{} + +// Successor ELM expression from https://cql.hl7.org/04-logicalspecification.html#successor. +type Successor struct{ *UnaryExpression } + +var _ IUnaryExpression = &Successor{} + +// IsNull is https://cql.hl7.org/04-logicalspecification.html#isnull. +type IsNull struct{ *UnaryExpression } + +var _ IUnaryExpression = &IsNull{} + +// IsFalse is https://cql.hl7.org/04-logicalspecification.html#isfalse. +type IsFalse struct{ *UnaryExpression } + +var _ IUnaryExpression = &IsFalse{} + +// IsTrue is https://cql.hl7.org/04-logicalspecification.html#istrue. +type IsTrue struct{ *UnaryExpression } + +var _ IUnaryExpression = &IsTrue{} + +// ToBoolean ELM expression from https://cql.hl7.org/09-b-cqlreference.html#toboolean. +type ToBoolean struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToBoolean{} + +// ToDateTime ELM expression from https://cql.hl7.org/04-logicalspecification.html#todatetime +type ToDateTime struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToDateTime{} + +// ToDate ELM expression from https://cql.hl7.org/04-logicalspecification.html#todate. +type ToDate struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToDate{} + +// ToDecimal ELM expression from https://cql.hl7.org/04-logicalspecification.html#todecimal. +type ToDecimal struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToDecimal{} + +// ToLong ELM expression from https://cql.hl7.org/04-logicalspecification.html#tolong. +type ToLong struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToLong{} + +// ToInteger ELM expression from https://cql.hl7.org/09-b-cqlreference.html#tointeger. +type ToInteger struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToInteger{} + +// ToQuantity ELM expression from https://cql.hl7.org/04-logicalspecification.html#toquantity. +type ToQuantity struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToQuantity{} + +// ToConcept ELM expression from https://cql.hl7.org/09-b-cqlreference.html#toconcept. +type ToConcept struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToConcept{} + +// ToString ELM expression from https://cql.hl7.org/09-b-cqlreference.html#tostring. +type ToString struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToString{} + +// ToTime ELM expression from https://cql.hl7.org/09-b-cqlreference.html#totime. +type ToTime struct{ *UnaryExpression } + +var _ IUnaryExpression = &ToTime{} + +// CalculateAge CQL expression type +type CalculateAge struct { + *UnaryExpression + Precision DateTimePrecision +} + +// BinaryExpression is a CQL expression that has two operands. The ELM representation may have +// additional operands (ex BinaryExpressionWithPrecision). +type BinaryExpression struct { + *Expression + Operands []IExpression +} + +// Left returns the Left expression (first operand) of the BinaryExpression. If not present, +// returns nil. +func (b *BinaryExpression) Left() IExpression { + if len(b.Operands) < 1 { + return nil + } + return b.Operands[0] +} + +// Right returns the Right expression (second operand) of the BinaryExpression. If not present, +// returns nil. +func (b *BinaryExpression) Right() IExpression { + if len(b.Operands) < 2 { + return nil + } + return b.Operands[1] +} + +// SetOperands sets the BinaryExpression's operands. +func (b *BinaryExpression) SetOperands(left, right IExpression) { + b.Operands = []IExpression{left, right} +} + +func (b *BinaryExpression) isBinaryExpression() {} + +// IBinaryExpression is an interface that all Binary Expressions meet. +// Get prefixes are used below so as to not conflict with struct property names. +type IBinaryExpression interface { + IExpression + // GetName returns the name of the BinaryExpression. + GetName() string + // Left returns the Left expression (first operand) of the BinaryExpression. If not present, + // returns nil. + Left() IExpression + // Right returns the Right expression (second operand) of the BinaryExpression. If not present, + // returns nil. + Right() IExpression + SetOperands(left, right IExpression) + // To differentiate IBinaryExpression from other interfaces like INaryExpression. + isBinaryExpression() +} + +// CanConvertQuantity ELM Expression from https://cql.hl7.org/04-logicalspecification.html#canconvertquantity. +type CanConvertQuantity struct{ *BinaryExpression } + +var _ IBinaryExpression = &CanConvertQuantity{} + +// Equal ELM Expression from https://cql.hl7.org/04-logicalspecification.html#equal. +type Equal struct{ *BinaryExpression } + +var _ IBinaryExpression = &Equal{} + +// Equivalent ELM Expression from https://cql.hl7.org/04-logicalspecification.html#equivalent. +type Equivalent struct{ *BinaryExpression } + +var _ IBinaryExpression = &Equivalent{} + +// Less ELM Expression https://cql.hl7.org/04-logicalspecification.html#less +type Less struct{ *BinaryExpression } + +var _ IBinaryExpression = &Less{} + +// Greater ELM Expression https://cql.hl7.org/04-logicalspecification.html#greater +type Greater struct{ *BinaryExpression } + +var _ IBinaryExpression = &Greater{} + +// LessOrEqual ELM Expression https://cql.hl7.org/04-logicalspecification.html#lessorequal +type LessOrEqual struct{ *BinaryExpression } + +var _ IBinaryExpression = &LessOrEqual{} + +// GreaterOrEqual ELM Expression https://cql.hl7.org/04-logicalspecification.html#greaterorequal +type GreaterOrEqual struct{ *BinaryExpression } + +var _ IBinaryExpression = &GreaterOrEqual{} + +// And is https://cql.hl7.org/04-logicalspecification.html#and. +type And struct{ *BinaryExpression } + +// Or is https://cql.hl7.org/04-logicalspecification.html#or +type Or struct{ *BinaryExpression } + +// XOr is https://cql.hl7.org/04-logicalspecification.html#xor +type XOr struct{ *BinaryExpression } + +// Implies is https://cql.hl7.org/04-logicalspecification.html#implies +type Implies struct{ *BinaryExpression } + +// Add ELM Expression https://cql.hl7.org/04-logicalspecification.html#add +type Add struct{ *BinaryExpression } + +// Subtract ELM Expression https://cql.hl7.org/04-logicalspecification.html#subtract +type Subtract struct{ *BinaryExpression } + +// Multiply ELM Expression https://cql.hl7.org/04-logicalspecification.html#multiply +type Multiply struct{ *BinaryExpression } + +// Divide ELM Expression https://cql.hl7.org/04-logicalspecification.html#divide +type Divide struct{ *BinaryExpression } + +// Modulo ELM Expression https://cql.hl7.org/04-logicalspecification.html#modulo +type Modulo struct{ *BinaryExpression } + +// TruncatedDivide ELM Expression https://cql.hl7.org/04-logicalspecification.html#truncateddivide +type TruncatedDivide struct{ *BinaryExpression } + +// Except ELM Expression https://cql.hl7.org/04-logicalspecification.html#except +// Except is a nary expression but we are only supporting two operands. +type Except struct{ *BinaryExpression } + +// Intersect ELM Expression https://cql.hl7.org/04-logicalspecification.html#intersect +// Intersect is a nary expression but we are only supporting two operands. +type Intersect struct{ *BinaryExpression } + +// Union ELM Expression https://cql.hl7.org/04-logicalspecification.html#union +// Union is a nary expression but we are only supporting two operands. +type Union struct{ *BinaryExpression } + +// BinaryExpressionWithPrecision represents a BinaryExpression with a precision property. +type BinaryExpressionWithPrecision struct { + *BinaryExpression + // Precision returns the precision of this BinaryExpression. It must be one of the following: + // https://cql.hl7.org/19-l-cqlsyntaxdiagrams.html#dateTimePrecision. + Precision DateTimePrecision +} + +// Before ELM expression from https://cql.hl7.org/04-logicalspecification.html#before. +type Before BinaryExpressionWithPrecision + +var _ IBinaryExpression = &Before{} + +// After ELM expression from https://cql.hl7.org/04-logicalspecification.html#after. +type After BinaryExpressionWithPrecision + +// SameOrBefore ELM expression from https://cql.hl7.org/04-logicalspecification.html#sameorbefore. +type SameOrBefore BinaryExpressionWithPrecision + +// SameOrAfter ELM expression from https://cql.hl7.org/04-logicalspecification.html#sameorafter. +type SameOrAfter BinaryExpressionWithPrecision + +// DifferenceBetween ELM expression from https://cql.hl7.org/04-logicalspecification.html#differencebetween. +type DifferenceBetween BinaryExpressionWithPrecision + +// In ELM expression from https://cql.hl7.org/04-logicalspecification.html#in. +type In BinaryExpressionWithPrecision + +// IncludedIn ELM expression from https://cql.hl7.org/04-logicalspecification.html#included-in. +type IncludedIn BinaryExpressionWithPrecision + +// InCodeSystem is https://cql.hl7.org/09-b-cqlreference.html#in-codesystem. +// This is not technically 1:1 with the ELM definition. The ELM defines Code, CodeSystem and +// CodeSystemExpression arguments, the last being seemingly impossible to set for for now we're +// treating this as a binary expression. +type InCodeSystem struct{ *BinaryExpression } + +// InValueSet is https://cql.hl7.org/09-b-cqlreference.html#in-valueset. +// This is not technically 1:1 with the ELM definition. The ELM defines Code, ValueSet and +// ValueSetExpression arguments, the last being seemingly impossible to set for for now we're +// treating this as a binary expression. +type InValueSet struct{ *BinaryExpression } + +// Contains ELM expression from https://cql.hl7.org/04-logicalspecification.html#contains. +type Contains BinaryExpressionWithPrecision + +// CalculateAgeAt ELM expression from https://cql.hl7.org/04-logicalspecification.html#calculateageat. +type CalculateAgeAt BinaryExpressionWithPrecision + +// INaryExpression is an interface that Expressions with any number of operands meet. +type INaryExpression interface { + IExpression + GetName() string + GetOperands() []IExpression + SetOperands([]IExpression) + // To differentiate INaryExpression from other interfaces like IBinaryExpression. + isNaryExpression() +} + +// NaryExpression that takes any number of operands including zero. The ELM representation may have +// additional operands. +type NaryExpression struct { + *Expression + Operands []IExpression +} + +// GetOperands returns the operands of the NaryExpression. +func (n *NaryExpression) GetOperands() []IExpression { + return n.Operands +} + +// SetOperands sets the NaryExpression's operands. +func (n *NaryExpression) SetOperands(ops []IExpression) { + n.Operands = ops +} + +func (n *NaryExpression) isNaryExpression() {} + +// Coalesce is https://cql.hl7.org/04-logicalspecification.html#coalesce. +type Coalesce struct{ *NaryExpression } + +// Concatenate is https://cql.hl7.org/04-logicalspecification.html#concatenate. +type Concatenate struct{ *NaryExpression } + +// Date is the functional syntax to create a Date https://cql.hl7.org/09-b-cqlreference.html#date-1. +type Date struct{ *NaryExpression } + +// DateTime is the functional syntax to create a to create a CQL DateTime +// https://cql.hl7.org/09-b-cqlreference.html#datetime-1. +type DateTime struct{ *NaryExpression } + +// Now is https://cql.hl7.org/04-logicalspecification.html#now. +// Note: in the future we may implement the OperatorExpression, and should convert this to +// one of those at that point. +type Now struct{ *NaryExpression } + +// TimeOfDay is https://cql.hl7.org/04-logicalspecification.html#timeofday +// Note: in the future we may implement the OperatorExpression, and should convert this to +// one of those at that point. +type TimeOfDay struct{ *NaryExpression } + +// Time is the functional syntax to create a CQL Time +// https://cql.hl7.org/09-b-cqlreference.html#time-1. +type Time struct{ *NaryExpression } + +// Today is https://cql.hl7.org/04-logicalspecification.html#today. +// Note: in the future we may implement the OperatorExpression, and should convert this to +// one of thse at that point. +type Today struct{ *NaryExpression } + +// ParameterRef defines a reference to a ParameterDef definition used in CQL expressions. +type ParameterRef struct { + *Expression + Name string + // LibraryName is empty for parameters defined in the local CQL library. Otherwise it is the + // local identifier of the included library. + LibraryName string +} + +// ValuesetRef defines a reference to a ValuesetDef definition used in CQL expressions. +type ValuesetRef struct { + *Expression + Name string + // LibraryName is empty for valuesets defined in the local CQL library. Otherwise it is the + // local identifier of the included library. + LibraryName string +} + +// CodeSystemRef defines a reference to a CodeSystemDef definition used in CQL expressions. +type CodeSystemRef struct { + *Expression + Name string + // LibraryName is empty for CodeSystems defined in the local CQL library. Otherwise it is the + // local identifier of the included library. + LibraryName string +} + +// ConceptRef defines a reference to a ConceptDef definition used in CQL expressions. +type ConceptRef struct { + *Expression + Name string + // LibraryName is empty for Concepts defined in the local CQL library. Otherwise it is the + // local identifier of the included library. + LibraryName string +} + +// CodeRef defines a reference to a Code definition used in CQL expressions. +type CodeRef struct { + *Expression + Name string + // LibraryName is empty for Codes defined in the local CQL library. Otherwise it is the + // local identifier of the included library. + LibraryName string +} + +// ExpressionRef defines a reference to a ExpressionDef definition used in CQL expressions. +type ExpressionRef struct { + *Expression + Name string + // LibraryName is empty for expressions defined in the local CQL library. Otherwise it is the + // local identifier of the included library. + LibraryName string +} + +// AliasRef defines a reference to a source within the scope of a query. +type AliasRef struct { + *Expression + Name string +} + +// QueryLetRef is similar to an AliasRef except specific to references to let clauses. +type QueryLetRef struct { + *Expression + Name string +} + +// FunctionRef defines a reference to a user defined function. +type FunctionRef struct { + *Expression + Name string + // LibraryName is empty for expressions defined in the local CQL library. Otherwise it is the + // local identifier of the included library. + LibraryName string + Operands []IExpression +} + +// OperandRef defines a reference to an operand within a function. +type OperandRef struct { + *Expression + Name string +} + +// UNARY EXPRESSION GETNAME() + +// GetName returns the name of the system operator. +func (a *As) GetName() string { return "As" } + +// GetName returns the name of the system operator. +func (i *Is) GetName() string { return "Is" } + +// GetName returns the name of the system operator. +func (e *Exists) GetName() string { return "Exists" } + +// GetName returns the name of the system operator. +func (n *Not) GetName() string { return "Not" } + +// GetName returns the name of the system operator. +func (f *First) GetName() string { return "First" } + +// GetName returns the name of the system operator. +func (l *Last) GetName() string { return "Last" } + +// GetName returns the name of the system operator. +func (s *SingletonFrom) GetName() string { return "As" } + +// GetName returns the name of the system operator. +func (a *Start) GetName() string { return "Start" } + +// GetName returns the name of the system operator. +func (a *End) GetName() string { return "End" } + +// GetName returns the name of the system operator. +func (a *Predecessor) GetName() string { return "Predecessor" } + +// GetName returns the name of the system operator. +func (a *Successor) GetName() string { return "Successor" } + +// GetName returns the name of the system operator. +func (a *IsNull) GetName() string { return "IsNull" } + +// GetName returns the name of the system operator. +func (a *IsFalse) GetName() string { return "IsFalse" } + +// GetName returns the name of the system operator. +func (a *IsTrue) GetName() string { return "IsTrue" } + +// GetName returns the name of the system operator. +func (a *ToBoolean) GetName() string { return "ToBoolean" } + +// GetName returns the name of the system operator. +func (a *ToDateTime) GetName() string { return "ToDateTime" } + +// GetName returns the name of the system operator. +func (a *ToDate) GetName() string { return "ToDate" } + +// GetName returns the name of the system operator. +func (a *ToDecimal) GetName() string { return "ToDecimal" } + +// GetName returns the name of the system operator. +func (a *ToLong) GetName() string { return "ToLong" } + +// GetName returns the name of the system operator. +func (a *ToInteger) GetName() string { return "ToInteger" } + +// GetName returns the name of the system operator. +func (a *ToQuantity) GetName() string { return "ToQuantity" } + +// GetName returns the name of the system operator. +func (a *ToConcept) GetName() string { return "ToConcept" } + +// GetName returns the name of the system operator. +func (a *ToString) GetName() string { return "ToString" } + +// GetName returns the name of the system operator. +func (a *ToTime) GetName() string { return "ToTime" } + +// GetName returns the name of the system operator. +func (a *CalculateAge) GetName() string { return "CalculateAge" } + +// GetName returns the name of the system operator. +func (a *Negate) GetName() string { return "Negate" } + +// BINARY EXPRESSION GETNAME() + +// GetName returns the name of the system operator. +func (a *CanConvertQuantity) GetName() string { return "CanConvertQuantity" } + +// GetName returns the name of the system operator. +func (a *Equal) GetName() string { return "Equal" } + +// GetName returns the name of the system operator. +func (a *Equivalent) GetName() string { return "Equivalent" } + +// GetName returns the name of the system operator. +func (a *Less) GetName() string { return "Less" } + +// GetName returns the name of the system operator. +func (a *Greater) GetName() string { return "Greater" } + +// GetName returns the name of the system operator. +func (a *LessOrEqual) GetName() string { return "LessOrEqual" } + +// GetName returns the name of the system operator. +func (a *GreaterOrEqual) GetName() string { return "GreaterOrEqual" } + +// GetName returns the name of the system operator. +func (a *And) GetName() string { return "And" } + +// GetName returns the name of the system operator. +func (a *Or) GetName() string { return "Or" } + +// GetName returns the name of the system operator. +func (a *XOr) GetName() string { return "XOr" } + +// GetName returns the name of the system operator. +func (a *Implies) GetName() string { return "Implies" } + +// GetName returns the name of the system operator. +func (a *Add) GetName() string { return "Add" } + +// GetName returns the name of the system operator. +func (a *Subtract) GetName() string { return "Subtract" } + +// GetName returns the name of the system operator. +func (a *Multiply) GetName() string { return "Multiply" } + +// GetName returns the name of the system operator. +func (a *Divide) GetName() string { return "Divide" } + +// GetName returns the name of the system operator. +func (a *Modulo) GetName() string { return "Modulo" } + +// GetName returns the name of the system operator. +func (a *TruncatedDivide) GetName() string { return "TruncatedDivide" } + +// GetName returns the name of the system operator. +func (a *Before) GetName() string { return "Before" } + +// GetName returns the name of the system operator. +func (a *After) GetName() string { return "After" } + +// GetName returns the name of the system operator. +func (a *SameOrBefore) GetName() string { return "SameOrBefore" } + +// GetName returns the name of the system operator. +func (a *SameOrAfter) GetName() string { return "SameOrAfter" } + +// GetName returns the name of the system operator. +func (a *DifferenceBetween) GetName() string { return "DifferenceBetween" } + +// GetName returns the name of the system operator. +func (a *In) GetName() string { return "In" } + +// GetName returns the name of the system operator. +func (a *IncludedIn) GetName() string { return "IncludedIn" } + +// GetName returns the name of the system operator. +func (a *InCodeSystem) GetName() string { return "InCodeSystem" } + +// GetName returns the name of the system operator. +func (a *InValueSet) GetName() string { return "InValueSet" } + +// GetName returns the name of the system operator. +func (a *Contains) GetName() string { return "Contains" } + +// GetName returns the name of the system operator. +func (a *CalculateAgeAt) GetName() string { return "CalculateAgeAt" } + +// GetName returns the name of the system operator. +func (a *Except) GetName() string { return "Except" } + +// GetName returns the name of the system operator. +func (a *Intersect) GetName() string { return "Intersect" } + +// GetName returns the name of the system operator. +func (a *Union) GetName() string { return "Union" } + +// NARY EXPRESSION GETNAME() + +// GetName returns the name of the system operator. +func (a *Coalesce) GetName() string { return "Coalesce" } + +// GetName returns the name of the system operator. +func (a *Concatenate) GetName() string { return "Concatenate" } + +// GetName returns the name of the system operator. +func (a *Date) GetName() string { return "Date" } + +// GetName returns the name of the system operator. +func (a *DateTime) GetName() string { return "DateTime" } + +// GetName returns the name of the system operator. +func (a *Now) GetName() string { return "Now" } + +// GetName returns the name of the system operator. +func (a *TimeOfDay) GetName() string { return "TimeOfDay" } + +// GetName returns the name of the system operator. +func (a *Time) GetName() string { return "Time" } + +// GetName returns the name of the system operator. +func (a *Today) GetName() string { return "Today" } diff --git a/model/model_test.go b/model/model_test.go new file mode 100644 index 0000000..73795bf --- /dev/null +++ b/model/model_test.go @@ -0,0 +1,96 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "testing" + + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" +) + +func TestBinaryExpression(t *testing.T) { + cases := []struct { + name string + exp *BinaryExpression + wantName string + wantLeft IExpression + wantRight IExpression + }{ + { + name: "Simple", + exp: &BinaryExpression{ + Operands: []IExpression{ + &Literal{Value: "10"}, + &Literal{Value: "20"}, + }, + }, + wantName: "test", + wantLeft: &Literal{Value: "10"}, + wantRight: &Literal{Value: "20"}, + }, + { + name: "Missing all operands", + exp: &BinaryExpression{}, + wantName: "test", + wantLeft: nil, + wantRight: nil, + }, + { + name: "Missing one operand", + exp: &BinaryExpression{ + Operands: []IExpression{ + &Literal{Value: "10"}, + }, + }, + wantName: "test", + wantLeft: &Literal{Value: "10"}, + wantRight: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if !cmp.Equal(tc.exp.Left(), tc.wantLeft) { + t.Errorf("GetLeft() = %v, want %v", tc.exp.Left(), tc.wantLeft) + } + if !cmp.Equal(tc.exp.Right(), tc.wantRight) { + t.Errorf("GetRight() = %v, want %v", tc.exp.Right(), tc.wantRight) + } + }) + } +} + +func TestNilTypeSpecifier(t *testing.T) { + t.Run("Nil Expression", func(t *testing.T) { + l := Literal{ + Expression: nil, + Value: "10", + } + if got := l.GetResultType(); got != types.Unset { + t.Errorf("%v.GetResultType() = %v, want types.Unsupported", l, got) + } + }) + + t.Run("Nil Element", func(t *testing.T) { + l := Literal{ + Expression: &Expression{Element: nil}, + Value: "10", + } + if got := l.GetResultType(); got != types.Unset { + t.Errorf("%v.GetResultType() = %v, want types.Unsupported", l, got) + } + }) +} diff --git a/parser/errors.go b/parser/errors.go new file mode 100644 index 0000000..d6347e3 --- /dev/null +++ b/parser/errors.go @@ -0,0 +1,211 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "fmt" + "strings" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/antlr4-go/antlr/v4" +) + +var _ antlr.ErrorListener = &visitor{} + +// LibraryErrors contains a list of CQL parsing errors that occurred within a single library. +type LibraryErrors struct { + LibKey result.LibKey + Errors []*ParsingError +} + +func (le *LibraryErrors) Error() string { + var msgs []string + for _, e := range le.Errors { + msgs = append(msgs, e.Error()) + } + return strings.Join(msgs, "\n") +} + +// Unwrap implements the Go standard errors package Unwrap() function. See +// https://pkg.go.dev/errors. +func (le *LibraryErrors) Unwrap() []error { + if le == nil { + return nil + } + errs := make([]error, 0, len(le.Errors)) + for _, err := range le.Errors { + errs = append(errs, err) + } + return errs +} + +// Append adds the given error to the list of ParsingErrors. +func (le *LibraryErrors) Append(e *ParsingError) { + le.Errors = append(le.Errors, e) +} + +// ParameterErrors contains a list of CQL parsing errors that occurred parsing a single parameter. +type ParameterErrors struct { + DefKey result.DefKey + Errors []*ParsingError +} + +func (pe *ParameterErrors) Error() string { + var msgs []string + for _, e := range pe.Errors { + msgs = append(msgs, e.Error()) + } + return strings.Join(msgs, "\n") +} + +// Unwrap implements the Go standard errors package Unwrap() function. See +// https://pkg.go.dev/errors. +func (pe *ParameterErrors) Unwrap() []error { + if pe == nil { + return nil + } + errs := make([]error, 0, len(pe.Errors)) + for _, err := range pe.Errors { + errs = append(errs, err) + } + return errs +} + +// Append adds the given error to the list of ParsingErrors. +func (pe *ParameterErrors) Append(e *ParsingError) { + pe.Errors = append(pe.Errors, e) +} + +// ErrorType is the type of parsing error. +type ErrorType string + +const ( + // SyntaxError is returned by the lexer when the CQL does not meet the grammar. + SyntaxError = ErrorType("SyntaxError") + // ValidationError is returned by the parser when the CQL meets the grammar, but does not meet + // some other validation rules (like referencing a non-existent expression definition). + ValidationError = ErrorType("ValidationError") + // InternalError occurs when the parser errors in an unexpected way. This is not a user error, nor + // a feature that we purposefully do not support. + InternalError = ErrorType("InternalError") + // UnsupportedError is return for CQL language features that are not yet supported. + UnsupportedError = ErrorType("UnsupportedError") +) + +// ErrorSeverity represents different ParsingError severity levels. +type ErrorSeverity string + +const ( + // ErrorSeverityInfo is informational. + ErrorSeverityInfo = ErrorSeverity("Info") + // ErrorSeverityWarning is a medium severity error. + ErrorSeverityWarning = ErrorSeverity("Warning") + // ErrorSeverityError is a high severity error. + ErrorSeverityError = ErrorSeverity("Error") +) + +// ParsingError represents a specific parser error and its location. +type ParsingError struct { + // High level message about the error. + Message string + // Line is the 1-based line number within source file where the error occurred. + Line int + // Column is the 0-based column number within source file where the error occurred. + Column int + // Type is the type of the error that occurred, such as SyntaxError or InternalError. + Type ErrorType + // Severity represents different severity levels. + Severity ErrorSeverity + // Cause is an optional, underlying error that caused the parsing error. + Cause error +} + +func (pe *ParsingError) Error() string { + if pe.Cause != nil { + return fmt.Sprintf("%d-%d %s: %s", pe.Line, pe.Column, pe.Message, pe.Cause) + } + return fmt.Sprintf("%d-%d %s", pe.Line, pe.Column, pe.Message) +} + +func (pe *ParsingError) Unwrap() error { + return pe.Cause +} + +// invalidExpression is a placeholder that allows parsing to continue so any additional +// errors can be reported by the parser. +type invalidExpression struct { + *model.Expression + ParsingError *ParsingError +} + +// badExpression reports a parsing error and returns a placeholder allowing parsing to continue. +func (v visitor) badExpression(msg string, ctx antlr.ParserRuleContext) invalidExpression { + return invalidExpression{ + ParsingError: v.reportError(msg, ctx), + Expression: model.ResultType(types.Any), + } +} + +// invalidTypeSpecifier is a placeholder that allows parsing to continue so any additional +// errors can be reported by the parser. +type invalidTypeSpecifier struct { + types.System + ParsingError *ParsingError +} + +// badTypeSpecifier reports a parsing error and returns a placeholder allowing parsing to continue. +func (v visitor) badTypeSpecifier(msg string, ctx antlr.ParserRuleContext) invalidTypeSpecifier { + return invalidTypeSpecifier{ + ParsingError: v.reportError(msg, ctx), + System: types.Any, + } +} + +// reportError reports an error within the visitor, and returns the ParsingError that may or may not +// be of use to the caller. +func (v *visitor) reportError(msg string, ctx antlr.ParserRuleContext) *ParsingError { + pe := &ParsingError{ + Message: msg, + Line: ctx.GetStart().GetLine(), + Column: ctx.GetStart().GetColumn(), + } + v.errors.Append(pe) + return pe +} + +// SyntaxError is called by ANTLR generated code the CQL does not meet the grammar. +func (v visitor) SyntaxError(recognizer antlr.Recognizer, offendingSymbol any, line, column int, + msg string, e antlr.RecognitionException) { + v.errors.Append(&ParsingError{Message: msg, Line: line, Column: column}) +} + +// These Report* functions needed to implement the error listener interface, but there is +// no action to be taken when parsing the CQL grammar so they do nothing. +func (v visitor) ReportAmbiguity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, + stopIndex int, exact bool, ambigAlts *antlr.BitSet, configs *antlr.ATNConfigSet) { + // Intentional +} + +func (v visitor) ReportAttemptingFullContext(recognizer antlr.Parser, dfa *antlr.DFA, + startIndex, stopIndex int, conflictingAlts *antlr.BitSet, configs *antlr.ATNConfigSet) { + // Intentional +} + +func (v visitor) ReportContextSensitivity(recognizer antlr.Parser, dfa *antlr.DFA, + startIndex, stopIndex, prediction int, configs *antlr.ATNConfigSet) { + // Intentional +} diff --git a/parser/errors_test.go b/parser/errors_test.go new file mode 100644 index 0000000..a759664 --- /dev/null +++ b/parser/errors_test.go @@ -0,0 +1,131 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "errors" + "testing" + + "github.com/google/cql/result" +) + +var ErrorBadFile = errors.New("bad file") + +func TestLibraryErrors(t *testing.T) { + // Create a LibraryError + libErr := &LibraryErrors{LibKey: result.LibKey{Name: "TESTLIB", Version: "1.0.0"}} + libErr.Append(&ParsingError{ + Message: "first parsing error", + Line: 1, + Column: 1, + Type: InternalError, + Severity: ErrorSeverityError, + }) + libErr.Append(&ParsingError{ + Message: "second parsing error", + Line: 2, + Column: 2, + Type: InternalError, + Severity: ErrorSeverityError, + Cause: ErrorBadFile, + }) + + // Make the library error a generic error + var gotErr error + gotErr = libErr + + // Can access LibraryErrors with errors.As + var wantLibErr *LibraryErrors + ok := errors.As(gotErr, &wantLibErr) + if !ok { + t.Errorf("errors.As(*LibraryErrors) = false, want true") + } + + // Unwraps the first parsing error + var wantParsingErr *ParsingError + ok = errors.As(gotErr, &wantParsingErr) + if !ok { + t.Errorf("errors.As(*ParsingErrors) = false, want true") + } + if wantParsingErr.Message != "first parsing error" { + t.Errorf("wantParsingErr.Message = %q, want %q", wantParsingErr.Message, "first parsing error") + } + + // Is unwraps all errors in the error tree + ok = errors.Is(gotErr, ErrorBadFile) + if !ok { + t.Errorf("errors.Is(gotErr, ErrorBadFile) = false, want true") + } + + wantErrorString := `1-1 first parsing error +2-2 second parsing error: bad file` + if gotErr.Error() != wantErrorString { + t.Errorf("gotErr.Error() = %q, want %q", gotErr.Error(), wantErrorString) + } +} + +func TestParameterErrors(t *testing.T) { + // Create a ParameterError + paramErr := &ParameterErrors{DefKey: result.DefKey{Name: "TESTDEF", Library: result.LibKey{Name: "TESTLIB", Version: "1.0.0"}}} + paramErr.Append(&ParsingError{ + Message: "first parsing error", + Line: 1, + Column: 1, + Type: InternalError, + Severity: ErrorSeverityError, + Cause: errors.New("first cause"), + }) + paramErr.Append(&ParsingError{ + Message: "second parsing error", + Line: 2, + Column: 2, + Type: InternalError, + Severity: ErrorSeverityError, + Cause: ErrorBadFile, + }) + + // Make the parameter error a generic error + var gotErr error + gotErr = paramErr + + // Can access ParameterErrors with errors.As + var wantParamErr *ParameterErrors + ok := errors.As(gotErr, &wantParamErr) + if !ok { + t.Errorf("errors.As(*ParameterErrors) = false, want true") + } + + // Unwraps the first parsing error + var wantParsingErr *ParsingError + ok = errors.As(gotErr, &wantParsingErr) + if !ok { + t.Errorf("errors.As(*ParsingErrors) = false, want true") + } + if wantParsingErr.Message != "first parsing error" { + t.Errorf("wantParsingErr.Message = %q, want %q", wantParsingErr.Message, "first parsing error") + } + + // Is unwraps all errors in the error tree + ok = errors.Is(gotErr, ErrorBadFile) + if !ok { + t.Errorf("errors.Is(gotErr, ErrorBadFile) = false, want true") + } + + wantErrorString := `1-1 first parsing error: first cause +2-2 second parsing error: bad file` + if gotErr.Error() != wantErrorString { + t.Errorf("gotErr.Error() = %q, want %q", gotErr.Error(), wantErrorString) + } +} diff --git a/parser/expressions.go b/parser/expressions.go new file mode 100644 index 0000000..d1adb1d --- /dev/null +++ b/parser/expressions.go @@ -0,0 +1,1175 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/google/cql/internal/convert" + "github.com/google/cql/internal/datehelpers" + "github.com/google/cql/internal/embeddata/third_party/cqframework/cql" + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/antlr4-go/antlr/v4" +) + +func (v *visitor) VisitExpression(tree antlr.Tree) model.IExpression { + // Manual dispatch of needed due to + // https://github.com/antlr/antlr4/issues/2504 + + var m model.IExpression + switch t := tree.(type) { + case *cql.QualifiedIdentifierExpressionContext: + m = v.VisitQualifiedIdentifierExpression(t) + case *cql.RetrieveContext: + m = v.VisitRetrieve(t) + case *cql.RetrieveExpressionContext: + m = v.VisitRetrieveExpression(t) + case *cql.TypeExpressionContext: + m = v.VisitTypeExpression(t) + case *cql.CastExpressionContext: + m = v.VisitCastExpression(t) + case *cql.BooleanExpressionContext: + m = v.VisitBooleanExpressionContext(t) + case *cql.MembershipExpressionContext: + m = v.VisitMembershipExpression(t) + case *cql.ReferentialIdentifierContext: + m = v.VisitReferentialIdentifier(t) + case *cql.FunctionContext: + m = v.VisitFunction(t) + case *cql.IfThenElseExpressionTermContext: + m = v.VisitIfThenElseExpression(t) + case *cql.IntervalSelectorTermContext: + m = v.VisitIntervalSelectorTerm(t) + case *cql.ListSelectorTermContext: + m = v.VisitListSelectorTerm(t) + case *cql.CodeSelectorTermContext: + m = v.VisitCodeSelectorTerm(t) + case *cql.SimpleStringLiteralContext: + m = buildLiteral(unquoteString(t.GetText()), types.String) + case *cql.SimpleNumberLiteralContext: + m = buildNumberLiteral(t.GetText()) + case *cql.SimpleLiteralContext: + m = v.VisitSimpleLiteral(t) + case *cql.LiteralTermContext: + m = v.VisitLiteralTerm(t) + case *cql.TimeBoundaryExpressionTermContext: + m = v.VisitTimeBoundaryExpressionTerm(t) + case *cql.ExistenceExpressionContext: + m = v.VisitExistenceExpression(t) + case *cql.ParenthesizedTermContext: + m = v.VisitParenthesizedTerm(t) + case *cql.QuerySourceContext: + m = v.VisitQuerySource(t) + case *cql.QueryContext: + m = v.VisitQuery(t) + case *cql.EqualityExpressionContext: + m = v.VisitEqualityExpression(t) + case *cql.InequalityExpressionContext: + m = v.VisitInequalityExpression(t) + case *cql.DifferenceBetweenExpressionContext: + m = v.VisitDifferenceBetweenExpression(t) + case *cql.InvocationExpressionTermContext: + m = v.VisitInvocationExpressionTerm(t) + case *cql.TimingExpressionContext: + m = v.VisitTimingExpression(t) + case *cql.AndExpressionContext: + m = v.VisitAndExpression(t) + case *cql.OrExpressionContext: + m = v.VisitOrExpression(t) + case *cql.ImpliesExpressionContext: + m = v.VisitImpliesExpression(t) + case *cql.InFixSetExpressionContext: + m = v.VisitInFixSetExpression(t) + case *cql.AdditionExpressionTermContext: + m = v.VisitAdditionExpressionTerm(t) + case *cql.MultiplicationExpressionTermContext: + m = v.VisitMultiplicationExpressionTerm(t) + case *cql.TimeUnitExpressionTermContext: + m = v.VisitTimeUnitExpressionTerm(t) + case *cql.TupleSelectorTermContext: + m = v.VisitTupleSelectorTerm(t) + case *cql.InstanceSelectorTermContext: + m = v.VisitInstanceSelectorTerm(t) + case *cql.CaseExpressionTermContext: + m = v.VisitCaseExpressionTerm(t) + case *cql.NotExpressionContext: + m = v.VisitNotExpression(t) + case *cql.PolarityExpressionTermContext: + m = v.VisitPolarityExpressionTerm(t) + case *cql.PredecessorExpressionTermContext: + m = v.VisitPredecessorExpressionTerm(t) + case *cql.SuccessorExpressionTermContext: + m = v.VisitSuccessorExpressionTerm(t) + case *cql.TypeExtentExpressionTermContext: + m = v.VisitTypeExtentExpressionTermContext(t) + case *cql.ElementExtractorExpressionTermContext: + m = v.VisitElementExtractorExpressionTerm(t) + + // All cases that have a single child and recurse to the child are handled below. For example in + // the CQL grammar the only child of QueryExpression is Query, so QueryExpression can be handled + // by recursing on it's only child. + case *cql.StatementContext, + *cql.DefinitionContext, + *cql.TermExpressionTermContext, + *cql.TermExpressionContext, + *cql.QueryExpressionContext, + *cql.InvocationTermContext, + *cql.MemberInvocationContext, + *cql.FunctionInvocationContext: + m = v.VisitExpression(tree.GetChild(0)) + default: + // Line and Column are not available for unsupported expressions. + pe := &ParsingError{Message: fmt.Sprintf("Internal Error - unsupported expression: %#v", tree)} + v.errors.Append(pe) + return invalidExpression{ParsingError: pe, Expression: model.ResultType(types.Any)} + } + + if m.GetResultType() == types.Unset { + // Line and Column are not available. + pe := &ParsingError{Message: fmt.Sprintf("Internal Error - Model Expression ResultType not set: %#v", m)} + v.errors.Append(pe) + return invalidExpression{ParsingError: pe, Expression: model.ResultType(types.Any)} + } + + return m +} + +// parseSTRING removes surrounding quotes from a STRING node that was produced using +// a call to `STRING()`. Grammar defined at https://cql.hl7.org/19-l-cqlsyntaxdiagrams.html#STRING. +func parseSTRING(n antlr.TerminalNode) string { + s := n.GetText() + + // TODO(b/302003569): strings should also be unescaped. + // CQL escaping rules do not match golang's so `strconv.Unquote()` cannot be used here. + return s[1 : len(s)-1] +} + +func (v *visitor) VisitParenthesizedTerm(ctx *cql.ParenthesizedTermContext) model.IExpression { + return v.VisitExpression(ctx.GetChild(1)) +} + +func (v *visitor) VisitTimeUnitExpressionTerm(ctx *cql.TimeUnitExpressionTermContext) model.IExpression { + // parses statements like: "date from expression" + // TODO: b/301606416 - Implement time units where left is dateTimePrecision. + dtc := ctx.GetChild(0).(*cql.DateTimeComponentContext) + switch component := dtc.GetChild(0).(type) { + case antlr.TerminalNode: + dateTimeComponent := component.GetText() + switch dateTimeComponent { + case "date": + return &model.ToDate{ + UnaryExpression: &model.UnaryExpression{ + Operand: v.VisitExpression(ctx.ExpressionTerm()), + Expression: model.ResultType(types.Date), + }, + } + } + } + return v.badExpression(fmt.Sprintf("unsupported date time component conversion (e.g. X in 'X from expression'). got: %s, only %v supported", dtc.GetText(), "date"), ctx) +} + +func (v *visitor) VisitTupleSelectorTerm(ctx *cql.TupleSelectorTermContext) model.IExpression { + tModel := &model.Tuple{} + tResult := &types.Tuple{ElementTypes: make(map[string]types.IType)} + for _, tes := range ctx.TupleSelector().AllTupleElementSelector() { + elem := &model.TupleElement{ + Name: v.parseReferentialIdentifier(tes.ReferentialIdentifier()), + Value: v.VisitExpression(tes.Expression()), + } + tModel.Elements = append(tModel.Elements, elem) + tResult.ElementTypes[elem.Name] = elem.Value.GetResultType() + } + tModel.Expression = model.ResultType(tResult) + return tModel +} + +func (v *visitor) VisitInstanceSelectorTerm(ctx *cql.InstanceSelectorTermContext) model.IExpression { + classType := v.VisitNamedTypeSpecifier(ctx.InstanceSelector().NamedTypeSpecifier()) + i := &model.Instance{ + Expression: model.ResultType(classType), + ClassType: classType, + } + + for _, ies := range ctx.InstanceSelector().AllInstanceElementSelector() { + name := v.parseReferentialIdentifier(ies.ReferentialIdentifier()) + value := v.VisitExpression(ies.Expression()) + + // Validate instance element against modelinfo and try to implicitly convert if needed. + miType, err := v.modelInfo.PropertyTypeSpecifier(classType, name) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + res, err := convert.OperandImplicitConverter(value.GetResultType(), miType, value, v.modelInfo) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + if !res.Matched { + return v.badExpression(fmt.Sprintf("element %q in %v should be implicitly convertible to type %v, but instead received type %v", name, classType, miType, value.GetResultType()), ctx) + } + + i.Elements = append(i.Elements, &model.InstanceElement{Name: name, Value: res.WrappedOperand}) + } + + return i +} + +// TODO: b/324580831 - Possibly add functional definition support for MaxValue and MinValue. +func (v *visitor) VisitTypeExtentExpressionTermContext(ctx *cql.TypeExtentExpressionTermContext) model.IExpression { + valueType := v.VisitNamedTypeSpecifier(ctx.NamedTypeSpecifier()) + switch valueType { + case types.Integer, + types.Long, + types.Decimal, + types.Quantity, + types.Date, + types.DateTime, + types.Time: + // Valid cases. + default: + return v.badExpression(fmt.Sprintf("unsupported type for %s expression: %s", ctx.GetText(), valueType.String()), ctx) + } + + t := ctx.GetText() + if strings.HasPrefix(t, "maximum") { + return &model.MaxValue{ValueType: valueType, Expression: model.ResultType(valueType)} + } else if strings.HasPrefix(t, "minimum") { + return &model.MinValue{ValueType: valueType, Expression: model.ResultType(valueType)} + } + return v.badExpression(fmt.Sprintf("unsupported type extent expression: %v", t), ctx) +} + +func (v *visitor) VisitIfThenElseExpression(ctx *cql.IfThenElseExpressionTermContext) model.IExpression { + // Children are ordered as: if, expr, then, expr, else, expr + cnd := v.VisitExpression(ctx.Expression(0)) + inferredCnd, err := convert.OperandImplicitConverter(cnd.GetResultType(), types.Boolean, cnd, v.modelInfo) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + if !inferredCnd.Matched { + return v.badExpression(fmt.Sprintf("could not implicitly convert %v to a %v", cnd.GetResultType(), types.Boolean), ctx) + } + thn := v.VisitExpression(ctx.Expression(1)) + els := v.VisitExpression(ctx.Expression(2)) + i, err := convert.InferMixed([]model.IExpression{thn, els}, v.modelInfo) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + return &model.IfThenElse{ + Condition: inferredCnd.WrappedOperand, + Then: i.WrappedOperands[0], + Else: i.WrappedOperands[1], + Expression: model.ResultType(i.UniformType), + } +} + +func (v *visitor) VisitSimpleLiteral(ctx *cql.SimpleLiteralContext) model.IExpression { + switch ctx.GetChild(0).(type) { + case *cql.SimpleStringLiteralContext: + return buildLiteral(ctx.GetText(), types.String) + case *cql.SimpleNumberLiteralContext: + return buildNumberLiteral(ctx.GetText()) + default: + return v.badExpression("internal error - grammar should never let us reach this point of VisitSimpleLiteral", ctx) + } +} + +func (v *visitor) VisitLiteralTerm(ctx *cql.LiteralTermContext) model.IExpression { + val := ctx.GetText() + switch t := ctx.GetChild(0).(type) { + case *cql.RatioLiteralContext: + return v.VisitRatioLiteral(t) + // Quantities are a special literal term that evaluate to an expression. + case *cql.QuantityLiteralContext: + q, err := v.VisitQuantityContext(t.Quantity()) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return &q + case *cql.NumberLiteralContext: + return buildNumberLiteral(val) + case *cql.StringLiteralContext: + return buildLiteral(unquoteString(val), types.String) + case *cql.BooleanLiteralContext: + return buildLiteral(val, types.Boolean) + case *cql.LongNumberLiteralContext: + return buildLiteral(val, types.Long) + case *cql.NullLiteralContext: + return buildLiteral(val, types.Any) + case *cql.DateTimeLiteralContext: + _, _, err := datehelpers.ParseDateTime(val, time.UTC) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return buildLiteral(val, types.DateTime) + case *cql.DateLiteralContext: + _, _, err := datehelpers.ParseDate(val, time.UTC) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return buildLiteral(val, types.Date) + case *cql.TimeLiteralContext: + _, _, err := datehelpers.ParseTime(val, time.UTC) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return buildLiteral(val, types.Time) + default: + return v.badExpression("internal error - grammar should never let us reach this point of VisitLiteralTerm", ctx) + } +} + +func buildNumberLiteral(val string) *model.Literal { + t := types.Integer + if strings.Contains(val, ".") { + t = types.Decimal + } + return buildLiteral(val, t) +} + +func buildLiteral(val string, t types.System) *model.Literal { + return &model.Literal{Value: val, Expression: model.ResultType(t)} +} + +func (v *visitor) VisitRatioLiteral(ctx *cql.RatioLiteralContext) model.IExpression { + quantities := ctx.Ratio().AllQuantity() + if len(quantities) != 2 { + return v.badExpression("", ctx) + } + + numerator, err := v.VisitQuantityContext(quantities[0]) + if err != nil { + return v.badExpression(fmt.Sprintf("internal error - unable to parse ratio literal, got invalid numerator, %s with error: %s", quantities[0], err.Error()), ctx) + } + denominator, err := v.VisitQuantityContext(quantities[1]) + if err != nil { + return v.badExpression(fmt.Sprintf("internal error - unable to parse ratio literal, got invalid denominator, %s with error: %s", quantities[1], err.Error()), ctx) + } + + return &model.Ratio{Numerator: numerator, Denominator: denominator, Expression: model.ResultType(types.Ratio)} +} + +// TODO(b/319155752) Add support for validating UCUM units. Only temporal values are supported for now. +func (v *visitor) VisitQuantityContext(ctx cql.IQuantityContext) (model.Quantity, error) { + numberContext := ctx.NUMBER() + unitContext := ctx.Unit() + + d, err := strconv.ParseFloat(numberContext.GetText(), 64) + if err != nil { + return model.Quantity{}, fmt.Errorf("internal error - unable to parse quantity numeral err: %v, value: %s", err, numberContext.GetText()) + } + + if unitContext == nil { + return model.Quantity{Value: d, Unit: model.ONEUNIT, Expression: model.ResultType(types.Quantity)}, nil + } + if unitContext.DateTimePrecision() != nil { + rs := unitContext.DateTimePrecision().GetText() + u := stringToTimeUnit(rs) + if u == model.UNSETUNIT { + return model.Quantity{}, fmt.Errorf("internal error - invalid quantity unit when parsing quantity, got: %s", unitContext.GetText()) + } + return model.Quantity{Value: d, Unit: u, Expression: model.ResultType(types.Quantity)}, nil + } + if unitContext.PluralDateTimePrecision() != nil { + rs := pluralToSingularDateTimePrecision(unitContext.PluralDateTimePrecision().GetText()) + u := stringToTimeUnit(rs) + if u == model.UNSETUNIT { + return model.Quantity{}, fmt.Errorf("internal error - invalid quantity unit when parsing quantity, got: %s", unitContext.GetText()) + } + return model.Quantity{Value: d, Unit: u, Expression: model.ResultType(types.Quantity)}, nil + } + fmt.Printf("Warning, parser found a quantity literal declaration with a UCUM unit value, these are not currently validated. got unit: %q", unitContext.STRING()) + return model.Quantity{Value: d, Unit: model.Unit(parseSTRING(unitContext.STRING())), Expression: model.ResultType(types.Quantity)}, nil +} + +// VisitReferentialIdentifier handles ReferentialIdentifiers. ReferentialIdentifiers are used +// throughout the CQL grammar as either a reference to a local definition or a definition in another +// CQL library. A reference to another library (ex libraryName.defName) will commonly be represented +// as (referentialIdentifier.referentialIdenfitier) in the CQL grammar. VisitReferentialIdentifier +// only handles local references returning returning model.XXXRef. If this is the local identifier +// of an included library then an error is returned as that should be handled in the calling +// visitor. +func (v *visitor) VisitReferentialIdentifier(ctx cql.IReferentialIdentifierContext) model.IExpression { + name := v.parseReferentialIdentifier(ctx) + if i := v.refs.ResolveInclude(name); i != nil { + return v.badExpression(fmt.Sprintf("internal error - referential identifier %v is a local identifier to an included library", name), ctx) + } + + modelFunc, err := v.refs.ResolveLocal(name) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return modelFunc() +} + +func (v *visitor) parseReferentialIdentifier(ctx cql.IReferentialIdentifierContext) string { + if ctx.Identifier() != nil { + return v.VisitIdentifier(ctx.Identifier()) + } + // Unsure why the CQL grammar splits this into Identifier/KeywordIdentifier. + return ctx.KeywordIdentifier().GetText() +} + +func (v *visitor) parseIdentifierOrFuntionIdentifier(ctx cql.IIdentifierOrFunctionIdentifierContext) string { + if ctx.Identifier() != nil { + return v.VisitIdentifier(ctx.Identifier()) + } + // Unsure why the CQL grammar splits this into Identifier/FunctionIdentifier. + return ctx.FunctionIdentifier().GetText() +} + +// VisitQualifiedIdentifierExpression handles QualifiedIdentifierExpressions, which are references +// to expressions that are defined either locally or in an included CQL library. +func (v *visitor) VisitQualifiedIdentifierExpression(ctx *cql.QualifiedIdentifierExpressionContext) model.IExpression { + // Parse the series of identifiers. + ids := []string{} + for _, q := range ctx.AllQualifierExpression() { + ids = append(ids, v.parseReferentialIdentifier(q.ReferentialIdentifier())) + } + ids = append(ids, v.parseReferentialIdentifier(ctx.ReferentialIdentifier())) + + if len(ids) == 1 { + // This must be a reference to a local identifier. + modelFunc, err := v.refs.ResolveLocal(ids[0]) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return modelFunc() + } + + var ref model.IExpression + var i int + var err error + lib := v.refs.ResolveInclude(ids[0]) + if lib != nil { + // This is a reference to a global expression where ids[0] is the included library identifier + // and ids[1] is the expression definition in the included library. + i = 2 + ref, err = v.resolveGlobalRef(lib.Local, v.parseReferentialIdentifier(ctx.ReferentialIdentifier())) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + } else { + // ids[0] is a reference to a local identifier. + i = 1 + modelFunc, err := v.refs.ResolveLocal(ids[0]) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + ref = modelFunc() + } + + // Any remaining identifiers are properties. Repeatedly wrap in model.Property for the remaining + // identifiers. + for _, id := range ids[i:] { + p := &model.Property{ + Source: ref, + Path: id, + } + + if p.Source.GetResultType() != nil { + propertyType, err := v.modelInfo.PropertyTypeSpecifier(p.Source.GetResultType(), p.Path) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + p.Expression = model.ResultType(propertyType) + } + ref = p + } + return ref +} + +func (v *visitor) resolveGlobalRef(libName, defName string) (model.IExpression, error) { + modelFunc, err := v.refs.ResolveGlobal(libName, defName) + if err != nil { + return nil, err + } + + switch typedM := modelFunc().(type) { + case *model.CodeRef: + typedM.LibraryName = libName + return typedM, nil + case *model.CodeSystemRef: + typedM.LibraryName = libName + return typedM, nil + case *model.ConceptRef: + typedM.LibraryName = libName + return typedM, nil + case *model.ParameterRef: + typedM.LibraryName = libName + return typedM, nil + case *model.ExpressionRef: + typedM.LibraryName = libName + return typedM, nil + case *model.ValuesetRef: + typedM.LibraryName = libName + return typedM, nil + } + + return nil, fmt.Errorf("internal error - global reference %s.%s is not a supported reference type", libName, defName) +} + +// VisitQualifiedIdentifier handles QualifiedIdentifiers, which are only used as the full qualified +// library name in library definitions or includes. +func (v *visitor) VisitQualifiedIdentifier(ctx cql.IQualifiedIdentifierContext) []string { + var ids []string + for _, c := range ctx.AllQualifier() { + id := v.VisitIdentifier(c.Identifier()) + ids = append(ids, id) + } + ids = append(ids, v.VisitIdentifier(ctx.Identifier())) + return ids +} + +func (v *visitor) VisitIntervalSelectorTerm(ctx *cql.IntervalSelectorTermContext) model.IExpression { + ictx := ctx.GetChild(0).(*cql.IntervalSelectorContext) + l := v.VisitExpression(ictx.Expression(0)) + h := v.VisitExpression(ictx.Expression(1)) + + // These are the supported interval types, which are based on the overloads for Successor and + // Predecessor operators. Low and high must be implicitly convertible to one of these types or an + // error is returned. + declared := [][]types.IType{ + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Long, types.Long}, + []types.IType{types.Decimal, types.Decimal}, + []types.IType{types.Quantity, types.Quantity}, + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}} + overloads := []convert.Overload[func() *model.Interval]{} + for _, o := range declared { + overload := convert.Overload[func() *model.Interval]{ + Operands: o, + Result: func() *model.Interval { + return &model.Interval{} + }, + } + overloads = append(overloads, overload) + } + + matched, err := convert.OverloadMatch([]model.IExpression{l, h}, overloads, v.modelInfo, "Interval") + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + interval := matched.Result() + interval.Low = matched.WrappedOperands[0] + interval.High = matched.WrappedOperands[1] + interval.Expression = model.ResultType(&types.Interval{PointType: interval.Low.GetResultType()}) + + if ictx.GetChild(1).(*antlr.TerminalNodeImpl).GetText() == "[" { + interval.LowInclusive = true + } + if ictx.GetChild(ictx.GetChildCount()-1).(*antlr.TerminalNodeImpl).GetText() == "]" { + interval.HighInclusive = true + } + + return interval +} + +func (v *visitor) VisitListSelectorTerm(ctx *cql.ListSelectorTermContext) model.IExpression { + lctx := ctx.GetChild(0).(*cql.ListSelectorContext) + + var listElemType types.IType + if lctx.TypeSpecifier() != nil { + listElemType = v.VisitTypeSpecifier(lctx.TypeSpecifier()) + } + + var elemExp []model.IExpression + for _, exp := range lctx.AllExpression() { + elemExp = append(elemExp, v.VisitExpression(exp)) + } + + // Empty list + if len(elemExp) == 0 { + if lctx.TypeSpecifier() == nil { + listElemType = types.Any + } + return &model.List{List: []model.IExpression{}, Expression: model.ResultType(&types.List{ElementType: listElemType})} + } + + // There is a List Type Specifier + if lctx.TypeSpecifier() != nil { + m := &model.List{Expression: model.ResultType(&types.List{ElementType: listElemType})} + for _, exp := range elemExp { + // Attempt to convert each list element to the type specifier type. + converted, err := convert.OperandImplicitConverter(exp.GetResultType(), listElemType, exp, v.modelInfo) + if err != nil { + return v.badExpression(fmt.Sprintf("internal error - converting the list element to the List type specifier: %v", err), lctx) + } + if !converted.Matched { + return v.badExpression(fmt.Sprintf("unable to convert list element (%v) to the declared List type specifier element type (%v)", exp.GetResultType(), listElemType), lctx) + } + m.List = append(m.List, converted.WrappedOperand) + } + return m + } + + // No List Type Specifier, try to convert all the elements to a common type, otherwise punt to + // Choice. + inferred, err := convert.InferMixed(elemExp, v.modelInfo) + if err != nil { + return v.badExpression(fmt.Sprintf("internal error - inferring the uniform type of a list literal: %v", err), lctx) + } + return &model.List{ + Expression: model.ResultType(&types.List{ElementType: inferred.UniformType}), + List: inferred.WrappedOperands, + } +} + +func (v *visitor) VisitCodeSelectorTerm(ctx *cql.CodeSelectorTermContext) *model.Code { + c := &model.Code{ + Code: parseSTRING(ctx.CodeSelector().STRING()), + System: v.parseCodeSystemIdentifier(ctx.CodeSelector().CodesystemIdentifier()), + Expression: model.ResultType(types.Code), + } + if ctx.CodeSelector().DisplayClause() != nil { + c.Display = parseSTRING(ctx.CodeSelector().DisplayClause().STRING()) + } + return c +} + +func (v *visitor) VisitTypeSpecifier(ctx cql.ITypeSpecifierContext) types.IType { + if ctx == nil { + return nil + } + + switch { + case ctx.NamedTypeSpecifier() != nil: + return v.VisitNamedTypeSpecifier(ctx.NamedTypeSpecifier()) + case ctx.ListTypeSpecifier() != nil: + return v.VisitListTypeSpecifier(ctx.ListTypeSpecifier()) + case ctx.IntervalTypeSpecifier() != nil: + return v.VisitIntervalTypeSpecifier(ctx.IntervalTypeSpecifier()) + case ctx.ChoiceTypeSpecifier() != nil: + return v.VisitChoiceTypeSpecifier(ctx.ChoiceTypeSpecifier()) + case ctx.TupleTypeSpecifier() != nil: + return v.VisitTupleTypeSpecifier(ctx.TupleTypeSpecifier()) + default: + return v.badTypeSpecifier("internal error - grammar should never let us reach this point of VisitTypeSpecifier", ctx) + } +} + +func (v *visitor) VisitNamedTypeSpecifier(ctx cql.INamedTypeSpecifierContext) types.IType { + // Construct the string type. + var ids []string + for _, c := range ctx.AllQualifier() { + id := v.VisitIdentifier(c.Identifier()) + ids = append(ids, id) + } + ref := ctx.ReferentialOrTypeNameIdentifier() + if ref.TypeNameIdentifier() != nil { + ids = append(ids, ref.TypeNameIdentifier().GetText()) + } else if ref.ReferentialIdentifier().Identifier() != nil { + ids = append(ids, v.VisitIdentifier(ref.ReferentialIdentifier().Identifier())) + } else if ref.ReferentialIdentifier().KeywordIdentifier() != nil { + ids = append(ids, ref.ReferentialIdentifier().KeywordIdentifier().GetText()) + } + + strType := strings.Join(ids, ".") + + // Convert the string type to an types.IType and validate it is a correct type. + sys := types.ToSystem(strType) + if !sys.Equal(types.Unset) { + return sys + } + + named, err := v.modelInfo.ToNamed(strType) + if err != nil { + return v.badTypeSpecifier(err.Error(), ctx) + } + return named +} + +func (v *visitor) VisitListTypeSpecifier(ctx cql.IListTypeSpecifierContext) *types.List { + return &types.List{ + ElementType: v.VisitTypeSpecifier(ctx.TypeSpecifier()), + } +} + +func (v *visitor) VisitIntervalTypeSpecifier(ctx cql.IIntervalTypeSpecifierContext) *types.Interval { + return &types.Interval{ + PointType: v.VisitTypeSpecifier(ctx.TypeSpecifier()), + } +} + +func (v *visitor) VisitChoiceTypeSpecifier(ctx cql.IChoiceTypeSpecifierContext) *types.Choice { + c := &types.Choice{ChoiceTypes: []types.IType{}} + for _, t := range ctx.AllTypeSpecifier() { + c.ChoiceTypes = append(c.ChoiceTypes, v.VisitTypeSpecifier(t)) + } + return c +} + +func (v *visitor) VisitTupleTypeSpecifier(ctx cql.ITupleTypeSpecifierContext) *types.Tuple { + t := &types.Tuple{ElementTypes: map[string]types.IType{}} + for _, elem := range ctx.AllTupleElementDefinition() { + name := v.parseReferentialIdentifier(elem.ReferentialIdentifier()) + t.ElementTypes[name] = v.VisitTypeSpecifier(elem.TypeSpecifier()) + } + return t +} + +func (v *visitor) VisitVersionSpecifier(ctx cql.IVersionSpecifierContext) string { + if ctx == nil { + return "" + } + return parseSTRING(ctx.STRING()) +} + +func (v *visitor) VisitCaseExpressionTerm(ctx *cql.CaseExpressionTermContext) model.IExpression { + caseModel := &model.Case{} + + for _, ctxCaseItem := range ctx.AllCaseExpressionItem() { + caseItem := &model.CaseItem{ + When: v.VisitExpression(ctxCaseItem.Expression(0)), + Then: v.VisitExpression(ctxCaseItem.Expression(1)), + } + caseModel.CaseItem = append(caseModel.CaseItem, caseItem) + } + + expr := ctx.AllExpression() + var err error + if len(expr) == 1 { + // There is no comparand + caseModel.Else = v.VisitExpression(expr[0]) + caseModel, err = v.booleanWhen(caseModel) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + } else { + // There is a comparand + caseModel.Comparand = v.VisitExpression(expr[0]) + caseModel.Else = v.VisitExpression(expr[1]) + caseModel, err = v.uniformWhen(caseModel) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + } + + // The CaseItem.Whens are wrapped in necessary conversions. Now we need to wrap CaseItem.Then and + // Else. + mixed := make([]model.IExpression, 0, len(caseModel.CaseItem)) + for _, caseItem := range caseModel.CaseItem { + mixed = append(mixed, caseItem.Then) + } + mixed = append(mixed, caseModel.Else) + + uniform, err := convert.InferMixed(mixed, v.modelInfo) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + for i := 0; i < len(caseModel.CaseItem); i++ { + caseModel.CaseItem[i].Then = uniform.WrappedOperands[i] + } + caseModel.Else = uniform.WrappedOperands[len(uniform.WrappedOperands)-1] + caseModel.Expression = model.ResultType(uniform.UniformType) + + return caseModel +} + +// booleanWhen is for when there is no comparand. Each CaseItem.When must evaluate to a Boolean or +// something implicitly convertible to a Boolean. booleanWhen wraps each CaseItem.When in the +// necessary conversion or returns an error if a CaseItem.When is not convertible. +func (v *visitor) booleanWhen(caseModel *model.Case) (*model.Case, error) { + for i := 0; i < len(caseModel.CaseItem); i++ { + res, err := convert.OperandImplicitConverter(caseModel.CaseItem[i].When.GetResultType(), types.Boolean, caseModel.CaseItem[i].When, v.modelInfo) + if err != nil { + return nil, err + } + if !res.Matched { + return nil, fmt.Errorf("could not implicitly convert %v to a %v", caseModel.CaseItem[i].When.GetResultType(), types.Boolean) + } + caseModel.CaseItem[i].When = res.WrappedOperand + } + + return caseModel, nil +} + +// uniformWhen is for when the comparand will be compared against each CaseItem.When. The comparand +// and all CaseItem.When must be the same type, or implicitly convertible to the same type. +// uniformWhen wraps all CaseItem.When in implicit conversion so all CaseItem.When are a uniform +// type. If there is no uniform type an error is returned. +func (v *visitor) uniformWhen(caseModel *model.Case) (*model.Case, error) { + mixed := make([]model.IExpression, 0, len(caseModel.CaseItem)) + for _, caseItem := range caseModel.CaseItem { + mixed = append(mixed, caseItem.When) + } + mixed = append(mixed, caseModel.Comparand) + + uniform, err := convert.InferMixed(mixed, v.modelInfo) + if err != nil { + return nil, err + } + + if uniform.PuntedToChoice { + return nil, fmt.Errorf("could not implicitly convert then comparand %v and cases %v to the same type", caseModel.Comparand.GetResultType(), convert.OperandsToString(mixed[0:len(mixed)-1])) + } + + for i := 0; i < len(caseModel.CaseItem); i++ { + caseModel.CaseItem[i].When = uniform.WrappedOperands[i] + } + caseModel.Comparand = uniform.WrappedOperands[len(uniform.WrappedOperands)-1] + + return caseModel, nil +} + +func (v *visitor) VisitInvocationExpressionTerm(ctx *cql.InvocationExpressionTermContext) model.IExpression { + // InvocationExpressionTerms can either be + // 1) Referencing a public definition in an included library. In this case the ExpressionTerm is + // the local identifier of the included library and the QualifiedInvocation is the identifier + // of the definition. + // 2) Accessing the property of an expression. In this case the ExpressionTerm is either an + // expression or an identifier for an expression. + expr := ctx.ExpressionTerm() + + // Case 1, the ExpressionTerm is the local identifier of an included library. + lib := v.isIncludedLibrary(expr) + if lib != nil { + switch r := ctx.QualifiedInvocation().GetChild(0).(type) { + case *cql.ReferentialIdentifierContext: + m, err := v.resolveGlobalRef(lib.Local, v.parseReferentialIdentifier(r)) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m + case *cql.QualifiedFunctionContext: + return v.parseQualifiedFunction(r, lib.Local) + } + } + + switch r := ctx.QualifiedInvocation().GetChild(0).(type) { + case *cql.ReferentialIdentifierContext: + // Case 2, accessing the property of an expression. + p := &model.Property{ + Source: v.VisitExpression(expr), + Path: v.parseReferentialIdentifier(r), + } + + if p.Source.GetResultType() != nil { + propertyType, err := v.modelInfo.PropertyTypeSpecifier(p.Source.GetResultType(), p.Path) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + p.Expression = model.ResultType(propertyType) + } + return p + case *cql.QualifiedFunctionContext: + // Case 3, fluent functions + name := v.parseIdentifierOrFuntionIdentifier(r.IdentifierOrFunctionIdentifier()) + + // Prepend expr as the first argument to the function call. + params := []antlr.Tree{expr} + if r.ParamList() != nil { + for _, expr := range r.ParamList().AllExpression() { + params = append(params, expr) + } + } + // Note the grammar does not allow qualified fluent functions (ex + // Patient.active.FHIRHelpers.ToBoolean()) so you can never call a fluent function in another + // library. + m, err := v.parseFunction("", name, params, true) + if err != nil { + return v.badExpression(fmt.Errorf("%w (may not be a fluent function)", err).Error(), ctx) + } + + return m + } + + return v.badExpression("internal error - grammar should never reach this point of InvocationExpressionTerm", ctx) +} + +// isIncludedLibrary checks if the this is the local identifier to an included library, returning +// the model.LibraryIdentifier if it is. +func (v *visitor) isIncludedLibrary(expr cql.IExpressionTermContext) *model.LibraryIdentifier { + invoc, ok := expr.GetChild(0).(*cql.InvocationTermContext) + if ok { + memInvoc, ok := invoc.GetChild(0).(*cql.MemberInvocationContext) + if ok { + ref, ok := memInvoc.GetChild(0).(*cql.ReferentialIdentifierContext) + if ok { + name := v.parseReferentialIdentifier(ref) + if i := v.refs.ResolveInclude(name); i != nil { + return i + } + } + } + } + return nil +} + +// createRetrieve creates a retrieve struct for the given type populated with modelinfo metadata. +// The caller can then add additional filters to it as needed. +func (v *visitor) createRetrieve(resourceType string) (*model.Retrieve, error) { + namedType, err := v.modelInfo.ToNamed(resourceType) + if err != nil { + return nil, err + } + tInfo, err := v.modelInfo.NamedTypeInfo(namedType) + if err != nil { + return nil, err + } + url, err := v.modelInfo.URL() + if err != nil { + return nil, err + } + + if !tInfo.Retrievable { + return nil, fmt.Errorf("tried to retrieve type %s, but this type is not retrievable", namedType) + } + split := strings.Split(resourceType, ".") + unqualifiedName := split[len(split)-1] + r := &model.Retrieve{ + DataType: fmt.Sprintf("{%v}%v", url, unqualifiedName), + TemplateID: tInfo.Identifier, + CodeProperty: tInfo.PrimaryCodePath, + Expression: model.ResultType(&types.List{ElementType: namedType}), + } + + return r, nil +} + +func (v *visitor) VisitRetrieve(ctx *cql.RetrieveContext) model.IExpression { + typ := v.VisitNamedTypeSpecifier(ctx.NamedTypeSpecifier()) + namedType, ok := typ.(*types.Named) + if !ok { + return v.badExpression(fmt.Sprintf("retrieves cannot be performed on type %v", typ), ctx) + } + + r, err := v.createRetrieve(namedType.TypeName) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + t := ctx.Terminology() + if t != nil { + if t.Expression() != nil { + r.Codes = v.VisitExpression(t.Expression()) + } else if t.QualifiedIdentifierExpression() != nil { + r.Codes = v.VisitExpression(t.QualifiedIdentifierExpression()) + } + } + + return r +} + +func (v *visitor) VisitRetrieveExpression(ctx *cql.RetrieveExpressionContext) model.IExpression { + return v.VisitRetrieve(ctx.Retrieve().(*cql.RetrieveContext)) +} + +func (v *visitor) VisitTypeExpression(ctx *cql.TypeExpressionContext) model.IExpression { + // Although Is and As are unary system operators, they do not need to be included in + // loadSystemOperators(). This is because there is no overload matching, they work for any operand + // type. + if ctx.GetChild(1).(antlr.TerminalNode).GetText() == "is" { + return &model.Is{ + UnaryExpression: &model.UnaryExpression{ + Operand: v.VisitExpression(ctx.GetChild(0)), + Expression: model.ResultType(types.Boolean), + }, + IsTypeSpecifier: v.VisitTypeSpecifier(ctx.TypeSpecifier()), + } + } + + return &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: v.VisitExpression(ctx.Expression()), + Expression: model.ResultType(v.VisitTypeSpecifier(ctx.TypeSpecifier())), + }, + AsTypeSpecifier: v.VisitTypeSpecifier(ctx.TypeSpecifier()), + Strict: false, + } +} + +func (v *visitor) VisitCastExpression(ctx *cql.CastExpressionContext) model.IExpression { + // Although As is a unary system operator, it does not need to be included in + // loadSystemOperators(). This is because there is no overload matching, it works for any operand + // type. + asType := v.VisitTypeSpecifier(ctx.TypeSpecifier()) + return &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: v.VisitExpression(ctx.Expression()), + Expression: model.ResultType(asType), + }, + AsTypeSpecifier: asType, + Strict: true, + } +} + +func (v *visitor) VisitIdentifier(ctx cql.IIdentifierContext) string { + if ctx.IDENTIFIER() != nil { + return ctx.IDENTIFIER().GetText() + } + if ctx.QUOTEDIDENTIFIER() != nil || ctx.DELIMITEDIDENTIFIER() != nil { + return unquoteString(ctx.GetText()) + } + + v.reportError("Invalid identifier", ctx) + + return ctx.GetText() +} + +// maybeGetChildNode returns the first child of type T within children, otherwise returns the +// default return value and false. defaultReturn should typically be nil if T is a pointer, and +// exists to prevent an allocation inside maybeGetChildNode. +func maybeGetChildNode[T antlr.Tree](children []antlr.Tree, defaultReturn T) (T, bool) { + for _, c := range children { + if node, ok := c.(T); ok { + return node, true + } + } + return defaultReturn, false +} + +// PluralToSingularDateTimePrecision converts a plural time precision (years) to singular (year). +func pluralToSingularDateTimePrecision(pluralPrecision string) string { + return strings.TrimSuffix(pluralPrecision, "s") +} + +// precisionFromContext returns the string precision from the given context if available. +// Currently only accepts a cql.DateTimePrecisionSpecifierContext or cql.DateTimePrecisionContext. +// Otherwise returns `UNSETDATETIMEPRECISION` (an empty string). +func precisionFromContext(ctx antlr.ParserRuleContext) model.DateTimePrecision { + if n, ok := maybeGetChildNode[*cql.DateTimePrecisionSpecifierContext](ctx.GetChildren(), nil); ok { + return stringToPrecision(n.GetChild(0).(*cql.DateTimePrecisionContext).GetText()) + } else if n, ok := maybeGetChildNode[*cql.DateTimePrecisionContext](ctx.GetChildren(), nil); ok { + return stringToPrecision(n.GetText()) + } + return model.UNSETDATETIMEPRECISION +} + +func stringToPrecision(s string) model.DateTimePrecision { + switch s { + case "year": + return model.YEAR + case "month": + return model.MONTH + case "week": + return model.WEEK + case "day": + return model.DAY + case "hour": + return model.HOUR + case "minute": + return model.MINUTE + case "second": + return model.SECOND + case "millisecond": + return model.MILLISECOND + } + return model.UNSETDATETIMEPRECISION +} + +func dateTimePrecisions() []model.DateTimePrecision { + return []model.DateTimePrecision{ + model.YEAR, + model.MONTH, + model.WEEK, + model.DAY, + model.HOUR, + model.MINUTE, + model.SECOND, + model.MILLISECOND, + } +} + +// funcNameWithPrecision converts a model.DateTimePrecision to a string used in a function name ex +// AfterYears. +func funcNameWithPrecision(name string, p model.DateTimePrecision) string { + pStr := "" + switch p { + case model.YEAR: + pStr = "Years" + case model.MONTH: + pStr = "Months" + case model.WEEK: + pStr = "Weeks" + case model.DAY: + pStr = "Days" + case model.HOUR: + pStr = "Hours" + case model.MINUTE: + pStr = "Minutes" + case model.SECOND: + pStr = "Seconds" + case model.MILLISECOND: + pStr = "Milliseconds" + } + return fmt.Sprintf("%s%s", name, pStr) +} + +// stringToTimeUnit converts a string to a model.Unit for temporal values. +// TODO(b/319326228): move common date/datetime logic into a temporal package. +func stringToTimeUnit(s string) model.Unit { + switch s { + case "year": + return model.YEARUNIT + case "month": + return model.MONTHUNIT + case "week": + return model.WEEKUNIT + case "day": + return model.DAYUNIT + case "hour": + return model.HOURUNIT + case "minute": + return model.MINUTEUNIT + case "second": + return model.SECONDUNIT + case "millisecond": + return model.MILLISECONDUNIT + } + return model.UNSETUNIT +} + +// unquoteString takes the given CQL string, removes the surrounding ' and unescapes it. +// Escaped to character mapping: https://cql.hl7.org/03-developersguide.html#literals. +// TODO(b/302003569): properly unescaping unicode characters is not yet supported +func unquoteString(s string) string { + s = s[1 : len(s)-1] + for i := 0; i < len(s)-1; i++ { + quoted := s[i : i+2] + var replace string + switch quoted { + case `\'`: + replace = `'` + case `\"`: + replace = `"` + case "\\`": + replace = "`" + case `\r`: + replace = "\r" + case `\n`: + replace = "\n" + case `\t`: + replace = "\t" + case `\f`: + replace = "\f" + case `\\`: + replace = `\` + } + if replace != "" { + s = s[0:i] + replace + s[i+2:len(s)] + } + } + return s +} diff --git a/parser/expressions_test.go b/parser/expressions_test.go new file mode 100644 index 0000000..83944f0 --- /dev/null +++ b/parser/expressions_test.go @@ -0,0 +1,1385 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" +) + +func TestParserExpressions_Errors(t *testing.T) { + tests := []struct { + name string + cql string + errContains []string + errCount int + }{ + { + name: "Invalid CQL", + cql: "abcdefg", + errContains: []string{"could not resolve", "abcdefg"}, + errCount: 1, + }, + { + name: "Interval Selector High And Low Type Mismatch", + cql: `Interval[3.0, @2015-01-01)`, + errContains: []string{"could not resolve Interval(System.Decimal, System.Date)"}, + errCount: 1, + }, + { + name: "List Selector With Type Specifier No Matching Types", + cql: `List{'Hello', 5}`, + errContains: []string{"unable to convert list element (System.String) to the declared List type specifier element type (System.Long)"}, + errCount: 1, + }, + { + name: "Instance Selector not implicitly convertible", + cql: "Quantity {value: 'wrong type', unit: 'mg'}", + errContains: []string{`element "value" in System.Quantity should be implicitly convertible to type System.Decimal, but instead received type System.String`}, + errCount: 1, + }, + { + name: "Invalid Date", + cql: "@2015-99", + errContains: []string{`want a layout like @YYYY-MM-DD`}, + errCount: 1, + }, + { + name: "Invalid DateTime", + cql: "@2015-21-13T99", + errContains: []string{`want a layout like @YYYY-MM-DDThh:mm:ss.fff(Z|(+/-hh:mm)`}, + errCount: 1, + }, + { + name: "Invalid Time", + cql: "@T99", + errContains: []string{`want a layout like @Thh:mm:ss.fff`}, + errCount: 1, + }, + { + name: "Instance Selector incorrect field", + cql: "Quantity {bogusfield: 'wrong type', unit: 'mg'}", + errContains: []string{`property "bogusfield" not found in Parent Type "System.Quantity" property not found in data model`}, + errCount: 1, + }, + { + name: "Resource not retrievable", + cql: `[ObservationStatus]`, + errContains: []string{"tried to retrieve type Named, but this type is not retrievable"}, + errCount: 1, + }, + { + name: "Retrieve unknown type", + cql: `[randomtype]`, + errContains: []string{"not found in data model", "retrieves cannot be"}, + errCount: 2, + }, + { + name: "Case No Comparand When Not Boolean", + cql: ` + case + when 'Apple' then 4 + else 5 + end`, + errContains: []string{"could not implicitly convert System.String to a System.Boolean"}, + errCount: 1, + }, + { + name: "Case with Comparand not Convertible", + cql: ` + case 5 + when 'Apple' then 4 + else 5 + end`, + errContains: []string{"could not implicitly convert then comparand System.Integer and cases System.String to the same type"}, + errCount: 1, + }, + { + name: "If then statement with non boolean condition", + cql: `if 42 then 3 else 4`, + errContains: []string{"could not implicitly convert"}, + errCount: 1, + }, + { + name: "Where Clause Not Implicitly Convertible To Boolean", + cql: `({1, 2, 3}) P where P`, + errContains: []string{"result of a where clause"}, + errCount: 1, + }, + { + name: "maximum String", + cql: "maximum String", + errContains: []string{"unsupported type for maximumString"}, + errCount: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := newFHIRParser(t).Libraries(context.Background(), wrapInLib(t, test.cql), Config{}) + + if err == nil { + t.Fatal("Parse succeeded, wanted error") + } + + var pe *LibraryErrors + if ok := errors.As(err, &pe); ok { + for _, ec := range test.errContains { + if !strings.Contains(pe.Error(), ec) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", + pe.Error(), ec) + } + } + + if len(pe.Errors) != test.errCount { + t.Errorf("Returned error (%s) had (%d) errors but expected (%d)", + err.Error(), len(pe.Errors), test.errCount) + } + } else { + t.Errorf("Unexpected test error (%s).", err.Error()) + } + }) + } +} + +func TestParserExpressions(t *testing.T) { + tests := []struct { + name string + desc string + cql string + want model.IExpression + }{ + { + name: "Case No Comparand", + cql: ` + case + when true then 4 + else 5 + end`, + want: &model.Case{ + Comparand: nil, + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: model.NewLiteral("true", types.Boolean), + Then: model.NewLiteral("4", types.Integer), + }, + }, + Else: model.NewLiteral("5", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + { + name: "Case With Comparand", + cql: ` + case 4 + when 5 then 6 + else 7 + end`, + want: &model.Case{ + Comparand: model.NewLiteral("4", types.Integer), + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: model.NewLiteral("5", types.Integer), + Then: model.NewLiteral("6", types.Integer), + }, + }, + Else: model.NewLiteral("7", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + { + name: "Case With Comparand Implictly Converted", + cql: ` + case 5.2 + when 5 then 6 + else 7 + end`, + want: &model.Case{ + Comparand: model.NewLiteral("5.2", types.Decimal), + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("5", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + Then: model.NewLiteral("6", types.Integer), + }, + }, + Else: model.NewLiteral("7", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + { + name: "Case Then Implicitly Converted", + cql: ` + case 4 + when 5 then 6.0 + when 7 then 8 + else 9L + end`, + want: &model.Case{ + Comparand: model.NewLiteral("4", types.Integer), + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: model.NewLiteral("5", types.Integer), + Then: model.NewLiteral("6.0", types.Decimal), + }, + &model.CaseItem{ + When: model.NewLiteral("7", types.Integer), + Then: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("8", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + }, + }, + Else: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("9L", types.Long), + Expression: model.ResultType(types.Decimal), + }, + }, + Expression: model.ResultType(types.Decimal), + }, + }, + { + name: "Case Then Implicitly Converted to Choice", + cql: ` + case 4 + when 5 then 6.0 + when 7 then 'Apple' + else 9L + end`, + want: &model.Case{ + Comparand: model.NewLiteral("4", types.Integer), + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: model.NewLiteral("5", types.Integer), + Then: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("6.0", types.Decimal), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.String, types.Long}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.String, types.Long}}, + }, + }, + &model.CaseItem{ + When: model.NewLiteral("7", types.Integer), + Then: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("Apple", types.String), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.String, types.Long}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.String, types.Long}}, + }, + }, + }, + Else: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("9L", types.Long), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.String, types.Long}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.String, types.Long}}, + }, + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Decimal, types.String, types.Long}}), + }, + }, + { + name: "As", + cql: "15 as String", + want: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("15", types.Integer), + Expression: model.ResultType(types.String), + }, + AsTypeSpecifier: types.String, + Strict: false, + }, + }, + { + name: "Cast As", + cql: "cast 15 as String", + want: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("15", types.Integer), + Expression: model.ResultType(types.String), + }, + AsTypeSpecifier: types.String, + Strict: true, + }, + }, + { + name: "Is", + cql: "15 is String", + want: &model.Is{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("15", types.Integer), + Expression: model.ResultType(types.Boolean), + }, + IsTypeSpecifier: types.String, + }, + }, + { + name: "If Then Else", + cql: "if 1 = 2 then 3 else 4", + want: &model.IfThenElse{ + Condition: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + Then: model.NewLiteral("3", types.Integer), + Else: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + { + name: "If Then Else with implicit result types", + cql: "if 1 = 2 then 3.0 else 4", + want: &model.IfThenElse{ + Condition: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + Then: model.NewLiteral("3.0", types.Decimal), + Else: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + Expression: model.ResultType(types.Decimal), + }, + }, + { + name: "predecessor of 1", + cql: "predecessor of 1", + want: &model.Predecessor{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("1", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "successor of @2023", + cql: "successor of @2023", + want: &model.Successor{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("@2023", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + }, + { + name: "Interval Low Inclusive High Exlusive", + cql: "Interval[10, 20)", + want: &model.Interval{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: &types.Interval{PointType: types.Integer}}, + }, + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: true, + HighInclusive: false, + }, + }, + { + name: "Interval Low Inclusive High Inclusive", + cql: "Interval[10, 20]", + want: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: true, + HighInclusive: true, + }, + }, + { + name: "Interval Low Exclusive High Exclusive", + cql: "Interval(10, 20)", + want: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: false, + HighInclusive: false, + }, + }, + { + name: "Interval Low Exclusive High Inclusive", + cql: "Interval(10, 20]", + want: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: false, + HighInclusive: true, + }, + }, + { + name: "Interval Type Implicitly Converted", + cql: "Interval(10, null]", + want: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + }, + LowInclusive: false, + HighInclusive: true, + }, + }, + { + name: "Interval low property", + cql: "Interval(10, 20].low", + want: &model.Property{ + Source: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: false, + HighInclusive: true, + }, + Path: "low", + Expression: model.ResultType(types.Integer), + }, + }, + { + name: "Interval high property", + cql: "Interval(10, 20].high", + want: &model.Property{ + Source: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: false, + HighInclusive: true, + }, + Path: "high", + Expression: model.ResultType(types.Integer), + }, + }, + { + name: "Interval highClosed property", + cql: "Interval(10, 20].highClosed", + want: &model.Property{ + Source: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: false, + HighInclusive: true, + }, + Path: "highClosed", + Expression: model.ResultType(types.Boolean), + }, + }, + { + name: "Interval lowClosed property", + cql: "Interval(10, 20].lowClosed", + want: &model.Property{ + Source: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: false, + HighInclusive: true, + }, + Path: "lowClosed", + Expression: model.ResultType(types.Boolean), + }, + }, + { + name: "Quoted Property", + cql: `Interval(10, 20]."lowClosed"`, + want: &model.Property{ + Source: &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("10", types.Integer), + High: model.NewLiteral("20", types.Integer), + LowInclusive: false, + HighInclusive: true, + }, + Path: "lowClosed", + Expression: model.ResultType(types.Boolean), + }, + }, + { + name: "Quantity value property", + cql: "6'gm/cm3'.value", + want: &model.Property{ + Source: &model.Quantity{ + Value: 6, + Unit: "gm/cm3", + Expression: model.ResultType(types.Quantity), + }, + Path: "value", + Expression: model.ResultType(types.Decimal), + }, + }, + { + name: "Tuple property", + cql: "Tuple{foo: 4}.foo", + want: &model.Property{ + Source: &model.Tuple{ + Elements: []*model.TupleElement{&model.TupleElement{Name: "foo", Value: model.NewLiteral("4", types.Integer)}}, + Expression: model.ResultType(&types.Tuple{ElementTypes: map[string]types.IType{"foo": types.Integer}}), + }, + Path: "foo", + Expression: model.ResultType(types.Integer), + }, + }, + { + name: "ListSelector No Type Specifier", + cql: "{'final', 'amended', 'corrected'}", + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.String}), + List: []model.IExpression{ + model.NewLiteral("final", types.String), + model.NewLiteral("amended", types.String), + model.NewLiteral("corrected", types.String), + }, + }, + }, + { + name: "ListSelector No Type Specifier with implicit conversions", + cql: "{10L, 1L, 20, 30}", + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Long}), + List: []model.IExpression{ + model.NewLiteral("10L", types.Long), + model.NewLiteral("1L", types.Long), + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("20", types.Integer), + Expression: model.ResultType(types.Long), + }, + }, + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("30", types.Integer), + Expression: model.ResultType(types.Long), + }, + }, + }, + }, + }, + { + name: "ListSelector with Type Specifier", + cql: "List{1, 2, 3}", + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + model.NewLiteral("3", types.Integer), + }, + }, + }, + { + name: "ListSelector With Type Specifier requiring implicit conversions", + cql: "List{1, 2}", + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Long}), + List: []model.IExpression{ + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("1", types.Integer), + Expression: model.ResultType(types.Long), + }, + }, + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("2", types.Integer), + Expression: model.ResultType(types.Long), + }, + }, + }, + }, + }, + { + name: "ListSelector Empty List No TypeSpecifier", + cql: "{}", + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Any}), + List: []model.IExpression{}, + }, + }, + { + name: "ListSelector Empty with TypeSpecifier", + cql: "List{}", + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{}, + }, + }, + { + name: "ListSelector mixed", + cql: "{1, 'hi'}", + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}), + List: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}), + Operand: model.NewLiteral("1", types.Integer), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + }, + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}), + Operand: model.NewLiteral("hi", types.String), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + }, + }, + }, + }, + { + name: "Literal Int", + cql: "10", + want: model.NewLiteral("10", types.Integer), + }, + { + name: "Literal Decimal", + cql: "10.1", + want: model.NewLiteral("10.1", types.Decimal), + }, + { + name: "Literal String", + cql: "'str'", + want: model.NewLiteral("str", types.String), + }, + { + name: "Literal Bool", + cql: "false", + want: model.NewLiteral("false", types.Boolean), + }, + { + name: "Literal Long", + cql: "1000000L", + want: model.NewLiteral("1000000L", types.Long), + }, + { + name: "Literal DateTime", + cql: "@2023-01-25T14:30:14.559", + want: model.NewLiteral("@2023-01-25T14:30:14.559", types.DateTime), + }, + { + name: "Literal Date", + cql: "@2023-01-25", + want: model.NewLiteral("@2023-01-25", types.Date), + }, + { + name: "Literal Time", + cql: "@T10:00:00.0", + want: model.NewLiteral("@T10:00:00.0", types.Time), + }, + { + name: "Literal Quantity", + cql: "6'gm/cm3'", + want: &model.Quantity{Value: 6, Unit: "gm/cm3", Expression: model.ResultType(types.Quantity)}, + }, + { + name: "Literal Quantity temporal", + cql: "1 year", + want: &model.Quantity{Value: 1, Unit: "year", Expression: model.ResultType(types.Quantity)}, + }, + { + name: "Literal Quantity temporal plural", + cql: "6 years", + want: &model.Quantity{Value: 6, Unit: "year", Expression: model.ResultType(types.Quantity)}, + }, + { + name: "Literal Ratio", + cql: "1 'cm':2 'cm'", + want: &model.Ratio{ + Numerator: model.Quantity{Value: 1, Unit: "cm", Expression: model.ResultType(types.Quantity)}, + Denominator: model.Quantity{Value: 2, Unit: "cm", Expression: model.ResultType(types.Quantity)}, + Expression: model.ResultType(types.Ratio), + }, + }, + { + name: "Time Unit Expression, 'date from'", + cql: dedent.Dedent(`date from @2013-01-01T00:00:00.0`), + want: &model.ToDate{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(types.Date), + }, + }, + }, + { + name: "Tuple Selector", + cql: "Tuple{code: 'foo', id: 4}", + want: &model.Tuple{ + Expression: model.ResultType(&types.Tuple{ElementTypes: map[string]types.IType{"code": types.String, "id": types.Integer}}), + Elements: []*model.TupleElement{ + &model.TupleElement{Name: "code", Value: model.NewLiteral("foo", types.String)}, + &model.TupleElement{Name: "id", Value: model.NewLiteral("4", types.Integer)}, + }, + }, + }, + { + name: "Instance Selector", + cql: "Code{code: 'foo', system: 'bar', display: 'the foo', version: '1.0'}", + want: &model.Instance{ + Expression: model.ResultType(types.Code), + ClassType: types.Code, + Elements: []*model.InstanceElement{ + &model.InstanceElement{Name: "code", Value: model.NewLiteral("foo", types.String)}, + &model.InstanceElement{Name: "system", Value: model.NewLiteral("bar", types.String)}, + &model.InstanceElement{Name: "display", Value: model.NewLiteral("the foo", types.String)}, + &model.InstanceElement{Name: "version", Value: model.NewLiteral("1.0", types.String)}, + }, + }, + }, + { + name: "Instance Selector with implicit conversion", + cql: "Quantity {value: 4, unit: 'mg'}", + want: &model.Instance{ + Expression: model.ResultType(types.Quantity), + ClassType: types.Quantity, + Elements: []*model.InstanceElement{ + &model.InstanceElement{ + Name: "value", + Value: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + }, + &model.InstanceElement{Name: "unit", Value: model.NewLiteral("mg", types.String)}, + }, + }, + }, + { + name: "NamedTypeSpecifier Quoted", + cql: `List<"System.String">{}`, + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.String}), + List: []model.IExpression{}, + }, + }, + { + name: "NamedTypeSpecifier Qualfied and Quoted", + cql: `List<"System".String>{}`, + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.String}), + List: []model.IExpression{}, + }, + }, + { + name: "NamedTypeSpecifier Qualfied and Unquoted", + cql: `List{}`, + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.String}), + List: []model.IExpression{}, + }, + }, + { + name: "NamedTypeSpecifier FHIR", + cql: `List{}`, + want: &model.List{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + List: []model.IExpression{}, + }, + }, + { + name: "maximum Integer", + cql: "maximum Integer", + want: &model.MaxValue{ValueType: types.Integer, Expression: model.ResultType(types.Integer)}, + }, + { + name: "minimum Decimal", + cql: "minimum Decimal", + want: &model.MinValue{ValueType: types.Decimal, Expression: model.ResultType(types.Decimal)}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), wrapInLib(t, test.cql), Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, getTESTRESULTModel(t, parsedLibs)); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestParserExpressions_SingleLibrary(t *testing.T) { + tests := []struct { + name string + desc string + cql string + want *model.Library + }{ + { + name: "Choice Type Specifier", + cql: `define function "Population"(P Choice): 4`, + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{{Name: "P", Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}})}}, + }, + }, + }, + }, + }, + { + name: "Tuple Type Specifier", + cql: `define function "Population"(P Tuple{apple Integer, banana String}): 4`, + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{{Name: "P", Expression: model.ResultType(&types.Tuple{ElementTypes: map[string]types.IType{"apple": types.Integer, "banana": types.String}})}}, + }, + }, + }, + }, + }, + { + name: "List Type Specifier", + cql: `define function "Population"(P List): 4`, + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{{Name: "P", Expression: model.ResultType(&types.List{ElementType: types.Integer})}}, + }, + }, + }, + }, + }, + { + name: "Interval Type Specifier", + cql: `define function "Population"(P Interval): 4`, + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{{Name: "P", Expression: model.ResultType(&types.Interval{PointType: types.Integer})}}, + }, + }, + }, + }, + }, + { + name: "Nested Alias Scopes", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define function ToConcept(concept FHIR.CodeableConcept): + System.Concept { + codes: concept.coding C return C, + display: concept.text.value + }`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "ToConcept", + AccessLevel: "PUBLIC", + Context: "Patient", + Element: &model.Element{ResultType: types.Concept}, + Expression: &model.Instance{ + Expression: &model.Expression{Element: &model.Element{ResultType: types.Concept}}, + ClassType: types.Concept, + Elements: []*model.InstanceElement{ + &model.InstanceElement{ + Name: "codes", + // We need to convert "concept.coding C return C" which returns a + // List to List to match the codes element in the Concept + // structured type. To do that the parser inserts "(concept.coding C return C) + // X return all FHIRHelper.ToCode(X)" + Value: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: types.Code}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "X", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Coding"}}), + // This is the "concept.coding C return C" query you see in the CQL which returns List + Source: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Coding"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "C", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Coding"}}), + Source: &model.Property{ + Source: &model.OperandRef{ + Name: "concept", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.CodeableConcept"}), + }, + Path: "coding", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Coding"}}), + }, + }, + }, + Return: &model.ReturnClause{ + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.Coding"}}, + Expression: &model.AliasRef{ + Name: "C", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Coding"})}, + Distinct: true, + }, + }, + }, + }, + Return: &model.ReturnClause{ + Element: &model.Element{ResultType: types.Code}, + Expression: &model.FunctionRef{ + LibraryName: "FHIRHelpers", + Name: "ToCode", + Operands: []model.IExpression{&model.AliasRef{ + Name: "X", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Coding"})}}, + Expression: model.ResultType(types.Code), + }, + Distinct: false, + }, + }, + }, + &model.InstanceElement{ + Name: "display", + Value: &model.Property{ + Path: "value", + Expression: model.ResultType(types.String), + Source: &model.Property{ + Path: "text", + Source: &model.OperandRef{ + Name: "concept", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.CodeableConcept"}), + }, + Expression: model.ResultType(&types.Named{TypeName: "FHIR.string"}), + }, + }, + }, + }, + }, + }, + Operands: []model.OperandDef{{Name: "concept", Expression: model.ResultType(&types.Named{TypeName: "FHIR.CodeableConcept"})}}, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), []string{test.cql}, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, parsedLibs[0]); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestIdentifiers(t *testing.T) { + // These are very targeted tests for various ways to write identifiers in CQL, + // following https://cql.hl7.org/03-developersguide.html#identifiers and + // https://cql.hl7.org/19-l-cqlsyntaxdiagrams.html#identifier. + // + // Some use cases are not yet implemented in the parser and would be skipped. + + tests := []struct { + // The identifier as it would appear in an authored CQL library. + given string + // The parsed name of the identifier as it should appear in the parsed model, + // which should be stripped from quoting and unescaped. + // If set to `expectError` then the given identifier is expected to cause + // `ParsingErrors` to be returned. + want string + + // True if this case is expected to return a ParseError + expectError bool + + // True to skip not-yet-implemented cases + skip bool + }{ + // Examples from https://cql.hl7.org/03-developersguide.html#identifiers. + // Simple identifiers: + {given: "Foo", want: "Foo"}, + {given: "Foo1", want: "Foo1"}, + {given: "_Foo", want: "_Foo"}, // discouraged, but allowed + {given: "foo", want: "foo"}, + // Delimited identifiers: + {given: "`Diagnosis`", want: "Diagnosis"}, + {given: "`Encounter, Performed`", want: "Encounter, Performed"}, + // Quoted identifiers: + {given: `"Diagnosis"`, want: "Diagnosis"}, + {given: `"Encounter, Performed"`, want: "Encounter, Performed"}, + // Delimited identifiers are unescaped: + {given: "`with\\'squote`", want: `with'squote`}, + {given: "`with\\\"dquote`", want: `with"dquote`}, + {given: "`with\\`backtick`", want: "with`backtick"}, + {given: "`with\\rCR`", want: "with\rCR"}, + {given: "`with\\nLF`", want: "with\nLF"}, + {given: "`with\\tTAB`", want: "with\tTAB"}, + {given: "`with\\fFF`", want: "with\fFF"}, + {given: "`with\\\\backslash`", want: `with\backslash`}, + {given: "`with\\u0020unicode`", want: "with unicode", skip: true}, + // Quoted identifiers are unescaped: + {given: "\"with\\'squote\"", want: `with'squote`}, + {given: "\"with\\\"dquote\"", want: `with"dquote`}, + {given: "\"with\\`backtick\"", want: "with`backtick"}, + {given: "\"with\\rCR\"", want: "with\rCR"}, + {given: "`with\\nLF`", want: "with\nLF"}, + {given: "\"with\\tTAB\"", want: "with\tTAB"}, + {given: "`with\\fFF`", want: "with\fFF"}, + {given: "\"with\\\\backslash\"", want: `with\backslash`}, + {given: "\"with\\u0020unicode\"", want: "with unicode", skip: true}, + // More valid simple identifiers: + {given: "fOO", want: "fOO"}, + {given: "CamelCase", want: "CamelCase"}, + {given: "d1g1ts", want: "d1g1ts"}, + {given: "numb34", want: "numb34"}, + {given: "under_score", want: "under_score"}, + {given: "underscore_", want: "underscore_"}, + // Additional valid quoted and delimited examples. Those are not pretty, but are allowed: + {given: `"with.dot"`, want: "with.dot"}, // "with-dot" -> with-dot + {given: `"with-dash"`, want: "with-dash"}, // "with-dash" -> with-dash + {given: `" "`, want: " "}, // "" -> a space + // Invalid simple identifiers which would require quoting or delimiting to be used: + {given: "0Foo", expectError: true}, + {given: " ", expectError: true}, + {given: "with-dash", expectError: true}, + {given: "unicode\\u0030", expectError: true}, + {given: "'singlequote'", expectError: true}, + } + + for _, tc := range tests { + t.Run(tc.given, func(t *testing.T) { + if tc.skip { + t.Skipf("not implemented yet") + } + + cql := fmt.Sprintf("define %s: 4", tc.given) + libs, err := newFHIRParser(t).Libraries(context.Background(), []string{cql}, Config{}) + if tc.expectError { + if err == nil { + t.Fatal("Libraries() expected parse error but got none") + } + var pe *LibraryErrors + if ok := errors.As(err, &pe); !ok { + t.Fatal("Libraries() expected parse error: ", err) + } + return + } + + gotName := libs[0].Statements.Defs[0].GetName() + if gotName != tc.want { + t.Errorf("Extracting name from parsed Library(%s) = [%v], want [%s]", cql, gotName, tc.want) + } + }) + + } +} + +func TestIdentifiableExpressions(t *testing.T) { + // TestIdentifiableExpressions tests that all locations that handle identifiers properly call + // VisitIdentifier. For detailed testing of VisitIdentifier see the TestIdentifiers suite. Some + // use cases are not yet implemented in the parser and would be skipped. + + identifiers := []struct { + // The identifier as it would appear in an authored CQL library. + given string + // The parsed name of the identifier as it should appear in the parsed model, + // which should be stripped from quoting and unescaped. + // If set to `expectError` then the given identifier is expected to cause + // `ParsingErrors` to be returned. + want string + + // True if this case is expected to return a ParseError + expectError bool + + // True to skip not-yet-implemented cases + skip bool + }{ + // Examples from https://cql.hl7.org/03-developersguide.html#identifiers. + // Simple identifier: + {given: "Foo", want: "Foo"}, + // Quoted identifier: + {given: `"Diagnosis"`, want: "Diagnosis"}, + // Delimited identifiers are unescaped: + {given: "`with\\'squote`", want: `with'squote`}, + // Quoted identifiers are unescaped: + {given: "\"with\\'squote\"", want: `with'squote`}, + // Invalid simple identifiers which would require quoting or delimiting to be used: + {given: "'singlequote'", expectError: true}, + } + + identifiableExpressions := map[string]struct { + // CQL template with placeholders for a supplied identifier. + cql string + + // Extract the parsed identifier name from the parsed library. + gotExtractor func(*model.Library) string + + // True to skip not-yet-implemented cases + skip bool + }{ + "parameterDefinition": { + cql: "parameter %s Integer", + gotExtractor: func(got *model.Library) string { + return got.Parameters[0].Name + }, + }, + "valuesetDefinition": { + cql: "valueset %s: 'TRIVIAL'", + gotExtractor: func(got *model.Library) string { + return got.Valuesets[0].Name + }, + }, + "expressionDefinition": { + cql: "define %s: ''", + gotExtractor: func(got *model.Library) string { + return got.Statements.Defs[0].GetName() + }, + }, + "from:": { + cql: dedent.Dedent(` + define %[1]s: '' + define TRIVIAL: from %[1]s TRIVIAL return TRIVIAL`), + gotExtractor: func(got *model.Library) string { + return got.Statements.Defs[1].GetExpression().(*model.Query).Source[0].Source.(*model.ExpressionRef).Name + }, + }, + "from-alias:": { + cql: dedent.Dedent(` + define OTHER: '' + define TRIVIAL: from OTHER %s`), + gotExtractor: func(got *model.Library) string { + return got.Statements.Defs[1].GetExpression().(*model.Query).Source[0].Alias + }, + }, + "from-return:": { + cql: dedent.Dedent(` + define OTHER: '' + define TRIVIAL: from OTHER TRIVIAL return %s`), + skip: true, + }, + "from-aggregate:": { + cql: dedent.Dedent(` + define OTHER: '' + define TRIVIAL: from OTHER TRIVIAL aggregate %s: 0`), + skip: true, + }, + "externalConstant": { + cql: "define TRIVIAL: %%%s", + skip: true, + }, + "let": { + cql: dedent.Dedent(` + define OTHER: '' + define TRIVIAL: from OTHER O let %[1]s :'' return %[1]s`), + skip: true, + }, + "localIdentifier": { + cql: "include TRIVIAL called %s", + skip: true, + }, + "functionDefinition": { + cql: "define function %s () : external", + gotExtractor: func(got *model.Library) string { + return got.Statements.Defs[0].GetName() + }, + }, + "functionDefinition-op-type": { + cql: "define function TRIVIAL (TRIVIAL %s) : external", + skip: true, + }, + "functionDefinition-op-name": { + cql: "define function TRIVIAL (%[1]s Integer): %[1]s+1", + gotExtractor: func(got *model.Library) string { + return got.Statements.Defs[0].(*model.FunctionDef).Operands[0].Name + }, + }, + "codeDefinition": { + cql: "code %s: 'TRIVIAL' from TRIVIAL", + skip: true, + }, + "codeDefinition-from-lib": { + cql: "code TRIVIAL: 'TRIVIAL' from %s.TRIVIAL", + skip: true, + }, + "codeDefinition-from-id": { + cql: "code TRIVIAL: 'TRIVIAL' from TRIVIAL.%s", + skip: true, + }, + "conceptDefinition": { + cql: "concept %s: { TRIVIAL }", + skip: true, + }, + "conceptDefinition-code": { + cql: "concept TRIVIAL: { %s }", + skip: true, + }, + "codesystemDefinition": { + cql: "codesystem %s: 'TRIVIAL'", + gotExtractor: func(got *model.Library) string { + return got.CodeSystems[0].Name + }, + }, + } + + for _, identifier := range identifiers { + for exprName, test := range identifiableExpressions { + t.Run(identifier.given+"_"+exprName, func(t *testing.T) { + t.Parallel() + if test.skip || identifier.skip { + t.Skipf("not implemented yet") + } + + cql := fmt.Sprintf(test.cql, identifier.given) + libs, err := newFHIRParser(t).Libraries(context.Background(), []string{cql}, Config{}) + if identifier.expectError { + if err == nil { + t.Fatal("Libraries() expected parse error but got none") + } + var pe *LibraryErrors + ok := errors.As(err, &pe) + if !ok { + t.Fatal("Libraries() expected parse error: ", err) + } + return + } + + gotName := test.gotExtractor(libs[0]) + + if gotName != identifier.want { + t.Errorf("Extracting name from parsed Library(%s) = [%v], want [%s]", cql, gotName, identifier.want) + } + }) + } + } +} + +func newFHIRParser(t testing.TB) *Parser { + t.Helper() + fhirMI, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + t.Fatalf("internal error - could not read fhir-modelinfo-4.0.1.xml: %v", err) + } + p, err := New(context.Background(), [][]byte{fhirMI}) + if err != nil { + t.Fatal("Could not create Parser: ", err) + } + return p +} + +// wrapInLib wraps the cql expression in a library and expression definition. +func wrapInLib(t testing.TB, cql string) []string { + t.Helper() + cqlLib := dedent.Dedent(fmt.Sprintf(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + context Patient + define TESTRESULT: %v`, cql)) + + return addFHIRHelpersLib(t, cqlLib) +} + +func addFHIRHelpersLib(t testing.TB, lib string) []string { + fhirHelpers, err := embeddata.FHIRHelpers.ReadFile("third_party/cqframework/FHIRHelpers-4.0.1.cql") + if err != nil { + t.Fatalf("internal error - could not read FHIRHelpers-4.0.1.cql: %v", err) + } + return []string{lib, string(fhirHelpers)} +} + +// getTESTRESULTModel finds the first TESTRESULT definition in any library and returns the model. +func getTESTRESULTModel(t testing.TB, parsedLibs []*model.Library) model.IExpression { + t.Helper() + + for _, parsedLib := range parsedLibs { + if parsedLib.Statements == nil { + continue + } + for _, def := range parsedLib.Statements.Defs { + if def.GetName() == "TESTRESULT" { + return def.GetExpression() + } + } + } + + t.Fatalf("Could not find TESTRESULT expression definition") + return nil +} diff --git a/parser/functions.go b/parser/functions.go new file mode 100644 index 0000000..d6d14e5 --- /dev/null +++ b/parser/functions.go @@ -0,0 +1,135 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "fmt" + + "github.com/google/cql/internal/embeddata/third_party/cqframework/cql" + "github.com/google/cql/internal/reference" + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/antlr4-go/antlr/v4" +) + +// VisitFunctionDefinition parses a user defined function and saves it in the reference resolver. +func (v *visitor) VisitFunctionDefinition(ctx *cql.FunctionDefinitionContext) *model.FunctionDef { + fd := &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Element: &model.Element{}, + Name: v.parseIdentifierOrFuntionIdentifier(ctx.IdentifierOrFunctionIdentifier()), + Context: v.currentModelContext, + AccessLevel: v.VisitAccessModifier(ctx.AccessModifier()), + }, + Operands: []model.OperandDef{}, + } + + if ctx.FluentModifier() != nil { + fd.Fluent = true + } + + v.refs.EnterScope() + defer v.refs.ExitScope() + + for _, ops := range ctx.AllOperandDefinition() { + op := v.VisitOperandDefinition(ops) + + f := func() model.IExpression { + return &model.OperandRef{Name: op.Name, Expression: model.ResultType(op.GetResultType())} + } + if err := v.refs.Alias(op.Name, f); err != nil { + v.reportError(err.Error(), ctx) + } + + fd.Operands = append(fd.Operands, op) + } + + if ctx.FunctionBody() != nil { + fd.Expression = v.VisitExpression(ctx.FunctionBody().Expression()) + + returnType := v.VisitTypeSpecifier(ctx.TypeSpecifier()) + if returnType != nil && !returnType.Equal(fd.Expression.GetResultType()) { + v.reportError(fmt.Sprintf("function body return type %v, does not match the specified return %v", fd.Expression.GetResultType(), returnType), ctx) + } + fd.ResultType = fd.Expression.GetResultType() + } else { + fd.External = true + returnType := v.VisitTypeSpecifier(ctx.TypeSpecifier()) + if returnType != nil { + fd.ResultType = returnType + } + } + + operandRef := []types.IType{} + for _, op := range fd.Operands { + operandRef = append(operandRef, op.GetResultType()) + } + + f := &reference.Func[func() model.IExpression]{ + Name: fd.Name, + Operands: operandRef, + // The Operands are left as nil, they will be set when we parse when this function is called. + Result: func() model.IExpression { + return &model.FunctionRef{Name: fd.Name, Operands: nil, Expression: model.ResultType(fd.ResultType)} + }, + IsPublic: fd.AccessLevel == model.Public, + IsFluent: fd.Fluent, + ValidateIsUnique: true, + } + if err := v.refs.DefineFunc(f); err != nil { + v.reportError(err.Error(), ctx) + } + + return fd +} + +func (v *visitor) VisitOperandDefinition(ctx cql.IOperandDefinitionContext) model.OperandDef { + return model.OperandDef{ + Name: v.parseReferentialIdentifier(ctx.ReferentialIdentifier()), + Expression: model.ResultType(v.VisitTypeSpecifier(ctx.TypeSpecifier())), + } +} + +// parseQualifiedFunction parses invocations of a global user defined function. +func (v *visitor) parseQualifiedFunction(ctx *cql.QualifiedFunctionContext, libraryName string) model.IExpression { + name := v.parseIdentifierOrFuntionIdentifier(ctx.IdentifierOrFunctionIdentifier()) + params := []antlr.Tree{} + if ctx.ParamList() != nil { + for _, expr := range ctx.ParamList().AllExpression() { + params = append(params, expr) + } + } + m, err := v.parseFunction(libraryName, name, params, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +// VisitFunction parses invocations of a local user defined function or built-in function. +func (v *visitor) VisitFunction(ctx *cql.FunctionContext) model.IExpression { + name := v.parseReferentialIdentifier(ctx.ReferentialIdentifier()) + params := []antlr.Tree{} + if ctx.ParamList() != nil { + for _, expr := range ctx.ParamList().AllExpression() { + params = append(params, expr) + } + } + m, err := v.parseFunction("", name, params, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} diff --git a/parser/functions_test.go b/parser/functions_test.go new file mode 100644 index 0000000..f331a67 --- /dev/null +++ b/parser/functions_test.go @@ -0,0 +1,426 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "context" + "errors" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" +) + +func TestFunctionSingleLibrary(t *testing.T) { + tests := []struct { + name string + desc string + cql string + want *model.Library + }{ + { + name: "FunctionDef", + cql: dedent.Dedent(` + define function "Population"(P Integer): 4 + `), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{{Name: "P", Expression: model.ResultType(types.Integer)}}, + }, + }, + }, + }, + }, + { + name: "FunctionDef with OperandRef", + cql: dedent.Dedent(` + define function P(P Integer): P + `), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "P", + AccessLevel: "PUBLIC", + Expression: &model.OperandRef{Name: "P", Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{{Name: "P", Expression: model.ResultType(types.Integer)}}, + }, + }, + }, + }, + }, + { + name: "FunctionDef return", + cql: dedent.Dedent(` + define function "Population"() returns Integer: 4 + `), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{}, + }, + }, + }, + }, + }, + { + name: "FunctionDef fluent with access modifier", + cql: dedent.Dedent(` + define private fluent function "Population"(): external + `), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PRIVATE", + Element: &model.Element{}, + }, + Operands: []model.OperandDef{}, + External: true, + Fluent: true, + }, + }, + }, + }, + }, + { + name: "FunctionDef fluent without access modifier", + cql: dedent.Dedent(` + define fluent function "Population"(): external + `), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Element: &model.Element{}, + }, + Operands: []model.OperandDef{}, + External: true, + Fluent: true, + }, + }, + }, + }, + }, + { + name: "FunctionDef returns and external", + cql: dedent.Dedent(` + define public function "Population"() returns String : external + `), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.String}, + }, + Operands: []model.OperandDef{}, + External: true, + }, + }, + }, + }, + }, + { + name: "FunctionRef Local", + cql: dedent.Dedent(` + define function "Population"(P Integer): 4 + define x: Population(5) + `), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.FunctionDef{ + ExpressionDef: &model.ExpressionDef{ + Name: "Population", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.OperandDef{{Name: "P", Expression: model.ResultType(types.Integer)}}, + }, + &model.ExpressionDef{ + Name: "x", + Expression: &model.FunctionRef{ + Name: "Population", + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), []string{test.cql}, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, parsedLibs[0]); diff != "" { + t.Errorf("%v\nParsing diff (-want +got):\n%s", test.desc, diff) + } + }) + } +} + +func TestFunctionMultipleLibraries(t *testing.T) { + tests := []struct { + name string + cql string + want *model.Library + }{ + { + name: "QualifiedFunction Global Reference", + cql: dedent.Dedent(` + library measure version '1.0' + include example.helpers version '1.0' called Helpers + define X: Helpers."public func"(5)`), + want: &model.Library{ + Identifier: &model.LibraryIdentifier{Local: "measure", Qualified: "measure", Version: "1.0"}, + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Expression: &model.FunctionRef{ + Name: "public func", + LibraryName: "Helpers", + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cqlLibs := []string{ + dedent.Dedent(` + library example.helpers version '1.0' + define public function "public func"(A Integer): 2 + define private function "private func"(A Integer): 3 `), + test.cql, + } + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), cqlLibs, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, parsedLibs[1]); diff != "" { + t.Errorf("Parsing diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestMalformedFunctionSingleLibrary(t *testing.T) { + tests := []struct { + name string + cql string + errContains []string + errCount int + }{ + { + name: "FunctionDef_ReturnTypeMismatch", + cql: dedent.Dedent(` + define function "Population"() returns String: 4 + `), + errContains: []string{"function body return type"}, + errCount: 1, + }, + { + name: "FunctionDef Already Exists", + cql: dedent.Dedent(` + define function A(): 4 + define function A(): 5 + `), + errContains: []string{"function A() already exists"}, + errCount: 1, + }, + { + name: "Matching function is not fluent", + cql: dedent.Dedent(` + define function Foo(a Integer): a + define Bar: 5.Foo() + `), + errContains: []string{"could not resolve Foo(System.Integer): no matching overloads (may not be a fluent function)"}, + errCount: 1, + }, + { + name: "FunctionRef Local Does Not Exist", + cql: dedent.Dedent(` + define X: P() + `), + errContains: []string{"could not resolve"}, + errCount: 1, + }, + { + name: "OperandDef Same Name", + cql: dedent.Dedent(` + define function "Population"(A Integer, A String): 4 + `), + errContains: []string{"alias A already exists"}, + errCount: 1, + }, + { + name: "OperandDef and ExpressionDef Same Name", + cql: dedent.Dedent(` + define A: 5 + define function "Population"(A Integer): 4 + `), + errContains: []string{"identifier A already exists"}, + errCount: 1, + }, + { + name: "OperandRef Does Not Exist", + cql: dedent.Dedent(` + define function "Population"(A Integer): B + `), + errContains: []string{"could not resolve the local"}, + errCount: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := newFHIRParser(t).Libraries(context.Background(), []string{test.cql}, Config{}) + if err == nil { + t.Fatal("Parsing succeeded, expected error") + } + + var pe *LibraryErrors + if ok := errors.As(err, &pe); ok { + for _, ec := range test.errContains { + if !strings.Contains(pe.Error(), ec) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", + err.Error(), test.errContains) + } + } + + if len(pe.Errors) != test.errCount { + t.Errorf("Returned error (%s) had (%d) errors but expected (%d)", + err.Error(), len(pe.Errors), test.errCount) + } + } else { + t.Errorf("Unexpected test error (%s).", err.Error()) + } + }) + } +} + +func TestMalformedFunctionMultipleLibraries(t *testing.T) { + tests := []struct { + name string + cql string + errContains []string + errCount int + }{ + { + name: "QualifiedFunction cannot resolve function name", + cql: dedent.Dedent(` + library measure version '1.0' + include example.helpers version '1.0' called Helpers + define X: Helpers."private func"(5)`), + errContains: []string{"could not resolve"}, + errCount: 1, + }, + { + name: "QualifiedFunction cannot resolve library name", + cql: dedent.Dedent(` + library measure version '1.0' + include example.helpers version '1.0' called Helpers + define X: NonExistent."private func"(5)`), + errContains: []string{ + "could not resolve the local reference to NonExistent", + "could not resolve private func(System.Any, System.Integer)"}, + errCount: 2, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cqlLibs := []string{ + dedent.Dedent(` + library example.helpers version '1.0' + define public function "public func"(A Integer): 2 + define private function "private func"(A Integer): 3 `), + tc.cql, + } + _, err := newFHIRParser(t).Libraries(context.Background(), cqlLibs, Config{}) + var pe *LibraryErrors + if ok := errors.As(err, &pe); ok { + for _, ec := range tc.errContains { + if !strings.Contains(pe.Error(), ec) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", + err.Error(), tc.errContains) + } + } + + if len(pe.Errors) != tc.errCount { + t.Errorf("Returned error (%s) had (%d) errors but expected (%d)", + err.Error(), len(pe.Errors), tc.errCount) + } + } else { + t.Errorf("Unexpected test error (%s).", err.Error()) + } + }) + } +} diff --git a/parser/library.go b/parser/library.go new file mode 100644 index 0000000..aebe401 --- /dev/null +++ b/parser/library.go @@ -0,0 +1,566 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "fmt" + "strings" + + "github.com/google/cql/internal/embeddata/third_party/cqframework/cql" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/internal/reference" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/antlr4-go/antlr/v4" + "slices" +) + +// VisitLibrary is the top level visitor for parsing a CQL library. +func (v *visitor) VisitLibrary(ctx cql.ILibraryContext) *model.Library { + library := &model.Library{ + Identifier: v.LibraryIdentifier(ctx), + } + v.makeCurrent(library.Identifier, ctx) + + // TODO: b/325500067 - CodeSystems should be visited before all Codes. + for _, def := range ctx.AllDefinition() { + switch d := def.GetChild(0).(type) { + case *cql.IncludeDefinitionContext: + library.Includes = append(library.Includes, v.VisitIncludeDefinition(d)) + case *cql.UsingDefinitionContext: + library.Usings = append(library.Usings, v.VisitUsingDefinition(d)) + case *cql.ParameterDefinitionContext: + library.Parameters = append(library.Parameters, v.VisitParameterDefinition(d)) + case *cql.CodesystemDefinitionContext: + library.CodeSystems = append(library.CodeSystems, v.VisitCodeSystemDefinition(d)) + case *cql.ConceptDefinitionContext: + library.Concepts = append(library.Concepts, v.VisitConceptDefinition(d)) + // TODO: b/326331584 - Support the CodeLiteralSelector format. + case *cql.CodeDefinitionContext: + library.Codes = append(library.Codes, v.VisitCodeDefinition(d)) + case *cql.ValuesetDefinitionContext: + library.Valuesets = append(library.Valuesets, v.VisitValuesetDefinition(d)) + default: + v.reportError("internal error - unsupported definition", ctx) + // TODO: b/301606416 - Add support for conceptDefinition. + } + } + + statements := &model.Statements{} + for _, stat := range ctx.AllStatement() { + switch s := stat.GetChild(0).(type) { + case *cql.ExpressionDefinitionContext: + statements.Defs = append(statements.Defs, v.VisitExpressionDefinition(s)) + case *cql.FunctionDefinitionContext: + statements.Defs = append(statements.Defs, v.VisitFunctionDefinition(s)) + case *cql.ContextDefinitionContext: + statements.Defs = append(statements.Defs, v.VisitContextDefinition(s)) + default: + v.reportError("internal error - unsupported statement", ctx) + } + } + + if len(statements.Defs) > 0 { + library.Statements = statements + } + + return library +} + +// LibraryIdentifier is the top level visitor for parsing a CQL libraries unique identifier. +// This function is meant to be called by the parser when creating the includes +// dependency graph. +func (v *visitor) LibraryIdentifier(ctx cql.ILibraryContext) *model.LibraryIdentifier { + if ctx.LibraryDefinition() != nil { + return v.VisitLibraryDefinition(ctx.LibraryDefinition()) + } + return nil +} + +// makeCurrent sets the current library within the visitor. +// This function should always be called before Visiting CQL context nodes. +func (v *visitor) makeCurrent(libID *model.LibraryIdentifier, ctx cql.ILibraryContext) { + if libID == nil { + // TODO(b/298104070): We should add a warning for unnamed libraries. It is unintuitive that you can have an + // unnamed library where all definitions are private. + v.refs.SetCurrentUnnamed() + return + } + if err := v.refs.SetCurrentLibrary(libID); err != nil { + v.reportError(err.Error(), ctx) + return + } +} + +// VisitParameter is the top level visitor for parsing parameters passed to the CQL Engine. +func (v *visitor) VisitParameter(ctx cql.ITermContext) model.IExpression { + switch t := ctx.(type) { + case *cql.IntervalSelectorTermContext: + return v.VisitIntervalSelectorTerm(t) + case *cql.ListSelectorTermContext: + return v.VisitListSelectorTerm(t) + case *cql.LiteralTermContext: + return v.VisitLiteralTerm(t) + } + v.reportError("must be a interval, list or literal", ctx) + return nil +} + +func (v *visitor) VisitLibraryDefinition(ctx cql.ILibraryDefinitionContext) *model.LibraryIdentifier { + qID := v.VisitQualifiedIdentifier(ctx.QualifiedIdentifier()) + return &model.LibraryIdentifier{ + Version: v.VisitVersionSpecifier(ctx.VersionSpecifier()), + Local: qID[len(qID)-1], + Qualified: strings.Join(qID, "."), + } +} + +// VisitIncludeDefinition returns the model for an include statement. It should be called as part of +// parsing the entire library. If you only need to parse the include statements, use +// LibraryIncludedIdentifiers. +func (v *visitor) VisitIncludeDefinition(ctx *cql.IncludeDefinitionContext) *model.Include { + qID := v.VisitQualifiedIdentifier(ctx.QualifiedIdentifier()) + i := &model.Include{ + Identifier: &model.LibraryIdentifier{ + Qualified: strings.Join(qID, "."), + Version: v.VisitVersionSpecifier(ctx.VersionSpecifier()), + }, + } + + if ctx.LocalIdentifier() != nil { + i.Identifier.Local = v.VisitIdentifier(ctx.LocalIdentifier().Identifier()) + } else { + i.Identifier.Local = qID[len(qID)-1] + } + + if err := v.refs.IncludeLibrary(i.Identifier, true); err != nil { + v.reportError(err.Error(), ctx) + } + return i +} + +// LibraryIncludedIdentifiers only parses the include statements returning their LibKeys. It is +// meant to be called by the parser when creating the includes dependency graph. +// LibraryIncludedIdentifiers does not add to the reference resolver. +func (v *visitor) LibraryIncludedIdentifiers(ctx cql.ILibraryContext) []result.LibKey { + includes := []result.LibKey{} + for _, def := range ctx.AllDefinition() { + includeDef, ok := def.GetChild(0).(*cql.IncludeDefinitionContext) + if ok { + qID := v.VisitQualifiedIdentifier(includeDef.QualifiedIdentifier()) + key := result.LibKey{ + Name: strings.Join(qID, "."), + Version: v.VisitVersionSpecifier(includeDef.VersionSpecifier()), + } + includes = append(includes, key) + } + } + return includes +} + +func (v *visitor) VisitUsingDefinition(ctx *cql.UsingDefinitionContext) *model.Using { + using := &model.Using{} + + if ctx.LocalIdentifier() != nil { + v.reportError(fmt.Sprintf("Using declaration does not support local identifiers but received %v", v.VisitIdentifier(ctx.LocalIdentifier().Identifier())), ctx) + } + + qID := v.VisitQualifiedIdentifier(ctx.QualifiedIdentifier()) + using.LocalIdentifier = strings.Join(qID, ".") + if ctx.VersionSpecifier() != nil { + using.Version = v.VisitVersionSpecifier(ctx.VersionSpecifier()) + } + + key := modelinfo.Key{Name: using.LocalIdentifier, Version: using.Version} + if err := v.modelInfo.SetUsing(key); err != nil { + v.reportError(err.Error(), ctx) + return using + } + + url, err := v.modelInfo.URL() + if err != nil { + v.reportError(err.Error(), ctx) + return using + } + using.URI = url + if v.currentModelContext == "" { + v.currentModelContext, err = v.modelInfo.DefaultContext() + if err != nil { + v.reportError(err.Error(), ctx) + return using + } + if v.currentModelContext == "" { + // Fall back to Patient as the default if not provided. + v.currentModelContext = "Patient" + } + } + + return using +} + +func (v *visitor) VisitParameterDefinition(ctx cql.IParameterDefinitionContext) *model.ParameterDef { + p := &model.ParameterDef{ + Name: v.VisitIdentifier(ctx.Identifier()), + AccessLevel: v.VisitAccessModifier(ctx.AccessModifier()), + Element: &model.Element{}, + } + + if ctx.TypeSpecifier() == nil && ctx.Expression() == nil { + v.reportError("Parameter definition must include a type or a default, but neither were found", ctx) + } + + if ctx.TypeSpecifier() != nil { + p.Element.ResultType = v.VisitTypeSpecifier(ctx.TypeSpecifier()) + } + + // If a default value for the parameter was provided it is stored in the expression. + if ctx.Expression() != nil { + p.Default = v.VisitExpression(ctx.Expression()) + + if ctx.TypeSpecifier() != nil && !p.Element.ResultType.Equal(p.Default.GetResultType()) { + // If the specified and default type do not match, report error and use the default type. + v.reportError(fmt.Sprintf("Parameter definition specified type %s does not match the type of default %s", ctx.TypeSpecifier().GetText(), ctx.Expression().GetText()), ctx) + } + // If the default is set the TypeSpecifier is optional, and the ResultType should be inferred + // from the default. + p.Element.ResultType = p.Default.GetResultType() + } + + f := func() model.IExpression { + return &model.ParameterRef{Name: p.Name, Expression: model.ResultType(p.GetResultType())} + } + d := &reference.Def[func() model.IExpression]{ + Name: p.Name, + Result: f, + IsPublic: p.AccessLevel == model.Public, + ValidateIsUnique: true, + } + if err := v.refs.Define(d); err != nil { + v.reportError(err.Error(), ctx) + } + return p +} + +func (v *visitor) VisitValuesetDefinition(ctx *cql.ValuesetDefinitionContext) *model.ValuesetDef { + vd := &model.ValuesetDef{ + Name: v.VisitIdentifier(ctx.Identifier()), + ID: parseSTRING(ctx.ValuesetId().STRING()), + AccessLevel: v.VisitAccessModifier(ctx.AccessModifier()), + Version: v.VisitVersionSpecifier(ctx.VersionSpecifier()), + Element: &model.Element{ResultType: types.ValueSet}, + } + + if codeSystems := ctx.Codesystems(); codeSystems != nil { + for _, cs := range codeSystems.AllCodesystemIdentifier() { + csr := v.parseCodeSystemIdentifier(cs) + vd.CodeSystems = append(vd.CodeSystems, csr) + } + } + + d := &reference.Def[func() model.IExpression]{ + Name: vd.Name, + Result: func() model.IExpression { + return &model.ValuesetRef{Name: vd.Name, Expression: model.ResultType(types.ValueSet)} + }, + IsPublic: vd.AccessLevel == model.Public, + ValidateIsUnique: true, + } + if err := v.refs.Define(d); err != nil { + v.reportError(err.Error(), ctx) + } + return vd +} + +func (v *visitor) VisitCodeSystemDefinition(ctx *cql.CodesystemDefinitionContext) *model.CodeSystemDef { + cs := &model.CodeSystemDef{ + Name: v.VisitIdentifier(ctx.Identifier()), + ID: parseSTRING(ctx.CodesystemId().STRING()), + Version: v.VisitVersionSpecifier(ctx.VersionSpecifier()), + AccessLevel: v.VisitAccessModifier(ctx.AccessModifier()), + Element: &model.Element{ResultType: types.CodeSystem}, + } + + d := &reference.Def[func() model.IExpression]{ + Name: cs.Name, + Result: func() model.IExpression { + return &model.CodeSystemRef{Name: cs.Name, Expression: model.ResultType(types.CodeSystem)} + }, + IsPublic: cs.AccessLevel == model.Public, + ValidateIsUnique: true, + } + if err := v.refs.Define(d); err != nil { + v.reportError(err.Error(), ctx) + } + return cs +} + +func (v *visitor) VisitConceptDefinition(ctx *cql.ConceptDefinitionContext) *model.ConceptDef { + var display string + if d := ctx.DisplayClause(); d != nil { + display = parseSTRING(d.STRING()) + } + + var codes []*model.CodeRef + for _, codeID := range ctx.AllCodeIdentifier() { + codes = append(codes, v.VisitCodeIdentifier(codeID)) + } + + c := &model.ConceptDef{ + Name: v.VisitIdentifier(ctx.Identifier()), + Codes: codes, + Display: display, + AccessLevel: v.VisitAccessModifier(ctx.AccessModifier()), + Element: &model.Element{ResultType: types.Concept}, + } + + d := &reference.Def[func() model.IExpression]{ + Name: c.Name, + Result: func() model.IExpression { + return &model.ConceptRef{Name: c.Name, Expression: model.ResultType(types.Concept)} + }, + IsPublic: c.AccessLevel == model.Public, + ValidateIsUnique: true, + } + if err := v.refs.Define(d); err != nil { + v.reportError(err.Error(), ctx) + } + return c +} + +func (v *visitor) VisitCodeDefinition(ctx *cql.CodeDefinitionContext) *model.CodeDef { + c := &model.CodeDef{ + Name: v.VisitIdentifier(ctx.Identifier()), + Code: parseSTRING(ctx.CodeId().STRING()), + CodeSystem: v.parseCodeSystemIdentifier(ctx.CodesystemIdentifier()), + AccessLevel: v.VisitAccessModifier(ctx.AccessModifier()), + Element: &model.Element{ResultType: types.Code}, + } + + if d := ctx.DisplayClause(); d != nil { + c.Display = parseSTRING(d.STRING()) + } + + def := &reference.Def[func() model.IExpression]{ + Name: c.Name, + Result: func() model.IExpression { + return &model.CodeRef{Name: c.Name, Expression: model.ResultType(types.Code)} + }, + IsPublic: c.AccessLevel == model.Public, + ValidateIsUnique: true, + } + if err := v.refs.Define(def); err != nil { + v.reportError(err.Error(), ctx) + } + return c +} + +func (v *visitor) parseCodeSystemIdentifier(ctx cql.ICodesystemIdentifierContext) *model.CodeSystemRef { + csr := &model.CodeSystemRef{} + csi := v.VisitIdentifier(ctx.Identifier()) + var libID string + var csExpr func() model.IExpression + var err error + if ctx.LibraryIdentifier() != nil { + libID = v.VisitIdentifier(ctx.LibraryIdentifier().Identifier()) + csExpr, err = v.refs.ResolveGlobal(libID, csi) + } else { + csExpr, err = v.refs.ResolveLocal(csi) + } + if err != nil { + v.reportError(err.Error(), ctx) + return csr + } + + csr, ok := csExpr().(*model.CodeSystemRef) + if !ok { + fullID := csi + if libID != "" { + fullID = libID + "." + csi + } + v.reportError(fmt.Sprintf("%v should be of type %v but instead got %v", fullID, types.CodeSystem, csExpr().GetResultType()), ctx) + } + return csr +} + +func (v *visitor) VisitCodeIdentifier(ctx cql.ICodeIdentifierContext) *model.CodeRef { + codeRef := &model.CodeRef{} + cID := v.VisitIdentifier(ctx.Identifier()) + var libID string + var codeExpr func() model.IExpression + var err error + if ctx.LibraryIdentifier() != nil { + libID = v.VisitIdentifier(ctx.LibraryIdentifier().Identifier()) + codeExpr, err = v.refs.ResolveGlobal(libID, cID) + } else { + codeExpr, err = v.refs.ResolveLocal(cID) + } + if err != nil { + v.reportError(err.Error(), ctx) + return codeRef + } + + codeRef, ok := codeExpr().(*model.CodeRef) + if !ok { + fullID := cID + if libID != "" { + fullID = libID + "." + cID + } + v.reportError(fmt.Sprintf("expected to find CodeRef for identifier %s, got %v", fullID, codeExpr()), ctx) + } + return codeRef +} + +func (v *visitor) VisitExpressionDefinition(ctx *cql.ExpressionDefinitionContext) *model.ExpressionDef { + ed := &model.ExpressionDef{ + Name: v.VisitIdentifier(ctx.Identifier()), + Context: v.currentModelContext, + AccessLevel: v.VisitAccessModifier(ctx.AccessModifier()), + Expression: v.VisitExpression(ctx.Expression()), + } + expRef := &model.ExpressionRef{Name: ed.Name} + + // Set the return type of the ExpressionDef to the return type of the inner expression, and set + // the ExpressionRef's result type. + if ed.Expression.GetResultType() != nil { + ed.Element = &model.Element{ResultType: ed.Expression.GetResultType()} + expRef.Expression = model.ResultType(ed.Expression.GetResultType()) + } + + d := &reference.Def[func() model.IExpression]{ + Name: ed.Name, + Result: func() model.IExpression { + return expRef + }, + IsPublic: ed.AccessLevel == model.Public, + ValidateIsUnique: true, + } + if err := v.refs.Define(d); err != nil { + v.reportError(err.Error(), ctx) + } + return ed +} + +func (v *visitor) VisitContextDefinition(ctx *cql.ContextDefinitionContext) *model.ExpressionDef { + cname := v.VisitIdentifier(ctx.Identifier()) + var ed *model.ExpressionDef + + if err := validateContext(cname); err != nil { + return &model.ExpressionDef{ + Name: cname, + Expression: v.badExpression(err.Error(), ctx), + } + } + + r, err := v.createRetrieve(cname) + if err != nil { + return &model.ExpressionDef{ + Name: cname, + Expression: v.badExpression(err.Error(), ctx), + } + } + + sf := &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: r, + }, + } + if r.GetResultType() != nil { + switch opType := r.GetResultType().(type) { + case *types.List: + sf.UnaryExpression.Expression = model.ResultType(opType.ElementType) + case types.System: + if opType == types.Any { + sf.UnaryExpression.Expression = model.ResultType(types.Any) + } + // This should not happen for context definition, and in future will be handled by overload + // matching for general singleton from. + return &model.ExpressionDef{ + Name: cname, + Expression: v.badExpression(fmt.Sprintf("SingletonFrom expected a List type or null as input, got: %v", opType), ctx), + } + default: + // This should not happen for context definition, and in future will be handled by overload + // matching for general singleton from. + return &model.ExpressionDef{ + Name: cname, + Expression: v.badExpression(fmt.Sprintf("SingletonFrom expected a List type or null as input, got: %v", opType), ctx), + } + } + + ed = &model.ExpressionDef{ + Name: cname, + Context: cname, + AccessLevel: model.Private, + Expression: sf, + Element: &model.Element{ResultType: sf.GetResultType()}, + } + } + + d := &reference.Def[func() model.IExpression]{ + Name: ed.Name, + Result: func() model.IExpression { + return &model.ExpressionRef{Name: ed.Name, Expression: model.ResultType(ed.GetResultType())} + }, + // Context definitions are always private. + IsPublic: false, + ValidateIsUnique: true, + } + if err := v.refs.Define(d); err != nil { + v.reportError(err.Error(), ctx) + } + return ed +} + +var supportedContexts = []string{"Patient"} + +func validateContext(ctx string) error { + if !slices.Contains(supportedContexts, ctx) { + return fmt.Errorf("error -- the CQL engine does not yet support the context %q, only %v are supported", ctx, supportedContexts) + } + // TODO: b/329250181 - Also validate contexts against model info, when other contexts are + // supported. + return nil +} + +func (v *visitor) VisitAccessModifier(ctx cql.IAccessModifierContext) model.AccessLevel { + if ctx == nil { + return model.Public + } + if ctx.GetChild(0).(antlr.TerminalNode).GetText() == "private" { + return model.Private + } + return model.Public +} + +type parsingErrors interface { + Append(e *ParsingError) + Error() string + Unwrap() []error +} + +type visitor struct { + *cql.BaseCqlVisitor + + modelInfo *modelinfo.ModelInfos + + // The current model context, e.g "Patient". + currentModelContext string + + refs *reference.Resolver[func() model.IExpression, func() model.IExpression] + + // Accumulated parsing errors to be returned to the caller. + errors parsingErrors +} diff --git a/parser/library_test.go b/parser/library_test.go new file mode 100644 index 0000000..a7af47e --- /dev/null +++ b/parser/library_test.go @@ -0,0 +1,2451 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "context" + "errors" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/lithammer/dedent" +) + +func TestMalformedCQLSingleLibrary(t *testing.T) { + tests := []struct { + name string + cql string + errContains []string + errCount int + }{ + { + name: "trival_string", + cql: "abcdefg", + errContains: []string{"extraneous input 'abcdefg'"}, + errCount: 1, + }, + { + name: "Code references non-existent CodeSystem", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '4.0.1' + code c: '1234' from invalid_code_system_id`), + errContains: []string{"could not resolve the local reference"}, + errCount: 1, + }, + { + name: "Code uses id that is not a CodeSystem", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '4.0.1' + parameter "a number" default 4000 + code c: '1234' from "a number"`), + errContains: []string{"should be of type System.CodeSystem"}, + errCount: 1, + }, + { + name: "no_such_resource", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '4.0.1' + define TestPatient: ["NoSuchPatient"] + define TestCondition: ["NoSuchCondition"]`), + errContains: []string{"NoSuchPatient", "retrieves cannot be", "NoSuchCondition", "retrieves cannot be"}, + errCount: 4, + }, + { + name: "no_such_source", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '4.0.1' + + define IsMale: [Patient] P + where BogusRef.gender = 'male'`), + errContains: []string{"BogusRef"}, + errCount: 1, + }, + { + // CQL->ELM requires context to be explicitly declared before referencing it, + // so require the same here. + name: "missing_context", + cql: dedent.Dedent(` + library PatientGender version '1.2.3' + using FHIR version '4.0.1' + define gender: Patient.gender`), + errContains: []string{"Patient"}, + errCount: 1, + }, + { + name: "no_such_expression", + cql: dedent.Dedent(` + library ReferenceAnotherDefine version '1.2.3' + using FHIR version '4.0.1' + define HasSampleObs: exists(NoSuchObs)`), + errContains: []string{"NoSuchObs"}, + errCount: 1, + }, + { + name: "Query_AliasAlreadyExists", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + parameter "P" default 4000 + define X: [Patient] P`), + errContains: []string{"already exists"}, + errCount: 1, + }, + { + name: "ExpressionDefinition_NameAlreadyExists", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + parameter "Population Size" default 4000 + define "Population Size": 4000`), + errContains: []string{"already exists"}, + errCount: 1, + }, + { + name: "ref_invalid_expression", + cql: dedent.Dedent(` + library ReferenceAnotherDefine version '1.2.3' + using FHIR version '4.0.1' + define BogusDef: [NoSuchResource] + define HasBogus: exists(BogusDef)`), + errContains: []string{"NoSuchResource", "retrieves cannot be"}, + errCount: 2, + }, + { + name: "ParameterDefinition_MissingTypeAndDefault", + cql: `parameter "Population Size"`, + errContains: []string{"Parameter definition must include a type or a default, but neither were found"}, + errCount: 1, + }, + { + name: "ParameterDefinition_DefaultAndSpecifiedTypeMismatch", + cql: `parameter "Population Name" String default 82`, + errContains: []string{"Parameter definition specified type String does not match the type of default 82"}, + errCount: 1, + }, + { + name: "InvalidFHIRVersionUsingStatement", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '3.0.0' + `), + errContains: []string{"FHIR 3.0.0 data model not found"}, + errCount: 1, + }, + { + name: "InvalidNonFHIRUsingStatement", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using QUIC version '3.0.0' + `), + errContains: []string{"QUIC 3.0.0 data model not found"}, + errCount: 1, + }, + { + name: "invalid_FHIR_Version_for_retrieve", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '3.0.0' + define TestPatient: ["Patient"]`), + errContains: []string{ + "FHIR 3.0.0 data model not found", + "using declaration has not been set", + "retrieves cannot be", + }, + errCount: 3, + }, + { + name: "unsupported_interval_operator", + cql: dedent.Dedent(` + library intervalOperatorUnsupported version '1.2.3' + using FHIR version '4.0.1' + + define "Has coronary heart disease": + exists ( + [Condition] c + where c.onset includes start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) + )`), + errContains: []string{"unsupported interval operator in timing expression"}, + errCount: 1, + }, + { + name: "Unexpected Result Type in TimeBoundaryExpression operand", + cql: `library intervalOperator version '1.2.3' + using FHIR version '4.0.1' + define "EndOfInterval": + end of @2013-01-01T00:00:00.0`, + errContains: []string{"could not resolve End(System.DateTime)"}, + errCount: 1, + }, + { + name: "ExpectedStringGotIdentifier.", + cql: `library intervalOperator version '1.2.3' + using FHIR version '4.0.1' + valueset "My Valueset": "This should be a single-quoted string"`, + errContains: []string{"expecting STRING"}, + errCount: 1, + }, + { + name: "Invalid Expression", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define "Param": expand 4 + `), + errContains: []string{"unsupported expression"}, + errCount: 1, + }, + { + name: "Using Declaration with Local Identifier", + cql: dedent.Dedent(` + using FHIR version '4.0.1' called FIRE + `), + errContains: []string{"Using declaration does not support local identifiers"}, + errCount: 1, + }, + { + name: "Code Selector references nonexisistent CodeSystem", + cql: "define Foo: Code '132' from cs display 'Severed Leg'", + errContains: []string{"could not resolve the local reference to cs"}, + errCount: 1, + }, + { + name: "Unsupported context", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '4.0.1' + context Practitioner`), + errContains: []string{"error -- the CQL engine does not yet support the context"}, + errCount: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := newFHIRParser(t).Libraries(context.Background(), []string{test.cql}, Config{}) + if err == nil { + t.Fatal("Parse succeeded, wanted error") + } + + var pe *LibraryErrors + if ok := errors.As(err, &pe); ok { + for _, ec := range test.errContains { + if !strings.Contains(pe.Error(), ec) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", + err.Error(), test.errContains) + } + } + + if len(pe.Errors) != test.errCount { + t.Errorf("Returned error (%s) had (%d) errors but expected (%d)", + err.Error(), len(pe.Errors), test.errCount) + } + } else { + t.Errorf("Unexpected test error (%s).", err.Error()) + } + }) + } +} + +func TestNoLibraries(t *testing.T) { + _, err := newFHIRParser(t).Libraries(context.Background(), []string{}, Config{}) + if err == nil { + t.Fatal("Parse succeeded, wanted error") + } + want := "no CQL libraries were provided" + if !strings.Contains(err.Error(), want) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err.Error(), want) + } +} +func TestMalformedCQLMultipleLibraries(t *testing.T) { + tests := []struct { + name string + cql string + errContains []string + errCount int + }{ + { + name: "IncludeDef already defined", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' + include example.helpers2 version '1.0' called helpers1`), + errContains: []string{"already exists"}, + errCount: 1, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + libs := []string{ + dedent.Dedent(` + library example.helpers1 version '1.0' + define public "public def": 2 + define private "private def": 3 `), + dedent.Dedent(` + library example.helpers2 version '1.0' + define public "public def": 4 + define private "private def": 5 `), + tc.cql, + } + _, err := newFHIRParser(t).Libraries(context.Background(), libs, Config{}) + if err == nil { + t.Errorf("Parsing succeeded, expected error") + } + + var pe *LibraryErrors + if ok := errors.As(err, &pe); ok { + for _, ec := range tc.errContains { + s := pe.Error() + if !strings.Contains(s, ec) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", + err.Error(), tc.errContains) + } + } + + if len(pe.Errors) != tc.errCount { + t.Errorf("Returned error (%s) had (%d) errors but expected (%d)", + err.Error(), len(pe.Errors), tc.errCount) + } + } else { + t.Errorf("Unexpected test error (%s).", err.Error()) + } + }) + } +} + +func TestMalformedIncludeDependenciesMultipleLibraries(t *testing.T) { + tests := []struct { + name string + cqlLibs []string + wantErr string + }{ + { + name: "library includes itself", + cqlLibs: []string{ + dedent.Dedent(` + library lib1 + include lib1`), + }, + wantErr: "found circular dependencies", + }, + { + name: "repeated library identifier", + cqlLibs: []string{ + dedent.Dedent(` + library lib1 + include lib2`), + dedent.Dedent(` + library lib1 + include lib3`), + }, + wantErr: `cql library "lib1" already imported`, + }, + { + name: "includes non-existant library", + cqlLibs: []string{ + dedent.Dedent(` + library lib1 + include lib2`), + }, + wantErr: "failed to import library", + }, + { + name: "Directly circular include", + cqlLibs: []string{ + dedent.Dedent(` + library lib1 + include lib2`), + dedent.Dedent(` + library lib2 + include lib1`), + }, + wantErr: "found circular dependencies", + }, + { + name: "Indirectly circular include", + cqlLibs: []string{ + dedent.Dedent(` + library lib1 + include lib2`), + dedent.Dedent(` + library lib2 + include lib3`), + dedent.Dedent(` + library lib3 + include lib1`), + }, + wantErr: "found circular dependencies", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := newFHIRParser(t).Libraries(context.Background(), tc.cqlLibs, Config{}) + if err == nil { + t.Fatal("Parse succeeded, wanted error") + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err.Error(), tc.wantErr) + } + }) + } +} + +func TestParserTopologicalSortMultipleLibraries(t *testing.T) { + tests := []struct { + name string + cqlLibs []string + want []*model.Library + }{ + { + name: "Deps declared in reverse order", + cqlLibs: []string{ + dedent.Dedent(` + include lib1 + include lib2`), + dedent.Dedent(` + library lib2 + include lib1`), + "library lib1", + }, + want: []*model.Library{ + &model.Library{ + Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1"}, + }, + &model.Library{ + Identifier: &model.LibraryIdentifier{Local: "lib2", Qualified: "lib2"}, + Includes: []*model.Include{ + {Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1"}}, + }, + }, + &model.Library{ + // Unnamed Library + Includes: []*model.Include{ + {Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1"}}, + {Identifier: &model.LibraryIdentifier{Local: "lib2", Qualified: "lib2"}}, + }, + }, + }, + }, + { + name: "Deps declared in unsorted", + cqlLibs: []string{ + dedent.Dedent(` + library lib3 + include lib2 + include lib1`), + dedent.Dedent(` + library lib2 + include lib1`), + "include lib3", + "library lib1", + }, + want: []*model.Library{ + &model.Library{ + Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1"}, + }, + &model.Library{ + Identifier: &model.LibraryIdentifier{Local: "lib2", Qualified: "lib2"}, + Includes: []*model.Include{ + {Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1"}}, + }, + }, + &model.Library{ + Identifier: &model.LibraryIdentifier{Local: "lib3", Qualified: "lib3"}, + Includes: []*model.Include{ + {Identifier: &model.LibraryIdentifier{Local: "lib2", Qualified: "lib2"}}, + {Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1"}}, + }, + }, + &model.Library{ + // Unnamed Library + Includes: []*model.Include{ + {Identifier: &model.LibraryIdentifier{Local: "lib3", Qualified: "lib3"}}, + }, + }, + }, + }, + { + name: "Two unnamed libraries do not clash", + cqlLibs: []string{ + "library lib1 version '1.0'", + "include lib1 version '1.0'", + "include lib1 version '1.0'", + }, + want: []*model.Library{ + &model.Library{ + Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1", Version: "1.0"}, + }, + &model.Library{ + // Unnamed Library + Includes: []*model.Include{ + {Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1", Version: "1.0"}}, + }, + }, + &model.Library{ + // Unnamed Library + Includes: []*model.Include{ + {Identifier: &model.LibraryIdentifier{Local: "lib1", Qualified: "lib1", Version: "1.0"}}, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := newFHIRParser(t).Libraries(context.Background(), test.cqlLibs, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("Parsing diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestParserTopologicalSortMultipleTopLevelLibraries(t *testing.T) { + cqlLibs := []string{ + dedent.Dedent(` + library lib3 + include lib2 + include lib1`), + dedent.Dedent(` + library measure1 + include lib1 + include lib2 + include lib3`), + dedent.Dedent(` + library measure2 + include lib3`), + "library lib2", + "library lib1", + } + + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), cqlLibs, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + + lastTwoIDs := []string{ + parsedLibs[len(parsedLibs)-1].Identifier.Qualified, + parsedLibs[len(parsedLibs)-2].Identifier.Qualified, + } + measureIDs := []string{"measure1", "measure2"} + // Topological sort isn't 100% deterministic so assert the last values are what we want. + if diff := cmp.Diff(measureIDs, lastTwoIDs, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" { + t.Errorf("%v\nLibraries(%v) parsing diff (-want +got):\n%v", measureIDs, lastTwoIDs, diff) + } +} + +func TestParserSingleLibrary(t *testing.T) { + tests := []struct { + name string + desc string + cql string + want *model.Library + }{ + { + name: "LibraryDef with version", + cql: "library Example.TrivialTest version '1.2.3'", + want: &model.Library{ + Identifier: &model.LibraryIdentifier{ + Local: "TrivialTest", + Qualified: "Example.TrivialTest", + Version: "1.2.3", + }, + }, + }, + { + name: "LibraryDef without version", + cql: "library TrivialTest", + want: &model.Library{ + Identifier: &model.LibraryIdentifier{ + Local: "TrivialTest", + Qualified: "TrivialTest", + }, + }, + }, + { + name: "Context", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + context Patient`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Patient", + Context: "Patient", + AccessLevel: "PRIVATE", + Expression: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + }, + }, + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.Patient"}}, + }, + }, + }, + }, + }, + { + name: "Retrieve no filter", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define TestPatient: ["Patient"]`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "TestPatient", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}}, + }, + }, + }, + }, + }, + { + name: "ValuesetDef", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/codesystem' + valueset "Diabetes": 'https://example.com/diabetes_value_set' + valueset "Versioned Diabetes": 'https://example.com/diabetes_value_set' version '1.0.0' + valueset "CodeSystems Diabetes": 'https://example.com/diabetes_value_set' codesystems { cs } + public valueset "Public Diabetes": 'https://example.com/diabetes_value_set' + private valueset "Private Diabetes": 'https://example.com/diabetes_value_set'`), + want: &model.Library{ + CodeSystems: []*model.CodeSystemDef{ + &model.CodeSystemDef{ + Name: "cs", + ID: "https://example.com/codesystem", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + }, + Valuesets: []*model.ValuesetDef{ + &model.ValuesetDef{ + Name: "Diabetes", + ID: "https://example.com/diabetes_value_set", + Element: &model.Element{ResultType: types.ValueSet}, + AccessLevel: "PUBLIC", + }, + &model.ValuesetDef{ + Name: "Versioned Diabetes", + ID: "https://example.com/diabetes_value_set", + Version: "1.0.0", + Element: &model.Element{ResultType: types.ValueSet}, + AccessLevel: "PUBLIC", + }, + &model.ValuesetDef{ + Name: "CodeSystems Diabetes", + ID: "https://example.com/diabetes_value_set", + CodeSystems: []*model.CodeSystemRef{ + &model.CodeSystemRef{ + Name: "cs", + Expression: model.ResultType(types.CodeSystem), + }, + }, + Element: &model.Element{ResultType: types.ValueSet}, + AccessLevel: "PUBLIC", + }, + &model.ValuesetDef{ + Name: "Public Diabetes", + ID: "https://example.com/diabetes_value_set", + Element: &model.Element{ResultType: types.ValueSet}, + AccessLevel: "PUBLIC", + }, + &model.ValuesetDef{ + Name: "Private Diabetes", + ID: "https://example.com/diabetes_value_set", + Element: &model.Element{ResultType: types.ValueSet}, + AccessLevel: "PRIVATE", + }, + }, + }, + }, + { + name: "CodeSystemDef", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/codesystem' + codesystem cs2: 'https://example.com/codesystem_2' + codesystem cs_with_version: 'https://example.com/codesystem_versioned' version '1.0' + private codesystem cs_private: 'https://example.com/codesystem_private' + `), + want: &model.Library{ + CodeSystems: []*model.CodeSystemDef{ + &model.CodeSystemDef{ + Name: "cs", + ID: "https://example.com/codesystem", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + &model.CodeSystemDef{ + Name: "cs2", + ID: "https://example.com/codesystem_2", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + &model.CodeSystemDef{ + Name: "cs_with_version", + ID: "https://example.com/codesystem_versioned", + Version: "1.0", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + &model.CodeSystemDef{ + Name: "cs_private", + ID: "https://example.com/codesystem_private", + AccessLevel: "PRIVATE", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + }, + }, + }, + { + name: "CodeDef", + cql: dedent.Dedent(` + codesystem cs_with_version: 'https://example.com/codesystem_versioned' version '1.0' + code c: '1234' from cs_with_version + code c_with_display: '12345' from cs_with_version display 'Super Special Display' + `), + want: &model.Library{ + CodeSystems: []*model.CodeSystemDef{ + &model.CodeSystemDef{ + Name: "cs_with_version", + ID: "https://example.com/codesystem_versioned", + Version: "1.0", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + }, + Codes: []*model.CodeDef{ + &model.CodeDef{ + Name: "c", + Code: "1234", + CodeSystem: &model.CodeSystemRef{ + Name: "cs_with_version", + Expression: model.ResultType(types.CodeSystem), + }, + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Code}, + }, + &model.CodeDef{ + Name: "c_with_display", + Code: "12345", + CodeSystem: &model.CodeSystemRef{ + Name: "cs_with_version", + Expression: model.ResultType(types.CodeSystem), + }, + Display: "Super Special Display", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Code}, + }, + }, + }, + }, + { + name: "ConceptDef", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/codesystem' + code c: '1234' from cs + code c2: '456' from cs + concept con: { c, c2 } + concept con_with_display: { c } display 'A medical condition' + private concept pvt_con: { c } + `), + want: &model.Library{ + CodeSystems: []*model.CodeSystemDef{ + &model.CodeSystemDef{ + Name: "cs", + ID: "https://example.com/codesystem", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + }, + Codes: []*model.CodeDef{ + &model.CodeDef{ + Name: "c", + Code: "1234", + CodeSystem: &model.CodeSystemRef{ + Name: "cs", + Expression: model.ResultType(types.CodeSystem), + }, + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Code}, + }, + &model.CodeDef{ + Name: "c2", + Code: "456", + CodeSystem: &model.CodeSystemRef{ + Name: "cs", + Expression: model.ResultType(types.CodeSystem), + }, + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Code}, + }, + }, + Concepts: []*model.ConceptDef{ + &model.ConceptDef{ + Name: "con", + Codes: []*model.CodeRef{ + &model.CodeRef{ + Name: "c", + Expression: model.ResultType(types.Code), + }, + &model.CodeRef{ + Name: "c2", + Expression: model.ResultType(types.Code), + }, + }, + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Concept}, + }, + &model.ConceptDef{ + Name: "con_with_display", + Codes: []*model.CodeRef{ + &model.CodeRef{ + Name: "c", + Expression: model.ResultType(types.Code), + }, + }, + Display: "A medical condition", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Concept}, + }, + &model.ConceptDef{ + Name: "pvt_con", + Codes: []*model.CodeRef{ + &model.CodeRef{ + Name: "c", + Expression: model.ResultType(types.Code), + }, + }, + AccessLevel: "PRIVATE", + Element: &model.Element{ResultType: types.Concept}, + }, + }, + }, + }, + { + name: "Code Selector", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/codesystem' + define Foo: Code '132' from cs display 'Severed Leg' + `), + want: &model.Library{ + CodeSystems: []*model.CodeSystemDef{ + &model.CodeSystemDef{ + Name: "cs", + ID: "https://example.com/codesystem", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Foo", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Code}, + Expression: &model.Code{ + Expression: model.ResultType(types.Code), + System: &model.CodeSystemRef{Expression: model.ResultType(types.CodeSystem), Name: "cs"}, + Code: "132", + Display: "Severed Leg", + }, + }, + }, + }, + }, + }, + { + name: "ExpressionDef with Access Modifier", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define public Pub: 8 + define private Priv: 4 + `), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Pub", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("8", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + &model.ExpressionDef{ + Name: "Priv", + Context: "Patient", + AccessLevel: "PRIVATE", + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + { + name: "Model Used Twice Does Not Overwrite", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define Res1: First({3}) + define Res2: First({4}) + `), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Res1", + Context: "Patient", + AccessLevel: model.Public, + Expression: &model.First{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("3", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + Element: &model.Element{ResultType: types.Integer}, + }, + &model.ExpressionDef{ + Name: "Res2", + Context: "Patient", + AccessLevel: model.Public, + Expression: &model.First{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("4", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + { + name: "ParameterDefinition", + cql: dedent.Dedent(` + parameter "Defined Type" Integer + parameter "Default And Inferred Type" default Interval[@2023-04-01T00:00:00.0, @2024-03-31T00:00:00.0) + public parameter "Public" Integer + private parameter "Private" Integer + `), + want: &model.Library{ + Identifier: nil, + Usings: nil, + Parameters: []*model.ParameterDef{ + &model.ParameterDef{ + Name: "Defined Type", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Integer}, + }, + &model.ParameterDef{ + Name: "Default And Inferred Type", + AccessLevel: "PUBLIC", + Default: &model.Interval{ + Low: model.NewLiteral("@2023-04-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2024-03-31T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Element: &model.Element{ResultType: &types.Interval{PointType: types.DateTime}}, + }, + &model.ParameterDef{ + Name: "Public", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Integer}, + }, + &model.ParameterDef{ + Name: "Private", + AccessLevel: "PRIVATE", + Element: &model.Element{ResultType: types.Integer}, + }, + }, + Statements: nil, + }, + }, + { + name: "KeywordIdentifier Reference", + cql: dedent.Dedent(` + define "descending": 32 + define population: descending`), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "descending", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("32", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + &model.ExpressionDef{ + Name: "population", + AccessLevel: "PUBLIC", + Expression: &model.ExpressionRef{Name: "descending", Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + { + name: "ExpressionReference unquoted references quoted", + cql: dedent.Dedent(` + define "age": 32 + define population: age`), + want: &model.Library{ + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "age", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("32", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + }, + &model.ExpressionDef{ + Name: "population", + AccessLevel: "PUBLIC", + Expression: &model.ExpressionRef{Name: "age", Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + { + name: "InvocationExpressionTerm local reference", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + + define X: [Observation] + define Y: X`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + &model.ExpressionDef{ + Name: "Y", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.ExpressionRef{Name: "X", Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}})}, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + }, + }, + }, + }, + { + name: "InvocationExpressionTerm local reference with property", + desc: "Also tests nested properties.", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + + define X: [Observation] + define Y: X.category.coding`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + &model.ExpressionDef{ + Name: "Y", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Property{ + Source: &model.Property{ + Source: &model.ExpressionRef{Name: "X", Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}})}, + Path: "category", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.CodeableConcept"}}), + }, + Path: "coding", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Coding"}}), + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Coding"}}}, + }, + }, + }, + }, + }, + { + name: "QuerySource holds expression", + desc: "() changes grammar to capture InvocationExpressionTerm not QualifiedIdentifierExpression", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + + define X: [Observation] + define Y: from (X.status) O`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + &model.ExpressionDef{ + Name: "Y", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.ObservationStatus"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "O", + Source: &model.Property{ + Source: &model.ExpressionRef{Name: "X", Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}})}, + Path: "status", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.ObservationStatus"}}), + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.ObservationStatus"}}), + }, + }, + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.ObservationStatus"}}}, + }, + }, + }, + }, + }, + { + name: "QualifiedIdentifierExpression local reference", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + + define X: [Observation] + define Y: from X O`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + &model.ExpressionDef{ + Name: "Y", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "O", + Source: &model.ExpressionRef{Name: "X", Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}})}, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + }, + }, + }, + }, + { + name: "QualifiedIdentifierExpression local reference with multiple properties", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define X: [Condition] O return O.code.coding.display`), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.List{ElementType: &types.Named{TypeName: "FHIR.string"}}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "O", + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Condition", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Condition", + CodeProperty: "code", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Condition"}}), + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Condition"}}), + }, + }, + Return: &model.ReturnClause{ + Distinct: true, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.string"}}}, + Expression: &model.Property{ + Source: &model.Property{ + Source: &model.Property{ + Source: &model.AliasRef{ + Name: "O", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Condition"}), + }, + Path: "code", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.CodeableConcept"}), + }, + Path: "coding", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Coding"}}), + }, + Path: "display", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.string"}}), + }, + }, + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.List{ElementType: &types.Named{TypeName: "FHIR.string"}}}}, + }, + }, + }, + }, + }, + { + name: "Null", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + + define nullValue: null + define nullFunction: exists("nullValue") + define nullEqual: "nullValue" = null + `), + want: &model.Library{ + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "nullValue", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: model.NewLiteral("null", types.Any), + Element: &model.Element{ResultType: types.Any}}, + &model.ExpressionDef{ + Name: "nullFunction", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Exists{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.List{ElementType: types.Any}), + Operand: &model.ExpressionRef{Expression: model.ResultType(types.Any), Name: "nullValue"}, + }, + AsTypeSpecifier: &types.List{ElementType: types.Any}, + }, + Expression: model.ResultType(types.Boolean)}, + }, + Element: &model.Element{ResultType: types.Boolean}, + }, + &model.ExpressionDef{ + Name: "nullEqual", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.ExpressionRef{Name: "nullValue", Expression: model.ResultType(types.Any)}, + model.NewLiteral("null", types.Any)}, + Expression: model.ResultType(types.Boolean), + }, + }, + Element: &model.Element{ResultType: types.Boolean}, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), []string{test.cql}, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, parsedLibs[0]); diff != "" { + t.Errorf("%v\nLibraries(%s) parsing diff (-want +got):\n%s", test.desc, test.cql, diff) + } + }) + } +} + +func TestParserMultipleLibraries(t *testing.T) { + tests := []struct { + name string + cql string + want *model.Library + }{ + { + name: "IncludeDef without called", + cql: `include example.helpers1 version '1.0'`, + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "helpers1", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + }, + }, + { + name: "IncludeDef without version", + cql: `include example.helpers2 called Helpers`, + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers2", + }, + }, + }, + }, + }, + { + name: "IncludeDef with called", + cql: `include example.helpers1 version '1.0' called Helpers`, + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + }, + }, + { + name: "IncludeDef multiple includes", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + include example.helpers2 called Helpers2`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers2", + Qualified: "example.helpers2", + }, + }, + }, + }, + }, + { + name: "Global Reference Parameter", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + define X: Helpers."public param"`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Expression: &model.ParameterRef{Name: "public param", LibraryName: "Helpers", Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + { + name: "Global Reference CodeSystem", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + define X: Helpers."public codesystem"`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.CodeSystem}, + Expression: &model.CodeSystemRef{Name: "public codesystem", LibraryName: "Helpers", Expression: model.ResultType(types.CodeSystem)}, + }, + }, + }, + }, + }, + { + name: "Global Reference ValueSet", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + define X: Helpers."public valueset"`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.ValueSet}, + Expression: &model.ValuesetRef{Name: "public valueset", LibraryName: "Helpers", Expression: model.ResultType(types.ValueSet)}, + }, + }, + }, + }, + }, + { + name: "InvocationExpressionTerm Global Reference", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + define X: Helpers."public def"`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Expression: &model.ExpressionRef{Name: "public def", LibraryName: "Helpers", Expression: model.ResultType(types.Integer)}, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + { + name: "InvocationExpressionTerm Global Reference with Property", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + define X: Helpers."fhir def".status`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Expression: &model.Property{ + Source: &model.ExpressionRef{Name: "fhir def", LibraryName: "Helpers", Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}})}, + Path: "status", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.ObservationStatus"}}), + }, + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.ObservationStatus"}}}, + }, + }, + }, + }, + }, + { + name: "QualifiedIdentifierExpression Global Reference", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + define X: from Helpers."public def" P`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Expression: &model.Query{ + Expression: model.ResultType(types.Integer), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "P", + Source: &model.ExpressionRef{Name: "public def", LibraryName: "Helpers", Expression: model.ResultType(types.Integer)}, + Expression: model.ResultType(types.Integer), + }}, + }, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + { + name: "QualifiedIdentifierExpression Global Reference with Property", + cql: dedent.Dedent(` + include example.helpers1 version '1.0' called Helpers + define X: from (4) P return Helpers."public interval".high`), + want: &model.Library{ + Includes: []*model.Include{ + { + Identifier: &model.LibraryIdentifier{ + Local: "Helpers", + Qualified: "example.helpers1", + Version: "1.0", + }, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "X", + AccessLevel: "PUBLIC", + Expression: &model.Query{ + Expression: model.ResultType(types.Integer), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "P", + Source: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + Return: &model.ReturnClause{ + Distinct: true, + Expression: &model.Property{ + Source: &model.ExpressionRef{Name: "public interval", LibraryName: "Helpers", Expression: model.ResultType(&types.Interval{PointType: types.Integer})}, + Path: "high", + Expression: model.ResultType(types.Integer), + }, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + libs := []string{ + dedent.Dedent(` + library example.helpers1 version '1.0' + using FHIR version '4.0.1' + parameter "public param" default 1 + codesystem "public codesystem": 'url-codesystem' + valueset "public valueset": 'url-valueset' + context Patient + define public "public def": 2 + define public "public interval": Interval[1, 2] + define private "private def": 3 + define public "fhir def": [Observation]`), + dedent.Dedent(` + library example.helpers2 + define public "public def": 4 + define private "private def": 5 `), + test.cql, + } + + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), libs, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + var gotLib *model.Library + for _, l := range parsedLibs { + if l.Identifier == nil { + gotLib = l + break + } + } + if diff := cmp.Diff(test.want, gotLib); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestParameters(t *testing.T) { + tests := []struct { + name string + passedParams map[result.DefKey]string + want map[result.DefKey]model.IExpression + }{ + { + name: "Literal Int", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "lit", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "4", + }, + want: map[result.DefKey]model.IExpression{ + result.DefKey{ + Name: "lit", + Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: model.NewLiteral("4", types.Integer)}, + }, + { + name: "Literal String", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "lit", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "'Hello'", + }, + want: map[result.DefKey]model.IExpression{ + result.DefKey{ + Name: "lit", + Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: model.NewLiteral("Hello", types.String)}, + }, + { + name: "List", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "list", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "{1, 2}", + }, + want: map[result.DefKey]model.IExpression{ + result.DefKey{Name: "list", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + }, + }, + }, + { + name: "Interval", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "interval", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + }, + want: map[result.DefKey]model.IExpression{ + result.DefKey{ + Name: "interval", + Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := newFHIRParser(t).Parameters(context.Background(), tc.passedParams, Config{}) + if err != nil { + t.Fatalf("Parse Parameters returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestMalformedParameters(t *testing.T) { + tests := []struct { + name string + passedParams map[result.DefKey]string + errContains string + }{ + { + name: "InvocationTerm", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "lit", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "invo", + }, + errContains: "must be a interval", + }, + { + name: "Expression Definition", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "lit", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "define population: 4", + }, + errContains: "must be a single", + }, + { + name: "Multiple Literals", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "lit", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "4 15", + }, + errContains: "must be a single", + }, + { + name: "No Literals", + passedParams: map[result.DefKey]string{ + result.DefKey{Name: "lit", Library: result.LibKey{Name: "Highly.Qualified", Version: "1.0"}}: "", + }, + errContains: "mismatched input", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := newFHIRParser(t).Parameters(context.Background(), tc.passedParams, Config{}) + if err == nil { + t.Fatalf("Parameters() did not fail parsing") + } + + if !strings.Contains(err.Error(), tc.errContains) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", err.Error(), tc.errContains) + } + }) + } +} + +func TestRealisticCQL(t *testing.T) { + tests := []struct { + name string + cql string + want *model.Library + }{ + { + name: "M1", + cql: dedent.Dedent(` + library Milestones version '1.0.0' + using FHIR version '4.0.1' + context Patient + + define Gender: Patient.gender + `), + want: &model.Library{ + Identifier: &model.LibraryIdentifier{ + Local: "Milestones", + Qualified: "Milestones", + Version: "1.0.0", + }, + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Patient", + Context: "Patient", + AccessLevel: "PRIVATE", + Expression: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + }, + }, + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.Patient"}}, + }, + &model.ExpressionDef{ + Name: "Gender", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Property{ + Source: &model.ExpressionRef{Name: "Patient", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"})}, + Path: "gender", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.AdministrativeGender"}), + }, + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.AdministrativeGender"}}, + }, + }, + }, + }, + }, + { + name: "AgeInYearsAt", + cql: dedent.Dedent(` + library TrivialTest version '1.2.3' + using FHIR version '4.0.1' + parameter "Measurement Period" Interval + context Patient + define "Initial Population": + AgeInYearsAt(end of "Measurement Period") in Interval[45, 65] + `), + want: &model.Library{ + Identifier: &model.LibraryIdentifier{ + Local: "TrivialTest", + Qualified: "TrivialTest", + Version: "1.2.3", + }, + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Parameters: []*model.ParameterDef{ + &model.ParameterDef{ + Name: "Measurement Period", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: &types.Interval{PointType: types.Date}}, + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Patient", + Context: "Patient", + AccessLevel: "PRIVATE", + Expression: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Retrieve{ + + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + }, + }, + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.Patient"}}, + }, + &model.ExpressionDef{ + Name: "Initial Population", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.CalculateAgeAt{ + // AgeInYears system function converted to a calculation that gets the birthdate + BinaryExpression: &model.BinaryExpression{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: types.Integer}, + }, + Operands: []model.IExpression{ + &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.ParameterRef{Name: "Measurement Period", Expression: model.ResultType(&types.Interval{PointType: types.Date})}, + Expression: model.ResultType(types.Date), + }, + }, + }, + }, + Precision: model.YEAR, + }, + &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + Low: model.NewLiteral("45", types.Integer), + High: model.NewLiteral("65", types.Integer), + LowInclusive: true, + HighInclusive: true, + }}, + Expression: model.ResultType(types.Boolean), + }, + }, + Element: &model.Element{ResultType: types.Boolean}, + }, + }, + }, + }, + }, + { + name: "M2_HasCoronaryHeartDisease", + cql: dedent.Dedent(` + library Measure version '1.2.3' + using FHIR version '4.0.1' + + valueset "Coronary arteriosclerosis": 'url-valueset-coronary' version '1.0.0' + valueset "Blood pressure": 'url-valueset-blood-pressure' version '1.0.0' + + parameter "Measurement Period" + default Interval[@2023-04-01T00:00:00.0, @2024-03-31T00:00:00.0) + + context Patient + + define "Has coronary heart disease": + exists ( + [Condition: "Coronary arteriosclerosis"] chd + where chd.onset as FHIR.dateTime before start of "Measurement Period" + ) + + define "Most recent blood pressure reading": + Last( + [Observation: "Blood pressure"] bp + where bp.status in {'final', 'amended', 'corrected'} + and bp.effective in "Measurement Period" + sort by effective desc + ) + + define "Most recent blood pressure reading below 150": + "Most recent blood pressure reading".value < 150 + + define "Initial Population": + AgeInYearsAt(start of "Measurement Period") > 80 + + define "Denominator": + "Initial Population" + and "Has coronary heart disease" + + define "Numerator": + "Initial Population" + and "Denominator" + and "Most recent blood pressure reading below 150" + `), + want: &model.Library{ + Identifier: &model.LibraryIdentifier{ + Local: "Measure", + Qualified: "Measure", + Version: "1.2.3", + }, + Usings: []*model.Using{ + &model.Using{ + LocalIdentifier: "FHIR", + Version: "4.0.1", + URI: "http://hl7.org/fhir", + }, + }, + Parameters: []*model.ParameterDef{ + &model.ParameterDef{ + Element: &model.Element{ResultType: &types.Interval{PointType: types.DateTime}}, + Name: "Measurement Period", + AccessLevel: "PUBLIC", + Default: &model.Interval{ + Low: model.NewLiteral("@2023-04-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2024-03-31T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + }, + }, + Valuesets: []*model.ValuesetDef{ + { + Name: "Coronary arteriosclerosis", + ID: "url-valueset-coronary", + Version: "1.0.0", + Element: &model.Element{ResultType: types.ValueSet}, + AccessLevel: "PUBLIC", + }, + { + Name: "Blood pressure", + ID: "url-valueset-blood-pressure", + Version: "1.0.0", + Element: &model.Element{ResultType: types.ValueSet}, + AccessLevel: "PUBLIC", + }, + }, + Statements: &model.Statements{ + Defs: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "Patient", + Context: "Patient", + AccessLevel: "PRIVATE", + Expression: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + }, + }, + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.Patient"}}, + }, + &model.ExpressionDef{ + Name: "Has coronary heart disease", + Context: "Patient", + AccessLevel: "PUBLIC", + Expression: &model.Exists{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Query{ + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "chd", + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Condition", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Condition", + CodeProperty: "code", + Codes: &model.ValuesetRef{Name: "Coronary arteriosclerosis", Expression: model.ResultType(types.ValueSet)}, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Condition"}}), + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Condition"}}), + }, + }, + Where: &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.FunctionRef{ + Expression: model.ResultType(types.DateTime), + Name: "ToDateTime", + LibraryName: "FHIRHelpers", + Operands: []model.IExpression{ + &model.As{ + AsTypeSpecifier: &types.Named{TypeName: "FHIR.dateTime"}, + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.dateTime"}), + Operand: &model.Property{ + Source: &model.AliasRef{Name: "chd", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Condition"})}, + Path: "onset", + Expression: model.ResultType(&types.Choice{ + ChoiceTypes: []types.IType{ + &types.Named{TypeName: "FHIR.dateTime"}, + &types.Named{TypeName: "FHIR.Age"}, + &types.Named{TypeName: "FHIR.Period"}, + &types.Named{TypeName: "FHIR.Range"}, + &types.Named{TypeName: "FHIR.string"}, + }, + }), + }, + }, + }, + }, + }, + + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.ParameterRef{Name: "Measurement Period", Expression: model.ResultType(&types.Interval{PointType: types.DateTime})}, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Condition"}}), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + Element: &model.Element{ResultType: types.Boolean}, + }, + &model.ExpressionDef{ + Name: "Most recent blood pressure reading", + Context: "Patient", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.Observation"}}, + Expression: &model.Last{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Observation"}), + Operand: &model.Query{ + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "bp", + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Codes: &model.ValuesetRef{ + Name: "Blood pressure", + Expression: model.ResultType(types.ValueSet), + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + Where: &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.FunctionRef{ + Expression: model.ResultType(types.String), + Name: "ToString", + LibraryName: "FHIRHelpers", + Operands: []model.IExpression{ + &model.Property{ + Source: &model.AliasRef{Name: "bp", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Observation"})}, + Path: "status", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.ObservationStatus"}), + }, + }, + }, + &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.String}), + List: []model.IExpression{ + model.NewLiteral("final", types.String), + model.NewLiteral("amended", types.String), + model.NewLiteral("corrected", types.String), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.FunctionRef{ + Expression: model.ResultType(types.DateTime), + Name: "ToDateTime", + LibraryName: "FHIRHelpers", + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.dateTime"}), + Operand: &model.Property{ + Source: &model.AliasRef{Name: "bp", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Observation"})}, + Path: "effective", + Expression: model.ResultType(&types.Choice{ + ChoiceTypes: []types.IType{ + &types.Named{TypeName: "FHIR.dateTime"}, + &types.Named{TypeName: "FHIR.Period"}, + &types.Named{TypeName: "FHIR.Timing"}, + &types.Named{TypeName: "FHIR.instant"}, + }}), + }, + }, + AsTypeSpecifier: &types.Named{TypeName: "FHIR.dateTime"}, + }, + }, + }, + &model.ParameterRef{Name: "Measurement Period", Expression: model.ResultType(&types.Interval{PointType: types.DateTime})}, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + }, + }, + Sort: &model.SortClause{ + ByItems: []model.ISortByItem{ + &model.SortByColumn{ + SortByItem: &model.SortByItem{ + Direction: model.DESCENDING, + }, + Path: "effective", + }, + }, + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + }, + }, + &model.ExpressionDef{ + Name: "Most recent blood pressure reading below 150", + Context: "Patient", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Boolean}, + Expression: &model.Less{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.FunctionRef{ + Expression: model.ResultType(types.Integer), + Name: "ToInteger", + LibraryName: "FHIRHelpers", + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.integer"}), + Operand: &model.Property{ + Source: &model.ExpressionRef{Name: "Most recent blood pressure reading", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Observation"})}, + Path: "value", + Expression: model.ResultType(&types.Choice{ + ChoiceTypes: []types.IType{ + &types.Named{TypeName: "FHIR.Quantity"}, + &types.Named{TypeName: "FHIR.CodeableConcept"}, + &types.Named{TypeName: "FHIR.string"}, + &types.Named{TypeName: "FHIR.boolean"}, + &types.Named{TypeName: "FHIR.integer"}, + &types.Named{TypeName: "FHIR.Range"}, + &types.Named{TypeName: "FHIR.Ratio"}, + &types.Named{TypeName: "FHIR.SampledData"}, + &types.Named{TypeName: "FHIR.time"}, + &types.Named{TypeName: "FHIR.dateTime"}, + &types.Named{TypeName: "FHIR.Period"}, + }, + }), + }, + }, + AsTypeSpecifier: &types.Named{TypeName: "FHIR.integer"}, + }, + }, + }, + model.NewLiteral("150", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + &model.ExpressionDef{ + Name: "Initial Population", + Context: "Patient", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Boolean}, + Expression: &model.Greater{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + + &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + }, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.ParameterRef{Name: "Measurement Period", Expression: model.ResultType(&types.Interval{PointType: types.DateTime})}, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + }, + Precision: model.YEAR, + }, + model.NewLiteral("80", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + &model.ExpressionDef{ + Name: "Denominator", + Context: "Patient", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Boolean}, + Expression: &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.ExpressionRef{Name: "Initial Population", Expression: model.ResultType(types.Boolean)}, + &model.ExpressionRef{Name: "Has coronary heart disease", Expression: model.ResultType(types.Boolean)}, + }, + }, + }, + }, + &model.ExpressionDef{ + Name: "Numerator", + Context: "Patient", + AccessLevel: "PUBLIC", + Element: &model.Element{ResultType: types.Boolean}, + Expression: &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.ExpressionRef{Name: "Initial Population", Expression: model.ResultType(types.Boolean)}, + &model.ExpressionRef{Name: "Denominator", Expression: model.ResultType(types.Boolean)}, + }, + }, + }, + &model.ExpressionRef{Name: "Most recent blood pressure reading below 150", Expression: model.ResultType(types.Boolean)}, + }, + }, + }, + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), []string{test.cql}, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, parsedLibs[0]); diff != "" { + t.Errorf("Parsing diff (-want +got):\n%s", diff) + } + }) + } +} diff --git a/parser/operator_expressions.go b/parser/operator_expressions.go new file mode 100644 index 0000000..e6e273c --- /dev/null +++ b/parser/operator_expressions.go @@ -0,0 +1,659 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/cql/internal/convert" + "github.com/google/cql/internal/embeddata/third_party/cqframework/cql" + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/antlr4-go/antlr/v4" +) + +// VisitTimingExpression expressions related to comparing one timing expression with another. +// Structured as expression, intervalOperatorPhrase, expression. +func (v *visitor) VisitTimingExpression(ctx *cql.TimingExpressionContext) model.IExpression { + // TODO(b/298104070): support other interval operator features, and refactor BeforeOrAfterInterval + // to its own function. + // Need to support the 7 remaining operators in third_party/cql/internal/embeddata/cqframework/Cql.g4 + var fnOperator string + var precision model.DateTimePrecision + intervalOperator := ctx.GetChild(1) + // if relativeOffset exists we need to wrap the right operand with the quantity offset information. + var relativeOffset string + var quantity model.Quantity + switch operator := intervalOperator.(type) { + case *cql.BeforeOrAfterIntervalOperatorPhraseContext: + var err error + if operator.QuantityOffset() != nil { + qo := operator.QuantityOffset() + quantity, err = v.VisitQuantityContext(qo.Quantity()) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + rq := qo.OffsetRelativeQualifier().GetText() + + if strings.Contains(rq, "or more") { + relativeOffset = "OrMore" + } else if strings.Contains(rq, "or less") { + relativeOffset = "OrLess" + } else { + return v.badExpression("internal error - grammar should not allow this offsetRelativeQualifier", ctx) + } + } + precision = precisionFromContext(operator) + opText := operator.GetText() + containsOnOr := strings.Contains(opText, "on or") || strings.Contains(opText, "or on") + containsAfter := strings.Contains(opText, "after") + containsBefore := strings.Contains(opText, "before") + if containsOnOr && containsBefore { + fnOperator = "SameOrBefore" + } else if containsOnOr && containsAfter { + fnOperator = "SameOrAfter" + } else if containsAfter { + fnOperator = "After" + } else if containsBefore { + fnOperator = "Before" + } else { + return v.badExpression("internal error - grammar should not allow this TimeBoundaryExpression", ctx) + } + case *cql.IncludedInIntervalOperatorPhraseContext: + precision = precisionFromContext(operator) + fnOperator = "IncludedIn" + case *cql.ConcurrentWithIntervalOperatorPhraseContext: + precision = precisionFromContext(operator) + // TODO(b/298104070): Support ConcurrentWithIntervalOperatorPhraseContext without 'or' + rq := operator.RelativeQualifier() + if rq == nil { + return v.badExpression("unsupported interval operator in timing expression", ctx) + } + opText := rq.GetText() + if strings.Contains(opText, "after") { + fnOperator = "SameOrAfter" + } else if strings.Contains(opText, "before") { + fnOperator = "SameOrBefore" + } else { + return v.badExpression("internal error - grammar should not allow this TimeBoundaryExpression", ctx) + } + default: + return v.badExpression("unsupported interval operator in timing expression", ctx) + } + + if precision != "" { + fnOperator = funcNameWithPrecision(fnOperator, precision) + } + m, err := v.parseFunction("", fnOperator, []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + // If the first node of this interval operator phrase expression is starts, ends, or occurs, we + // need to wrap the left operand. We do not need to do this for the right operator as it arrives + // here already wrapped in an `end` ANTLR node. + // Only some intervalOperatorPhrase expressions may optionally start with starts, ends, or occurs: + // https://cql.hl7.org/19-l-cqlsyntaxdiagrams.html#intervalOperatorPhrase + if n, ok := intervalOperator.GetChild(0).(antlr.TerminalNode); ok { + be, ok := m.(model.IBinaryExpression) + if !ok { + return v.badExpression("internal error -- timing expression did not produce a BinaryExpression", ctx) + } + switch n.GetText() { + case "starts": + startExpr, err := v.resolveFunction("", "Start", []model.IExpression{be.Left()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + be.SetOperands(startExpr, be.Right()) + case "ends": + endExpr, err := v.resolveFunction("", "End", []model.IExpression{be.Left()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + be.SetOperands(endExpr, be.Right()) + case "occurs": + // TODO(b/331923068): support occurs. In many cases this may be a no op. + return v.badExpression("'occurs' is not yet supported in timing expressions", ctx) + } + } + + if relativeOffset != "" { + return v.constructRelativeOffsetModel(ctx, m, &quantity, fnOperator, relativeOffset) + } + return m +} + +// constructRelativeOffsetModel constructs a custom In model when a relative offset operator exists. +// We only perform these operations in some cases for the beforeOrAfterIntervalOperatorPhrase all +// other operators shouldn't set the relativeOffset. In cases where the arguments are not temporal +// we need to perform some conversions to get the nested operands to the same types. +func (v *visitor) constructRelativeOffsetModel(ctx *cql.TimingExpressionContext, m model.IExpression, quantity *model.Quantity, fnOperator, relativeOffset string) model.IExpression { + be, ok := m.(model.IBinaryExpression) + if !ok { + return v.badExpression("internal error -- timing expression did not produce a BinaryExpression", ctx) + } + r := be.Right() + l := be.Left() + switch relativeOffset { + case "OrMore": + switch fnOperator { + case "Before", "SameOrBefore": + // Attempt to apply the following transformations + // Interval[a, b] -> Interval[MinValue(Interval.PointType()), Start(Interval[a,b]) - quantityOffset] + // value -> Interval[MinValue(value.ResultType()), value - quantityOffset] + r, err := v.wrapIntervalInExpr(r, &model.Start{}) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + subtract, err := v.resolveFunction("", "Subtract", []model.IExpression{r, quantity}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + resultType := subtract.GetResultType() + r = &model.Interval{ + Low: &model.MinValue{ValueType: resultType, Expression: model.ResultType(resultType)}, + High: subtract, + LowInclusive: true, + HighInclusive: fnOperator == "SameOrBefore", + Expression: model.ResultType(&types.Interval{PointType: resultType}), + } + inExpr, err := v.resolveFunction("", "In", []model.IExpression{l, r}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return inExpr + case "After", "SameOrAfter": + // Attempt to apply the following transformations + // Interval[a, b] -> Interval[End(Interval[a, b]) + quantityOffset, MaxValue(End(Interval.PointType())] + // value -> Interval[value + quantityOffset, MaxValue(value.ResultType())] + r, err := v.wrapIntervalInExpr(r, &model.End{}) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + add, err := v.resolveFunction("", "Add", []model.IExpression{r, quantity}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + resultType := add.GetResultType() + r = &model.Interval{ + Low: add, + High: &model.MaxValue{ValueType: resultType, Expression: model.ResultType(resultType)}, + LowInclusive: fnOperator == "SameOrAfter", + HighInclusive: true, + Expression: model.ResultType(&types.Interval{PointType: resultType}), + } + inExpr, err := v.resolveFunction("", "In", []model.IExpression{l, r}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return inExpr + } + return v.badExpression(fmt.Sprintf("internal error - got invalid function operator name when evaluating 'or more' operator: %s", fnOperator), ctx) + case "OrLess": + switch fnOperator { + case "Before", "SameOrBefore": + // Attempt to apply the following transformations + // Interval[a, b] -> Interval[Start(Interval[a, b]) - quantityOffset, Start(Interval[a,b])] + // value -> Interval[value - quantityOffset, value] + wrappedR, err := v.wrapIntervalInExpr(r, &model.Start{}) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + subtract, err := v.resolveFunction("", "Subtract", []model.IExpression{wrappedR, quantity}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + resultType := subtract.GetResultType() + wrappedR, err = v.implicitConvertExpression(wrappedR, subtract.GetResultType()) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + wrappedR = &model.Interval{ + Low: subtract, + High: wrappedR, + LowInclusive: true, + HighInclusive: fnOperator == "SameOrBefore", + Expression: model.ResultType(&types.Interval{PointType: resultType}), + } + inExpr, err := v.resolveFunction("", "In", []model.IExpression{l, wrappedR}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return inExpr + case "After", "SameOrAfter": + // Attempt to apply the following transformations + // Interval[a, b] -> Interval[End(Interval[a, b]), End(Interval[a,b]) + quantityOffset] + // value -> Interval[value, value + quantityOffset] + wrappedR, err := v.wrapIntervalInExpr(r, &model.End{}) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + add, err := v.resolveFunction("", "Add", []model.IExpression{wrappedR, quantity}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + resultType := add.GetResultType() + wrappedR, err = v.implicitConvertExpression(wrappedR, add.GetResultType()) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + wrappedR = &model.Interval{ + Low: wrappedR, + High: add, + LowInclusive: true, + HighInclusive: fnOperator == "SameOrAfter", + Expression: model.ResultType(&types.Interval{PointType: resultType}), + } + inExpr, err := v.resolveFunction("", "In", []model.IExpression{l, wrappedR}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return inExpr + } + return v.badExpression(fmt.Sprintf("internal error - got invalid function operator name when evaluating 'or less' operator: %s", fnOperator), ctx) + } + return v.badExpression("internal error - grammar should not allow this TimeBoundaryExpression", ctx) +} + +// implicitConvertExpression converts an expression to a desired type if result types don't already match. +func (v *visitor) implicitConvertExpression(expr model.IExpression, desiredType types.IType) (model.IExpression, error) { + exprType := expr.GetResultType() + if desiredType == exprType { + return expr, nil + } + converted, err := convert.OperandImplicitConverter(exprType, desiredType, expr, v.modelInfo) + if err != nil { + return nil, err + } + if !converted.Matched { + return nil, fmt.Errorf("internal error - implicit conversion of unable to convert operator to: %v, got: %v", desiredType, exprType) + } + return converted.WrappedOperand, nil +} + +// wrapIntervalInExpr if passed an interval expression, wraps it in the desired expression. +func (v *visitor) wrapIntervalInExpr(expr model.IExpression, wrapper model.IExpression) (model.IExpression, error) { + switch expr.GetResultType().(type) { + case *types.Interval: + if _, ok := wrapper.(*model.Start); ok { + return v.resolveFunction("", "Start", []model.IExpression{expr}, false) + } else if _, ok := wrapper.(*model.End); ok { + return v.resolveFunction("", "End", []model.IExpression{expr}, false) + } else { + return nil, fmt.Errorf("internal error - tried to wrap interval expression in unsupported expression type: %v", expr) + } + } + return expr, nil +} + +func (v *visitor) VisitExistenceExpression(ctx *cql.ExistenceExpressionContext) model.IExpression { + m, err := v.parseFunction("", "Exists", []antlr.Tree{ctx.Expression()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitTimeBoundaryExpressionTerm(ctx *cql.TimeBoundaryExpressionTermContext) model.IExpression { + name := ctx.GetChild(0).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch name { + case "start": + m, err = v.parseFunction("", "Start", []antlr.Tree{ctx.GetChild(2)}, false) + case "end": + m, err = v.parseFunction("", "End", []antlr.Tree{ctx.GetChild(2)}, false) + default: + return v.badExpression("internal error - grammar should not allow this TimeBoundaryExpression", ctx) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + return m +} + +func (v *visitor) VisitMembershipExpression(ctx *cql.MembershipExpressionContext) model.IExpression { + op := ctx.GetChild(1).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch op { + case "in": + funcName := "In" + if r := v.VisitExpression(ctx.Expression(1)).GetResultType(); r == types.CodeSystem { + funcName = "InCodeSystem" + } else if r == types.ValueSet { + funcName = "InValueSet" + } + m, err = v.parseFunction("", funcName, []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case "contains": + m, err = v.parseFunction("", "Contains", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + precision := precisionFromContext(ctx) + switch r := m.(type) { + case *model.In: + r.Precision = precision + return r + case *model.Contains: + r.Precision = precision + return r + case *model.InCodeSystem, *model.InValueSet: + return r + } + + // Grammar shouldn't let us get here. + return v.badExpression(fmt.Sprintf("unsupported membership expression: %v", op), ctx) +} + +func (v *visitor) VisitEqualityExpression(ctx *cql.EqualityExpressionContext) model.IExpression { + name := ctx.GetChild(1).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch name { + case "=", "!=": + m, err = v.parseFunction("", "Equal", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case "~", "!~": + m, err = v.parseFunction("", "Equivalent", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + default: + return v.badExpression("internal error - grammar should not allow this EqualityExpression", ctx) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + switch name { + case "!=", "!~": + return &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Operand: m, + Expression: model.ResultType(types.Boolean), + }, + } + } + + return m +} + +func (v *visitor) VisitInequalityExpression(ctx *cql.InequalityExpressionContext) model.IExpression { + name := ctx.GetChild(1).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch name { + case "<": + m, err = v.parseFunction("", "Less", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case ">": + m, err = v.parseFunction("", "Greater", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case "<=": + m, err = v.parseFunction("", "LessOrEqual", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + case ">=": + m, err = v.parseFunction("", "GreaterOrEqual", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + default: + return v.badExpression("internal error - grammar should not allow this InequalityExpression", ctx) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + + return m +} + +func (v *visitor) VisitBooleanExpressionContext(ctx *cql.BooleanExpressionContext) model.IExpression { + not := false + var is string + if ctx.GetChild(2).(*antlr.TerminalNodeImpl).GetText() == "not" { + not = true + is = ctx.GetChild(3).(*antlr.TerminalNodeImpl).GetText() + } else { + is = ctx.GetChild(2).(*antlr.TerminalNodeImpl).GetText() + } + + var m model.IExpression + var err error + switch is { + case "null": + m, err = v.parseFunction("", "IsNull", []antlr.Tree{ctx.Expression()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + case "false": + m, err = v.parseFunction("", "IsFalse", []antlr.Tree{ctx.Expression()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + case "true": + m, err = v.parseFunction("", "IsTrue", []antlr.Tree{ctx.Expression()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + } + + if not { + return &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: m, + }, + } + } + + return m +} + +func (v *visitor) VisitAdditionExpressionTerm(ctx *cql.AdditionExpressionTermContext) model.IExpression { + name := ctx.GetChild(1).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch name { + case "+": + m, err = v.parseFunction("", "Add", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + case "-": + m, err = v.parseFunction("", "Subtract", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + case "&": + m, err = v.parseConcatenate(v.VisitExpression(ctx.GetChild(0)), v.VisitExpression(ctx.GetChild(2))) + default: + return v.badExpression("internal error - grammar should not allow this AdditionExpressionTerm", ctx) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +// When concatenating with &, null arguments are treated as empty strings. This is handled by +// wrapping Coalesce operators around each operand. & does not have a way to be called as a function +// so we handle without calling parseFunction. The Concatenate() function does not convert null to +// empty strings. +func (v *visitor) parseConcatenate(left model.IExpression, right model.IExpression) (model.IExpression, error) { + overload := []convert.Overload[func() model.IExpression]{ + { + Operands: []types.IType{types.String, types.String}, + Result: func() model.IExpression { + return &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.String), + }, + } + }, + }, + } + matched, err := convert.OverloadMatch([]model.IExpression{left, right}, overload, v.modelInfo, "&") + if err != nil { + return nil, err + } + + m, ok := matched.Result().(*model.Concatenate) + if !ok { + return nil, errors.New("internal error - resolving concatenate returned unexpected type") + } + m.SetOperands([]model.IExpression{ + &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{matched.WrappedOperands[0], model.NewLiteral("", types.String)}, + Expression: model.ResultType(types.String), + }, + }, + &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{matched.WrappedOperands[1], model.NewLiteral("", types.String)}, + Expression: model.ResultType(types.String), + }, + }, + }) + return m, nil +} + +func (v *visitor) VisitMultiplicationExpressionTerm(ctx *cql.MultiplicationExpressionTermContext) model.IExpression { + name := ctx.GetChild(1).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch name { + case "*": + m, err = v.parseFunction("", "Multiply", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + case "/": + m, err = v.parseFunction("", "Divide", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + case "mod": + m, err = v.parseFunction("", "Modulo", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + case "div": + m, err = v.parseFunction("", "TruncatedDivide", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + default: + return v.badExpression("internal error - grammar should not allow this MultiplicationExpressionTerm", ctx) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitNotExpression(ctx *cql.NotExpressionContext) model.IExpression { + m, err := v.parseFunction("", "Not", []antlr.Tree{ctx.Expression()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitAndExpression(ctx *cql.AndExpressionContext) model.IExpression { + m, err := v.parseFunction("", "And", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitOrExpression(ctx *cql.OrExpressionContext) model.IExpression { + op := "Or" + if ctx.GetChild(1).(antlr.TerminalNode).GetText() == "xor" { + op = "Xor" + } + m, err := v.parseFunction("", op, []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitImpliesExpression(ctx *cql.ImpliesExpressionContext) model.IExpression { + m, err := v.parseFunction("", "Implies", []antlr.Tree{ctx.Expression(0), ctx.Expression(1)}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitInFixSetExpression(ctx *cql.InFixSetExpressionContext) model.IExpression { + name := ctx.GetChild(1).(antlr.TerminalNode).GetText() + var m model.IExpression + var err error + switch name { + case "|", "union": + m, err = v.parseFunction("", "Union", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + case "intersect": + m, err = v.parseFunction("", "Intersect", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + case "except": + m, err = v.parseFunction("", "Except", []antlr.Tree{ctx.GetChild(0), ctx.GetChild(2)}, false) + default: + return v.badExpression("internal error - grammar should not allow this InFixSetExpression", ctx) + } + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitElementExtractorExpressionTerm(ctx *cql.ElementExtractorExpressionTermContext) model.IExpression { + m, err := v.parseFunction("", "SingletonFrom", []antlr.Tree{ctx.ExpressionTerm()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +// TODO(b/310991895) Add support for `difference in X of`. +func (v *visitor) VisitDifferenceBetweenExpression(ctx *cql.DifferenceBetweenExpressionContext) model.IExpression { + precision := stringToPrecision(pluralToSingularDateTimePrecision(ctx.PluralDateTimePrecision().GetText())) + op := "DifferenceBetween" + if precision != "" { + op = funcNameWithPrecision(op, precision) + } + m, err := v.parseFunction("", op, []antlr.Tree{ctx.ExpressionTerm(0), ctx.ExpressionTerm(1)}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitPolarityExpressionTerm(ctx *cql.PolarityExpressionTermContext) model.IExpression { + if ctx.GetChild(0).(antlr.TerminalNode).GetText() == "+" { + return v.VisitExpression(ctx.ExpressionTerm()) + } + + // If polarity is negative we need to check if the nested expression is intended to be the minimum + // value for integers and longs. + expr := ctx.ExpressionTerm() + expr.GetText() + if expr.GetText() == "2147483648" { + return model.NewLiteral("-2147483648", types.Integer) + } else if expr.GetText() == "9223372036854775808L" { + return model.NewLiteral("-9223372036854775808L", types.Long) + } + m, err := v.parseFunction("", "Negate", []antlr.Tree{expr}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitPredecessorExpressionTerm(ctx *cql.PredecessorExpressionTermContext) model.IExpression { + m, err := v.parseFunction("", "Predecessor", []antlr.Tree{ctx.ExpressionTerm()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} + +func (v *visitor) VisitSuccessorExpressionTerm(ctx *cql.SuccessorExpressionTermContext) model.IExpression { + m, err := v.parseFunction("", "Successor", []antlr.Tree{ctx.ExpressionTerm()}, false) + if err != nil { + return v.badExpression(err.Error(), ctx) + } + return m +} diff --git a/parser/operator_expressions_test.go b/parser/operator_expressions_test.go new file mode 100644 index 0000000..05814ee --- /dev/null +++ b/parser/operator_expressions_test.go @@ -0,0 +1,1144 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "context" + "errors" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" +) + +func TestOperatorExpressions(t *testing.T) { + tests := []struct { + name string + desc string + cql string + want model.IExpression + }{ + { + name: "Concatenate &", + cql: "'Hi' & 'Hello'", + want: &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{model.NewLiteral("Hi", types.String), model.NewLiteral("", types.String)}, + Expression: model.ResultType(types.String), + }, + }, + &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{model.NewLiteral("Hello", types.String), model.NewLiteral("", types.String)}, + Expression: model.ResultType(types.String), + }, + }, + }, + Expression: model.ResultType(types.String), + }, + }, + }, + { + name: "Concatenate +", + cql: "'Hi' + 'Hello'", + want: &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("Hi", types.String), + model.NewLiteral("Hello", types.String), + }, + Expression: model.ResultType(types.String), + }, + }, + }, + { + name: "Arithmetic Addition", + cql: "1 + 2", + want: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Subtraction", + cql: "1 - 2", + want: &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Multiplication", + cql: "1 * 2", + want: &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Modulo with different types", + cql: "40L mod 3", + want: &model.Modulo{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("40L", types.Long), + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("3", types.Integer), + Expression: model.ResultType(types.Long), + }, + }, + }, + Expression: model.ResultType(types.Long), + }, + }, + }, + { + name: "Arithmetic Truncated Divide", + cql: "40 div 3", + want: &model.TruncatedDivide{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("40", types.Integer), + model.NewLiteral("3", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Divide Integer by Decimal", + cql: "40 / 3.1234567", + want: &model.Divide{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("40", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + model.NewLiteral("3.1234567", types.Decimal), + }, + Expression: model.ResultType(types.Decimal), + }, + }, + }, + { + name: "Arithmetic Order Of Operations", + cql: "1 * 3 + 5", + want: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("3", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Order Of Operations 2", + cql: "1 + 3 * 5", + want: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("3", types.Integer), + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Negate", + cql: "-4", + want: &model.Negate{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "+ does not negate", + cql: "+4", + want: model.NewLiteral("4", types.Integer), + }, + { + name: "Timing Expression with temporal Interval Operator (before)", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) before start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "TimingExpression On Or Before", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) on or before start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "TimingExpression Before Or On Start Of Interval", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) before or on start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "TimingExpression After Start Of Interval", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) after start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.After{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "TimingExpression After Or On Start Of Interval", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) after or on start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + LowInclusive: true, + HighInclusive: false, + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "TimingExpression On Or After Start Of Interval", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) on or after start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "TimingExpression On Or After Start Of Interval With Precision", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) on or after year of start of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.SameOrAfter{ + Precision: model.YEAR, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "BeforeOrAfterIntervalOperatorPhraseContext Date 1 Year Or Less On Or Before Date", + cql: "@2020 1 year or less on or before @2022", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020", types.Date), + &model.Interval{ + Low: &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2022", types.Date), + &model.Quantity{Value: 1, Unit: "year", Expression: model.ResultType(types.Quantity)}, + }, + Expression: model.ResultType(types.Date), + }, + }, + High: model.NewLiteral("@2022", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "BeforeOrAfterIntervalOperatorPhraseContext Interval Starts 1 Year Or Less On Or Before End Of Interval", + cql: "Interval[@2022, @2024] starts 1 year or less on or after end of Interval[@2020, @2022]", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2022", "@2024", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + &model.Interval{ + Low: &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2020", "@2022", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + High: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2020", "@2022", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + &model.Quantity{Value: 1, Unit: "year", Expression: model.ResultType(types.Quantity)}, + }, + Expression: model.ResultType(types.Date), + }, + }, + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "BeforeOrAfterIntervalOperatorPhraseContext Date 1 Year Or More On Or After End Of Interval", + cql: "@2022 1 year or more on or after end of Interval[@2020, @2022]", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2022", types.Date), + &model.Interval{ + Low: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2020", "@2022", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + &model.Quantity{Value: 1, Unit: "year", Expression: model.ResultType(types.Quantity)}, + }, + Expression: model.ResultType(types.Date), + }, + }, + High: &model.MaxValue{ValueType: types.Date, Expression: model.ResultType(types.Date)}, + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "BeforeOrAfterIntervalOperatorPhraseContext Date 1 Year Or More On Or Before Start Of Interval", + cql: "@2022 1 year or more on or before start of Interval[@2020, @2022]", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2022", types.Date), + &model.Interval{ + Low: &model.MinValue{ValueType: types.Date, Expression: model.ResultType(types.Date)}, + High: &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2020", "@2022", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + &model.Quantity{Value: 1, Unit: "year", Expression: model.ResultType(types.Quantity)}, + }, + Expression: model.ResultType(types.Date), + }, + }, + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Difference in Months Between @2014-01-01 and @2014-02-01", + cql: "difference in months between @2014-01-01 and @2014-02-01", + want: &model.DifferenceBetween{ + Precision: model.MONTH, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2014-01-01", types.Date), + model.NewLiteral("@2014-02-01", types.Date), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "1 included in Interval[0, 5] returns in expresssion", + cql: "1 included in Interval[0, 5]", + // Since left operator is of point type, model should be implicitly converted to model.In. + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + &model.Interval{ + Low: model.NewLiteral("0", types.Integer), + High: model.NewLiteral("5", types.Integer), + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Interval[@2012, @2014] during Interval[@2010, @2020]", + cql: "Interval[@2012, @2014] during Interval[@2010, @2020]", + want: &model.IncludedIn{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2012", types.Date), + High: model.NewLiteral("@2014", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + &model.Interval{ + Low: model.NewLiteral("@2010", types.Date), + High: model.NewLiteral("@2020", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IsNull", + cql: "null is null", + want: &model.IsNull{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IsFalse", + cql: "null is false", + want: &model.IsFalse{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Boolean, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IsTrue", + cql: "null is true", + want: &model.IsTrue{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Boolean, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IsTrue with Not", + cql: "null is not true", + want: &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: &model.IsTrue{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Boolean, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + }, + }, + { + name: "Less", + cql: "15 < 10", + want: &model.Less{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("15", types.Integer), + model.NewLiteral("10", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Greater", + cql: "15 > 10", + want: &model.Greater{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("15", types.Integer), + model.NewLiteral("10", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "LessOrEqual", + cql: "15 <= 10", + want: &model.LessOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("15", types.Integer), + model.NewLiteral("10", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "GreaterOrEqual", + cql: "15 >= 10", + want: &model.GreaterOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("15", types.Integer), + model.NewLiteral("10", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Equal", + cql: "1 = 1", + want: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Equivalent", + cql: "1 ~ 1", + want: &model.Equivalent{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "TimeBoundaryExpression End Of Interval", + cql: "end of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + { + name: "MembershipExpression In With Precision", + cql: "@2013-01-01T00:00:00.0 in year of Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.In{ + Precision: model.YEAR, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "MembershipExpression In Without Precision", + cql: "@2013-01-01T00:00:00.0 in Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0)", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "MembershipExpression Contains With Precision", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) contains year of @2013-01-01T00:00:00.0", + want: &model.Contains{ + Precision: model.YEAR, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "MembershipExpression Contains Without Precision", + cql: "Interval[@2013-01-01T00:00:00.0, @2014-01-01T00:00:00.0) contains @2013-01-01T00:00:00.0", + want: &model.Contains{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + High: model.NewLiteral("@2014-01-01T00:00:00.0", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: false, + }, + model.NewLiteral("@2013-01-01T00:00:00.0", types.DateTime), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "not", + cql: "not true", + want: &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("true", types.Boolean), + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "And", + cql: "true and false", + want: &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("false", types.Boolean), + }, + }, + }, + }, + { + name: "Or", + cql: "false or true", + want: &model.Or{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("false", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + }, + { + name: "XOr", + cql: "true xor false", + want: &model.XOr{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("false", types.Boolean), + }, + }, + }, + }, + { + name: "Implies", + cql: "false implies true", + want: &model.Implies{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("false", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + }, + { + name: "Except", + cql: "{1, 4} except {'hi'}", + want: &model.Except{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Operands: []model.IExpression{ + model.NewList([]string{"1", "4"}, types.Integer), + model.NewList([]string{"hi"}, types.String), + }, + }, + }, + }, + { + name: "Intersect", + cql: "{1, 4} intersect {1}", + want: &model.Intersect{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Operands: []model.IExpression{ + model.NewList([]string{"1", "4"}, types.Integer), + model.NewList([]string{"1"}, types.Integer), + }, + }, + }, + }, + { + name: "Union", + cql: "{1, 4} union {1}", + want: &model.Union{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Operands: []model.IExpression{ + model.NewList([]string{"1", "4"}, types.Integer), + model.NewList([]string{"1"}, types.Integer), + }, + }, + }, + }, + { + name: "Union | syntax", + cql: "{1} | {1}", + want: &model.Union{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Operands: []model.IExpression{ + model.NewList([]string{"1"}, types.Integer), + model.NewList([]string{"1"}, types.Integer), + }, + }, + }, + }, + { + name: "SingletonFrom non-functional syntax", + cql: "singleton from {1}", + want: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), wrapInLib(t, test.cql), Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, getTESTRESULTModel(t, parsedLibs)); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestOperatorExpressions_Errors(t *testing.T) { + tests := []struct { + name string + cql string + errContains []string + errCount int + }{ + { + name: "invalid addition overload", + cql: `2 + @2024`, + errContains: []string{"could not resolve Add(System.Integer, System.Date)"}, + errCount: 1, + }, + { + name: "invalid division overload", + cql: `2 / @2024`, + errContains: []string{"could not resolve Divide(System.Integer, System.Date)"}, + errCount: 1, + }, + { + name: "intersect with no common types", + cql: `{3} intersect {@2024}`, + errContains: []string{"no common types between System.Integer and System.Date"}, + errCount: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := newFHIRParser(t).Libraries(context.Background(), wrapInLib(t, test.cql), Config{}) + if err == nil { + t.Fatal("Parsing succeeded, expected error") + } + + var pe *LibraryErrors + if ok := errors.As(err, &pe); ok { + for _, ec := range test.errContains { + if !strings.Contains(pe.Error(), ec) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", + pe.Error(), ec) + } + } + + if len(pe.Errors) != test.errCount { + t.Errorf("Returned error (%s) had (%d) errors but expected (%d)", + err.Error(), len(pe.Errors), test.errCount) + } + } else { + t.Errorf("Unexpected test error (%s).", err.Error()) + } + }) + } +} + +func TestClinicalOperatorExpressions(t *testing.T) { + tests := []struct { + name string + cql string + want model.IExpression + }{ + { + name: "Code in CodeSystem", + cql: dedent.Dedent(` + codesystem cs: 'ExampleCodeSystem' + code c: '1234' from cs + using FHIR version '4.0.1' + context Patient + define TESTRESULT: c in cs`), + want: &model.InCodeSystem{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.CodeRef{Name: "c", Expression: model.ResultType(types.Code)}, + &model.CodeSystemRef{Name: "cs", Expression: model.ResultType(types.CodeSystem)}, + }, + Expression: &model.Expression{Element: &model.Element{ResultType: types.Boolean}}, + }, + }, + }, + { + name: "InCodeSystem(Code, CodeSystemRef)", + cql: dedent.Dedent(` + codesystem cs: 'ExampleCodeSystem' + code c: '1234' from cs + using FHIR version '4.0.1' + context Patient + define TESTRESULT: InCodeSystem(c, cs)`), + want: &model.InCodeSystem{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.CodeRef{Name: "c", Expression: model.ResultType(types.Code)}, + &model.CodeSystemRef{Name: "cs", Expression: model.ResultType(types.CodeSystem)}, + }, + Expression: &model.Expression{Element: &model.Element{ResultType: types.Boolean}}, + }, + }, + }, + { + name: "Code in ValueSet", + cql: dedent.Dedent(` + codesystem cs: 'ExampleCodeSystem' + valueset vs: 'ExampleValueset' + code c: '1234' from cs + using FHIR version '4.0.1' + context Patient + define TESTRESULT: c in vs`), + want: &model.InValueSet{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.CodeRef{Name: "c", Expression: model.ResultType(types.Code)}, + &model.ValuesetRef{Name: "vs", Expression: model.ResultType(types.ValueSet)}, + }, + Expression: &model.Expression{Element: &model.Element{ResultType: types.Boolean}}, + }, + }, + }, + { + name: "InValueSet(Code, ValueSetRef)", + cql: dedent.Dedent(` + codesystem cs: 'ExampleCodeSystem' + valueset vs: 'ExampleValueset' + code c: '1234' from cs + using FHIR version '4.0.1' + context Patient + define TESTRESULT: InValueSet(c, vs)`), + want: &model.InValueSet{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.CodeRef{Name: "c", Expression: model.ResultType(types.Code)}, + &model.ValuesetRef{Name: "vs", Expression: model.ResultType(types.ValueSet)}, + }, + Expression: &model.Expression{Element: &model.Element{ResultType: types.Boolean}}, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), []string{test.cql}, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, getTESTRESULTModel(t, parsedLibs)); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} diff --git a/parser/operators.go b/parser/operators.go new file mode 100644 index 0000000..a4820dd --- /dev/null +++ b/parser/operators.go @@ -0,0 +1,1957 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/cql/internal/convert" + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/antlr4-go/antlr/v4" +) + +// parseFunction uses the reference resolver to resolve the function, visits the operands, and sets +// the operands in the model. For some built-in functions it also sets the ResultType. libraryName +// should be an empty string for local functions. +func (v *visitor) parseFunction(libraryName, funcName string, ctxOperands []antlr.Tree, calledFluently bool) (model.IExpression, error) { + operands := make([]model.IExpression, 0) + for _, expr := range ctxOperands { + o := v.VisitExpression(expr) + operands = append(operands, o) + } + return v.resolveFunction(libraryName, funcName, operands, calledFluently) +} + +// resolveFunction uses the reference resolver to resolve the function, perform implicit +// conversions, and set the wrapped operands in the model.IExpression. For some built-in functions +// it also sets the ResultType. libraryName should be an empty string for local functions. +// This function takes operands as []model.IExpression, whereas parseFunction takes un parsed antlr +// trees as operands. +func (v *visitor) resolveFunction(libraryName, funcName string, operands []model.IExpression, calledFluently bool) (model.IExpression, error) { + var resolved *convert.MatchedOverload[func() model.IExpression] + var err error + if libraryName != "" { + resolved, err = v.refs.ResolveGlobalFunc(libraryName, funcName, operands, calledFluently, v.modelInfo) + } else { + resolved, err = v.refs.ResolveLocalFunc(funcName, operands, calledFluently, v.modelInfo) + } + if err != nil { + return nil, err + } + + // Handle special cases such as setting the result type based on an operand. + r := resolved.Result() + switch t := r.(type) { + case *model.Coalesce: + return v.parseCoalesce(t, resolved.WrappedOperands) + case *model.Message: + if len(resolved.WrappedOperands) != 5 { + return nil, errors.New("internal error - resolving message function returned incorrect argument") + } + t.Source = resolved.WrappedOperands[0] + t.Condition = resolved.WrappedOperands[1] + t.Code = resolved.WrappedOperands[2] + t.Severity = resolved.WrappedOperands[3] + t.Message = resolved.WrappedOperands[4] + t.Expression = model.ResultType(resolved.WrappedOperands[0].GetResultType()) + case *model.Except: + // For Except the left side is the result type. + t.Expression = model.ResultType(resolved.WrappedOperands[0].GetResultType()) + case *model.Intersect: + listTypeLeft := resolved.WrappedOperands[0].GetResultType().(*types.List) + listTypeRight := resolved.WrappedOperands[1].GetResultType().(*types.List) + listElemType, err := convert.Intersect(listTypeLeft.ElementType, listTypeRight.ElementType) + if err != nil { + return nil, err + } + t.Expression = model.ResultType(&types.List{ElementType: listElemType}) + case *model.Union: + listTypeLeft := resolved.WrappedOperands[0].GetResultType().(*types.List) + listTypeRight := resolved.WrappedOperands[1].GetResultType().(*types.List) + listElemType, err := convert.DeDuplicate([]types.IType{listTypeLeft.ElementType, listTypeRight.ElementType}) + if err != nil { + return nil, err + } + t.Expression = model.ResultType(&types.List{ElementType: listElemType}) + case *model.End: + pointType := resolved.WrappedOperands[0].GetResultType().(*types.Interval) + t.Expression = model.ResultType(pointType.PointType) + case *model.Start: + pointType := resolved.WrappedOperands[0].GetResultType().(*types.Interval) + t.Expression = model.ResultType(pointType.PointType) + case *model.First: + // First(List) T is a special case because the ResultType is not known until invocation. + listType := resolved.WrappedOperands[0].GetResultType().(*types.List) + t.Expression = model.ResultType(listType.ElementType) + case *model.Last: + // Last(List) T is a special case because the ResultType is not known until invocation. + listType := resolved.WrappedOperands[0].GetResultType().(*types.List) + t.Expression = model.ResultType(listType.ElementType) + case *model.Predecessor: + t.Expression = model.ResultType(resolved.WrappedOperands[0].GetResultType()) + case *model.Successor: + t.Expression = model.ResultType(resolved.WrappedOperands[0].GetResultType()) + case *model.SingletonFrom: + // SingletonFrom(List) T is a special case because the ResultType is not known until invocation. + listType := resolved.WrappedOperands[0].GetResultType().(*types.List) + t.Expression = model.ResultType(listType.ElementType) + case *model.CalculateAge: + // AgeInYears() is a special case as it takes 0 operands but the model.CalculateAge has 1 + // operand, the patient's birthday. + bday, err := v.patientBirthDateExpression() + if err != nil { + return nil, err + } + + // Currently the FHIR modelinfo Patient Birthday Expression returns System.Date. However, in + // case in the future it returns something else, try to convert to System.DateTime to match the + // AgeInYears(DateTime) overload. + res, err := convert.OperandImplicitConverter(bday.GetResultType(), types.DateTime, bday, v.modelInfo) + if err != nil { + return nil, err + } + if !res.Matched { + return nil, fmt.Errorf("internal error - could not implicitly convert the Patient Birthday Expression of type %v to %v", bday.GetResultType(), types.DateTime) + } + + resolved.WrappedOperands = []model.IExpression{res.WrappedOperand} + case *model.CalculateAgeAt: + if len(resolved.WrappedOperands) == 1 { + // AgeInYearsAt(asOf Date) is a special case as it takes 1 operand but the + // model.CalculateAgeAt has 2 operand, the patient's birthday. + bday, err := v.patientBirthDateExpression() + if err != nil { + return nil, err + } + + // For FHIR modelinfo the Patient Birthday Expression should be System.Date so we may need to + // convert it to DateTime to match the AgeInYearsAt(DateTime, DateTime) overload. + res, err := convert.OperandImplicitConverter(bday.GetResultType(), resolved.WrappedOperands[0].GetResultType(), bday, v.modelInfo) + if err != nil { + return nil, err + } + if !res.Matched { + return nil, fmt.Errorf("internal error - could not implicitly convert the Patient Birthday Expression of type %v to %v", bday.GetResultType(), resolved.WrappedOperands[0].GetResultType()) + } + + // The operands should be AgeInYearsAt(convertedBirthDate) + resolved.WrappedOperands = []model.IExpression{res.WrappedOperand, resolved.WrappedOperands[0]} + } + } + + // Set Operands. + switch t := r.(type) { + case model.IUnaryExpression: + t.SetOperand(resolved.WrappedOperands[0]) + case model.IBinaryExpression: + t.SetOperands(resolved.WrappedOperands[0], resolved.WrappedOperands[1]) + return r, nil + case model.INaryExpression: + t.SetOperands(resolved.WrappedOperands) + case *model.FunctionRef: + t.LibraryName = libraryName + t.Operands = resolved.WrappedOperands + return r, nil + case *model.Message: + // Message is not a function or a *nary expression but extends + // Expression directly + return r, nil + default: + return nil, errors.New("internal error - resolving function returned an unsupported type") + } + + return r, nil +} + +// loadSystemOperators defines all CQL System Operators in the reference resolver. The operands +// are not set here, but are instead set when we parse the function invocation in VisitFunction. For +// some System Operators like Last(List) T we also set the return type in VisitFunction as the +// return type is not known until the function is invoked. +func (p *Parser) loadSystemOperators() error { + systemOperators := []struct { + // name is the function name. + name string + // operands holds all of the overloads for this function name. For example: + // Foo(A Integer, B Integer) + // Foo(A Integer, B Long) + // Operands: [[Integer, Integer], [Integer, Long]] + operands [][]types.IType + model func() model.IExpression + }{ + // LOGICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#logical-operators-3 + { + name: "And", + operands: [][]types.IType{ + {types.Boolean, types.Boolean}, + }, + model: func() model.IExpression { + return &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Implies", + operands: [][]types.IType{ + {types.Boolean, types.Boolean}, + }, + model: func() model.IExpression { + return &model.Implies{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Not", + operands: [][]types.IType{ + {types.Boolean}, + }, + model: func() model.IExpression { + return &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Or", + operands: [][]types.IType{ + {types.Boolean, types.Boolean}, + }, + model: func() model.IExpression { + return &model.Or{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Xor", + operands: [][]types.IType{ + {types.Boolean, types.Boolean}, + }, + model: func() model.IExpression { + return &model.XOr{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + // TYPE OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#type-operators-1 + { + name: "CanConvertQuantity", + operands: [][]types.IType{ + {types.Quantity, types.String}, + }, + model: func() model.IExpression { + return &model.CanConvertQuantity{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "ToBoolean", + operands: [][]types.IType{ + {types.Boolean}, + {types.Decimal}, + {types.Long}, + {types.Integer}, + {types.String}}, + model: func() model.IExpression { + return &model.ToBoolean{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "ToDateTime", + operands: [][]types.IType{ + {types.DateTime}, + {types.Date}, + {types.String}}, + model: func() model.IExpression { + return &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + }, + } + }, + }, + { + name: "ToDate", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + {types.String}}, + model: func() model.IExpression { + return &model.ToDate{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Date), + }, + } + }, + }, + { + name: "ToDecimal", + operands: [][]types.IType{ + {types.Decimal}, + {types.Long}, + {types.Integer}, + {types.String}, + {types.Boolean}}, + model: func() model.IExpression { + return &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Decimal), + }, + } + }, + }, + { + name: "ToLong", + operands: [][]types.IType{ + {types.Long}, + {types.Integer}, + {types.String}, + {types.Boolean}}, + model: func() model.IExpression { + return &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Long), + }, + } + }, + }, + { + name: "ToInteger", + operands: [][]types.IType{ + {types.Integer}, + {types.Long}, + {types.String}, + {types.Boolean}}, + model: func() model.IExpression { + return &model.ToInteger{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + }, + } + }, + }, + { + name: "ToQuantity", + operands: [][]types.IType{ + {types.Quantity}, + {types.Decimal}, + {types.Integer}, + {types.Ratio}, + {types.String}}, + model: func() model.IExpression { + return &model.ToQuantity{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Quantity), + }, + } + }, + }, + { + name: "ToConcept", + operands: [][]types.IType{ + {types.Code}, + {&types.List{ElementType: types.Code}}}, + model: func() model.IExpression { + return &model.ToConcept{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Concept), + }, + } + }, + }, + { + name: "ToString", + operands: [][]types.IType{ + {types.String}, + {types.Integer}, + {types.Long}, + {types.Decimal}, + {types.Quantity}, + {types.Ratio}, + {types.Date}, + {types.DateTime}, + {types.Time}, + {types.Boolean}}, + model: func() model.IExpression { + return &model.ToString{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.String), + }, + } + }, + }, + { + name: "ToTime", + operands: [][]types.IType{ + {types.Time}, + {types.String}}, + model: func() model.IExpression { + return &model.ToTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Time), + }, + } + }, + }, + // NULLOGICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#nullological-operators-3 + { + name: "Coalesce", + operands: [][]types.IType{ + {convert.GenericType, convert.GenericType}, + {convert.GenericType, convert.GenericType, convert.GenericType}, + {convert.GenericType, convert.GenericType, convert.GenericType, convert.GenericType}, + {convert.GenericType, convert.GenericType, convert.GenericType, convert.GenericType, convert.GenericType}, + {&types.List{ElementType: types.Any}}, + }, + model: func() model.IExpression { + return &model.Coalesce{ + NaryExpression: &model.NaryExpression{}, + } + }, + }, + { + name: "IsNull", + operands: [][]types.IType{ + {types.Any}, + }, + model: func() model.IExpression { + return &model.IsNull{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "IsFalse", + operands: [][]types.IType{ + {types.Boolean}, + }, + model: func() model.IExpression { + return &model.IsFalse{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "IsTrue", + operands: [][]types.IType{ + {types.Boolean}, + }, + model: func() model.IExpression { + return &model.IsTrue{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + // COMPARISON OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#comparison-operators-4 + { + name: "Equal", + operands: [][]types.IType{ + {convert.GenericType, convert.GenericType}, + }, + model: func() model.IExpression { + return &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Equivalent", + operands: [][]types.IType{ + {convert.GenericType, convert.GenericType}, + }, + model: func() model.IExpression { + return &model.Equivalent{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Less", + operands: [][]types.IType{ + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Long, types.Long}, + []types.IType{types.Decimal, types.Decimal}, + []types.IType{types.Quantity, types.Quantity}, + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + []types.IType{types.String, types.String}, + }, + model: func() model.IExpression { + return &model.Less{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Greater", + operands: [][]types.IType{ + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Long, types.Long}, + []types.IType{types.Decimal, types.Decimal}, + []types.IType{types.Quantity, types.Quantity}, + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + []types.IType{types.String, types.String}, + }, + model: func() model.IExpression { + return &model.Greater{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "LessOrEqual", + operands: [][]types.IType{ + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Long, types.Long}, + []types.IType{types.Decimal, types.Decimal}, + []types.IType{types.Quantity, types.Quantity}, + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + []types.IType{types.String, types.String}, + }, + model: func() model.IExpression { + return &model.LessOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "GreaterOrEqual", + operands: [][]types.IType{ + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Long, types.Long}, + []types.IType{types.Decimal, types.Decimal}, + []types.IType{types.Quantity, types.Quantity}, + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + []types.IType{types.String, types.String}, + }, + model: func() model.IExpression { + return &model.GreaterOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + // ARITHMETIC OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#arithmetic-operators-4 + { + name: "Add", + operands: [][]types.IType{{types.Integer, types.Integer}}, + model: addModel(types.Integer), + }, + { + name: "Add", + operands: [][]types.IType{{types.Long, types.Long}}, + model: addModel(types.Long), + }, + { + name: "Add", + operands: [][]types.IType{{types.Decimal, types.Decimal}}, + model: addModel(types.Decimal), + }, + { + name: "Add", + operands: [][]types.IType{{types.Quantity, types.Quantity}}, + model: addModel(types.Quantity), + }, + { + name: "Negate", + operands: [][]types.IType{{types.Integer}}, + model: negateModel(types.Integer), + }, + { + name: "Negate", + operands: [][]types.IType{{types.Long}}, + model: negateModel(types.Long), + }, + { + name: "Negate", + operands: [][]types.IType{{types.Decimal}}, + model: negateModel(types.Decimal), + }, + { + name: "Negate", + operands: [][]types.IType{{types.Quantity}}, + model: negateModel(types.Quantity), + }, + { + name: "Subtract", + operands: [][]types.IType{{types.Integer, types.Integer}}, + model: subtractModel(types.Integer), + }, + { + name: "Subtract", + operands: [][]types.IType{{types.Long, types.Long}}, + model: subtractModel(types.Long), + }, + { + name: "Subtract", + operands: [][]types.IType{{types.Decimal, types.Decimal}}, + model: subtractModel(types.Decimal), + }, + { + name: "Subtract", + operands: [][]types.IType{{types.Quantity, types.Quantity}}, + model: subtractModel(types.Quantity), + }, + { + name: "Multiply", + operands: [][]types.IType{{types.Integer, types.Integer}}, + model: multiplyModel(types.Integer), + }, + { + name: "Multiply", + operands: [][]types.IType{{types.Long, types.Long}}, + model: multiplyModel(types.Long), + }, + { + name: "Multiply", + operands: [][]types.IType{{types.Decimal, types.Decimal}}, + model: multiplyModel(types.Decimal), + }, + { + name: "Multiply", + operands: [][]types.IType{{types.Quantity, types.Quantity}}, + model: multiplyModel(types.Quantity), + }, + { + name: "Divide", + operands: [][]types.IType{{types.Decimal, types.Decimal}}, + model: divideModel(types.Decimal), + }, + { + name: "Divide", + operands: [][]types.IType{{types.Quantity, types.Quantity}}, + model: divideModel(types.Quantity), + }, + { + name: "TruncatedDivide", + operands: [][]types.IType{{types.Integer, types.Integer}}, + model: truncatedDivideModel(types.Integer), + }, + { + name: "TruncatedDivide", + operands: [][]types.IType{{types.Long, types.Long}}, + model: truncatedDivideModel(types.Long), + }, + { + name: "TruncatedDivide", + operands: [][]types.IType{{types.Decimal, types.Decimal}}, + model: truncatedDivideModel(types.Decimal), + }, + { + name: "TruncatedDivide", + operands: [][]types.IType{{types.Quantity, types.Quantity}}, + model: truncatedDivideModel(types.Quantity), + }, + { + name: "Modulo", + operands: [][]types.IType{{types.Integer, types.Integer}}, + model: modModel(types.Integer), + }, + { + name: "Modulo", + operands: [][]types.IType{{types.Long, types.Long}}, + model: modModel(types.Long), + }, + { + name: "Modulo", + operands: [][]types.IType{{types.Decimal, types.Decimal}}, + model: modModel(types.Decimal), + }, + { + name: "Modulo", + operands: [][]types.IType{{types.Quantity, types.Quantity}}, + model: modModel(types.Quantity), + }, + { + name: "Predecessor", + operands: [][]types.IType{ + {types.Integer}, + {types.Long}, + {types.Decimal}, + {types.Quantity}, + {types.Date}, + {types.DateTime}, + {types.Time}, + }, + model: func() model.IExpression { + return &model.Predecessor{ + UnaryExpression: &model.UnaryExpression{}, + } + }, + }, + { + name: "Successor", + operands: [][]types.IType{ + {types.Integer}, + {types.Long}, + {types.Decimal}, + {types.Quantity}, + {types.Date}, + {types.DateTime}, + {types.Time}, + }, + model: func() model.IExpression { + return &model.Successor{ + UnaryExpression: &model.UnaryExpression{}, + } + }, + }, + // STRING OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#string-operators-3 + // It is not clear whether Add(String, String) is a supported system operator overload. + { + name: "Add", + operands: [][]types.IType{ + {types.String, types.String}, + }, + model: func() model.IExpression { + return &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.String), + }, + } + }, + }, + { + name: "Concatenate", + operands: [][]types.IType{ + {types.String, types.String}, + }, + model: func() model.IExpression { + return &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.String), + }, + } + }, + }, + // DATE AND TIME OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#datetime-operators-2 + { + name: "Add", + operands: [][]types.IType{{types.Date, types.Quantity}}, + model: addModel(types.Date), + }, + { + name: "Add", + operands: [][]types.IType{{types.DateTime, types.Quantity}}, + model: addModel(types.DateTime), + }, + { + name: "Add", + operands: [][]types.IType{{types.Time, types.Quantity}}, + model: addModel(types.Time), + }, + { + name: "After", + // See generatePrecisionTimingOverloads() for more overloads. + operands: [][]types.IType{ + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + }, + model: func() model.IExpression { + return &model.After{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Before", + // See generatePrecisionTimingOverloads() for more overloads. + operands: [][]types.IType{ + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + }, + model: func() model.IExpression { + return &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Date", + operands: [][]types.IType{ + []types.IType{types.Integer}, + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer}, + }, + model: func() model.IExpression { + return &model.Date{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.Date), + }, + } + }, + }, + { + name: "DateTime", + operands: [][]types.IType{ + []types.IType{types.Integer}, + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Integer, types.Decimal}, + }, + model: func() model.IExpression { + return &model.DateTime{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.DateTime), + }, + } + }, + }, + { + name: "Now", + operands: [][]types.IType{{}}, + model: func() model.IExpression { + return &model.Now{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.DateTime), + }, + } + }, + }, + { + name: "TimeOfDay", + operands: [][]types.IType{{}}, + model: func() model.IExpression { + return &model.TimeOfDay{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.Time), + }, + } + }, + }, + { + name: "SameOrAfter", + // See generatePrecisionTimingOverloads() for more overloads. + operands: [][]types.IType{ + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + }, + model: func() model.IExpression { + return &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "SameOrBefore", + // See generatePrecisionTimingOverloads() for more overloads. + operands: [][]types.IType{ + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + }, + model: func() model.IExpression { + return &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Subtract", + operands: [][]types.IType{{types.Date, types.Quantity}}, + model: subtractModel(types.Date), + }, + { + name: "Subtract", + operands: [][]types.IType{{types.DateTime, types.Quantity}}, + model: subtractModel(types.DateTime), + }, + { + name: "Subtract", + operands: [][]types.IType{{types.Time, types.Quantity}}, + model: subtractModel(types.Time), + }, + { + name: "Time", + operands: [][]types.IType{ + []types.IType{types.Integer}, + []types.IType{types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer}, + []types.IType{types.Integer, types.Integer, types.Integer, types.Integer}, + }, + model: func() model.IExpression { + return &model.Time{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.Time), + }, + } + }, + }, + { + name: "Today", + operands: [][]types.IType{{}}, + model: func() model.IExpression { + return &model.Today{ + NaryExpression: &model.NaryExpression{ + Expression: model.ResultType(types.Date), + }, + } + }, + }, + // INTERVAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#interval-operators-3 + { + name: "After", + // See generatePrecisionTimingOverloads() for more overloads. + operands: comparableIntervalOverloads, + model: func() model.IExpression { + return &model.After{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Before", + operands: comparableIntervalOverloads, + model: func() model.IExpression { + return &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Contains", + operands: [][]types.IType{ + {convert.GenericList, convert.GenericType}, + // TODO(b/301606416): Add support for ContainsYears, ContainsDays... + []types.IType{&types.Interval{PointType: types.Integer}, types.Integer}, + []types.IType{&types.Interval{PointType: types.Long}, types.Long}, + []types.IType{&types.Interval{PointType: types.Decimal}, types.Decimal}, + []types.IType{&types.Interval{PointType: types.Quantity}, types.Quantity}, + []types.IType{&types.Interval{PointType: types.String}, types.String}, + []types.IType{&types.Interval{PointType: types.Date}, types.Date}, + []types.IType{&types.Interval{PointType: types.DateTime}, types.DateTime}, + []types.IType{&types.Interval{PointType: types.Time}, types.Time}, + }, + model: func() model.IExpression { + return &model.Contains{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "End", + operands: [][]types.IType{ + {&types.Interval{PointType: types.Any}}, + }, + model: func() model.IExpression { + return &model.End{ + UnaryExpression: &model.UnaryExpression{}, + } + }, + }, + { + name: "In", + operands: [][]types.IType{ + {convert.GenericType, convert.GenericList}, + []types.IType{types.Integer, &types.Interval{PointType: types.Integer}}, + []types.IType{types.Long, &types.Interval{PointType: types.Long}}, + []types.IType{types.Decimal, &types.Interval{PointType: types.Decimal}}, + []types.IType{types.Quantity, &types.Interval{PointType: types.Quantity}}, + []types.IType{types.String, &types.Interval{PointType: types.String}}, + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: func() model.IExpression { + return &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "InYears", + operands: [][]types.IType{ + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + }, + model: inModel(model.YEAR), + }, + { + name: "InMonths", + operands: [][]types.IType{ + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + }, + model: inModel(model.MONTH), + }, + { + name: "InDays", + operands: [][]types.IType{ + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + }, + model: inModel(model.DAY), + }, + { + name: "InHours", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.HOUR), + }, + { + name: "InMinutes", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.MINUTE), + }, + { + name: "InSeconds", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.SECOND), + }, + { + name: "InMilliseconds", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.MILLISECOND), + }, + { + name: "IncludedIn", + operands: [][]types.IType{ + // op (left Interval, right Interval) Boolean + []types.IType{&types.Interval{PointType: types.Integer}, &types.Interval{PointType: types.Integer}}, + []types.IType{&types.Interval{PointType: types.Long}, &types.Interval{PointType: types.Long}}, + []types.IType{&types.Interval{PointType: types.Decimal}, &types.Interval{PointType: types.Decimal}}, + []types.IType{&types.Interval{PointType: types.Quantity}, &types.Interval{PointType: types.Quantity}}, + []types.IType{&types.Interval{PointType: types.String}, &types.Interval{PointType: types.String}}, + []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}}, + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}}, + }, + model: func() model.IExpression { + return &model.IncludedIn{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + // Included in for point type overloads is a macro for the In operator. + { + name: "IncludedIn", + operands: [][]types.IType{ + // op (left T, right Interval) Boolean + []types.IType{types.Integer, &types.Interval{PointType: types.Integer}}, + []types.IType{types.Long, &types.Interval{PointType: types.Long}}, + []types.IType{types.Decimal, &types.Interval{PointType: types.Decimal}}, + []types.IType{types.Quantity, &types.Interval{PointType: types.Quantity}}, + []types.IType{types.String, &types.Interval{PointType: types.String}}, + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: func() model.IExpression { + return &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "IncludedInYears", + operands: [][]types.IType{ + []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}}, + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + }, + model: includedInModel(model.YEAR), + }, + { + name: "IncludedInYears", + operands: [][]types.IType{ + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + }, + model: inModel(model.YEAR), + }, + { + name: "IncludedInMonths", + operands: [][]types.IType{ + []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}}, + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + }, + model: includedInModel(model.MONTH), + }, + { + name: "IncludedInMonths", + operands: [][]types.IType{ + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + }, + model: inModel(model.MONTH), + }, + { + name: "IncludedInDays", + operands: [][]types.IType{ + []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}}, + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + }, + model: includedInModel(model.DAY), + }, + { + name: "IncludedInDays", + operands: [][]types.IType{ + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + }, + model: inModel(model.DAY), + }, + { + name: "IncludedInHours", + operands: [][]types.IType{ + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}}, + }, + model: includedInModel(model.HOUR), + }, + { + name: "IncludedInHours", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.HOUR), + }, + { + name: "IncludedInMinutes", + operands: [][]types.IType{ + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}}, + }, + model: includedInModel(model.MINUTE), + }, + { + name: "IncludedInMinutes", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.MINUTE), + }, + { + name: "IncludedInSeconds", + operands: [][]types.IType{ + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}}, + }, + model: includedInModel(model.SECOND), + }, + { + name: "IncludedInSeconds", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.SECOND), + }, + { + name: "IncludedInMilliseconds", + operands: [][]types.IType{ + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}}, + }, + model: includedInModel(model.MILLISECOND), + }, + { + name: "IncludedInMilliseconds", + operands: [][]types.IType{ + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + }, + model: inModel(model.MILLISECOND), + }, + { + name: "SameOrAfter", + // See generatePrecisionTimingOverloads() for more overloads. + operands: comparableIntervalOverloads, + model: func() model.IExpression { + return &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "SameOrBefore", + // See generatePrecisionTimingOverloads() for more overloads. + operands: comparableIntervalOverloads, + model: func() model.IExpression { + return &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "Start", + operands: [][]types.IType{ + {&types.Interval{PointType: types.Any}}, + }, + model: func() model.IExpression { + return &model.Start{ + UnaryExpression: &model.UnaryExpression{}, + } + }, + }, + // LIST OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#list-operators-2 + { + name: "Except", + operands: [][]types.IType{{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}}}, + model: func() model.IExpression { + return &model.Except{ + BinaryExpression: &model.BinaryExpression{}, + } + }, + }, + { + name: "Exists", + operands: [][]types.IType{ + {&types.List{ElementType: types.Any}}}, + model: func() model.IExpression { + return &model.Exists{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "First", + operands: [][]types.IType{ + {&types.List{ElementType: types.Any}}}, + model: func() model.IExpression { + return &model.First{ + UnaryExpression: &model.UnaryExpression{}, + } + }, + }, + { + name: "Intersect", + operands: [][]types.IType{{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}}}, + model: func() model.IExpression { + return &model.Intersect{ + BinaryExpression: &model.BinaryExpression{}, + } + }, + }, + { + name: "Last", + operands: [][]types.IType{ + {&types.List{ElementType: types.Any}}}, + model: func() model.IExpression { + return &model.Last{ + UnaryExpression: &model.UnaryExpression{}, + } + }, + }, + { + name: "SingletonFrom", + operands: [][]types.IType{ + {&types.List{ElementType: types.Any}}}, + model: func() model.IExpression { + return &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{}, + } + }, + }, + { + name: "Union", + operands: [][]types.IType{{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}}}, + model: func() model.IExpression { + return &model.Union{ + BinaryExpression: &model.BinaryExpression{}, + } + }, + }, + // CLINICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#clinical-operators-3 + { + name: "AgeInYears", + operands: [][]types.IType{[]types.IType{}}, + model: calculateAgeModel(model.YEAR), + }, + { + name: "AgeInMonths", + operands: [][]types.IType{[]types.IType{}}, + model: calculateAgeModel(model.MONTH), + }, + { + name: "AgeInWeeks", + operands: [][]types.IType{[]types.IType{}}, + model: calculateAgeModel(model.WEEK), + }, + { + name: "AgeInDays", + operands: [][]types.IType{[]types.IType{}}, + model: calculateAgeModel(model.DAY), + }, + { + name: "AgeInHours", + operands: [][]types.IType{[]types.IType{}}, + model: calculateAgeModel(model.HOUR), + }, + { + name: "AgeInMinutes", + operands: [][]types.IType{[]types.IType{}}, + model: calculateAgeModel(model.MINUTE), + }, + { + name: "AgeInSeconds", + operands: [][]types.IType{[]types.IType{}}, + model: calculateAgeModel(model.SECOND), + }, + { + name: "AgeInYearsAt", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeAtModel(model.YEAR), + }, + { + name: "AgeInMonthsAt", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeAtModel(model.MONTH), + }, + { + name: "AgeInWeeksAt", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeAtModel(model.WEEK), + }, + { + name: "AgeInDaysAt", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeAtModel(model.DAY), + }, + { + name: "AgeInHoursAt", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeAtModel(model.HOUR), + }, + { + name: "AgeInMinutesAt", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeAtModel(model.MINUTE), + }, + { + name: "AgeInSecondsAt", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeAtModel(model.SECOND), + }, + { + name: "CalculateAgeInYears", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeModel(model.YEAR), + }, + { + name: "CalculateAgeInMonths", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeModel(model.MONTH), + }, + { + name: "CalculateAgeInWeeks", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeModel(model.WEEK), + }, + { + name: "CalculateAgeInDays", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeModel(model.DAY), + }, + { + name: "CalculateAgeInHours", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}}, + model: calculateAgeModel(model.HOUR), + }, + { + name: "CalculateAgeInMinutes", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeModel(model.MINUTE), + }, + { + name: "CalculateAgeInSeconds", + operands: [][]types.IType{ + {types.Date}, + {types.DateTime}, + }, + model: calculateAgeModel(model.SECOND), + }, + { + name: "CalculateAgeInYearsAt", + operands: [][]types.IType{ + {types.Date, types.Date}, + {types.DateTime, types.DateTime}, + }, + model: calculateAgeAtModel(model.YEAR), + }, + { + name: "CalculateAgeInMonthsAt", + operands: [][]types.IType{ + {types.Date, types.Date}, + {types.DateTime, types.DateTime}, + }, + model: calculateAgeAtModel(model.MONTH), + }, + { + name: "CalculateAgeInWeeksAt", + operands: [][]types.IType{ + {types.Date, types.Date}, + {types.DateTime, types.DateTime}, + }, + model: calculateAgeAtModel(model.WEEK), + }, + { + name: "CalculateAgeInDaysAt", + operands: [][]types.IType{ + {types.Date, types.Date}, + {types.DateTime, types.DateTime}, + }, + model: calculateAgeAtModel(model.DAY), + }, + { + name: "CalculateAgeInHoursAt", + operands: [][]types.IType{ + {types.DateTime, types.DateTime}, + }, + model: calculateAgeAtModel(model.HOUR), + }, + { + name: "CalculateAgeInMinutesAt", + operands: [][]types.IType{ + {types.DateTime, types.DateTime}, + }, + model: calculateAgeAtModel(model.MINUTE), + }, + { + name: "CalculateAgeInSecondsAt", + operands: [][]types.IType{ + {types.DateTime, types.DateTime}, + }, + model: calculateAgeAtModel(model.SECOND), + }, + { + name: "InCodeSystem", + operands: [][]types.IType{ + {types.Code, types.CodeSystem}, + {&types.List{ElementType: types.Code}, types.CodeSystem}, + {types.Concept, types.CodeSystem}, + {&types.List{ElementType: types.Concept}, types.CodeSystem}, + }, + model: func() model.IExpression { + return &model.InCodeSystem{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + name: "InValueSet", + operands: [][]types.IType{ + {types.Code, types.ValueSet}, + {&types.List{ElementType: types.Code}, types.ValueSet}, + {types.Concept, types.ValueSet}, + {&types.List{ElementType: types.Concept}, types.ValueSet}, + }, + model: func() model.IExpression { + return &model.InValueSet{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + } + }, + }, + { + // ERRORS AND MESSAGING - https://cql.hl7.org/09-b-cqlreference.html#errors-and-messaging + // The ELM for `Message` is states that all arguments besides the Source are optional. + // However, the Docs and the Java engine seem to expect message to only support the 5 + // argument overload. For now we are only supporting the 5 argument variant but we can + // re-address this at a later date. + name: "Message", + operands: [][]types.IType{ + {types.Any, types.Boolean, types.String, types.String, types.String}, + }, + model: func() model.IExpression { + return &model.Message{} + }, + }, + } + + for _, b := range systemOperators { + for _, operand := range b.operands { + if err := p.refs.DefineBuiltinFunc(b.name, operand, b.model); err != nil { + return err + } + } + } + + if err := p.generatePrecisionTimingOverloads(); err != nil { + return err + } + + if err := p.generateDifferenceBetweenOverloads(); err != nil { + return err + } + + return nil +} + +var comparableIntervalOverloads = [][]types.IType{ + // op (left Interval, right Interval) Boolean + []types.IType{&types.Interval{PointType: types.Integer}, &types.Interval{PointType: types.Integer}}, + []types.IType{&types.Interval{PointType: types.Long}, &types.Interval{PointType: types.Long}}, + []types.IType{&types.Interval{PointType: types.Decimal}, &types.Interval{PointType: types.Decimal}}, + []types.IType{&types.Interval{PointType: types.Quantity}, &types.Interval{PointType: types.Quantity}}, + []types.IType{&types.Interval{PointType: types.String}, &types.Interval{PointType: types.String}}, + []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}}, + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}}, + // op (left T, right Interval) Boolean + []types.IType{types.Integer, &types.Interval{PointType: types.Integer}}, + []types.IType{types.Long, &types.Interval{PointType: types.Long}}, + []types.IType{types.Decimal, &types.Interval{PointType: types.Decimal}}, + []types.IType{types.Quantity, &types.Interval{PointType: types.Quantity}}, + []types.IType{types.String, &types.Interval{PointType: types.String}}, + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + // op (left Interval, right T) Boolean + []types.IType{&types.Interval{PointType: types.Integer}, types.Integer}, + []types.IType{&types.Interval{PointType: types.Long}, types.Long}, + []types.IType{&types.Interval{PointType: types.Decimal}, types.Decimal}, + []types.IType{&types.Interval{PointType: types.Quantity}, types.Quantity}, + []types.IType{&types.Interval{PointType: types.String}, types.String}, + []types.IType{&types.Interval{PointType: types.Date}, types.Date}, + []types.IType{&types.Interval{PointType: types.DateTime}, types.DateTime}, + []types.IType{&types.Interval{PointType: types.Time}, types.Time}, +} + +func (p *Parser) generatePrecisionTimingOverloads() error { + overloads := [][]types.IType{ + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + // (left Interval, right Interval) Boolean + []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}}, + []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}}, + []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}}, + // (left T, right Interval) Boolean + []types.IType{types.Date, &types.Interval{PointType: types.Date}}, + []types.IType{types.DateTime, &types.Interval{PointType: types.DateTime}}, + []types.IType{types.Time, &types.Interval{PointType: types.Time}}, + // (left Interval, right T) Boolean + []types.IType{&types.Interval{PointType: types.Date}, types.Date}, + []types.IType{&types.Interval{PointType: types.DateTime}, types.DateTime}, + []types.IType{&types.Interval{PointType: types.Time}, types.Time}, + } + + for _, precision := range dateTimePrecisions() { + name := funcNameWithPrecision("After", precision) + for _, overload := range overloads { + if err := p.refs.DefineBuiltinFunc(name, overload, afterModel(precision)); err != nil { + return err + } + } + } + for _, precision := range dateTimePrecisions() { + name := funcNameWithPrecision("Before", precision) + for _, overload := range overloads { + if err := p.refs.DefineBuiltinFunc(name, overload, beforeModel(precision)); err != nil { + return err + } + } + } + for _, precision := range dateTimePrecisions() { + name := funcNameWithPrecision("SameOrAfter", precision) + for _, overload := range overloads { + if err := p.refs.DefineBuiltinFunc(name, overload, sameOrAfterModel(precision)); err != nil { + return err + } + } + } + for _, precision := range dateTimePrecisions() { + name := funcNameWithPrecision("SameOrBefore", precision) + for _, overload := range overloads { + if err := p.refs.DefineBuiltinFunc(name, overload, sameOrBeforeModel(precision)); err != nil { + return err + } + } + } + return nil +} + +func (p *Parser) generateDifferenceBetweenOverloads() error { + overloads := [][]types.IType{ + []types.IType{types.Date, types.Date}, + []types.IType{types.DateTime, types.DateTime}, + []types.IType{types.Time, types.Time}, + } + + for _, precision := range dateTimePrecisions() { + name := funcNameWithPrecision("DifferenceBetween", precision) + for _, overload := range overloads { + if err := p.refs.DefineBuiltinFunc(name, overload, differenceBetweenModel(precision)); err != nil { + return err + } + } + } + return nil +} + +func differenceBetweenModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.DifferenceBetween{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + }, + Precision: precision, + } + } +} + +func afterModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.After{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + Precision: precision, + } + } +} + +func beforeModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + Precision: precision, + } + } +} + +func sameOrAfterModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + Precision: precision, + } + } +} + +func sameOrBeforeModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + Precision: precision, + } + } +} + +// Returns an expression containing the patient's birth date property, as defined by the model info. +// For FHIR model info this should return a System Date. +func (v *visitor) patientBirthDateExpression() (model.IExpression, error) { + pDate, err := v.modelInfo.PatientBirthDatePropertyName() + if err != nil { + return nil, err + } + // Ideally this should be handled in VisitInvocationExpressionTerm, but this property isn't really + // CQL, and did not go through the ANTLR parser so we handle it here. + // The goal is to turn birthDate.value into + // &model.Property{ + // Source: &model.Property{Source: &model.ExpressionRef{"Patient"}, Path: "birthDate"}, + // Path: "value", + // } + propertyComponents := strings.Split(pDate, ".") + + var source model.IExpression + // This references a statement created by `context Patient` expressions, which must exist + // to access in-context patient properties like this. + sourceFunc, err := v.refs.ResolveLocal("Patient") + if err != nil { + return nil, err + } + source = sourceFunc() + for _, component := range propertyComponents { + propertyType, err := v.modelInfo.PropertyTypeSpecifier(source.GetResultType(), component) + if err != nil { + return nil, err + } + source = &model.Property{ + Source: source, + Path: component, + Expression: model.ResultType(propertyType), + } + } + return source, nil +} + +func calculateAgeModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.CalculateAge{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + }, + Precision: precision, + } + } +} + +func calculateAgeAtModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + }, + Precision: precision, + } + } +} + +func inModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + Precision: precision, + } + } +} + +func includedInModel(precision model.DateTimePrecision) func() model.IExpression { + return func() model.IExpression { + return &model.IncludedIn{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + }, + Precision: precision, + } + } +} + +func addModel(resultType types.System) func() model.IExpression { + return func() model.IExpression { + return &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(resultType), + }, + } + } +} + +func negateModel(resultType types.System) func() model.IExpression { + return func() model.IExpression { + return &model.Negate{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(resultType), + }, + } + } +} + +func subtractModel(resultType types.System) func() model.IExpression { + return func() model.IExpression { + return &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(resultType), + }, + } + } +} + +func multiplyModel(resultType types.System) func() model.IExpression { + return func() model.IExpression { + return &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(resultType), + }, + } + } +} + +func divideModel(resultType types.System) func() model.IExpression { + return func() model.IExpression { + return &model.Divide{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(resultType), + }, + } + } +} + +func truncatedDivideModel(resultType types.System) func() model.IExpression { + return func() model.IExpression { + return &model.TruncatedDivide{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(resultType), + }, + } + } +} + +func modModel(resultType types.System) func() model.IExpression { + return func() model.IExpression { + return &model.Modulo{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(resultType), + }, + } + } +} + +func (v *visitor) parseCoalesce(m *model.Coalesce, operands []model.IExpression) (model.IExpression, error) { + if len(operands) == 1 { + // This is the list overload. + lType, ok := operands[0].GetResultType().(*types.List) + if !ok { + return nil, fmt.Errorf("internal error - Coalesce() overload with one operand should be of type list") + } + m.SetOperands(operands) + m.Expression = model.ResultType(lType.ElementType) + return m, nil + } + + // This does not match the described behaviour of Coalesce in the spec, but based on community + // discussions it is the correct behaviour. + res, err := convert.InferMixed(operands, v.modelInfo) + if err != nil { + return nil, err + } + if res.PuntedToChoice { + return nil, fmt.Errorf("all operands in coalesce(%v) must be implicitly convertible to the same type", convert.OperandsToString(operands)) + } + m.SetOperands(res.WrappedOperands) + m.Expression = model.ResultType(res.UniformType) + return m, nil +} diff --git a/parser/operators_test.go b/parser/operators_test.go new file mode 100644 index 0000000..f503a02 --- /dev/null +++ b/parser/operators_test.go @@ -0,0 +1,1260 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "context" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" +) + +func TestBuiltInFunctions(t *testing.T) { + tests := []struct { + name string + cql string + want model.IExpression + }{ + // LOGICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#logical-operators-3 + { + name: "Not", + cql: "Not(true)", + want: &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("true", types.Boolean), + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "And", + cql: "And(true, false)", + want: &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("false", types.Boolean), + }, + }, + }, + }, + { + name: "Or", + cql: "Or(false, true)", + want: &model.Or{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("false", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + }, + { + name: "XOr", + cql: "Xor(true, false)", + want: &model.XOr{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("false", types.Boolean), + }, + }, + }, + }, + { + name: "Implies", + cql: "Implies(false, true)", + want: &model.Implies{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("false", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + }, + // TYPE OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#type-operators-1 + { + name: "CanConvertQuantity", + cql: dedent.Dedent(`CanConvertQuantity(1 'cm', 'm')`), + want: &model.CanConvertQuantity{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Quantity{Value: 1, Unit: "cm", Expression: model.ResultType(types.Quantity)}, + model.NewLiteral("m", types.String), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "ToBoolean", + cql: "ToBoolean(5)", + want: &model.ToBoolean{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "ToDateTime with Date", + cql: "ToDateTime(@1999-10-01)", + want: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: model.NewLiteral("@1999-10-01", types.Date), + }, + }, + }, + { + name: "ToDateTime with string", + cql: "ToDateTime('@2014-01-25T14:30:14.559')", + want: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: model.NewLiteral("@2014-01-25T14:30:14.559", types.String), + }, + }, + }, + { + name: "ToDate with DateTime", + cql: "ToDate(@2014-01-25T14:30:14.559)", + want: &model.ToDate{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Date), + Operand: model.NewLiteral("@2014-01-25T14:30:14.559", types.DateTime), + }, + }, + }, + { + name: "ToDate with string", + cql: "ToDate('@2014-01-25')", + want: &model.ToDate{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Date), + Operand: model.NewLiteral("@2014-01-25", types.String), + }, + }, + }, + { + name: "ToDecimal", + cql: "ToDecimal(5)", + want: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Decimal), + Operand: model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "ToLong", + cql: "ToLong(5)", + want: &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Long), + Operand: model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "ToInteger", + cql: "ToInteger(5)", + want: &model.ToInteger{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "ToQuantity", + cql: "ToQuantity(5)", + want: &model.ToQuantity{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Quantity), + Operand: model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "ToConcept", + cql: "ToConcept(Code{code: 'foo', system: 'bar', version: '1.0', display: 'severed leg' })", + want: &model.ToConcept{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Concept), + Operand: &model.Instance{ + ClassType: types.Code, + Elements: []*model.InstanceElement{ + &model.InstanceElement{Name: "code", Value: model.NewLiteral("foo", types.String)}, + &model.InstanceElement{Name: "system", Value: model.NewLiteral("bar", types.String)}, + &model.InstanceElement{Name: "version", Value: model.NewLiteral("1.0", types.String)}, + &model.InstanceElement{Name: "display", Value: model.NewLiteral("severed leg", types.String)}, + }, + Expression: model.ResultType(types.Code), + }, + }, + }, + }, + { + name: "ToString", + cql: "ToString(5)", + want: &model.ToString{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.String), + Operand: model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "ToTime", + cql: "ToTime('hello')", + want: &model.ToTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Time), + Operand: model.NewLiteral("hello", types.String), + }, + }, + }, + // NULLOGICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#nullological-operators-3 + { + name: "Coalesce 2 Operands", + cql: "Coalesce(4, 5.0)", + want: &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Decimal), + Operand: model.NewLiteral("4", types.Integer), + }, + }, + model.NewLiteral("5.0", types.Decimal), + }, + Expression: model.ResultType(types.Decimal), + }, + }, + }, + { + name: "Coalesce 3 Operands", + cql: "Coalesce(4, null, 5)", + want: &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("4", types.Integer), + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Integer, + }, + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Coalesce 4 Operands", + cql: "Coalesce(3, 4, 5, 6)", + want: &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("3", types.Integer), + model.NewLiteral("4", types.Integer), + model.NewLiteral("5", types.Integer), + model.NewLiteral("6", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Coalesce 5 Operands", + cql: "Coalesce(3, 4, 5, 6, 7)", + want: &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("3", types.Integer), + model.NewLiteral("4", types.Integer), + model.NewLiteral("5", types.Integer), + model.NewLiteral("6", types.Integer), + model.NewLiteral("7", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Coalesce List Operand", + cql: "Coalesce({4, 5})", + want: &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("4", types.Integer), + model.NewLiteral("5", types.Integer), + }, + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "IsNull", + cql: "IsNull(5)", + want: &model.IsNull{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("5", types.Integer), + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IsFalse", + cql: "IsFalse(false)", + want: &model.IsFalse{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("false", types.Boolean), + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IsTrue", + cql: "IsTrue(false)", + want: &model.IsTrue{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("false", types.Boolean), + Expression: model.ResultType(types.Boolean), + }, + }, + }, + // COMPARISON OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#comparison-operators-4 + { + name: "Equal", + cql: "Equal(5, 5)", + want: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Equivalent", + cql: "Equivalent(5, 5)", + want: &model.Equivalent{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Less", + cql: "Less(5, 5)", + want: &model.Less{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Greater", + cql: "Greater(5, 5)", + want: &model.Greater{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "LessOrEqual", + cql: "LessOrEqual(5, 5)", + want: &model.LessOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "GreaterOrEqual", + cql: "GreaterOrEqual(5, 5)", + want: &model.GreaterOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + model.NewLiteral("5", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + // ARITHMETIC OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#arithmetic-operators-4 + { + name: "Arithmetic Addition", + cql: "Add(1, 2)", + want: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Subtraction", + cql: "Subtract(1, 2)", + want: &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Multiplication", + cql: "Multiply(1, 2)", + want: &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Arithmetic Modulo with different types", + cql: "Modulo(40L, 3)", + want: &model.Modulo{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("40L", types.Long), + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("3", types.Integer), + Expression: model.ResultType(types.Long), + }, + }, + }, + Expression: model.ResultType(types.Long), + }, + }, + }, + { + name: "Arithmetic Truncated Divide", + cql: "TruncatedDivide(40, 3)", + want: &model.TruncatedDivide{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("40", types.Integer), + model.NewLiteral("3", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Negate", + cql: "Negate(4)", + want: &model.Negate{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Predecessor for Date", + cql: "Predecessor(@2023)", + want: &model.Predecessor{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("@2023", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + }, + { + name: "Successor for Integer", + cql: "Successor(41)", + want: &model.Successor{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("41", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + }, + // STRING OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#string-operators-3 + { + name: "Add Strings", + cql: "Add('Hi', 'Hello')", + want: &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("Hi", types.String), + model.NewLiteral("Hello", types.String), + }, + Expression: model.ResultType(types.String), + }, + }, + }, + { + name: "Concatenate", + cql: "Concatenate('Hi', 'Hello')", + want: &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("Hi", types.String), + model.NewLiteral("Hello", types.String), + }, + Expression: model.ResultType(types.String), + }, + }, + }, + // DATE AND TIME OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#datetime-operators-2 + { + name: "After", + cql: "After(1, Interval[2, 3])", + want: &model.After{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewInclusiveInterval("2", "3", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "After With Precision", + cql: "AfterDays(@2023-01-01, @2023-01-01)", + want: &model.After{ + Precision: model.DAY, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2023-01-01", types.Date), + model.NewLiteral("@2023-01-01", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Before", + cql: "Before(1, Interval[2, 3])", + want: &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewInclusiveInterval("2", "3", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Before With Precision", + cql: "BeforeDays(@2023-01-01, @2023-01-01)", + want: &model.Before{ + Precision: model.DAY, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2023-01-01", types.Date), + model.NewLiteral("@2023-01-01", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Date", + cql: "Date(2014, 10, 3)", + want: &model.Date{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{model.NewLiteral("2014", types.Integer), model.NewLiteral("10", types.Integer), model.NewLiteral("3", types.Integer)}, + Expression: model.ResultType(types.Date), + }, + }, + }, + { + name: "DateTime", + cql: "DateTime(2014, 10, 3, 6, 30, 15, 500, 7.3)", + want: &model.DateTime{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{model.NewLiteral("2014", types.Integer), model.NewLiteral("10", types.Integer), model.NewLiteral("3", types.Integer), model.NewLiteral("6", types.Integer), model.NewLiteral("30", types.Integer), model.NewLiteral("15", types.Integer), model.NewLiteral("500", types.Integer), model.NewLiteral("7.3", types.Decimal)}, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + { + name: "DifferenceBetween", + cql: "DifferenceBetweenYears(@2023-01-01, @2023-01-01)", + want: &model.DifferenceBetween{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2023-01-01", types.Date), + model.NewLiteral("@2023-01-01", types.Date), + }, + Expression: model.ResultType(types.Integer), + }, + Precision: model.YEAR, + }, + }, + { + name: "Now()", + cql: "Now()", + want: &model.Now{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{}, + Expression: model.ResultType(types.DateTime), + }, + }, + }, + { + name: "TimeOfDay()", + cql: "TimeOfDay()", + want: &model.TimeOfDay{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{}, + Expression: model.ResultType(types.Time), + }, + }, + }, + { + name: "SameOrAfter", + cql: "SameOrAfter(1, Interval[2, 3])", + want: &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewInclusiveInterval("2", "3", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "SameOrAfter With Precision", + cql: "SameOrAfterDays(@2023-01-01, @2023-01-01)", + want: &model.SameOrAfter{ + Precision: model.DAY, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2023-01-01", types.Date), + model.NewLiteral("@2023-01-01", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "SameOrBefore", + cql: "SameOrBefore(1, Interval[2, 3])", + want: &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewInclusiveInterval("2", "3", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "SameOrBefore With Precision", + cql: "SameOrBeforeDays(@2023-01-01, @2023-01-01)", + want: &model.SameOrBefore{ + Precision: model.DAY, + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2023-01-01", types.Date), + model.NewLiteral("@2023-01-01", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "Time", + cql: "Time(6, 30, 15, 500)", + want: &model.Time{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{model.NewLiteral("6", types.Integer), model.NewLiteral("30", types.Integer), model.NewLiteral("15", types.Integer), model.NewLiteral("500", types.Integer)}, + Expression: model.ResultType(types.Time), + }, + }, + }, + { + name: "Today()", + cql: "Today()", + want: &model.Today{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{}, + Expression: model.ResultType(types.Date), + }, + }, + }, + // INTERVAL OPERATORS - https://cql.hl7.org/04-logicalspecification.html#interval-operators + { + name: "Contains", + cql: "Contains({3}, 1)", + want: &model.Contains{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("3", types.Integer), + }, + }, + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "End", + cql: "End(Interval[1, 4])", + want: &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("1", types.Integer), + High: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + LowInclusive: true, + HighInclusive: true, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "In", + cql: "In(1, {3})", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("3", types.Integer), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "InMonths", + cql: "InMonths(@2020-03, Interval[@2020-03, @2022-03])", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03", types.Date), + &model.Interval{ + Low: model.NewLiteral("@2020-03", types.Date), + High: model.NewLiteral("@2022-03", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.MONTH, + }, + }, + { + name: "InSeconds", + cql: "InSeconds(@2024-03-31T00:00:05.000Z, Interval[@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z])", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2024-03-31T00:00:05.000Z", types.DateTime), + &model.Interval{ + Low: model.NewLiteral("@2024-03-31T00:00:00.000Z", types.DateTime), + High: model.NewLiteral("@2025-03-31T00:00:00.000Z", types.DateTime), + Expression: model.ResultType(&types.Interval{PointType: types.DateTime}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.SECOND, + }, + }, + { + name: "IncludedIn for point type", + cql: "IncludedIn(@2010, Interval[@2010, @2020])", + want: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2010", types.Date), + &model.Interval{ + Low: model.NewLiteral("@2010", types.Date), + High: model.NewLiteral("@2020", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IncludedIn interval overload", + cql: "IncludedIn(Interval[@2015, @2016], Interval[@2010, @2020])", + want: &model.IncludedIn{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2015", types.Date), + High: model.NewLiteral("@2016", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + &model.Interval{ + Low: model.NewLiteral("@2010", types.Date), + High: model.NewLiteral("@2020", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + { + name: "IncludedInYears interval overload", + cql: "IncludedInYears(Interval[@2015, @2016], Interval[@2010, @2020])", + want: &model.IncludedIn{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Interval{ + Low: model.NewLiteral("@2015", types.Date), + High: model.NewLiteral("@2016", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + &model.Interval{ + Low: model.NewLiteral("@2010", types.Date), + High: model.NewLiteral("@2020", types.Date), + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + LowInclusive: true, + HighInclusive: true, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.YEAR, + }, + }, + { + name: "Start", + cql: "Start(Interval[1, 4])", + want: &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.Interval{ + Low: model.NewLiteral("1", types.Integer), + High: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + LowInclusive: true, + HighInclusive: true, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + // LIST OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#list-operators-2 + { + name: "Except", + cql: "Except({1}, {1})", + want: &model.Except{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{&model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + }, + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + }, + { + name: "First", + cql: "First({1})", + want: &model.First{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Intersect", + cql: "Intersect({1}, {1})", + want: &model.Intersect{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{&model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + }, + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + }, + { + name: "Last", + cql: "Last({1})", + want: &model.Last{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "SingletonFrom", + cql: "SingletonFrom({1})", + want: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + }, + { + name: "Union", + cql: "Union({1}, {'hi'})", + want: &model.Union{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewList([]string{"1"}, types.Integer), + model.NewList([]string{"hi"}, types.String), + }, + Expression: model.ResultType(&types.List{ElementType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}), + }, + }, + }, + // CLINICAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#clinical-operators-3 + { + name: "AgeInYears", + cql: "AgeInYears()", + want: &model.CalculateAge{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + }, + }, + }, + Precision: model.YEAR, + }, + }, + { + name: "AgeInDays", + cql: "AgeInDays()", + want: &model.CalculateAge{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + }, + }, + }, + Precision: model.DAY, + }, + }, + { + name: "AgeInDaysAt Date Overload", + cql: "AgeInDaysAt(@2023-01-01)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + model.NewLiteral("@2023-01-01", types.Date), + }, + }, + Precision: model.DAY, + }, + }, + { + name: "AgeInDaysAt DateTime Overload", + cql: "AgeInDaysAt(@2022-01-01T12:00:00.000)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.DateTime), + Operand: &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + }, + }, + model.NewLiteral("@2022-01-01T12:00:00.000", types.DateTime), + }, + }, + Precision: model.DAY, + }, + }, + { + name: "AgeInMonthsAt", + cql: "AgeInMonthsAt(@2023-01-01)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + model.NewLiteral("@2023-01-01", types.Date), + }, + }, + Precision: model.MONTH, + }, + }, + { + name: "CalculateAgeInDaysAt", + cql: "CalculateAgeInDaysAt(@2022-01-01, @2023-01-01)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("@2022-01-01", types.Date), + model.NewLiteral("@2023-01-01", types.Date), + }, + }, + Precision: model.DAY, + }, + }, + { + name: "CalculateAgeInYearsAt", + cql: "CalculateAgeInYearsAt(@2022-01-01, @2023-01-01)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("@2022-01-01", types.Date), + model.NewLiteral("@2023-01-01", types.Date), + }, + }, + Precision: model.YEAR, + }, + }, + { + name: "CalculateAgeInHoursAt", + cql: "CalculateAgeInHoursAt(@2022-01-01T12:00:00.000, @2023-01-01T12:00:00.000)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("@2022-01-01T12:00:00.000", types.DateTime), + model.NewLiteral("@2023-01-01T12:00:00.000", types.DateTime), + }, + }, + Precision: model.HOUR, + }, + }, + { + name: "CalculateAgeInMinutesAt", + cql: "CalculateAgeInMinutesAt(@2022-01-01T12:00:00.000, @2023-01-01T12:00:00.000)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("@2022-01-01T12:00:00.000", types.DateTime), + model.NewLiteral("@2023-01-01T12:00:00.000", types.DateTime), + }, + }, + Precision: model.MINUTE, + }, + }, + { + name: "CalculateAgeInSecondsAt", + cql: "CalculateAgeInSecondsAt(@2022-01-01T12:00:00.000, @2023-01-01T12:00:00.000)", + want: &model.CalculateAgeAt{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("@2022-01-01T12:00:00.000", types.DateTime), + model.NewLiteral("@2023-01-01T12:00:00.000", types.DateTime), + }, + }, + Precision: model.SECOND, + }, + }, + // ERRORS AND MESSAGES - https://cql.hl7.org/09-b-cqlreference.html#errors-and-messaging + { + name: "Message with all args", + cql: "Message(1.0, true, '100', 'Message', 'Test Message')", + want: &model.Message{ + Source: model.NewLiteral("1.0", types.Decimal), + Condition: model.NewLiteral("true", types.Boolean), + Code: model.NewLiteral("100", types.String), + Severity: model.NewLiteral("Message", types.String), + Message: model.NewLiteral("Test Message", types.String), + Expression: model.ResultType(types.Decimal), + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), wrapInLib(t, tc.cql), Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.want, getTESTRESULTModel(t, parsedLibs)); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..1a09e34 --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,237 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package parser offers a CQL parser that produces an intermediate ELM like data structure for +// evaluation. +package parser + +import ( + "context" + "fmt" + "strings" + + "github.com/google/cql/internal/embeddata/third_party/cqframework/cql" + "github.com/google/cql/internal/modelinfo" + "github.com/google/cql/internal/reference" + "github.com/google/cql/model" + "github.com/google/cql/result" + "github.com/antlr4-go/antlr/v4" + "gopkg.in/gyuho/goraph.v2" +) + +// Config configures the parsing of CQL. +type Config struct { + // Empty for now, but in the future will contain options like EnableListPromotion. +} + +// New returns a new Parser initialized to the data models. +func New(ctx context.Context, dataModels [][]byte) (*Parser, error) { + p := &Parser{ + refs: reference.NewResolver[func() model.IExpression, func() model.IExpression](), + } + mi, err := modelinfo.New(dataModels) + if err != nil { + return nil, err + } + p.modelInfo = mi + + if err := p.loadSystemOperators(); err != nil { + return nil, err + } + + return p, nil +} + +// Parser parses CQL library and parameter strings into our intermediate ELM like data structure. +// The parser is responsible for all validation and implicit conversions. +type Parser struct { + modelInfo *modelinfo.ModelInfos + refs *reference.Resolver[func() model.IExpression, func() model.IExpression] +} + +// DataModel returns the parsed model info. +func (p *Parser) DataModel() *modelinfo.ModelInfos { + p.modelInfo.ResetUsing() + return p.modelInfo +} + +// Libraries parses the CQL libraries into a list of model.Library or an error. +// Underlying parsing issues will return a ParsingErrors struct that users can check for and +// report to the user accordingly. +// TODO: b/332337287 - Investigate returning results as a map now that libraries are being sorted. +func (p *Parser) Libraries(ctx context.Context, cqlLibs []string, config Config) ([]*model.Library, error) { + if cqlLibs == nil || len(cqlLibs) == 0 { + return nil, result.NewEngineError("", result.ErrLibraryParsing, fmt.Errorf("no CQL libraries were provided")) + } + + p.refs.ClearDefs() + sortedLibraries, err := p.topologicalSortLibraries(cqlLibs) + if err != nil { + // TODO: b/301606416 Return errors with library name from topological sort. + return nil, result.NewEngineError("", result.ErrLibraryParsing, err) + } + + libs := []*model.Library{} + for _, lexedLib := range sortedLibraries { + vis := visitor{ + BaseCqlVisitor: &cql.BaseCqlVisitor{}, + errors: &LibraryErrors{LibKey: lexedLib.key}, + modelInfo: p.modelInfo, + refs: p.refs, + } + lib := vis.VisitLibrary(lexedLib.ctx) + if len(vis.errors.Unwrap()) > 0 { + return nil, vis.errors + } + libs = append(libs, lib) + } + return libs, nil +} + +type lexedLib struct { + key result.LibKey + ctx cql.ILibraryContext +} + +// topologicalSortLibraries parses the CQL libraries into ANTLR, topologically sorts their +// dependencies and returns a sorted list of lexedLib. +func (p *Parser) topologicalSortLibraries(cqlLibs []string) ([]lexedLib, error) { + // lexedLibraries maps graph ID to library that has been lexed. + lexedLibraries := make(map[string]lexedLib, len(cqlLibs)) + // includeDependencies maps a library to its dependencies. + includeDependencies := make(map[result.LibKey][]result.LibKey, len(cqlLibs)) + graph := goraph.NewGraph() + + for _, cqlText := range cqlLibs { + vis := visitor{ + BaseCqlVisitor: &cql.BaseCqlVisitor{}, + errors: &LibraryErrors{}, + modelInfo: p.modelInfo, + refs: p.refs, + } + + lex := cql.NewCqlLexer(antlr.NewInputStream(cqlText)) + par := cql.NewCqlParser(antlr.NewCommonTokenStream(lex, 0)) + + lex.AddErrorListener(vis) + par.AddErrorListener(vis) + + libContext := par.Library() + libKey := result.LibKeyFromModel(vis.LibraryIdentifier(libContext)) + + // Return if the lexer found syntax errors. + if len(vis.errors.Unwrap()) > 0 { + libErrs := vis.errors.(*LibraryErrors) + // Need to set the libKey here because it is not included when we create the visitor. + libErrs.LibKey = libKey + return nil, libErrs + } + + lexedLibraries[libKey.Key()] = lexedLib{key: libKey, ctx: libContext} + includeDependencies[libKey] = vis.LibraryIncludedIdentifiers(libContext) + + if ok := graph.AddNode(goraph.NewNode(libKey.Key())); !ok { + return nil, fmt.Errorf("cql library %q already imported", libKey.String()) + } + } + // Build graph DAG links. + for libID, deps := range includeDependencies { + libNode := goraph.NewNode(libID.Key()) + for _, includedID := range deps { + includedNode := goraph.NewNode(includedID.Key()) + if err := graph.AddEdge(includedNode.ID(), libNode.ID(), 1); err != nil { + return nil, fmt.Errorf("failed to import library %q, dependency graph could not resolve with error: %w", includedID, err) + } + } + } + sortedLibraryIDs, isValidDag := goraph.TopologicalSort(graph) + if !isValidDag { + // TODO: b/332600632 - Add which library has circular dependencies to error output. + return nil, fmt.Errorf("included cql libraries are not valid, found circular dependencies") + } + + sortedLibs := make([]lexedLib, 0, len(sortedLibraryIDs)) + for _, libID := range sortedLibraryIDs { + sortedLibs = append(sortedLibs, lexedLibraries[libID.String()]) + } + return sortedLibs, nil +} + +// Parameters parses CQL literals into model.IExpressions. Each param should be a CQL literal, not an +// expression definition, valueset or other CQL construct. +func (p *Parser) Parameters(ctx context.Context, params map[result.DefKey]string, config Config) (map[result.DefKey]model.IExpression, error) { + if params == nil { + return nil, nil + } + parsedParams := make(map[result.DefKey]model.IExpression, len(params)) + for k, v := range params { + e, err := p.parameter(k, v) + if err != nil { + return nil, err + } + parsedParams[k] = e + } + return parsedParams, nil +} + +// parameter parses an individual CQL literal. The CQL spec does not specify anything beyond that +// the environment passes parameters. We have chosen to take passed parameters as CQL literals. +func (p *Parser) parameter(key result.DefKey, input string) (model.IExpression, error) { + p.refs.ClearDefs() + + vis := visitor{ + BaseCqlVisitor: &cql.BaseCqlVisitor{}, + errors: &ParameterErrors{DefKey: key, Errors: []*ParsingError{}}, + modelInfo: p.modelInfo, + refs: p.refs, + } + lex := cql.NewCqlLexer(antlr.NewInputStream(input)) + par := cql.NewCqlParser(antlr.NewCommonTokenStream(lex, 0)) + + lex.AddErrorListener(vis) + par.AddErrorListener(vis) + + // We start parsing at the term precedence in the CQL grammar. VisitParameter() will throw an + // error if the parsed CQL is not a literal. + t := par.Term() + if len(vis.errors.Unwrap()) > 0 { + return nil, vis.errors + } + + // Remove whitespace and check if the input string is equal to to the parsed string. This ensures + // there is no extraneous input that the parser did not match. + if strings.Join(strings.Fields(input), "") != t.GetText() { + return nil, &ParameterErrors{ + DefKey: key, + Errors: []*ParsingError{{Message: "must be a single literal"}}, + } + } + + m := vis.VisitParameter(t) + if len(vis.errors.Unwrap()) > 0 { + return nil, vis.errors + } + return m, nil +} + +// printTree is a debugging function that prints the entire parsed Antlr Tree. +func printTree(vis visitor, input string) { + lex := cql.NewCqlLexer(antlr.NewInputStream(input)) + par := cql.NewCqlParser(antlr.NewCommonTokenStream(lex, 0)) + + lex.AddErrorListener(vis) + par.AddErrorListener(vis) + + fmt.Printf("Antlr Parsed Tree \n%v\n", antlr.TreesStringTree(par.Library(), par.GetRuleNames(), par)) +} diff --git a/parser/query.go b/parser/query.go new file mode 100644 index 0000000..e2422d2 --- /dev/null +++ b/parser/query.go @@ -0,0 +1,386 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "errors" + "fmt" + + "github.com/google/cql/internal/convert" + "github.com/google/cql/internal/embeddata/third_party/cqframework/cql" + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/antlr4-go/antlr/v4" +) + +func (v *visitor) VisitQuery(ctx *cql.QueryContext) model.IExpression { + // Top level scope for the main query source aliases. + v.refs.EnterScope() + defer v.refs.ExitScope() + + q := &model.Query{} + var err error + + q, err = v.parseSourceClause(ctx.SourceClause(), q) + if err != nil { + return v.badExpression(err.Error(), ctx.SourceClause()) + } + + q, err = v.parseLetClause(ctx.LetClause(), q) + if err != nil { + return v.badExpression(err.Error(), ctx.LetClause()) + } + + for _, inc := range ctx.AllQueryInclusionClause() { + q, err = v.parseIncusionClause(inc, q) + if err != nil { + return v.badExpression(err.Error(), inc) + } + } + + q, err = v.parseWhereClause(ctx.WhereClause(), q) + if err != nil { + return v.badExpression(err.Error(), ctx.WhereClause()) + } + + q, err = v.parseSortClause(ctx.SortClause(), q) + if err != nil { + return v.badExpression(err.Error(), ctx.SortClause()) + } + + q, err = v.parseAggregateClause(ctx.AggregateClause(), q) + if err != nil { + return v.badExpression(err.Error(), ctx.AggregateClause()) + } + + if ctx.AggregateClause() == nil { + // parseReturnClauseAndSetResultType could parse or in the case of multi-source queries inserts + // a return clause. It also sets the result type, even if there is no return clause. + q, err = v.parseReturnClauseAndSetResultType(ctx.ReturnClause(), q) + if err != nil { + return v.badExpression(err.Error(), ctx.ReturnClause()) + } + } + return q +} + +func (v *visitor) VisitQuerySource(ctx *cql.QuerySourceContext) model.IExpression { + if ctx.Retrieve() != nil { + return v.VisitExpression(ctx.Retrieve()) + } + if ctx.QualifiedIdentifierExpression() != nil { + return v.VisitExpression(ctx.QualifiedIdentifierExpression()) + } + if ctx.Expression() != nil { + return v.VisitExpression(ctx.Expression()) + } + return v.badExpression("internal error - the grammar should prevent us from landing here", ctx) +} + +func (v *visitor) parseSourceClause(sc cql.ISourceClauseContext, q *model.Query) (*model.Query, error) { + // If the keyword from is used in the query then it will start with a TerminalNode. + _, hasFrom := sc.GetChild(0).(antlr.TerminalNode) + if len(sc.AllAliasedQuerySource()) > 1 && !hasFrom { + return nil, fmt.Errorf("for multi-source queries the keyword from is required") + } + + for _, aqs := range sc.AllAliasedQuerySource() { + // Aliases are defined in the top level scope of the query. + as, err := v.parseAliasedQuerySource(aqs) + if err != nil { + return nil, err + } + q.Source = append(q.Source, as) + } + + return q, nil +} + +func (v *visitor) parseLetClause(lc cql.ILetClauseContext, q *model.Query) (*model.Query, error) { + if lc == nil { + return q, nil + } + + for _, let := range lc.AllLetClauseItem() { + l := &model.LetClause{ + Expression: v.VisitExpression(let.Expression()), + Identifier: v.VisitIdentifier(let.Identifier()), + } + l.Element = &model.Element{ResultType: l.Expression.GetResultType()} + + f := func() model.IExpression { + return &model.QueryLetRef{Name: l.Identifier, Expression: model.ResultType(l.GetResultType())} + } + // Aliases are defined in the top level scope of the query. + if err := v.refs.Alias(l.Identifier, f); err != nil { + return nil, err + } + + q.Let = append(q.Let, l) + } + return q, nil +} + +func (v *visitor) parseIncusionClause(inc cql.IQueryInclusionClauseContext, q *model.Query) (*model.Query, error) { + v.refs.EnterScope() + defer v.refs.ExitScope() + + var aqs cql.IAliasedQuerySourceContext + var exp cql.IExpressionContext + var with bool + if inc.WithClause() != nil { + with = true + aqs = inc.WithClause().AliasedQuerySource() + exp = inc.WithClause().Expression() + } else { + with = false + aqs = inc.WithoutClause().AliasedQuerySource() + exp = inc.WithoutClause().Expression() + } + + aqsModel, err := v.parseAliasedQuerySource(aqs) + if err != nil { + return nil, err + } + + expModel := v.VisitExpression(exp) + res, err := convert.OperandImplicitConverter(expModel.GetResultType(), types.Boolean, expModel, v.modelInfo) + if err != nil { + return nil, err + } + if !res.Matched { + return nil, fmt.Errorf("result of a query inclusion clause must be implicitly convertible to a boolean, could not convert %v to boolean", expModel.GetResultType()) + } + + rClause := &model.RelationshipClause{ + Element: &model.Element{ResultType: types.Boolean}, + Expression: aqsModel.Source, + Alias: aqsModel.Alias, + SuchThat: res.WrappedOperand, + } + + if with { + q.Relationship = append(q.Relationship, &model.With{RelationshipClause: rClause}) + } else { + q.Relationship = append(q.Relationship, &model.Without{RelationshipClause: rClause}) + } + return q, nil +} + +func (v *visitor) parseWhereClause(wc cql.IWhereClauseContext, q *model.Query) (*model.Query, error) { + if wc == nil { + return q, nil + } + + wExp := v.VisitExpression(wc.Expression()) + + res, err := convert.OperandImplicitConverter(wExp.GetResultType(), types.Boolean, wExp, v.modelInfo) + if err != nil { + return nil, err + } + if !res.Matched { + return nil, fmt.Errorf("result of a where clause must be implicitly convertible to a boolean, could not convert %v to boolean", wExp.GetResultType()) + } + q.Where = res.WrappedOperand + return q, nil +} + +func (v *visitor) parseSortClause(sc cql.ISortClauseContext, q *model.Query) (*model.Query, error) { + // TODO(b/316961394): Add check for sortability for CQL query sort columns. + // TODO(b/317008490): Implement sort by expression. + if sc == nil { + return q, nil + } + + var sortByItems []model.ISortByItem + if sbd, found := maybeGetChildNode[*cql.SortDirectionContext](sc.GetChildren(), nil); found { + sortDir, err := parseSortDirection(sbd.GetText()) + if err != nil { + return nil, err + } + sortByItems = []model.ISortByItem{ + &model.SortByDirection{ + SortByItem: &model.SortByItem{ + Direction: sortDir, + }, + }, + } + } else if sbi, found := maybeGetChildNode[*cql.SortByItemContext](sc.GetChildren(), nil); found { + sortDir, err := parseSortDirection(sbi.SortDirection().GetText()) + if err != nil { + return nil, err + } + + // TODO(b/317402356): Add static type checking for column paths. + sortByItems = []model.ISortByItem{ + &model.SortByColumn{ + SortByItem: &model.SortByItem{ + Direction: sortDir, + }, + Path: sbi.ExpressionTerm().GetText(), + }, + } + } else { + return nil, errors.New("item or direction to sort by was not found") + } + + q.Sort = &model.SortClause{ByItems: sortByItems} + return q, nil +} + +func parseSortDirection(s string) (model.SortDirection, error) { + switch s { + case "ascending", "asc": + return model.ASCENDING, nil + case "descending", "desc": + return model.DESCENDING, nil + } + return model.UNSETSORTDIRECTION, fmt.Errorf("unsupported sort direction, expected asc, ascending, desc or descending, got: %s", s) +} + +func (v *visitor) parseAggregateClause(ac cql.IAggregateClauseContext, q *model.Query) (*model.Query, error) { + if ac == nil { + return q, nil + } + + aModel := &model.AggregateClause{ + Identifier: v.VisitIdentifier(ac.Identifier()), + } + + if _, ok := ac.GetChild(1).(antlr.TerminalNode); ok { + aModel.Distinct = ac.GetChild(1).(antlr.TerminalNode).GetText() == "distinct" + } + + if ac.StartingClause() != nil { + if ac.StartingClause().SimpleLiteral() != nil { + aModel.Starting = v.VisitExpression(ac.StartingClause().SimpleLiteral()) + } else if ac.StartingClause().Quantity() != nil { + aModel.Starting = v.VisitExpression(ac.StartingClause().Quantity()) + } else if ac.StartingClause().Expression() != nil { + aModel.Starting = v.VisitExpression(ac.StartingClause().Expression()) + } else { + return nil, fmt.Errorf("internal error - grammar should not allow another StartingClause") + } + } else { + aModel.Starting = model.NewLiteral("null", types.Any) + } + + // Define an alias for aggregation variable. + v.refs.EnterScope() + defer v.refs.ExitScope() + v.refs.Alias(aModel.Identifier, func() model.IExpression { + return &model.AliasRef{Name: aModel.Identifier, Expression: model.ResultType(aModel.Starting.GetResultType())} + }) + + aModel.Expression = v.VisitExpression(ac.Expression()) + aModel.Element = &model.Element{ResultType: aModel.Expression.GetResultType()} + + // The result of the query is the result of the last iteration of the aggregate expression. + q.Expression = model.ResultType(aModel.GetResultType()) + q.Aggregate = aModel + + return q, nil +} + +func (v *visitor) parseReturnClauseAndSetResultType(rc cql.IReturnClauseContext, q *model.Query) (*model.Query, error) { + atLeastOneSourceList := false + for _, as := range q.Source { + _, ok := as.GetResultType().(*types.List) + if ok { + atLeastOneSourceList = true + break + } + } + + if rc == nil && len(q.Source) > 1 { + // For multi-source queries if there is no return clase the parser inserts a Tuple selector. + tModel := &model.Tuple{} + tType := &types.Tuple{ElementTypes: make(map[string]types.IType)} + for _, aSource := range q.Source { + aRef, err := v.refs.ResolveLocal(aSource.Alias) + if err != nil { + return nil, err + } + tModel.Elements = append(tModel.Elements, &model.TupleElement{Name: aSource.Alias, Value: aRef()}) + tType.ElementTypes[aSource.Alias] = aRef().GetResultType() + } + + tModel.Expression = model.ResultType(tType) + q.Return = &model.ReturnClause{ + Distinct: true, + Expression: tModel, + Element: &model.Element{ResultType: tType}, + } + + q.Expression = model.ResultType(tType) + if atLeastOneSourceList { + q.Expression = model.ResultType(&types.List{ElementType: q.Expression.GetResultType()}) + } + return q, nil + } + + if rc == nil { + // No return clause and there is only a single source. + q.Expression = model.ResultType(q.Source[0].GetResultType()) + return q, nil + } + + // There is a return clause. + rModel := &model.ReturnClause{ + Expression: v.VisitExpression(rc.Expression()), + Distinct: true, + } + rModel.Element = &model.Element{ResultType: rModel.Expression.GetResultType()} + if rc.GetChildCount() == 3 && rc.GetChild(1).(antlr.TerminalNode).GetText() == "all" { + rModel.Distinct = false + } + q.Return = rModel + + if atLeastOneSourceList { + q.Expression = model.ResultType(&types.List{ElementType: rModel.GetResultType()}) + } else { + q.Expression = model.ResultType(rModel.GetResultType()) + } + return q, nil +} + +func (v *visitor) parseAliasedQuerySource(aqs cql.IAliasedQuerySourceContext) (*model.AliasedSource, error) { + alias := v.VisitIdentifier(aqs.Alias().Identifier()) + source := v.VisitExpression(aqs.QuerySource()) + aqsModel := &model.AliasedSource{ + Alias: alias, + Source: source, + Expression: model.ResultType(source.GetResultType()), + } + + // If the AliasedSource is a List, when referencing the AliasRef inside the query it + // should refer to a single element of the AliasedSource list. + // For example in [Observation] O Where O.status = 'final' the Aliased reference O when resolved + // inside O.status should be of type FHIR.Observation instead of List. + aliasRefResultType := aqsModel.GetResultType() + listAliasResultType, ok := aqsModel.GetResultType().(*types.List) + if ok { + // If it's a list, we set the alias ref ResultType to the ElementType. + aliasRefResultType = listAliasResultType.ElementType + } + + f := func() model.IExpression { + return &model.AliasRef{Name: alias, Expression: model.ResultType(aliasRefResultType)} + } + if err := v.refs.Alias(alias, f); err != nil { + return nil, err + } + return aqsModel, nil +} diff --git a/parser/query_test.go b/parser/query_test.go new file mode 100644 index 0000000..3b7b4a4 --- /dev/null +++ b/parser/query_test.go @@ -0,0 +1,655 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/google/cql/model" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" +) + +func TestQuery(t *testing.T) { + tests := []struct { + name string + desc string + cql string + want model.IExpression + }{ + { + name: "Query", + cql: dedent.Dedent(`define TESTRESULT: [Observation] o`), + want: &model.Query{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + Source: []*model.AliasedSource{ + { + Alias: "o", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + }, + }, + { + name: "Query with Filtered Retrieve", + cql: dedent.Dedent(` + define TESTRESULT: [Observation: "Blood pressure"] bp`), + want: &model.Query{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + Source: []*model.AliasedSource{ + { + Alias: "bp", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Codes: &model.ValuesetRef{Name: "Blood pressure", Expression: model.ResultType(types.ValueSet)}, + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + }, + }, + { + name: "Let", + cql: dedent.Dedent(`define TESTRESULT: [Observation] O let A: 4, B: 5 return A`), + want: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Source: []*model.AliasedSource{ + { + Alias: "O", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + Let: []*model.LetClause{ + &model.LetClause{ + Expression: model.NewLiteral("4", types.Integer), + Identifier: "A", + Element: &model.Element{ResultType: types.Integer}, + }, + &model.LetClause{ + Expression: model.NewLiteral("5", types.Integer), + Identifier: "B", + Element: &model.Element{ResultType: types.Integer}, + }, + }, + Return: &model.ReturnClause{ + Element: &model.Element{ResultType: types.Integer}, + Distinct: true, + Expression: &model.QueryLetRef{ + Name: "A", + Expression: model.ResultType(types.Integer), + }, + }, + }, + }, + { + name: "Query with relationship", + cql: dedent.Dedent(`define TESTRESULT: ({3, 4}) O with ({4, 5}) C such that O = C`), + want: &model.Query{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: &types.List{ElementType: types.Integer}}, + }, + Source: []*model.AliasedSource{ + { + Alias: "O", + Source: model.NewList([]string{"3", "4"}, types.Integer), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Relationship: []model.IRelationshipClause{ + &model.With{ + RelationshipClause: &model.RelationshipClause{ + Element: &model.Element{ResultType: types.Boolean}, + Expression: model.NewList([]string{"4", "5"}, types.Integer), + Alias: "C", + SuchThat: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.AliasRef{Name: "O", Expression: model.ResultType(types.Integer)}, + &model.AliasRef{Name: "C", Expression: model.ResultType(types.Integer)}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Query without relationship", + cql: dedent.Dedent(`define TESTRESULT: ({3, 4}) O without ({4, 5}) C such that O = C`), + want: &model.Query{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: &types.List{ElementType: types.Integer}}, + }, + Source: []*model.AliasedSource{ + { + Alias: "O", + Source: model.NewList([]string{"3", "4"}, types.Integer), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Relationship: []model.IRelationshipClause{ + &model.Without{ + RelationshipClause: &model.RelationshipClause{ + Element: &model.Element{ResultType: types.Boolean}, + Expression: model.NewList([]string{"4", "5"}, types.Integer), + Alias: "C", + SuchThat: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.AliasRef{Name: "O", Expression: model.ResultType(types.Integer)}, + &model.AliasRef{Name: "C", Expression: model.ResultType(types.Integer)}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Multi-source", + cql: dedent.Dedent(`define TESTRESULT: from [Observation] O, [Patient] P`), + want: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"O": &types.Named{TypeName: "FHIR.Observation"}, "P": &types.Named{TypeName: "FHIR.Patient"}}}}), + Source: []*model.AliasedSource{ + { + Alias: "O", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + { + Alias: "P", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + }, + Return: &model.ReturnClause{ + Distinct: true, + Element: &model.Element{ResultType: &types.Tuple{ElementTypes: map[string]types.IType{"O": &types.Named{TypeName: "FHIR.Observation"}, "P": &types.Named{TypeName: "FHIR.Patient"}}}}, + Expression: &model.Tuple{ + Expression: model.ResultType(&types.Tuple{ElementTypes: map[string]types.IType{"O": &types.Named{TypeName: "FHIR.Observation"}, "P": &types.Named{TypeName: "FHIR.Patient"}}}), + Elements: []*model.TupleElement{ + &model.TupleElement{ + Name: "O", + Value: &model.AliasRef{Name: "O", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Observation"})}, + }, + &model.TupleElement{ + Name: "P", + Value: &model.AliasRef{Name: "P", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"})}, + }, + }, + }, + }, + }, + }, + { + name: "Multi-source on non lists", + cql: dedent.Dedent(`define TESTRESULT: from (4) O, ('hi') P`), + want: &model.Query{ + Expression: model.ResultType(&types.Tuple{ElementTypes: map[string]types.IType{"O": types.Integer, "P": types.String}}), + Source: []*model.AliasedSource{ + { + Alias: "O", + Source: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Integer), + }, + { + Alias: "P", + Source: model.NewLiteral("hi", types.String), + Expression: model.ResultType(types.String), + }, + }, + Return: &model.ReturnClause{ + Distinct: true, + Element: &model.Element{ResultType: &types.Tuple{ElementTypes: map[string]types.IType{"O": types.Integer, "P": types.String}}}, + Expression: &model.Tuple{ + Expression: model.ResultType(&types.Tuple{ElementTypes: map[string]types.IType{"O": types.Integer, "P": types.String}}), + Elements: []*model.TupleElement{ + &model.TupleElement{ + Name: "O", + Value: &model.AliasRef{Name: "O", Expression: model.ResultType(types.Integer)}, + }, + &model.TupleElement{ + Name: "P", + Value: &model.AliasRef{Name: "P", Expression: model.ResultType(types.String)}, + }, + }, + }, + }, + }, + }, + { + name: "Multi-source with return", + cql: dedent.Dedent(`define TESTRESULT: from ({4, 6}) O, ('hi') P return 5`), + want: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Source: []*model.AliasedSource{ + { + Alias: "O", + Source: model.NewList([]string{"4", "6"}, types.Integer), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + { + Alias: "P", + Source: model.NewLiteral("hi", types.String), + Expression: model.ResultType(types.String), + }, + }, + Return: &model.ReturnClause{ + Distinct: true, + Element: &model.Element{ResultType: types.Integer}, + Expression: model.NewLiteral("5", types.Integer), + }, + }, + }, + { + name: "Query with Sort By Direction", + cql: dedent.Dedent(` + define TESTRESULT: + ({@2013, @2014, @2015}) dl + sort desc + `), + want: &model.Query{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: &types.List{ElementType: types.Date}}, + }, + Source: []*model.AliasedSource{ + { + Alias: "dl", + Source: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Date}), + List: []model.IExpression{ + buildLiteral("@2013", types.Date), + buildLiteral("@2014", types.Date), + buildLiteral("@2015", types.Date), + }, + }, + Expression: model.ResultType(&types.List{ElementType: types.Date}), + }, + }, + Sort: &model.SortClause{ + ByItems: []model.ISortByItem{ + &model.SortByDirection{SortByItem: &model.SortByItem{Direction: model.DESCENDING}}, + }, + }, + }, + }, + { + name: "Query with Sort On Column", + cql: dedent.Dedent(` + define TESTRESULT: + [Observation: "Blood pressure"] bp + sort by effective desc`), + want: &model.Query{ + Expression: &model.Expression{ + Element: &model.Element{ResultType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}, + }, + Source: []*model.AliasedSource{ + { + Alias: "bp", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Codes: &model.ValuesetRef{Name: "Blood pressure", Expression: model.ResultType(types.ValueSet)}, + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + Sort: &model.SortClause{ + ByItems: []model.ISortByItem{ + &model.SortByColumn{ + SortByItem: &model.SortByItem{Direction: model.DESCENDING}, + Path: "effective", + }, + }, + }, + }, + }, + { + name: "Aggregate", + cql: "define TESTRESULT: ({1, 2, 3}) N aggregate R starting 1: R * N", + want: &model.Query{ + Expression: model.ResultType(types.Integer), + Source: []*model.AliasedSource{ + { + Alias: "N", + Source: model.NewList([]string{"1", "2", "3"}, types.Integer), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Aggregate: &model.AggregateClause{ + Element: &model.Element{ResultType: types.Integer}, + Identifier: "R", + Starting: model.NewLiteral("1", types.Integer), + Distinct: false, + Expression: &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.AliasRef{Name: "R", Expression: model.ResultType(types.Integer)}, + &model.AliasRef{Name: "N", Expression: model.ResultType(types.Integer)}, + }, + }, + }, + }, + }, + }, + { + name: "Distinct aggregate", + cql: "define TESTRESULT: ({1, 2, 3}) N aggregate distinct R starting 'hi': R", + want: &model.Query{ + Expression: model.ResultType(types.String), + Source: []*model.AliasedSource{ + { + Alias: "N", + Source: model.NewList([]string{"1", "2", "3"}, types.Integer), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Aggregate: &model.AggregateClause{ + Element: &model.Element{ResultType: types.String}, + Identifier: "R", + Starting: model.NewLiteral("hi", types.String), + Distinct: true, + Expression: &model.AliasRef{Name: "R", Expression: model.ResultType(types.String)}, + }, + }, + }, + { + name: "Aggregate without starting", + cql: "define TESTRESULT: ({1, 2, 3}) N aggregate R : R", + want: &model.Query{ + Expression: model.ResultType(types.Any), + Source: []*model.AliasedSource{ + { + Alias: "N", + Source: model.NewList([]string{"1", "2", "3"}, types.Integer), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Aggregate: &model.AggregateClause{ + Element: &model.Element{ResultType: types.Any}, + Identifier: "R", + Starting: model.NewLiteral("null", types.Any), + Distinct: false, + Expression: &model.AliasRef{Name: "R", Expression: model.ResultType(types.Any)}, + }, + }, + }, + { + name: "Relationship and Aggregate have the same alias name", + cql: dedent.Dedent(` + define TESTRESULT: ({1, 2, 3}) N + with ({4, 5}) R such that R = N + aggregate R starting 1: R * N`), + want: &model.Query{ + Expression: model.ResultType(types.Integer), + Source: []*model.AliasedSource{ + { + Alias: "N", + Source: model.NewList([]string{"1", "2", "3"}, types.Integer), + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Relationship: []model.IRelationshipClause{ + &model.With{ + RelationshipClause: &model.RelationshipClause{ + Element: &model.Element{ResultType: types.Boolean}, + Expression: model.NewList([]string{"4", "5"}, types.Integer), + Alias: "R", + SuchThat: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.AliasRef{Name: "R", Expression: model.ResultType(types.Integer)}, + &model.AliasRef{Name: "N", Expression: model.ResultType(types.Integer)}, + }, + }, + }, + }, + }, + }, + Aggregate: &model.AggregateClause{ + Element: &model.Element{ResultType: types.Integer}, + Identifier: "R", + Starting: model.NewLiteral("1", types.Integer), + Distinct: false, + Expression: &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.AliasRef{Name: "R", Expression: model.ResultType(types.Integer)}, + &model.AliasRef{Name: "N", Expression: model.ResultType(types.Integer)}, + }, + }, + }, + }, + }, + }, + { + name: "Query with Return", + cql: `define TESTRESULT: [Observation] o return o`, + want: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + Source: []*model.AliasedSource{ + { + Alias: "o", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + Return: &model.ReturnClause{ + Expression: &model.AliasRef{Name: "o", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Observation"})}, + Element: &model.Element{ResultType: &types.Named{TypeName: "FHIR.Observation"}}, + Distinct: true, + }, + }, + }, + { + name: "Query with All Return", + cql: `define TESTRESULT: [Observation] o return all 4`, + want: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Source: []*model.AliasedSource{ + { + Alias: "o", + Source: &model.Retrieve{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + }, + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + }, + Return: &model.ReturnClause{ + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + Distinct: false, + }, + }, + }, + { + name: "Query with Return, Source is not List", + cql: `define TESTRESULT: (5) o return all 4`, + want: &model.Query{ + Expression: model.ResultType(types.Integer), + Source: []*model.AliasedSource{ + { + Alias: "o", + Source: model.NewLiteral("5", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + Return: &model.ReturnClause{ + Expression: model.NewLiteral("4", types.Integer), + Element: &model.Element{ResultType: types.Integer}, + Distinct: false, + }, + }, + }, + { + name: "Query with Where Wrapped FHIRHelpers ToBoolean", + cql: `define TESTRESULT: [Patient] P where P.active`, + want: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "P", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Patient", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Patient", + CodeProperty: "", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Patient"}}), + }, + }, + }, + Where: &model.FunctionRef{ + Expression: &model.Expression{Element: &model.Element{ResultType: types.Boolean}}, + Name: "ToBoolean", + LibraryName: "FHIRHelpers", + Operands: []model.IExpression{ + &model.Property{ + Source: &model.AliasRef{Name: "P", Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"})}, + Path: "active", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.boolean"}), + }, + }, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cqlLib := dedent.Dedent(fmt.Sprintf(` + valueset "Blood pressure": 'https://test/file1' + using FHIR version '4.0.1' + context Patient + %v`, test.cql)) + + parsedLibs, err := newFHIRParser(t).Libraries(context.Background(), []string{cqlLib}, Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(test.want, getTESTRESULTModel(t, parsedLibs)); diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestQuery_Errors(t *testing.T) { + tests := []struct { + name string + cql string + errContains []string + errCount int + }{ + { + name: "Multi-source queries must start with from", + cql: "[Patient] P, [Observation] O", + errContains: []string{"for multi-source queries the keyword from is required"}, + errCount: 1, + }, + { + name: "Inclusion clause not implicitly convertible to boolean", + cql: "[Patient] P with [Observation] O such that 'not a bool'", + errContains: []string{"result of a query inclusion clause must be implicitly convertible to a boolean, could not convert System.String to boolean"}, + errCount: 1, + }, + { + name: "Where clause not implicitly convertible to boolean", + cql: "[Patient] P where 'not a bool'", + errContains: []string{"result of a where clause must be implicitly convertible to a boolean, could not convert System.String to boolean"}, + errCount: 1, + }, + { + name: "Source and Let alias have same name", + cql: "[Patient] P let P: 4", + errContains: []string{"alias P already exists"}, + errCount: 1, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := newFHIRParser(t).Libraries(context.Background(), wrapInLib(t, test.cql), Config{}) + if err == nil { + t.Fatalf("Parsing succeeded, expected error") + } + + var pe *LibraryErrors + if ok := errors.As(err, &pe); ok { + for _, ec := range test.errContains { + if !strings.Contains(pe.Error(), ec) { + t.Errorf("Returned error (%s) did not contain expected string (%s)", + pe.Error(), ec) + } + } + + if len(pe.Errors) != test.errCount { + t.Errorf("Returned error (%s) had (%d) errors but expected (%d)", + err.Error(), len(pe.Errors), test.errCount) + } + } else { + t.Errorf("Unexpected test error (%s).", err.Error()) + } + }) + } +} diff --git a/protos/cql_beam.proto b/protos/cql_beam.proto new file mode 100644 index 0000000..7c62d43 --- /dev/null +++ b/protos/cql_beam.proto @@ -0,0 +1,41 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; + +package google.cql.proto; + +import "google/protobuf/timestamp.proto"; +import "protos/cql_result.proto"; + +option java_multiple_files = true; +option go_package = "github.com/google/cql/protos/cql_beam_go_proto"; + +// The results of the evaluated CQL for a particular id and timestamp. +message BeamResult { + // The id of the result. Usually this is the Patient ID but can be overriden. + optional string id = 1; + // The timestamp at which the CQL was evaluated. + optional google.protobuf.Timestamp evaluation_timestamp = 2; + // The result of the CQL evaluation. + optional Libraries result = 3; +} + +// Indicates an error that occured at some phase of CQL processing. +message BeamError { + // A message describing the error. + optional string error_message = 1; + // A URI referencing the error's source. This could be a file path to a + // malformed input, resource ID, or other item. + optional string source_uri = 2; +} diff --git a/protos/cql_beam_go_proto/cql_beam.pb.go b/protos/cql_beam_go_proto/cql_beam.pb.go new file mode 100644 index 0000000..b42bb26 --- /dev/null +++ b/protos/cql_beam_go_proto/cql_beam.pb.go @@ -0,0 +1,268 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.29.0 +// protoc v3.21.12 +// source: protos/cql_beam.proto + +package cql_beam_go_proto + +import ( + cql_result_go_proto "github.com/google/cql/protos/cql_result_go_proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The results of the evaluated CQL for a particular id and timestamp. +type BeamResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The id of the result. Usually this is the Patient ID but can be overriden. + Id *string `protobuf:"bytes,1,opt,name=id,proto3,oneof" json:"id,omitempty"` + // The timestamp at which the CQL was evaluated. + EvaluationTimestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=evaluation_timestamp,json=evaluationTimestamp,proto3,oneof" json:"evaluation_timestamp,omitempty"` + // The result of the CQL evaluation. + Result *cql_result_go_proto.Libraries `protobuf:"bytes,3,opt,name=result,proto3,oneof" json:"result,omitempty"` +} + +func (x *BeamResult) Reset() { + *x = BeamResult{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_beam_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BeamResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeamResult) ProtoMessage() {} + +func (x *BeamResult) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_beam_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeamResult.ProtoReflect.Descriptor instead. +func (*BeamResult) Descriptor() ([]byte, []int) { + return file_protos_cql_beam_proto_rawDescGZIP(), []int{0} +} + +func (x *BeamResult) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *BeamResult) GetEvaluationTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.EvaluationTimestamp + } + return nil +} + +func (x *BeamResult) GetResult() *cql_result_go_proto.Libraries { + if x != nil { + return x.Result + } + return nil +} + +// Indicates an error that occured at some phase of CQL processing. +type BeamError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A message describing the error. + ErrorMessage *string `protobuf:"bytes,1,opt,name=error_message,json=errorMessage,proto3,oneof" json:"error_message,omitempty"` + // A URI referencing the error's source. This could be a file path to a + // malformed input, resource ID, or other item. + SourceUri *string `protobuf:"bytes,2,opt,name=source_uri,json=sourceUri,proto3,oneof" json:"source_uri,omitempty"` +} + +func (x *BeamError) Reset() { + *x = BeamError{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_beam_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BeamError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BeamError) ProtoMessage() {} + +func (x *BeamError) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_beam_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BeamError.ProtoReflect.Descriptor instead. +func (*BeamError) Descriptor() ([]byte, []int) { + return file_protos_cql_beam_proto_rawDescGZIP(), []int{1} +} + +func (x *BeamError) GetErrorMessage() string { + if x != nil && x.ErrorMessage != nil { + return *x.ErrorMessage + } + return "" +} + +func (x *BeamError) GetSourceUri() string { + if x != nil && x.SourceUri != nil { + return *x.SourceUri + } + return "" +} + +var File_protos_cql_beam_proto protoreflect.FileDescriptor + +var file_protos_cql_beam_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x71, 0x6c, 0x5f, 0x62, 0x65, 0x61, + 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x73, 0x2f, 0x63, 0x71, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xda, 0x01, 0x0a, 0x0a, 0x42, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x52, 0x0a, 0x14, 0x65, 0x76, 0x61, 0x6c, 0x75, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x48, 0x01, 0x52, 0x13, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x06, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x48, 0x02, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x17, 0x0a, 0x15, + 0x5f, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x22, 0x7a, 0x0a, 0x09, 0x42, 0x65, 0x61, 0x6d, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x28, 0x0a, + 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x09, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x72, 0x69, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x0d, 0x0a, + 0x0b, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x69, 0x42, 0x32, 0x50, 0x01, + 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x63, 0x71, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, + 0x71, 0x6c, 0x5f, 0x62, 0x65, 0x61, 0x6d, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_protos_cql_beam_proto_rawDescOnce sync.Once + file_protos_cql_beam_proto_rawDescData = file_protos_cql_beam_proto_rawDesc +) + +func file_protos_cql_beam_proto_rawDescGZIP() []byte { + file_protos_cql_beam_proto_rawDescOnce.Do(func() { + file_protos_cql_beam_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_cql_beam_proto_rawDescData) + }) + return file_protos_cql_beam_proto_rawDescData +} + +var file_protos_cql_beam_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_protos_cql_beam_proto_goTypes = []interface{}{ + (*BeamResult)(nil), // 0: google.cql.proto.BeamResult + (*BeamError)(nil), // 1: google.cql.proto.BeamError + (*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp + (*cql_result_go_proto.Libraries)(nil), // 3: google.cql.proto.Libraries +} +var file_protos_cql_beam_proto_depIdxs = []int32{ + 2, // 0: google.cql.proto.BeamResult.evaluation_timestamp:type_name -> google.protobuf.Timestamp + 3, // 1: google.cql.proto.BeamResult.result:type_name -> google.cql.proto.Libraries + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_protos_cql_beam_proto_init() } +func file_protos_cql_beam_proto_init() { + if File_protos_cql_beam_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_protos_cql_beam_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BeamResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_beam_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BeamError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_protos_cql_beam_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_protos_cql_beam_proto_msgTypes[1].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_protos_cql_beam_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_protos_cql_beam_proto_goTypes, + DependencyIndexes: file_protos_cql_beam_proto_depIdxs, + MessageInfos: file_protos_cql_beam_proto_msgTypes, + }.Build() + File_protos_cql_beam_proto = out.File + file_protos_cql_beam_proto_rawDesc = nil + file_protos_cql_beam_proto_goTypes = nil + file_protos_cql_beam_proto_depIdxs = nil +} diff --git a/protos/cql_result.proto b/protos/cql_result.proto new file mode 100644 index 0000000..54422ae --- /dev/null +++ b/protos/cql_result.proto @@ -0,0 +1,179 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; + +package google.cql.proto; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; +import "google/type/date.proto"; +import "google/type/timeofday.proto"; +import "protos/cql_types.proto"; + +option java_multiple_files = true; +option go_package = "github.com/google/cql/protos/cql_result_go_proto"; + +message Libraries { + // List of CQL libraries that were evaluated. + repeated Library libraries = 1; +} + +message Library { + // Qualified name of the library. + optional string name = 1; + // Version of the library. + optional string version = 2; + // Maps the named of the expression defintion to the result. + map expr_defs = 3; +} + +message Value { + // Proto representation of a CQL Value. For CQL nulls the value oneof field + // will be unset. + // TODO(b/301606416): Consider supporting typed nulls. + oneof value { + bool boolean_value = 1; + string string_value = 2; + int32 integer_value = 3; + int64 long_value = 4; + double decimal_value = 5; + Quantity quantity_value = 6; + Ratio ratio_value = 7; + Date date_value = 8; + DateTime date_time_value = 9; + Time time_value = 10; + Interval interval_value = 11; + List list_value = 12; + Tuple tuple_value = 13; + Named named_value = 14; + CodeSystem code_system_value = 15; + ValueSet value_set_value = 16; + Code code_value = 17; + Concept concept_value = 18; + } +} + +message Quantity { + optional double value = 1; + optional string unit = 2; +} + +message Ratio { + optional Quantity numerator = 1; + optional Quantity denominator = 2; +} + +message Date { + optional google.type.Date date = 1; + optional Precision precision = 2; + enum Precision { + PRECISION_UNSPECIFIED = 0; + PRECISION_YEAR = 1; + PRECISION_MONTH = 2; + PRECISION_DAY = 3; + } +} + +message DateTime { + // date is normalized to UTC, which is more conventient and less confusing for + // most use cases. However, the orginal time zone is lost in the process. + optional google.protobuf.Timestamp date = 1; + optional Precision precision = 2; + enum Precision { + PRECISION_UNSPECIFIED = 0; + PRECISION_YEAR = 1; + PRECISION_MONTH = 2; + PRECISION_DAY = 3; + PRECISION_HOUR = 4; + PRECISION_MINUTE = 5; + PRECISION_SECOND = 6; + PRECISION_MILLISECOND = 7; + } +} + +message Time { + optional google.type.TimeOfDay date = 1; + optional Precision precision = 2; + enum Precision { + PRECISION_UNSPECIFIED = 0; + PRECISION_HOUR = 1; + PRECISION_MINUTE = 2; + PRECISION_SECOND = 3; + PRECISION_MILLISECOND = 4; + } +} + +message Interval { + optional Value low = 1; + optional Value high = 2; + optional bool low_inclusive = 3; + optional bool high_inclusive = 4; + // StaticType is used for the RuntimeType of the interval when the interval + // low and high are null. Otherwise, the RuntimeType can be inferred by + // inspecting the type of the low and high values. + // TODO(b/301606416): Remove this field once we support typed nulls. + optional IntervalType static_type = 5; +} + +// List is wrapped in a message because oneof does not support repeated fields. +message List { + repeated Value value = 1; + // StaticType is used for the RuntimeType of the list when the list is empty. + // Otherwise, the RuntimeType can be inferred by inspecting the type of each + // of the elements in the list. + optional ListType static_type = 2; +} + +// Tuple is wrapped in a message because oneof does not support map fields. +message Tuple { + map value = 1; + // RuntimeType could be a tuple type or if this was a Class instance could be + // the class type (FHIR.Patient, System.Quantity...). + oneof RuntimeType { + TupleType tuple_type = 2; + NamedType named_type = 3; + } +} + +// Named is the proto representation of a CQL Named type (type defined in the +// data model). google.protobuf.Any should be a type in FHIR Proto, but it hard +// to work with. In the future, we may want to convert this to a map simlilar to Tuple. +message Named { + optional google.protobuf.Any value = 1; + optional NamedType runtime_type = 2; +} + +message CodeSystem { + optional string id = 1; + optional string version = 2; +} + +message ValueSet { + optional string id = 1; + optional string version = 2; + repeated CodeSystem code_systems = 3; +} + +message Concept { + repeated Code codes = 1; + optional string display = 2; +} + +message Code { + optional string code = 1; + optional string display = 2; + optional string system = 3; + optional string version = 4; +} diff --git a/protos/cql_result_go_proto/cql_result.pb.go b/protos/cql_result_go_proto/cql_result.pb.go new file mode 100644 index 0000000..edb6847 --- /dev/null +++ b/protos/cql_result_go_proto/cql_result.pb.go @@ -0,0 +1,2045 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.29.0 +// protoc v3.21.12 +// source: protos/cql_result.proto + +package cql_result_go_proto + +import ( + cql_types_go_proto "github.com/google/cql/protos/cql_types_go_proto" + date "google.golang.org/genproto/googleapis/type/date" + timeofday "google.golang.org/genproto/googleapis/type/timeofday" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Date_Precision int32 + +const ( + Date_PRECISION_UNSPECIFIED Date_Precision = 0 + Date_PRECISION_YEAR Date_Precision = 1 + Date_PRECISION_MONTH Date_Precision = 2 + Date_PRECISION_DAY Date_Precision = 3 +) + +// Enum value maps for Date_Precision. +var ( + Date_Precision_name = map[int32]string{ + 0: "PRECISION_UNSPECIFIED", + 1: "PRECISION_YEAR", + 2: "PRECISION_MONTH", + 3: "PRECISION_DAY", + } + Date_Precision_value = map[string]int32{ + "PRECISION_UNSPECIFIED": 0, + "PRECISION_YEAR": 1, + "PRECISION_MONTH": 2, + "PRECISION_DAY": 3, + } +) + +func (x Date_Precision) Enum() *Date_Precision { + p := new(Date_Precision) + *p = x + return p +} + +func (x Date_Precision) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Date_Precision) Descriptor() protoreflect.EnumDescriptor { + return file_protos_cql_result_proto_enumTypes[0].Descriptor() +} + +func (Date_Precision) Type() protoreflect.EnumType { + return &file_protos_cql_result_proto_enumTypes[0] +} + +func (x Date_Precision) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Date_Precision.Descriptor instead. +func (Date_Precision) EnumDescriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{5, 0} +} + +type DateTime_Precision int32 + +const ( + DateTime_PRECISION_UNSPECIFIED DateTime_Precision = 0 + DateTime_PRECISION_YEAR DateTime_Precision = 1 + DateTime_PRECISION_MONTH DateTime_Precision = 2 + DateTime_PRECISION_DAY DateTime_Precision = 3 + DateTime_PRECISION_HOUR DateTime_Precision = 4 + DateTime_PRECISION_MINUTE DateTime_Precision = 5 + DateTime_PRECISION_SECOND DateTime_Precision = 6 + DateTime_PRECISION_MILLISECOND DateTime_Precision = 7 +) + +// Enum value maps for DateTime_Precision. +var ( + DateTime_Precision_name = map[int32]string{ + 0: "PRECISION_UNSPECIFIED", + 1: "PRECISION_YEAR", + 2: "PRECISION_MONTH", + 3: "PRECISION_DAY", + 4: "PRECISION_HOUR", + 5: "PRECISION_MINUTE", + 6: "PRECISION_SECOND", + 7: "PRECISION_MILLISECOND", + } + DateTime_Precision_value = map[string]int32{ + "PRECISION_UNSPECIFIED": 0, + "PRECISION_YEAR": 1, + "PRECISION_MONTH": 2, + "PRECISION_DAY": 3, + "PRECISION_HOUR": 4, + "PRECISION_MINUTE": 5, + "PRECISION_SECOND": 6, + "PRECISION_MILLISECOND": 7, + } +) + +func (x DateTime_Precision) Enum() *DateTime_Precision { + p := new(DateTime_Precision) + *p = x + return p +} + +func (x DateTime_Precision) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DateTime_Precision) Descriptor() protoreflect.EnumDescriptor { + return file_protos_cql_result_proto_enumTypes[1].Descriptor() +} + +func (DateTime_Precision) Type() protoreflect.EnumType { + return &file_protos_cql_result_proto_enumTypes[1] +} + +func (x DateTime_Precision) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DateTime_Precision.Descriptor instead. +func (DateTime_Precision) EnumDescriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{6, 0} +} + +type Time_Precision int32 + +const ( + Time_PRECISION_UNSPECIFIED Time_Precision = 0 + Time_PRECISION_HOUR Time_Precision = 1 + Time_PRECISION_MINUTE Time_Precision = 2 + Time_PRECISION_SECOND Time_Precision = 3 + Time_PRECISION_MILLISECOND Time_Precision = 4 +) + +// Enum value maps for Time_Precision. +var ( + Time_Precision_name = map[int32]string{ + 0: "PRECISION_UNSPECIFIED", + 1: "PRECISION_HOUR", + 2: "PRECISION_MINUTE", + 3: "PRECISION_SECOND", + 4: "PRECISION_MILLISECOND", + } + Time_Precision_value = map[string]int32{ + "PRECISION_UNSPECIFIED": 0, + "PRECISION_HOUR": 1, + "PRECISION_MINUTE": 2, + "PRECISION_SECOND": 3, + "PRECISION_MILLISECOND": 4, + } +) + +func (x Time_Precision) Enum() *Time_Precision { + p := new(Time_Precision) + *p = x + return p +} + +func (x Time_Precision) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Time_Precision) Descriptor() protoreflect.EnumDescriptor { + return file_protos_cql_result_proto_enumTypes[2].Descriptor() +} + +func (Time_Precision) Type() protoreflect.EnumType { + return &file_protos_cql_result_proto_enumTypes[2] +} + +func (x Time_Precision) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Time_Precision.Descriptor instead. +func (Time_Precision) EnumDescriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{7, 0} +} + +type Libraries struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of CQL libraries that were evaluated. + Libraries []*Library `protobuf:"bytes,1,rep,name=libraries,proto3" json:"libraries,omitempty"` +} + +func (x *Libraries) Reset() { + *x = Libraries{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Libraries) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Libraries) ProtoMessage() {} + +func (x *Libraries) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Libraries.ProtoReflect.Descriptor instead. +func (*Libraries) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{0} +} + +func (x *Libraries) GetLibraries() []*Library { + if x != nil { + return x.Libraries + } + return nil +} + +type Library struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Qualified name of the library. + Name *string `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"` + // Version of the library. + Version *string `protobuf:"bytes,2,opt,name=version,proto3,oneof" json:"version,omitempty"` + // Maps the named of the expression defintion to the result. + ExprDefs map[string]*Value `protobuf:"bytes,3,rep,name=expr_defs,json=exprDefs,proto3" json:"expr_defs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Library) Reset() { + *x = Library{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Library) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Library) ProtoMessage() {} + +func (x *Library) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Library.ProtoReflect.Descriptor instead. +func (*Library) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{1} +} + +func (x *Library) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *Library) GetVersion() string { + if x != nil && x.Version != nil { + return *x.Version + } + return "" +} + +func (x *Library) GetExprDefs() map[string]*Value { + if x != nil { + return x.ExprDefs + } + return nil +} + +type Value struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Proto representation of a CQL Value. For CQL nulls the value oneof field + // will be unset. + // TODO(b/301606416): Consider supporting typed nulls. + // + // Types that are assignable to Value: + // + // *Value_BooleanValue + // *Value_StringValue + // *Value_IntegerValue + // *Value_LongValue + // *Value_DecimalValue + // *Value_QuantityValue + // *Value_RatioValue + // *Value_DateValue + // *Value_DateTimeValue + // *Value_TimeValue + // *Value_IntervalValue + // *Value_ListValue + // *Value_TupleValue + // *Value_NamedValue + // *Value_CodeSystemValue + // *Value_ValueSetValue + // *Value_CodeValue + // *Value_ConceptValue + Value isValue_Value `protobuf_oneof:"value"` +} + +func (x *Value) Reset() { + *x = Value{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Value) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Value) ProtoMessage() {} + +func (x *Value) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Value.ProtoReflect.Descriptor instead. +func (*Value) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{2} +} + +func (m *Value) GetValue() isValue_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *Value) GetBooleanValue() bool { + if x, ok := x.GetValue().(*Value_BooleanValue); ok { + return x.BooleanValue + } + return false +} + +func (x *Value) GetStringValue() string { + if x, ok := x.GetValue().(*Value_StringValue); ok { + return x.StringValue + } + return "" +} + +func (x *Value) GetIntegerValue() int32 { + if x, ok := x.GetValue().(*Value_IntegerValue); ok { + return x.IntegerValue + } + return 0 +} + +func (x *Value) GetLongValue() int64 { + if x, ok := x.GetValue().(*Value_LongValue); ok { + return x.LongValue + } + return 0 +} + +func (x *Value) GetDecimalValue() float64 { + if x, ok := x.GetValue().(*Value_DecimalValue); ok { + return x.DecimalValue + } + return 0 +} + +func (x *Value) GetQuantityValue() *Quantity { + if x, ok := x.GetValue().(*Value_QuantityValue); ok { + return x.QuantityValue + } + return nil +} + +func (x *Value) GetRatioValue() *Ratio { + if x, ok := x.GetValue().(*Value_RatioValue); ok { + return x.RatioValue + } + return nil +} + +func (x *Value) GetDateValue() *Date { + if x, ok := x.GetValue().(*Value_DateValue); ok { + return x.DateValue + } + return nil +} + +func (x *Value) GetDateTimeValue() *DateTime { + if x, ok := x.GetValue().(*Value_DateTimeValue); ok { + return x.DateTimeValue + } + return nil +} + +func (x *Value) GetTimeValue() *Time { + if x, ok := x.GetValue().(*Value_TimeValue); ok { + return x.TimeValue + } + return nil +} + +func (x *Value) GetIntervalValue() *Interval { + if x, ok := x.GetValue().(*Value_IntervalValue); ok { + return x.IntervalValue + } + return nil +} + +func (x *Value) GetListValue() *List { + if x, ok := x.GetValue().(*Value_ListValue); ok { + return x.ListValue + } + return nil +} + +func (x *Value) GetTupleValue() *Tuple { + if x, ok := x.GetValue().(*Value_TupleValue); ok { + return x.TupleValue + } + return nil +} + +func (x *Value) GetNamedValue() *Named { + if x, ok := x.GetValue().(*Value_NamedValue); ok { + return x.NamedValue + } + return nil +} + +func (x *Value) GetCodeSystemValue() *CodeSystem { + if x, ok := x.GetValue().(*Value_CodeSystemValue); ok { + return x.CodeSystemValue + } + return nil +} + +func (x *Value) GetValueSetValue() *ValueSet { + if x, ok := x.GetValue().(*Value_ValueSetValue); ok { + return x.ValueSetValue + } + return nil +} + +func (x *Value) GetCodeValue() *Code { + if x, ok := x.GetValue().(*Value_CodeValue); ok { + return x.CodeValue + } + return nil +} + +func (x *Value) GetConceptValue() *Concept { + if x, ok := x.GetValue().(*Value_ConceptValue); ok { + return x.ConceptValue + } + return nil +} + +type isValue_Value interface { + isValue_Value() +} + +type Value_BooleanValue struct { + BooleanValue bool `protobuf:"varint,1,opt,name=boolean_value,json=booleanValue,proto3,oneof"` +} + +type Value_StringValue struct { + StringValue string `protobuf:"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof"` +} + +type Value_IntegerValue struct { + IntegerValue int32 `protobuf:"varint,3,opt,name=integer_value,json=integerValue,proto3,oneof"` +} + +type Value_LongValue struct { + LongValue int64 `protobuf:"varint,4,opt,name=long_value,json=longValue,proto3,oneof"` +} + +type Value_DecimalValue struct { + DecimalValue float64 `protobuf:"fixed64,5,opt,name=decimal_value,json=decimalValue,proto3,oneof"` +} + +type Value_QuantityValue struct { + QuantityValue *Quantity `protobuf:"bytes,6,opt,name=quantity_value,json=quantityValue,proto3,oneof"` +} + +type Value_RatioValue struct { + RatioValue *Ratio `protobuf:"bytes,7,opt,name=ratio_value,json=ratioValue,proto3,oneof"` +} + +type Value_DateValue struct { + DateValue *Date `protobuf:"bytes,8,opt,name=date_value,json=dateValue,proto3,oneof"` +} + +type Value_DateTimeValue struct { + DateTimeValue *DateTime `protobuf:"bytes,9,opt,name=date_time_value,json=dateTimeValue,proto3,oneof"` +} + +type Value_TimeValue struct { + TimeValue *Time `protobuf:"bytes,10,opt,name=time_value,json=timeValue,proto3,oneof"` +} + +type Value_IntervalValue struct { + IntervalValue *Interval `protobuf:"bytes,11,opt,name=interval_value,json=intervalValue,proto3,oneof"` +} + +type Value_ListValue struct { + ListValue *List `protobuf:"bytes,12,opt,name=list_value,json=listValue,proto3,oneof"` +} + +type Value_TupleValue struct { + TupleValue *Tuple `protobuf:"bytes,13,opt,name=tuple_value,json=tupleValue,proto3,oneof"` +} + +type Value_NamedValue struct { + NamedValue *Named `protobuf:"bytes,14,opt,name=named_value,json=namedValue,proto3,oneof"` +} + +type Value_CodeSystemValue struct { + CodeSystemValue *CodeSystem `protobuf:"bytes,15,opt,name=code_system_value,json=codeSystemValue,proto3,oneof"` +} + +type Value_ValueSetValue struct { + ValueSetValue *ValueSet `protobuf:"bytes,16,opt,name=value_set_value,json=valueSetValue,proto3,oneof"` +} + +type Value_CodeValue struct { + CodeValue *Code `protobuf:"bytes,17,opt,name=code_value,json=codeValue,proto3,oneof"` +} + +type Value_ConceptValue struct { + ConceptValue *Concept `protobuf:"bytes,18,opt,name=concept_value,json=conceptValue,proto3,oneof"` +} + +func (*Value_BooleanValue) isValue_Value() {} + +func (*Value_StringValue) isValue_Value() {} + +func (*Value_IntegerValue) isValue_Value() {} + +func (*Value_LongValue) isValue_Value() {} + +func (*Value_DecimalValue) isValue_Value() {} + +func (*Value_QuantityValue) isValue_Value() {} + +func (*Value_RatioValue) isValue_Value() {} + +func (*Value_DateValue) isValue_Value() {} + +func (*Value_DateTimeValue) isValue_Value() {} + +func (*Value_TimeValue) isValue_Value() {} + +func (*Value_IntervalValue) isValue_Value() {} + +func (*Value_ListValue) isValue_Value() {} + +func (*Value_TupleValue) isValue_Value() {} + +func (*Value_NamedValue) isValue_Value() {} + +func (*Value_CodeSystemValue) isValue_Value() {} + +func (*Value_ValueSetValue) isValue_Value() {} + +func (*Value_CodeValue) isValue_Value() {} + +func (*Value_ConceptValue) isValue_Value() {} + +type Quantity struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value *float64 `protobuf:"fixed64,1,opt,name=value,proto3,oneof" json:"value,omitempty"` + Unit *string `protobuf:"bytes,2,opt,name=unit,proto3,oneof" json:"unit,omitempty"` +} + +func (x *Quantity) Reset() { + *x = Quantity{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Quantity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Quantity) ProtoMessage() {} + +func (x *Quantity) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Quantity.ProtoReflect.Descriptor instead. +func (*Quantity) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{3} +} + +func (x *Quantity) GetValue() float64 { + if x != nil && x.Value != nil { + return *x.Value + } + return 0 +} + +func (x *Quantity) GetUnit() string { + if x != nil && x.Unit != nil { + return *x.Unit + } + return "" +} + +type Ratio struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Numerator *Quantity `protobuf:"bytes,1,opt,name=numerator,proto3,oneof" json:"numerator,omitempty"` + Denominator *Quantity `protobuf:"bytes,2,opt,name=denominator,proto3,oneof" json:"denominator,omitempty"` +} + +func (x *Ratio) Reset() { + *x = Ratio{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Ratio) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ratio) ProtoMessage() {} + +func (x *Ratio) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ratio.ProtoReflect.Descriptor instead. +func (*Ratio) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{4} +} + +func (x *Ratio) GetNumerator() *Quantity { + if x != nil { + return x.Numerator + } + return nil +} + +func (x *Ratio) GetDenominator() *Quantity { + if x != nil { + return x.Denominator + } + return nil +} + +type Date struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Date *date.Date `protobuf:"bytes,1,opt,name=date,proto3,oneof" json:"date,omitempty"` + Precision *Date_Precision `protobuf:"varint,2,opt,name=precision,proto3,enum=google.cql.proto.Date_Precision,oneof" json:"precision,omitempty"` +} + +func (x *Date) Reset() { + *x = Date{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Date) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Date) ProtoMessage() {} + +func (x *Date) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Date.ProtoReflect.Descriptor instead. +func (*Date) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{5} +} + +func (x *Date) GetDate() *date.Date { + if x != nil { + return x.Date + } + return nil +} + +func (x *Date) GetPrecision() Date_Precision { + if x != nil && x.Precision != nil { + return *x.Precision + } + return Date_PRECISION_UNSPECIFIED +} + +type DateTime struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Date *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=date,proto3,oneof" json:"date,omitempty"` + Precision *DateTime_Precision `protobuf:"varint,2,opt,name=precision,proto3,enum=google.cql.proto.DateTime_Precision,oneof" json:"precision,omitempty"` +} + +func (x *DateTime) Reset() { + *x = DateTime{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DateTime) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DateTime) ProtoMessage() {} + +func (x *DateTime) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DateTime.ProtoReflect.Descriptor instead. +func (*DateTime) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{6} +} + +func (x *DateTime) GetDate() *timestamppb.Timestamp { + if x != nil { + return x.Date + } + return nil +} + +func (x *DateTime) GetPrecision() DateTime_Precision { + if x != nil && x.Precision != nil { + return *x.Precision + } + return DateTime_PRECISION_UNSPECIFIED +} + +type Time struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Date *timeofday.TimeOfDay `protobuf:"bytes,1,opt,name=date,proto3,oneof" json:"date,omitempty"` + Precision *Time_Precision `protobuf:"varint,2,opt,name=precision,proto3,enum=google.cql.proto.Time_Precision,oneof" json:"precision,omitempty"` +} + +func (x *Time) Reset() { + *x = Time{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Time) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Time) ProtoMessage() {} + +func (x *Time) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Time.ProtoReflect.Descriptor instead. +func (*Time) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{7} +} + +func (x *Time) GetDate() *timeofday.TimeOfDay { + if x != nil { + return x.Date + } + return nil +} + +func (x *Time) GetPrecision() Time_Precision { + if x != nil && x.Precision != nil { + return *x.Precision + } + return Time_PRECISION_UNSPECIFIED +} + +type Interval struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Low *Value `protobuf:"bytes,1,opt,name=low,proto3,oneof" json:"low,omitempty"` + High *Value `protobuf:"bytes,2,opt,name=high,proto3,oneof" json:"high,omitempty"` + LowInclusive *bool `protobuf:"varint,3,opt,name=low_inclusive,json=lowInclusive,proto3,oneof" json:"low_inclusive,omitempty"` + HighInclusive *bool `protobuf:"varint,4,opt,name=high_inclusive,json=highInclusive,proto3,oneof" json:"high_inclusive,omitempty"` + // StaticType is used for the RuntimeType of the interval when the interval + // low and high are null. Otherwise, the RuntimeType can be inferred by + // inspecting the type of the low and high values. + // TODO(b/301606416): Remove this field once we support typed nulls. + StaticType *cql_types_go_proto.IntervalType `protobuf:"bytes,5,opt,name=static_type,json=staticType,proto3,oneof" json:"static_type,omitempty"` +} + +func (x *Interval) Reset() { + *x = Interval{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Interval) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Interval) ProtoMessage() {} + +func (x *Interval) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Interval.ProtoReflect.Descriptor instead. +func (*Interval) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{8} +} + +func (x *Interval) GetLow() *Value { + if x != nil { + return x.Low + } + return nil +} + +func (x *Interval) GetHigh() *Value { + if x != nil { + return x.High + } + return nil +} + +func (x *Interval) GetLowInclusive() bool { + if x != nil && x.LowInclusive != nil { + return *x.LowInclusive + } + return false +} + +func (x *Interval) GetHighInclusive() bool { + if x != nil && x.HighInclusive != nil { + return *x.HighInclusive + } + return false +} + +func (x *Interval) GetStaticType() *cql_types_go_proto.IntervalType { + if x != nil { + return x.StaticType + } + return nil +} + +// List is wrapped in a message because oneof does not support repeated fields. +type List struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value []*Value `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"` + // StaticType is used for the RuntimeType of the list when the list is empty. + // Otherwise, the RuntimeType can be inferred by inspecting the type of each + // of the elements in the list. + StaticType *cql_types_go_proto.ListType `protobuf:"bytes,2,opt,name=static_type,json=staticType,proto3,oneof" json:"static_type,omitempty"` +} + +func (x *List) Reset() { + *x = List{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *List) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*List) ProtoMessage() {} + +func (x *List) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use List.ProtoReflect.Descriptor instead. +func (*List) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{9} +} + +func (x *List) GetValue() []*Value { + if x != nil { + return x.Value + } + return nil +} + +func (x *List) GetStaticType() *cql_types_go_proto.ListType { + if x != nil { + return x.StaticType + } + return nil +} + +// Tuple is wrapped in a message because oneof does not support map fields. +type Tuple struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value map[string]*Value `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // RuntimeType could be a tuple type or if this was a Class instance could be + // the class type (FHIR.Patient, System.Quantity...). + // + // Types that are assignable to RuntimeType: + // + // *Tuple_TupleType + // *Tuple_NamedType + RuntimeType isTuple_RuntimeType `protobuf_oneof:"RuntimeType"` +} + +func (x *Tuple) Reset() { + *x = Tuple{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Tuple) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Tuple) ProtoMessage() {} + +func (x *Tuple) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Tuple.ProtoReflect.Descriptor instead. +func (*Tuple) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{10} +} + +func (x *Tuple) GetValue() map[string]*Value { + if x != nil { + return x.Value + } + return nil +} + +func (m *Tuple) GetRuntimeType() isTuple_RuntimeType { + if m != nil { + return m.RuntimeType + } + return nil +} + +func (x *Tuple) GetTupleType() *cql_types_go_proto.TupleType { + if x, ok := x.GetRuntimeType().(*Tuple_TupleType); ok { + return x.TupleType + } + return nil +} + +func (x *Tuple) GetNamedType() *cql_types_go_proto.NamedType { + if x, ok := x.GetRuntimeType().(*Tuple_NamedType); ok { + return x.NamedType + } + return nil +} + +type isTuple_RuntimeType interface { + isTuple_RuntimeType() +} + +type Tuple_TupleType struct { + TupleType *cql_types_go_proto.TupleType `protobuf:"bytes,2,opt,name=tuple_type,json=tupleType,proto3,oneof"` +} + +type Tuple_NamedType struct { + NamedType *cql_types_go_proto.NamedType `protobuf:"bytes,3,opt,name=named_type,json=namedType,proto3,oneof"` +} + +func (*Tuple_TupleType) isTuple_RuntimeType() {} + +func (*Tuple_NamedType) isTuple_RuntimeType() {} + +// Named is the proto representation of a CQL Named type (type defined in the +// data model). google.protobuf.Any should be a type in FHIR Proto, but it hard +// to work with. In the future, we may want to convert this to a map simlilar to Tuple. +type Named struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value *anypb.Any `protobuf:"bytes,1,opt,name=value,proto3,oneof" json:"value,omitempty"` + RuntimeType *cql_types_go_proto.NamedType `protobuf:"bytes,2,opt,name=runtime_type,json=runtimeType,proto3,oneof" json:"runtime_type,omitempty"` +} + +func (x *Named) Reset() { + *x = Named{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Named) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Named) ProtoMessage() {} + +func (x *Named) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Named.ProtoReflect.Descriptor instead. +func (*Named) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{11} +} + +func (x *Named) GetValue() *anypb.Any { + if x != nil { + return x.Value + } + return nil +} + +func (x *Named) GetRuntimeType() *cql_types_go_proto.NamedType { + if x != nil { + return x.RuntimeType + } + return nil +} + +type CodeSystem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id *string `protobuf:"bytes,1,opt,name=id,proto3,oneof" json:"id,omitempty"` + Version *string `protobuf:"bytes,2,opt,name=version,proto3,oneof" json:"version,omitempty"` +} + +func (x *CodeSystem) Reset() { + *x = CodeSystem{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CodeSystem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CodeSystem) ProtoMessage() {} + +func (x *CodeSystem) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CodeSystem.ProtoReflect.Descriptor instead. +func (*CodeSystem) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{12} +} + +func (x *CodeSystem) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *CodeSystem) GetVersion() string { + if x != nil && x.Version != nil { + return *x.Version + } + return "" +} + +type ValueSet struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id *string `protobuf:"bytes,1,opt,name=id,proto3,oneof" json:"id,omitempty"` + Version *string `protobuf:"bytes,2,opt,name=version,proto3,oneof" json:"version,omitempty"` + CodeSystems []*CodeSystem `protobuf:"bytes,3,rep,name=code_systems,json=codeSystems,proto3" json:"code_systems,omitempty"` +} + +func (x *ValueSet) Reset() { + *x = ValueSet{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ValueSet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValueSet) ProtoMessage() {} + +func (x *ValueSet) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValueSet.ProtoReflect.Descriptor instead. +func (*ValueSet) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{13} +} + +func (x *ValueSet) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *ValueSet) GetVersion() string { + if x != nil && x.Version != nil { + return *x.Version + } + return "" +} + +func (x *ValueSet) GetCodeSystems() []*CodeSystem { + if x != nil { + return x.CodeSystems + } + return nil +} + +type Concept struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Codes []*Code `protobuf:"bytes,1,rep,name=codes,proto3" json:"codes,omitempty"` + Display *string `protobuf:"bytes,2,opt,name=display,proto3,oneof" json:"display,omitempty"` +} + +func (x *Concept) Reset() { + *x = Concept{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Concept) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Concept) ProtoMessage() {} + +func (x *Concept) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Concept.ProtoReflect.Descriptor instead. +func (*Concept) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{14} +} + +func (x *Concept) GetCodes() []*Code { + if x != nil { + return x.Codes + } + return nil +} + +func (x *Concept) GetDisplay() string { + if x != nil && x.Display != nil { + return *x.Display + } + return "" +} + +type Code struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code *string `protobuf:"bytes,1,opt,name=code,proto3,oneof" json:"code,omitempty"` + Display *string `protobuf:"bytes,2,opt,name=display,proto3,oneof" json:"display,omitempty"` + System *string `protobuf:"bytes,3,opt,name=system,proto3,oneof" json:"system,omitempty"` + Version *string `protobuf:"bytes,4,opt,name=version,proto3,oneof" json:"version,omitempty"` +} + +func (x *Code) Reset() { + *x = Code{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_result_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Code) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Code) ProtoMessage() {} + +func (x *Code) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_result_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Code.ProtoReflect.Descriptor instead. +func (*Code) Descriptor() ([]byte, []int) { + return file_protos_cql_result_proto_rawDescGZIP(), []int{15} +} + +func (x *Code) GetCode() string { + if x != nil && x.Code != nil { + return *x.Code + } + return "" +} + +func (x *Code) GetDisplay() string { + if x != nil && x.Display != nil { + return *x.Display + } + return "" +} + +func (x *Code) GetSystem() string { + if x != nil && x.System != nil { + return *x.System + } + return "" +} + +func (x *Code) GetVersion() string { + if x != nil && x.Version != nil { + return *x.Version + } + return "" +} + +var File_protos_cql_result_proto protoreflect.FileDescriptor + +var file_protos_cql_result_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x71, 0x6c, 0x5f, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x6f, 0x66, 0x64, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x71, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x44, 0x0a, 0x09, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, + 0x73, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, + 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, + 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x22, 0xf2, 0x01, 0x0a, 0x07, 0x4c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x44, + 0x0a, 0x09, 0x65, 0x78, 0x70, 0x72, 0x5f, 0x64, 0x65, 0x66, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x45, 0x78, 0x70, + 0x72, 0x44, 0x65, 0x66, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x65, 0x78, 0x70, 0x72, + 0x44, 0x65, 0x66, 0x73, 0x1a, 0x54, 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x72, 0x44, 0x65, 0x66, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0x87, 0x08, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x6f, 0x6f, + 0x6c, 0x65, 0x61, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x48, 0x00, 0x52, 0x0c, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0c, + 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0a, + 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x48, 0x00, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x25, 0x0a, + 0x0d, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0c, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, 0x0a, 0x0e, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x00, 0x52, 0x0d, 0x71, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x0b, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, + 0x65, 0x48, 0x00, 0x52, 0x09, 0x64, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, + 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x48, 0x00, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x48, 0x00, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, 0x0a, + 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, + 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x48, 0x00, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, + 0x52, 0x09, 0x6c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x0b, 0x74, + 0x75, 0x70, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x74, 0x75, 0x70, + 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x64, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x4e, 0x61, 0x6d, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, 0x0f, + 0x63, 0x6f, 0x64, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x44, 0x0a, 0x0f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x53, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65, 0x74, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x64, + 0x65, 0x48, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x40, + 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, + 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x63, 0x65, 0x70, 0x74, + 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x63, 0x65, 0x70, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x51, 0x0a, 0x08, 0x51, 0x75, 0x61, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, + 0x12, 0x17, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, + 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x22, 0xa7, 0x01, 0x0a, + 0x05, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x3d, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x51, 0x75, 0x61, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x41, 0x0a, 0x0b, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, + 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x51, 0x75, + 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, + 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6e, 0x75, 0x6d, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, + 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x22, 0xf2, 0x01, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x2a, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x65, + 0x48, 0x00, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x09, 0x70, + 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x48, 0x01, 0x52, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, + 0x22, 0x62, 0x0a, 0x09, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, + 0x15, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x52, 0x45, 0x43, + 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x59, 0x45, 0x41, 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, + 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x10, + 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, + 0x41, 0x59, 0x10, 0x03, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x42, 0x0c, 0x0a, + 0x0a, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xdf, 0x02, 0x0a, 0x08, + 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x48, 0x00, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x47, 0x0a, + 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x2e, 0x50, 0x72, 0x65, + 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x01, 0x52, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x22, 0xbd, 0x01, 0x0a, 0x09, 0x50, 0x72, 0x65, 0x63, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, + 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x12, 0x0a, 0x0e, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x59, 0x45, 0x41, + 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, + 0x5f, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x52, 0x45, 0x43, + 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x41, 0x59, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x50, + 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x04, 0x12, + 0x14, 0x0a, 0x10, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x4e, + 0x55, 0x54, 0x45, 0x10, 0x05, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, + 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x50, + 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x4c, 0x4c, 0x49, 0x53, 0x45, + 0x43, 0x4f, 0x4e, 0x44, 0x10, 0x07, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x42, + 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x97, 0x02, + 0x0a, 0x04, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x44, 0x61, 0x79, 0x48, 0x00, 0x52, 0x04, + 0x64, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x2e, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x01, 0x52, 0x09, + 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x22, 0x81, 0x01, 0x0a, + 0x09, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x52, + 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, + 0x4f, 0x4e, 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x52, 0x45, + 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, + 0x14, 0x0a, 0x10, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x43, + 0x4f, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x52, 0x45, 0x43, 0x49, 0x53, 0x49, + 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x4c, 0x4c, 0x49, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x10, 0x04, + 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x72, + 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xce, 0x02, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x03, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, + 0x77, 0x88, 0x01, 0x01, 0x12, 0x30, 0x0a, 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x01, 0x52, 0x04, 0x68, + 0x69, 0x67, 0x68, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x0d, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, + 0x0c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x88, 0x01, 0x01, + 0x12, 0x2a, 0x0a, 0x0e, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, + 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x03, 0x52, 0x0d, 0x68, 0x69, 0x67, 0x68, + 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x88, 0x01, 0x01, 0x12, 0x44, 0x0a, 0x0b, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x79, 0x70, + 0x65, 0x48, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x88, + 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6c, 0x6f, 0x77, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x68, + 0x69, 0x67, 0x68, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x73, 0x69, 0x76, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x87, 0x01, 0x0a, 0x04, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x40, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, + 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x88, + 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x05, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x12, 0x38, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, + 0x75, 0x70, 0x6c, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x75, + 0x70, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x09, 0x74, 0x75, 0x70, 0x6c, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x61, 0x6d, 0x65, + 0x64, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x54, 0x79, + 0x70, 0x65, 0x1a, 0x51, 0x0a, 0x0a, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x22, 0x98, 0x01, 0x0a, 0x05, 0x4e, 0x61, 0x6d, 0x65, 0x64, 0x12, 0x2f, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x41, 0x6e, 0x79, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x43, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, + 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x64, 0x54, 0x79, + 0x70, 0x65, 0x48, 0x01, 0x52, 0x0b, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0f, + 0x0a, 0x0d, 0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x53, 0x0a, 0x0a, 0x43, 0x6f, 0x64, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x13, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, + 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x92, 0x01, 0x0a, 0x08, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65, + 0x74, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x3f, 0x0a, 0x0c, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, + 0x6f, 0x64, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x63, 0x6f, 0x64, 0x65, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42, 0x0a, 0x0a, + 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x62, 0x0a, 0x07, 0x43, 0x6f, 0x6e, + 0x63, 0x65, 0x70, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x63, 0x6f, 0x64, + 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x07, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x88, 0x01, + 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0xa6, 0x01, + 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x1d, 0x0a, 0x07, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x01, 0x52, 0x07, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x88, 0x01, 0x01, 0x12, 0x1b, + 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, + 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x42, + 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x34, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x63, 0x71, + 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x71, 0x6c, 0x5f, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_protos_cql_result_proto_rawDescOnce sync.Once + file_protos_cql_result_proto_rawDescData = file_protos_cql_result_proto_rawDesc +) + +func file_protos_cql_result_proto_rawDescGZIP() []byte { + file_protos_cql_result_proto_rawDescOnce.Do(func() { + file_protos_cql_result_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_cql_result_proto_rawDescData) + }) + return file_protos_cql_result_proto_rawDescData +} + +var file_protos_cql_result_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_protos_cql_result_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_protos_cql_result_proto_goTypes = []interface{}{ + (Date_Precision)(0), // 0: google.cql.proto.Date.Precision + (DateTime_Precision)(0), // 1: google.cql.proto.DateTime.Precision + (Time_Precision)(0), // 2: google.cql.proto.Time.Precision + (*Libraries)(nil), // 3: google.cql.proto.Libraries + (*Library)(nil), // 4: google.cql.proto.Library + (*Value)(nil), // 5: google.cql.proto.Value + (*Quantity)(nil), // 6: google.cql.proto.Quantity + (*Ratio)(nil), // 7: google.cql.proto.Ratio + (*Date)(nil), // 8: google.cql.proto.Date + (*DateTime)(nil), // 9: google.cql.proto.DateTime + (*Time)(nil), // 10: google.cql.proto.Time + (*Interval)(nil), // 11: google.cql.proto.Interval + (*List)(nil), // 12: google.cql.proto.List + (*Tuple)(nil), // 13: google.cql.proto.Tuple + (*Named)(nil), // 14: google.cql.proto.Named + (*CodeSystem)(nil), // 15: google.cql.proto.CodeSystem + (*ValueSet)(nil), // 16: google.cql.proto.ValueSet + (*Concept)(nil), // 17: google.cql.proto.Concept + (*Code)(nil), // 18: google.cql.proto.Code + nil, // 19: google.cql.proto.Library.ExprDefsEntry + nil, // 20: google.cql.proto.Tuple.ValueEntry + (*date.Date)(nil), // 21: google.type.Date + (*timestamppb.Timestamp)(nil), // 22: google.protobuf.Timestamp + (*timeofday.TimeOfDay)(nil), // 23: google.type.TimeOfDay + (*cql_types_go_proto.IntervalType)(nil), // 24: google.cql.proto.IntervalType + (*cql_types_go_proto.ListType)(nil), // 25: google.cql.proto.ListType + (*cql_types_go_proto.TupleType)(nil), // 26: google.cql.proto.TupleType + (*cql_types_go_proto.NamedType)(nil), // 27: google.cql.proto.NamedType + (*anypb.Any)(nil), // 28: google.protobuf.Any +} +var file_protos_cql_result_proto_depIdxs = []int32{ + 4, // 0: google.cql.proto.Libraries.libraries:type_name -> google.cql.proto.Library + 19, // 1: google.cql.proto.Library.expr_defs:type_name -> google.cql.proto.Library.ExprDefsEntry + 6, // 2: google.cql.proto.Value.quantity_value:type_name -> google.cql.proto.Quantity + 7, // 3: google.cql.proto.Value.ratio_value:type_name -> google.cql.proto.Ratio + 8, // 4: google.cql.proto.Value.date_value:type_name -> google.cql.proto.Date + 9, // 5: google.cql.proto.Value.date_time_value:type_name -> google.cql.proto.DateTime + 10, // 6: google.cql.proto.Value.time_value:type_name -> google.cql.proto.Time + 11, // 7: google.cql.proto.Value.interval_value:type_name -> google.cql.proto.Interval + 12, // 8: google.cql.proto.Value.list_value:type_name -> google.cql.proto.List + 13, // 9: google.cql.proto.Value.tuple_value:type_name -> google.cql.proto.Tuple + 14, // 10: google.cql.proto.Value.named_value:type_name -> google.cql.proto.Named + 15, // 11: google.cql.proto.Value.code_system_value:type_name -> google.cql.proto.CodeSystem + 16, // 12: google.cql.proto.Value.value_set_value:type_name -> google.cql.proto.ValueSet + 18, // 13: google.cql.proto.Value.code_value:type_name -> google.cql.proto.Code + 17, // 14: google.cql.proto.Value.concept_value:type_name -> google.cql.proto.Concept + 6, // 15: google.cql.proto.Ratio.numerator:type_name -> google.cql.proto.Quantity + 6, // 16: google.cql.proto.Ratio.denominator:type_name -> google.cql.proto.Quantity + 21, // 17: google.cql.proto.Date.date:type_name -> google.type.Date + 0, // 18: google.cql.proto.Date.precision:type_name -> google.cql.proto.Date.Precision + 22, // 19: google.cql.proto.DateTime.date:type_name -> google.protobuf.Timestamp + 1, // 20: google.cql.proto.DateTime.precision:type_name -> google.cql.proto.DateTime.Precision + 23, // 21: google.cql.proto.Time.date:type_name -> google.type.TimeOfDay + 2, // 22: google.cql.proto.Time.precision:type_name -> google.cql.proto.Time.Precision + 5, // 23: google.cql.proto.Interval.low:type_name -> google.cql.proto.Value + 5, // 24: google.cql.proto.Interval.high:type_name -> google.cql.proto.Value + 24, // 25: google.cql.proto.Interval.static_type:type_name -> google.cql.proto.IntervalType + 5, // 26: google.cql.proto.List.value:type_name -> google.cql.proto.Value + 25, // 27: google.cql.proto.List.static_type:type_name -> google.cql.proto.ListType + 20, // 28: google.cql.proto.Tuple.value:type_name -> google.cql.proto.Tuple.ValueEntry + 26, // 29: google.cql.proto.Tuple.tuple_type:type_name -> google.cql.proto.TupleType + 27, // 30: google.cql.proto.Tuple.named_type:type_name -> google.cql.proto.NamedType + 28, // 31: google.cql.proto.Named.value:type_name -> google.protobuf.Any + 27, // 32: google.cql.proto.Named.runtime_type:type_name -> google.cql.proto.NamedType + 15, // 33: google.cql.proto.ValueSet.code_systems:type_name -> google.cql.proto.CodeSystem + 18, // 34: google.cql.proto.Concept.codes:type_name -> google.cql.proto.Code + 5, // 35: google.cql.proto.Library.ExprDefsEntry.value:type_name -> google.cql.proto.Value + 5, // 36: google.cql.proto.Tuple.ValueEntry.value:type_name -> google.cql.proto.Value + 37, // [37:37] is the sub-list for method output_type + 37, // [37:37] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 37, // [37:37] is the sub-list for extension extendee + 0, // [0:37] is the sub-list for field type_name +} + +func init() { file_protos_cql_result_proto_init() } +func file_protos_cql_result_proto_init() { + if File_protos_cql_result_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_protos_cql_result_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Libraries); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Library); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Value); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Quantity); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Ratio); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Date); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DateTime); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Time); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Interval); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*List); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Tuple); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Named); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CodeSystem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ValueSet); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Concept); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_result_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Code); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_protos_cql_result_proto_msgTypes[1].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[2].OneofWrappers = []interface{}{ + (*Value_BooleanValue)(nil), + (*Value_StringValue)(nil), + (*Value_IntegerValue)(nil), + (*Value_LongValue)(nil), + (*Value_DecimalValue)(nil), + (*Value_QuantityValue)(nil), + (*Value_RatioValue)(nil), + (*Value_DateValue)(nil), + (*Value_DateTimeValue)(nil), + (*Value_TimeValue)(nil), + (*Value_IntervalValue)(nil), + (*Value_ListValue)(nil), + (*Value_TupleValue)(nil), + (*Value_NamedValue)(nil), + (*Value_CodeSystemValue)(nil), + (*Value_ValueSetValue)(nil), + (*Value_CodeValue)(nil), + (*Value_ConceptValue)(nil), + } + file_protos_cql_result_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[4].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[5].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[6].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[7].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[8].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[9].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[10].OneofWrappers = []interface{}{ + (*Tuple_TupleType)(nil), + (*Tuple_NamedType)(nil), + } + file_protos_cql_result_proto_msgTypes[11].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[12].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[13].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[14].OneofWrappers = []interface{}{} + file_protos_cql_result_proto_msgTypes[15].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_protos_cql_result_proto_rawDesc, + NumEnums: 3, + NumMessages: 18, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_protos_cql_result_proto_goTypes, + DependencyIndexes: file_protos_cql_result_proto_depIdxs, + EnumInfos: file_protos_cql_result_proto_enumTypes, + MessageInfos: file_protos_cql_result_proto_msgTypes, + }.Build() + File_protos_cql_result_proto = out.File + file_protos_cql_result_proto_rawDesc = nil + file_protos_cql_result_proto_goTypes = nil + file_protos_cql_result_proto_depIdxs = nil +} diff --git a/protos/cql_types.proto b/protos/cql_types.proto new file mode 100644 index 0000000..78c24b7 --- /dev/null +++ b/protos/cql_types.proto @@ -0,0 +1,76 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; + +package google.cql.proto; + +option java_multiple_files = true; +option go_package = "github.com/google/cql/protos/cql_types_go_proto"; + +message CQLType { + oneof type { + SystemType system_type = 1; + NamedType named_type = 2; + IntervalType interval_type = 3; + ListType list_type = 4; + ChoiceType choice_type = 5; + TupleType tuple_type = 6; + } +} + +message SystemType { + optional Type type = 1; + enum Type { + TYPE_UNSPECIFIED = 0; + TYPE_ANY = 1; + TYPE_STRING = 2; + TYPE_BOOLEAN = 3; + TYPE_INTEGER = 4; + TYPE_LONG = 5; + TYPE_DECIMAL = 6; + TYPE_QUANTITY = 7; + TYPE_RATIO = 8; + TYPE_DATE = 9; + TYPE_DATE_TIME = 10; + TYPE_TIME = 11; + TYPE_VALUE_SET = 12; + TYPE_CODE_SYSTEM = 13; + TYPE_VOCABULARY = 14; + TYPE_CODE = 15; + TYPE_CONCEPT = 16; + } +} + +message NamedType { + // The fully qualified name of the type such as FHIR.EnrollmentResponseStatus + // or System.Integer. + optional string type_name = 1; +} + +message IntervalType { + optional CQLType point_type = 1; +} + +message ListType { + optional CQLType element_type = 1; +} + +message ChoiceType { + repeated CQLType choice_types = 1; +} + +message TupleType { + // Maps the element name to the type of the element. + map element_types = 1; +} diff --git a/protos/cql_types_go_proto/cql_types.pb.go b/protos/cql_types_go_proto/cql_types.pb.go new file mode 100644 index 0000000..f8f28b4 --- /dev/null +++ b/protos/cql_types_go_proto/cql_types.pb.go @@ -0,0 +1,797 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.29.0 +// protoc v3.21.12 +// source: protos/cql_types.proto + +package cql_types_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SystemType_Type int32 + +const ( + SystemType_TYPE_UNSPECIFIED SystemType_Type = 0 + SystemType_TYPE_ANY SystemType_Type = 1 + SystemType_TYPE_STRING SystemType_Type = 2 + SystemType_TYPE_BOOLEAN SystemType_Type = 3 + SystemType_TYPE_INTEGER SystemType_Type = 4 + SystemType_TYPE_LONG SystemType_Type = 5 + SystemType_TYPE_DECIMAL SystemType_Type = 6 + SystemType_TYPE_QUANTITY SystemType_Type = 7 + SystemType_TYPE_RATIO SystemType_Type = 8 + SystemType_TYPE_DATE SystemType_Type = 9 + SystemType_TYPE_DATE_TIME SystemType_Type = 10 + SystemType_TYPE_TIME SystemType_Type = 11 + SystemType_TYPE_VALUE_SET SystemType_Type = 12 + SystemType_TYPE_CODE_SYSTEM SystemType_Type = 13 + SystemType_TYPE_VOCABULARY SystemType_Type = 14 + SystemType_TYPE_CODE SystemType_Type = 15 + SystemType_TYPE_CONCEPT SystemType_Type = 16 +) + +// Enum value maps for SystemType_Type. +var ( + SystemType_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "TYPE_ANY", + 2: "TYPE_STRING", + 3: "TYPE_BOOLEAN", + 4: "TYPE_INTEGER", + 5: "TYPE_LONG", + 6: "TYPE_DECIMAL", + 7: "TYPE_QUANTITY", + 8: "TYPE_RATIO", + 9: "TYPE_DATE", + 10: "TYPE_DATE_TIME", + 11: "TYPE_TIME", + 12: "TYPE_VALUE_SET", + 13: "TYPE_CODE_SYSTEM", + 14: "TYPE_VOCABULARY", + 15: "TYPE_CODE", + 16: "TYPE_CONCEPT", + } + SystemType_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "TYPE_ANY": 1, + "TYPE_STRING": 2, + "TYPE_BOOLEAN": 3, + "TYPE_INTEGER": 4, + "TYPE_LONG": 5, + "TYPE_DECIMAL": 6, + "TYPE_QUANTITY": 7, + "TYPE_RATIO": 8, + "TYPE_DATE": 9, + "TYPE_DATE_TIME": 10, + "TYPE_TIME": 11, + "TYPE_VALUE_SET": 12, + "TYPE_CODE_SYSTEM": 13, + "TYPE_VOCABULARY": 14, + "TYPE_CODE": 15, + "TYPE_CONCEPT": 16, + } +) + +func (x SystemType_Type) Enum() *SystemType_Type { + p := new(SystemType_Type) + *p = x + return p +} + +func (x SystemType_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SystemType_Type) Descriptor() protoreflect.EnumDescriptor { + return file_protos_cql_types_proto_enumTypes[0].Descriptor() +} + +func (SystemType_Type) Type() protoreflect.EnumType { + return &file_protos_cql_types_proto_enumTypes[0] +} + +func (x SystemType_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SystemType_Type.Descriptor instead. +func (SystemType_Type) EnumDescriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{1, 0} +} + +type CQLType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Type: + // + // *CQLType_SystemType + // *CQLType_NamedType + // *CQLType_IntervalType + // *CQLType_ListType + // *CQLType_ChoiceType + // *CQLType_TupleType + Type isCQLType_Type `protobuf_oneof:"type"` +} + +func (x *CQLType) Reset() { + *x = CQLType{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CQLType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CQLType) ProtoMessage() {} + +func (x *CQLType) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_types_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CQLType.ProtoReflect.Descriptor instead. +func (*CQLType) Descriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{0} +} + +func (m *CQLType) GetType() isCQLType_Type { + if m != nil { + return m.Type + } + return nil +} + +func (x *CQLType) GetSystemType() *SystemType { + if x, ok := x.GetType().(*CQLType_SystemType); ok { + return x.SystemType + } + return nil +} + +func (x *CQLType) GetNamedType() *NamedType { + if x, ok := x.GetType().(*CQLType_NamedType); ok { + return x.NamedType + } + return nil +} + +func (x *CQLType) GetIntervalType() *IntervalType { + if x, ok := x.GetType().(*CQLType_IntervalType); ok { + return x.IntervalType + } + return nil +} + +func (x *CQLType) GetListType() *ListType { + if x, ok := x.GetType().(*CQLType_ListType); ok { + return x.ListType + } + return nil +} + +func (x *CQLType) GetChoiceType() *ChoiceType { + if x, ok := x.GetType().(*CQLType_ChoiceType); ok { + return x.ChoiceType + } + return nil +} + +func (x *CQLType) GetTupleType() *TupleType { + if x, ok := x.GetType().(*CQLType_TupleType); ok { + return x.TupleType + } + return nil +} + +type isCQLType_Type interface { + isCQLType_Type() +} + +type CQLType_SystemType struct { + SystemType *SystemType `protobuf:"bytes,1,opt,name=system_type,json=systemType,proto3,oneof"` +} + +type CQLType_NamedType struct { + NamedType *NamedType `protobuf:"bytes,2,opt,name=named_type,json=namedType,proto3,oneof"` +} + +type CQLType_IntervalType struct { + IntervalType *IntervalType `protobuf:"bytes,3,opt,name=interval_type,json=intervalType,proto3,oneof"` +} + +type CQLType_ListType struct { + ListType *ListType `protobuf:"bytes,4,opt,name=list_type,json=listType,proto3,oneof"` +} + +type CQLType_ChoiceType struct { + ChoiceType *ChoiceType `protobuf:"bytes,5,opt,name=choice_type,json=choiceType,proto3,oneof"` +} + +type CQLType_TupleType struct { + TupleType *TupleType `protobuf:"bytes,6,opt,name=tuple_type,json=tupleType,proto3,oneof"` +} + +func (*CQLType_SystemType) isCQLType_Type() {} + +func (*CQLType_NamedType) isCQLType_Type() {} + +func (*CQLType_IntervalType) isCQLType_Type() {} + +func (*CQLType_ListType) isCQLType_Type() {} + +func (*CQLType_ChoiceType) isCQLType_Type() {} + +func (*CQLType_TupleType) isCQLType_Type() {} + +type SystemType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type *SystemType_Type `protobuf:"varint,1,opt,name=type,proto3,enum=google.cql.proto.SystemType_Type,oneof" json:"type,omitempty"` +} + +func (x *SystemType) Reset() { + *x = SystemType{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_types_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SystemType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemType) ProtoMessage() {} + +func (x *SystemType) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_types_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemType.ProtoReflect.Descriptor instead. +func (*SystemType) Descriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{1} +} + +func (x *SystemType) GetType() SystemType_Type { + if x != nil && x.Type != nil { + return *x.Type + } + return SystemType_TYPE_UNSPECIFIED +} + +type NamedType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The fully qualified name of the type such as FHIR.EnrollmentResponseStatus + // or System.Integer. + TypeName *string `protobuf:"bytes,1,opt,name=type_name,json=typeName,proto3,oneof" json:"type_name,omitempty"` +} + +func (x *NamedType) Reset() { + *x = NamedType{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NamedType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NamedType) ProtoMessage() {} + +func (x *NamedType) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_types_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NamedType.ProtoReflect.Descriptor instead. +func (*NamedType) Descriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{2} +} + +func (x *NamedType) GetTypeName() string { + if x != nil && x.TypeName != nil { + return *x.TypeName + } + return "" +} + +type IntervalType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PointType *CQLType `protobuf:"bytes,1,opt,name=point_type,json=pointType,proto3,oneof" json:"point_type,omitempty"` +} + +func (x *IntervalType) Reset() { + *x = IntervalType{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_types_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IntervalType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IntervalType) ProtoMessage() {} + +func (x *IntervalType) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_types_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IntervalType.ProtoReflect.Descriptor instead. +func (*IntervalType) Descriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{3} +} + +func (x *IntervalType) GetPointType() *CQLType { + if x != nil { + return x.PointType + } + return nil +} + +type ListType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ElementType *CQLType `protobuf:"bytes,1,opt,name=element_type,json=elementType,proto3,oneof" json:"element_type,omitempty"` +} + +func (x *ListType) Reset() { + *x = ListType{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_types_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListType) ProtoMessage() {} + +func (x *ListType) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_types_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListType.ProtoReflect.Descriptor instead. +func (*ListType) Descriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{4} +} + +func (x *ListType) GetElementType() *CQLType { + if x != nil { + return x.ElementType + } + return nil +} + +type ChoiceType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChoiceTypes []*CQLType `protobuf:"bytes,1,rep,name=choice_types,json=choiceTypes,proto3" json:"choice_types,omitempty"` +} + +func (x *ChoiceType) Reset() { + *x = ChoiceType{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_types_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChoiceType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChoiceType) ProtoMessage() {} + +func (x *ChoiceType) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_types_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChoiceType.ProtoReflect.Descriptor instead. +func (*ChoiceType) Descriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{5} +} + +func (x *ChoiceType) GetChoiceTypes() []*CQLType { + if x != nil { + return x.ChoiceTypes + } + return nil +} + +type TupleType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Maps the element name to the type of the element. + ElementTypes map[string]*CQLType `protobuf:"bytes,1,rep,name=element_types,json=elementTypes,proto3" json:"element_types,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *TupleType) Reset() { + *x = TupleType{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_cql_types_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TupleType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TupleType) ProtoMessage() {} + +func (x *TupleType) ProtoReflect() protoreflect.Message { + mi := &file_protos_cql_types_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TupleType.ProtoReflect.Descriptor instead. +func (*TupleType) Descriptor() ([]byte, []int) { + return file_protos_cql_types_proto_rawDescGZIP(), []int{6} +} + +func (x *TupleType) GetElementTypes() map[string]*CQLType { + if x != nil { + return x.ElementTypes + } + return nil +} + +var File_protos_cql_types_proto protoreflect.FileDescriptor + +var file_protos_cql_types_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x71, 0x6c, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x91, 0x03, 0x0a, 0x07, 0x43, + 0x51, 0x4c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x64, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, + 0x61, 0x6d, 0x65, 0x64, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, + 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x45, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x0c, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x39, 0x0a, 0x09, + 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x08, 0x6c, + 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x63, 0x68, 0x6f, 0x69, 0x63, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x68, + 0x6f, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x74, 0x75, 0x70, 0x6c, + 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x54, 0x75, 0x70, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x09, 0x74, 0x75, 0x70, + 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x89, + 0x03, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3a, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x22, 0xb5, 0x02, 0x0a, 0x04, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x41, 0x4e, 0x59, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, + 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x47, 0x45, 0x52, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x4f, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x44, 0x45, 0x43, 0x49, 0x4d, 0x41, 0x4c, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x51, 0x55, 0x41, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0x07, 0x12, + 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x10, 0x08, 0x12, + 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x45, 0x10, 0x09, 0x12, 0x12, + 0x0a, 0x0e, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, + 0x10, 0x0a, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x10, + 0x0b, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x5f, + 0x53, 0x45, 0x54, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, + 0x44, 0x45, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x10, 0x0d, 0x12, 0x13, 0x0a, 0x0f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x56, 0x4f, 0x43, 0x41, 0x42, 0x55, 0x4c, 0x41, 0x52, 0x59, 0x10, 0x0e, + 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x0f, 0x12, + 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x43, 0x45, 0x50, 0x54, 0x10, + 0x10, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3b, 0x0a, 0x09, 0x4e, 0x61, + 0x6d, 0x65, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x74, 0x79, + 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x5c, 0x0a, 0x0c, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, + 0x51, 0x4c, 0x54, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x09, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x5e, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x51, 0x4c, 0x54, 0x79, + 0x70, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x4a, 0x0a, 0x0a, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x51, 0x4c, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x22, 0xbb, 0x01, 0x0a, 0x09, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x52, 0x0a, 0x0d, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x1a, 0x5a, 0x0a, 0x11, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x63, 0x71, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x51, 0x4c, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, + 0x33, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x63, 0x71, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x73, 0x2f, 0x63, 0x71, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x67, 0x6f, 0x5f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_protos_cql_types_proto_rawDescOnce sync.Once + file_protos_cql_types_proto_rawDescData = file_protos_cql_types_proto_rawDesc +) + +func file_protos_cql_types_proto_rawDescGZIP() []byte { + file_protos_cql_types_proto_rawDescOnce.Do(func() { + file_protos_cql_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_cql_types_proto_rawDescData) + }) + return file_protos_cql_types_proto_rawDescData +} + +var file_protos_cql_types_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_protos_cql_types_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_protos_cql_types_proto_goTypes = []interface{}{ + (SystemType_Type)(0), // 0: google.cql.proto.SystemType.Type + (*CQLType)(nil), // 1: google.cql.proto.CQLType + (*SystemType)(nil), // 2: google.cql.proto.SystemType + (*NamedType)(nil), // 3: google.cql.proto.NamedType + (*IntervalType)(nil), // 4: google.cql.proto.IntervalType + (*ListType)(nil), // 5: google.cql.proto.ListType + (*ChoiceType)(nil), // 6: google.cql.proto.ChoiceType + (*TupleType)(nil), // 7: google.cql.proto.TupleType + nil, // 8: google.cql.proto.TupleType.ElementTypesEntry +} +var file_protos_cql_types_proto_depIdxs = []int32{ + 2, // 0: google.cql.proto.CQLType.system_type:type_name -> google.cql.proto.SystemType + 3, // 1: google.cql.proto.CQLType.named_type:type_name -> google.cql.proto.NamedType + 4, // 2: google.cql.proto.CQLType.interval_type:type_name -> google.cql.proto.IntervalType + 5, // 3: google.cql.proto.CQLType.list_type:type_name -> google.cql.proto.ListType + 6, // 4: google.cql.proto.CQLType.choice_type:type_name -> google.cql.proto.ChoiceType + 7, // 5: google.cql.proto.CQLType.tuple_type:type_name -> google.cql.proto.TupleType + 0, // 6: google.cql.proto.SystemType.type:type_name -> google.cql.proto.SystemType.Type + 1, // 7: google.cql.proto.IntervalType.point_type:type_name -> google.cql.proto.CQLType + 1, // 8: google.cql.proto.ListType.element_type:type_name -> google.cql.proto.CQLType + 1, // 9: google.cql.proto.ChoiceType.choice_types:type_name -> google.cql.proto.CQLType + 8, // 10: google.cql.proto.TupleType.element_types:type_name -> google.cql.proto.TupleType.ElementTypesEntry + 1, // 11: google.cql.proto.TupleType.ElementTypesEntry.value:type_name -> google.cql.proto.CQLType + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name +} + +func init() { file_protos_cql_types_proto_init() } +func file_protos_cql_types_proto_init() { + if File_protos_cql_types_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_protos_cql_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CQLType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SystemType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NamedType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IntervalType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_types_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_types_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChoiceType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_cql_types_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TupleType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_protos_cql_types_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*CQLType_SystemType)(nil), + (*CQLType_NamedType)(nil), + (*CQLType_IntervalType)(nil), + (*CQLType_ListType)(nil), + (*CQLType_ChoiceType)(nil), + (*CQLType_TupleType)(nil), + } + file_protos_cql_types_proto_msgTypes[1].OneofWrappers = []interface{}{} + file_protos_cql_types_proto_msgTypes[2].OneofWrappers = []interface{}{} + file_protos_cql_types_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_protos_cql_types_proto_msgTypes[4].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_protos_cql_types_proto_rawDesc, + NumEnums: 1, + NumMessages: 8, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_protos_cql_types_proto_goTypes, + DependencyIndexes: file_protos_cql_types_proto_depIdxs, + EnumInfos: file_protos_cql_types_proto_enumTypes, + MessageInfos: file_protos_cql_types_proto_msgTypes, + }.Build() + File_protos_cql_types_proto = out.File + file_protos_cql_types_proto_rawDesc = nil + file_protos_cql_types_proto_goTypes = nil + file_protos_cql_types_proto_depIdxs = nil +} diff --git a/protos/gen_protos.sh b/protos/gen_protos.sh new file mode 100755 index 0000000..5f9a26f --- /dev/null +++ b/protos/gen_protos.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Run this script from the root of the CQL repository to regenerate these +# protos. + +if ! [ -x "$(command -v protoc)" ]; then + echo 'protoc missing, please install the protobuf compiler. On linux: sudo apt install -y protobuf-compiler' +fi + +if [ "${GOPATH}" = "" ]; then + echo "Setting temp GOPATH environment variable, creating GOPATH directory \$HOME/go" + GOPATH="${HOME}/go" + mkdir -p $GOPATH +fi + +if [ "${GOBIN}" = "" ]; then + echo "Setting temp GOBIN environment variable, creating GOBIN directory in \$GOPATH/bin" + GOBIN="${GOPATH}/bin" + mkdir -p $GOBIN +fi + +PROTO_TMP_DIR=/tmp/protostaging +mkdir -p $PROTO_TMP_DIR + +go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.29.0 +PATH=$PATH:$GOBIN + +GOOGLEAPIS_DIR=/tmp/googleapis +if [ ! -d "$GOOGLEAPIS_DIR" ] ; then + mkdir -p /tmp/googleapis + git clone https://github.com/googleapis/googleapis.git /tmp/googleapis +else + echo "warn: skipping cloning github.com/googleapis/googleapis.git because /tmp/googleapis already exists" +fi + +find protos -type f -name '*.proto' -exec protoc --proto_path=/tmp/googleapis --proto_path=. --go_out=$PROTO_TMP_DIR {} \; + +cp -R $PROTO_TMP_DIR/github.com/google/cql/protos/* protos/ diff --git a/result/convert.go b/result/convert.go new file mode 100644 index 0000000..58ba78c --- /dev/null +++ b/result/convert.go @@ -0,0 +1,183 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package result + +import ( + "errors" + "fmt" + + "google.golang.org/protobuf/proto" +) + +// ErrCannotConvert is an error that is returned when a conversion cannot be performed. +var ErrCannotConvert = errors.New("internal error - cannot convert") + +// IsNull returns true if the provided Value is a null. +func IsNull(v Value) bool { + return v.GolangValue() == nil +} + +// ToBool takes a CQL Boolean and returns the underlying golang value, a bool. +func ToBool(v Value) (bool, error) { + b, ok := v.GolangValue().(bool) + if !ok { + return false, fmt.Errorf("%w %v to a boolean", ErrCannotConvert, v.RuntimeType()) + } + return b, nil +} + +// ToString takes a CQL String and returns the underlying golang value, a string. +func ToString(v Value) (string, error) { + s, ok := v.GolangValue().(string) + if !ok { + return "", fmt.Errorf("%w %v to a string", ErrCannotConvert, v.RuntimeType()) + } + return s, nil +} + +// ToInt32 takes a CQL Integer and returns the underlying golang value, an int32. +func ToInt32(v Value) (int32, error) { + i, ok := v.GolangValue().(int32) + if !ok { + return 0, fmt.Errorf("%w %v to a int32", ErrCannotConvert, v.RuntimeType()) + } + return i, nil +} + +// ToInt64 takes a CQL Long and returns the underlying golang value, an int64. +func ToInt64(o Value) (int64, error) { + l, ok := o.GolangValue().(int64) + if !ok { + return 0, fmt.Errorf("%w %v to a int64", ErrCannotConvert, o.RuntimeType()) + } + return l, nil +} + +// ToFloat64 takes a CQL Float and returns the underlying golang value, a float64. +func ToFloat64(v Value) (float64, error) { + d, ok := v.GolangValue().(float64) + if !ok { + return 0, fmt.Errorf("%w %v to a float64", ErrCannotConvert, v.RuntimeType()) + } + return d, nil +} + +// ToQuantity takes a CQL Quantity and returns the underlying golang value, a Quantity. +func ToQuantity(v Value) (Quantity, error) { + i, ok := v.GolangValue().(Quantity) + if !ok { + return Quantity{}, fmt.Errorf("%w %v to a Quantity", ErrCannotConvert, v.RuntimeType()) + } + return i, nil +} + +// ToRatio takes a CQL Ratio and returns the underlying golang value, a Ratio. +func ToRatio(v Value) (Ratio, error) { + r, ok := v.GolangValue().(Ratio) + if !ok { + return Ratio{}, fmt.Errorf("%w %v to a Ratio", ErrCannotConvert, v.RuntimeType()) + } + return r, nil +} + +// ToDateTime takes a CQL Date, Time or DateTime and returns the underlying golang value, a +// DateTime. Since Date, Time and DateTime share the same underlying golang value DateTime() +// can be used to handle both Date, Time and DateTime generically. +func ToDateTime(v Value) (DateTime, error) { + switch t := v.GolangValue().(type) { + case DateTime: + return t, nil + case Date: + return DateTime(t), nil + case Time: + return DateTime(t), nil + default: + return DateTime{}, fmt.Errorf("%w %v to a DateTime", ErrCannotConvert, v.RuntimeType()) + } +} + +// ToInterval takes a CQL Interval and returns the underlying golang value, an Interval. +func ToInterval(v Value) (Interval, error) { + i, ok := v.GolangValue().(Interval) + if !ok { + return Interval{}, fmt.Errorf("%w %v to a Interval", ErrCannotConvert, v.RuntimeType()) + } + return i, nil +} + +// ToSlice takes a CQL Slice and returns the underlying golang value, a []Value. +func ToSlice(v Value) ([]Value, error) { + l, ok := v.GolangValue().(List) + if !ok { + return nil, fmt.Errorf("%w %v to a []Value", ErrCannotConvert, v.RuntimeType()) + } + return l.Value, nil +} + +// ToTuple takes a CQL Tuple and returns the underlying golang value, a map[string]Value. +func ToTuple(v Value) (map[string]Value, error) { + t, ok := v.GolangValue().(Tuple) + if !ok { + return nil, fmt.Errorf("%w %v to a map[string]Value", ErrCannotConvert, v.RuntimeType()) + } + return t.Value, nil +} + +// ToProto takes a CQL Named type and returns the underlying golang value, a proto.Message. Named +// types are any type defined in the data model. The proto.Message is a FHIR Proto. See +// https://github.com/google/fhir for more details. +func ToProto(v Value) (proto.Message, error) { + t, ok := v.GolangValue().(Named) + if !ok { + return nil, fmt.Errorf("%w %v to a proto.Message", ErrCannotConvert, v.RuntimeType()) + } + return t.Value, nil +} + +// ToCodeSystem takes a CQL CodeSystem and returns the underlying golang value, a CodeSystem. +func ToCodeSystem(o Value) (CodeSystem, error) { + i, ok := o.GolangValue().(CodeSystem) + if !ok { + return CodeSystem{}, fmt.Errorf("%w %v to a CodeSystem", ErrCannotConvert, o.RuntimeType()) + } + return i, nil +} + +// ToValueSet takes a CQL ValueSet and returns the underlying golang value, a ValueSet. +func ToValueSet(o Value) (ValueSet, error) { + i, ok := o.GolangValue().(ValueSet) + if !ok { + return ValueSet{}, fmt.Errorf("%w %v to a ValueSet", ErrCannotConvert, o.RuntimeType()) + } + return i, nil +} + +// ToConcept takes a CQL Concept and returns the underlying golang value, a Concept. +func ToConcept(v Value) (Concept, error) { + c, ok := v.GolangValue().(Concept) + if !ok { + return Concept{}, fmt.Errorf("%w %v to a Concept", ErrCannotConvert, v.RuntimeType()) + } + return c, nil +} + +// ToCode takes a CQL Code and returns the underlying golang value, a Code. +func ToCode(v Value) (Code, error) { + i, ok := v.GolangValue().(Code) + if !ok { + return Code{}, fmt.Errorf("%w %v to a Code", ErrCannotConvert, v.RuntimeType()) + } + return i, nil +} diff --git a/result/convert_test.go b/result/convert_test.go new file mode 100644 index 0000000..e759814 --- /dev/null +++ b/result/convert_test.go @@ -0,0 +1,522 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package result + +import ( + "errors" + "testing" + "time" + + "github.com/google/cql/model" + "github.com/google/cql/types" + c4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/codes_go_proto" + r4patientpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/patient_go_proto" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestToInt32(t *testing.T) { + tests := []struct { + name string + input Value + want int32 + }{ + { + name: "Integer", + input: newOrFatal(t, 4), + want: 4, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToInt32(test.input) + if err != nil { + t.Fatalf("Int32(%v) failed: %v", test.input, err) + } + if got != test.want { + t.Errorf("Int32(%v) got: %v want: %v", test.input, got, test.want) + } + }) + } +} + +func TestToInt32Error(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToInt32(input) + if err == nil { + t.Fatalf("Int32(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("Int32() got error %v want %v", err, want) + } +} + +func TestInt64(t *testing.T) { + tests := []struct { + name string + input Value + want int64 + }{ + { + name: "Long", + input: newOrFatal(t, int64(4)), + want: int64(4), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToInt64(test.input) + if err != nil { + t.Fatalf("Int64(%v) failed: %v", test.input, err) + } + if got != test.want { + t.Errorf("Int64(%v) got: %v want: %v", test.input, got, test.want) + } + }) + } +} + +func TestInt64Error(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToInt64(input) + if err == nil { + t.Fatalf("Int64(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("Int64() got error %v want %v", err, want) + } +} + +func TestToFloat64(t *testing.T) { + tests := []struct { + name string + input Value + want float64 + }{ + { + name: "Decimal", + input: newOrFatal(t, 4.0), + want: 4.0, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToFloat64(test.input) + if err != nil { + t.Fatalf("Float64(%v) failed: %v", test.input, err) + } + if got != test.want { + t.Errorf("Float64(%v) got: %v want: %v", test.input, got, test.want) + } + }) + } +} + +func TestFloat64Error(t *testing.T) { + input := newOrFatal(t, "hello") + want := "cannot convert" + _, err := ToFloat64(input) + if err == nil { + t.Fatalf("Float64(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("Returned error (%s) did not contain expected (%s)", err, want) + } +} + +func TestToQuantity(t *testing.T) { + tests := []struct { + name string + input Value + want Quantity + }{ + { + name: "Quantity", + input: newOrFatal(t, Quantity{Value: 4.0, Unit: "day"}), + want: Quantity{Value: 4.0, Unit: "day"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToQuantity(test.input) + if err != nil { + t.Fatalf("ToQuantity(%v) failed: %v", test.input, err) + } + if got != test.want { + t.Errorf("ToQuantity(%v) got: %v want: %v", test.input, got, test.want) + } + }) + } +} + +func TestQuantityError(t *testing.T) { + input := newOrFatal(t, "hello") + want := "cannot convert" + _, err := ToQuantity(input) + if err == nil { + t.Fatalf("ToQuantity(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToQuantity() got error %v want %v", err, want) + } +} + +func TestToRatio(t *testing.T) { + tests := []struct { + name string + input Value + want Ratio + }{ + { + name: "Ratio", + input: newOrFatal(t, Ratio{Numerator: Quantity{Value: 4.0, Unit: "day"}, Denominator: Quantity{Value: 5.0, Unit: "day"}}), + want: Ratio{Numerator: Quantity{Value: 4.0, Unit: "day"}, Denominator: Quantity{Value: 5.0, Unit: "day"}}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToRatio(test.input) + if err != nil { + t.Fatalf("ToRatio(%v) failed: %v", test.input, err) + } + if got != test.want { + t.Errorf("ToRatio(%v) got: %v want: %v", test.input, got, test.want) + } + }) + } +} + +func TestRatioError(t *testing.T) { + input := newOrFatal(t, "hello") + want := "cannot convert" + _, err := ToRatio(input) + if err == nil { + t.Fatalf("ToRatio(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToRatio() got error %v want %v", err, want) + } +} + +func TestToSlice(t *testing.T) { + tests := []struct { + name string + input Value + want []Value + }{ + { + name: "List", + input: newOrFatal(t, List{Value: []Value{newOrFatal(t, 4)}}), + want: []Value{newOrFatal(t, 4)}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToSlice(test.input) + if err != nil { + t.Fatalf("ToSlice(%v) failed: %v", test.input, err) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ToSlice(%v) returned diff (-want +got):\n%s", test.input, diff) + } + }) + } +} + +func TestToSliceError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToSlice(input) + if err == nil { + t.Fatalf("ToSlice(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToSlice() got error %v want %v", err, want) + } +} + +func TestToTuple(t *testing.T) { + tests := []struct { + name string + input Value + want map[string]Value + }{ + { + name: "Tuple", + input: newOrFatal(t, Tuple{Value: map[string]Value{"Apple": newOrFatal(t, 4)}}), + want: map[string]Value{"Apple": newOrFatal(t, 4)}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToTuple(test.input) + if err != nil { + t.Fatalf("ToTuple(%v) failed: %v", test.input, err) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ToTuple(%v) returned diff (-want +got):\n%s", test.input, diff) + } + }) + } +} + +func TestToTupleError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToTuple(input) + if err == nil { + t.Fatalf("ToTuple(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToTuple() got error %v want %v", err, want) + } +} + +func TestToProto(t *testing.T) { + tests := []struct { + name string + input Value + want proto.Message + }{ + { + name: "Proto", + input: newOrFatal(t, Named{Value: &r4patientpb.Patient_GenderCode{Value: c4pb.AdministrativeGenderCode_MALE}}), + want: &r4patientpb.Patient_GenderCode{Value: c4pb.AdministrativeGenderCode_MALE}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToProto(test.input) + if err != nil { + t.Fatalf("ToProto(%v) failed: %v", test.input, err) + } + if diff := cmp.Diff(test.want, got, protocmp.Transform()); diff != "" { + t.Errorf("ToProto(%v) returned diff (-want +got):\n%s", test.input, diff) + } + }) + } +} + +func TestToProtoError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToProto(input) + if err == nil { + t.Fatalf("ToProto(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToProto() got error %v want %v", err, want) + } +} +func TestToDateTime(t *testing.T) { + tests := []struct { + name string + input Value + want DateTime + }{ + { + name: "Date", + input: newOrFatal(t, Date{Date: time.Date(2023, time.March, 5, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + want: DateTime{Date: time.Date(2023, time.March, 5, 0, 0, 0, 0, time.UTC), Precision: model.DAY}, + }, + { + name: "DateTime", + input: newOrFatal(t, DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC)}), + want: DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC)}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToDateTime(test.input) + if err != nil { + t.Fatalf("ToDateTime(%v) failed: %v", test.input, err) + } + if got != test.want { + t.Errorf("ToDateTime(%v) got: %v want: %v", test.input, got, test.want) + } + }) + } +} + +func TestToDateTimeError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToDateTime(input) + if err == nil { + t.Fatalf("ToDateTime(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToDateTime() got error %v want %v", err, want) + } +} + +func TestToInterval(t *testing.T) { + tests := []struct { + name string + input Value + want Interval + }{ + { + name: "Interval", + input: newOrFatal(t, Interval{ + Low: newOrFatal(t, 4), + High: newOrFatal(t, 5), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + want: Interval{ + Low: newOrFatal(t, 4), + High: newOrFatal(t, 5), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToInterval(test.input) + if err != nil { + t.Fatalf("ToInterval(%v) failed: %v", test.input, err) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ToInterval(%v) returned diff (-want +got):\n%s", test.input, diff) + } + }) + } +} + +func TestIntervalError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToInterval(input) + if err == nil { + t.Fatalf("ToInterval(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToInterval() got error %v want %v", err, want) + } +} + +func TestToCode(t *testing.T) { + tests := []struct { + name string + input Value + want Code + }{ + { + name: "ToCode", + input: newOrFatal(t, Code{System: "foo", Code: "bar", Display: "the foo", Version: "1.0"}), + want: Code{System: "foo", Code: "bar", Display: "the foo", Version: "1.0"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToCode(test.input) + if err != nil { + t.Fatalf("ToCode(%v) failed: %v", test.input, err) + } + if got != test.want { + t.Errorf("ToCode(%v) got: %v want: %v", test.input, got, test.want) + } + }) + } +} + +func TestToCodeError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToCode(input) + if err == nil { + t.Fatalf("ToCode(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToCode() got error %v want %v", err, want) + } +} + +func TestToCodeSystem(t *testing.T) { + tests := []struct { + name string + input Value + want CodeSystem + }{ + { + name: "CodeSystem", + input: newOrFatal(t, CodeSystem{ID: "example.com", Version: "1.0"}), + want: CodeSystem{ID: "example.com", Version: "1.0"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := ToCodeSystem(test.input) + if err != nil { + t.Fatalf("ToCodeSystem(%v) failed: %v", test.input, err) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ToCodeSystem(%v) returned diff (-want +got):\n%s", test.input, diff) + } + }) + } +} + +func TestToCodeSystemError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToCodeSystem(input) + if err == nil { + t.Fatalf("ToCodeSystem(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToCodeSystem() got error %v want %v", err, want) + } +} + +func TestToValueSet(t *testing.T) { + test := struct { + name string + input Value + want ValueSet + }{ + name: "ValueSet", + input: newOrFatal(t, ValueSet{ID: "example.com", Version: "1.0"}), + want: ValueSet{ID: "example.com", Version: "1.0"}, + } + t.Run(test.name, func(t *testing.T) { + got, err := ToValueSet(test.input) + if err != nil { + t.Fatalf("ToValueSet(%v) failed: %v", test.input, err) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("ToValueSet(%v) returned diff (-want +got):\n%s", test.input, diff) + } + }) +} + +func TestToValueSetError(t *testing.T) { + input := newOrFatal(t, 4.0) + want := "cannot convert" + _, err := ToValueSet(input) + if err == nil { + t.Fatalf("ToValueSet(%v) succeeded, want error", input) + } + if !errors.Is(err, ErrCannotConvert) { + t.Errorf("ToValueSet() got error %v want %v", err, want) + } +} diff --git a/result/result.go b/result/result.go new file mode 100644 index 0000000..996737b --- /dev/null +++ b/result/result.go @@ -0,0 +1,185 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package result defines the evaluation results that can be returned by the CQL Engine. +package result + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/google/cql/model" + crpb "github.com/google/cql/protos/cql_result_go_proto" + "google.golang.org/protobuf/proto" + "github.com/pborman/uuid" +) + +// Libraries returns the results of the evaluation of a set of CQL Libraries. The inner +// map[string]Value maps the name of each expression definition to the resulting CQL Value. +// The outer map[LibKey] maps CQL Libraries to the Expression Definitions within the library. +type Libraries map[LibKey]map[string]Value + +type cqlLibJSON struct { + Name string `json:"libName"` + Version string `json:"libVersion"` + ExpDefs map[string]Value `json:"expressionDefinitions"` +} + +// MarshalJSON returns the CQL Results as a JSON. The JSON will be a list of CQL libraries +// formatted like the following: +// +// [{ +// 'libName': 'TESTLIB', +// 'libVersion': '1.0.0', +// 'expressionDefinitions': {'ExpDef': 3, 'ExpDef2': 4}, +// }, ...], +func (l Libraries) MarshalJSON() ([]byte, error) { + r := []cqlLibJSON{} + for k, v := range l { + r = append(r, cqlLibJSON{ + Name: k.Name, + Version: k.Version, + ExpDefs: v, + }) + } + + return json.Marshal(r) +} + +// Proto converts Libraries to a proto. +func (l Libraries) Proto() (*crpb.Libraries, error) { + pbLibraries := &crpb.Libraries{ + Libraries: make([]*crpb.Library, 0, len(l)), + } + + for libKey, lib := range l { + pbLib := crpb.Library{ + Name: proto.String(libKey.Name), + Version: proto.String(libKey.Version), + ExprDefs: make(map[string]*crpb.Value, len(lib)), + } + for defName, def := range lib { + pbRes, err := def.Proto() + if err != nil { + return nil, err + } + pbLib.ExprDefs[defName] = pbRes + } + pbLibraries.Libraries = append(pbLibraries.Libraries, &pbLib) + } + return pbLibraries, nil +} + +// LibrariesFromProto converts a proto to Libraries. +func LibrariesFromProto(pb *crpb.Libraries) (Libraries, error) { + libraries := Libraries{} + for _, lib := range pb.Libraries { + libKey := LibKey{Name: lib.GetName(), Version: lib.GetVersion()} + libMap := make(map[string]Value, len(lib.ExprDefs)) + for defName, resultpb := range lib.ExprDefs { + val, err := NewFromProto(resultpb) + if err != nil { + return nil, err + } + libMap[defName] = val + } + libraries[libKey] = libMap + } + return libraries, nil +} + +// LibKey is the unique identifier for a CQL Library. +type LibKey struct { + // Name is the fully qualified identifier of the CQL library. + Name string + // Version is empty if no version was specified. + Version string + // Unnamed libraries do not have a library identifier. Unnamed libraries cannot be referenced, and + // all definitions are private. Use UnnamedLibKey() to create an unnamed library. + IsUnnamed bool +} + +// UnnamedLibKey returns a LibKey for a library without an identifier. The Name will be "Unnamed +// Library" and the Version will be a random UUID. +func UnnamedLibKey() LibKey { + return LibKey{Name: "Unnamed Library", Version: uuid.New(), IsUnnamed: true} +} + +// LibKeyFromModel is a convenience constructor that returns a LibKey from a +// model.LibraryIdentifier. If the model.LibraryIdentifier is nil, an UnnamedLibKey is returned. +func LibKeyFromModel(lib *model.LibraryIdentifier) LibKey { + if lib == nil { + return UnnamedLibKey() + } + return LibKey{Name: lib.Qualified, Version: lib.Version, IsUnnamed: false} +} + +// Key returns a unique string key representation of the LibKey. +// A space is added between the name and version to avoid naming clashes. +func (l LibKey) Key() string { + // TODO b/301606416 - Since identifiers can contain spaces, this is not a unique key. + return l.Name + " " + l.Version +} + +// String returns a printable representation of LibKey. +func (l LibKey) String() string { + if l.IsUnnamed { + return "Unnamed Library" + } + if l.Version == "" { + return l.Name + } + return l.Name + " " + l.Version +} + +// DefKey is the unique identifier for a CQL Expression Definition, Parameter or ValueSet. +type DefKey struct { + Name string + Library LibKey +} + +// EngineErrorType is the type of error to be set on the EngineError. +type EngineErrorType error + +var ( + // ErrLibraryParsing is returned when a library could not be properly parsed. + ErrLibraryParsing = errors.New("failed to parse library") + // ErrParameterParsing is returned when a parameter could not be parsed. + ErrParameterParsing = errors.New("failed to parse parameter") + // ErrEvaluationError is returned when a runtime error occurs during CQL evaluation. + ErrEvaluationError = errors.New("failed during CQL evaluation") +) + +// EngineError is returned when the CQL Engine fails during parsing or execution. +type EngineError struct { + Resource string + ErrType EngineErrorType + Err error + // TODO b/338270701 - Add a stack trace to the error for the interpreter. +} + +// NewEngineError returns a new EngineError with the given resource, engine error type and error. +// err should be the nested error that was returned during parsing or evaluation. +func NewEngineError(resource string, errType EngineErrorType, err error) EngineError { + return EngineError{Resource: resource, ErrType: errType, Err: err} +} + +func (e EngineError) Error() string { + return fmt.Sprintf("%s: %s, %s", e.ErrType.Error(), e.Resource, e.Err.Error()) +} + +func (e EngineError) Unwrap() error { + return e.Err +} diff --git a/result/result_test.go b/result/result_test.go new file mode 100644 index 0000000..b21bd3a --- /dev/null +++ b/result/result_test.go @@ -0,0 +1,137 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package result + +import ( + "testing" + + crpb "github.com/google/cql/protos/cql_result_go_proto" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestLibraries_MarshalJSON(t *testing.T) { + tests := []struct { + name string + unmarshalled Libraries + want string + }{ + { + name: "Libraries", + unmarshalled: Libraries{LibKey{Name: "Highly.Qualified", Version: "1.0"}: map[string]Value{"DefName": newOrFatal(t, 1)}}, + want: `[{"libName":"Highly.Qualified","libVersion":"1.0","expressionDefinitions":{"DefName":{"@type":"System.Integer","value":1}}}]`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.unmarshalled.MarshalJSON() + if err != nil { + t.Fatalf("Json marshalling failed %v", err) + } + if diff := cmp.Diff(tc.want, string(got)); diff != "" { + t.Errorf("json.Marshal() returned unexpected diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestLibraries_ProtoAndBack(t *testing.T) { + tests := []struct { + name string + libraries Libraries + wantProto *crpb.Libraries + }{ + { + name: "Multiple expression definitions", + libraries: Libraries{ + LibKey{Name: "TESTLIB", Version: "1.0.0"}: map[string]Value{ + "TESTRESULT": newOrFatal(t, 1), + "TESTRESULT2": newOrFatal(t, 2), + }, + }, + wantProto: &crpb.Libraries{ + Libraries: []*crpb.Library{ + &crpb.Library{ + Name: proto.String("TESTLIB"), + Version: proto.String("1.0.0"), + ExprDefs: map[string]*crpb.Value{ + "TESTRESULT": &crpb.Value{ + Value: &crpb.Value_IntegerValue{IntegerValue: 1}, + }, + "TESTRESULT2": &crpb.Value{ + Value: &crpb.Value_IntegerValue{IntegerValue: 2}, + }, + }, + }, + }, + }, + }, + { + name: "Multiple libraries", + libraries: Libraries{ + LibKey{Name: "TESTLIB", Version: "1.0.0"}: map[string]Value{"TESTRESULT": newOrFatal(t, 1)}, + LibKey{Name: "TESTLIB", Version: "2.0.0"}: map[string]Value{"TESTRESULT": newOrFatal(t, 4)}, + }, + wantProto: &crpb.Libraries{ + Libraries: []*crpb.Library{ + &crpb.Library{ + Name: proto.String("TESTLIB"), + Version: proto.String("1.0.0"), + ExprDefs: map[string]*crpb.Value{ + "TESTRESULT": &crpb.Value{ + Value: &crpb.Value_IntegerValue{IntegerValue: 1}, + }, + }, + }, + &crpb.Library{ + Name: proto.String("TESTLIB"), + Version: proto.String("2.0.0"), + ExprDefs: map[string]*crpb.Value{ + "TESTRESULT": &crpb.Value{ + Value: &crpb.Value_IntegerValue{IntegerValue: 4}, + }, + }, + }, + }, + }, + }, + { + name: "Empty Library", + libraries: Libraries{}, + wantProto: &crpb.Libraries{Libraries: []*crpb.Library{}}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotProto, err := tc.libraries.Proto() + if err != nil { + t.Errorf("Proto() returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantProto, gotProto, protocmp.Transform(), protocmp.SortRepeatedFields(&crpb.Libraries{}, "libraries")); diff != "" { + t.Errorf("Proto() returned unexpected diff (-want +got):\n%s", diff) + } + + gotLib, err := LibrariesFromProto(gotProto) + if err != nil { + t.Errorf("LibrariesFromProto() returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.libraries, gotLib); diff != "" { + t.Errorf("LibrariesFromProto() returned unexpected diff (-want +got):\n%s", diff) + } + }) + } +} diff --git a/result/value.go b/result/value.go new file mode 100644 index 0000000..211ba71 --- /dev/null +++ b/result/value.go @@ -0,0 +1,1334 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package result + +import ( + "encoding/json" + "errors" + "fmt" + "slices" + "strings" + "time" + + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + datepb "google.golang.org/genproto/googleapis/type/date" + timeofdaypb "google.golang.org/genproto/googleapis/type/timeofday" + + "github.com/google/cql/internal/datehelpers" + "github.com/google/cql/model" + crpb "github.com/google/cql/protos/cql_result_go_proto" + "github.com/google/cql/types" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +// Value is a CQL Value evaluated by the interpreter. +type Value struct { + goValue any + runtimeType types.IType + sourceExpr model.IExpression + sourceVals []Value +} + +// GolangValue returns the underlying Golang value representing the CQL value. Specifically: +// CQL Null returns Golang nil +// CQL Boolean returns Golang bool +// CQL String returns Golang string +// CQL Integer returns Golang int32 +// CQL Long returns Golang int64 +// CQL Decimal returns Golang float64 +// CQL Quantity returns Golang Quantity struct +// CQL Ratio returns Golang Ratio struct +// CQL Date returns Golang Date struct +// CQL DateTime returns Golang DateTime struct +// CQL Time returns Golang Time struct +// CQL Interval returns Golang Interval struct +// CQL List returns Golang List struct +// CQL Tuple returns Golang Tuple struct +// CQL Named (a type defined in the data model) returns Golang Proto struct +// CQL CodeSystem returns Golang CodeSystem struct +// CQL ValueSet returns Golang ValueSet struct +// CQL Concept returns Golang Concept struct +// CQL Code returns Goland Code struct +// +// You can call GolangValue() and type switch to handle values. Alternatively, if you know that the +// result will be a specific type such as an int32, it is recommended to use the result.ToInt32() +// helper function. +func (v Value) GolangValue() any { return v.goValue } + +// RuntimeType returns the type used by the Is system operator +// https://cql.hl7.org/09-b-cqlreference.html#is. This may be different than the type statically +// determined by the Parser. For example, if the Parser statically determines the type to be +// Choice the runtime type will be the actual type during evaluation, either +// Integer, String or Null. In some cases where a runtime is not known (for example an empty list, +// or an interval with where low and high are nulls) this will fall back to the static type. +func (v Value) RuntimeType() types.IType { + switch t := v.goValue.(type) { + case Interval: + return inferIntervalType(t) + case List: + return inferListType(t.Value, t.StaticType) + default: + return v.runtimeType + } +} + +// SourceExpression is the CQL expression that created this value. For instance, if the returned +// result is from the CQL expression "a < b", the source expression will be the `model.Less` struct. +func (v Value) SourceExpression() model.IExpression { return v.sourceExpr } + +// SourceValues returns the underlying values that were used by the SourceExpression to compute the +// returned value. The ordering of source values is not guaranteed to have any meaning, although +// expressions that produce them should attempt to preserve order when it does have meaning. For +// instance, for the value returned by "a < b", the source values are `a` and `b`. +// +// Source Values will have their own sources, creating a recursive tree structure that allows users +// to trace through the tree of expressions and values used to create it. +func (v Value) SourceValues() []Value { return v.sourceVals } + +// For simple types, we can just marshal the value and type. +// More complex representations are handled in marshalJSON() functions of specific types. +type simpleJSONMessage struct { + Type json.RawMessage `json:"@type"` + Value any `json:"value"` +} + +// customJSONMarshaler is an interface for types that need to marshal their own JSON representation. +// I.E. types that are not simple types. +type customJSONMarshaler interface { + // marshalJSON accepts a bytes array of the type string and returns the JSON representation. + marshalJSON(json.RawMessage) ([]byte, error) +} + +// MarshalJSON returns the value as a JSON string. +// Uses CQL-Serialization spec as a template: +// https://github.com/cqframework/clinical_quality_language/wiki/CQL-Serialization +func (v Value) MarshalJSON() ([]byte, error) { + rt, err := v.RuntimeType().MarshalJSON() + if err != nil { + return nil, err + } + + // TODO: b/301606416 - Vocabulary support. + switch gv := v.goValue.(type) { + case customJSONMarshaler: + return gv.marshalJSON(rt) + case bool, float64, int32, int64, string, nil: + return json.Marshal(simpleJSONMessage{ + Value: gv, + Type: rt, + }) + case Date: + date, err := datehelpers.DateString(gv.Date, gv.Precision) + if err != nil { + return nil, err + } + return json.Marshal(simpleJSONMessage{ + Type: rt, + Value: date, + }) + case DateTime: + dt, err := datehelpers.DateTimeString(gv.Date, gv.Precision) + if err != nil { + return nil, err + } + return json.Marshal(simpleJSONMessage{ + Type: rt, + Value: dt, + }) + case Time: + t, err := datehelpers.TimeString(gv.Date, gv.Precision) + if err != nil { + return nil, err + } + return json.Marshal(simpleJSONMessage{ + Type: rt, + Value: t, + }) + case List: + // Lists don't embed the type so they can be directly marshalled. + return json.Marshal(gv.Value) + case Tuple: + // Tuples don't embed the type so they can be directly marshalled. + return json.Marshal(gv.Value) + default: + return nil, fmt.Errorf("tried to marshal unsupported type %T, %w", gv, errUnsupportedType) + } +} + +// Proto converts Value to a proto. The source expression and source values are dropped. +func (v Value) Proto() (*crpb.Value, error) { + pbValue := &crpb.Value{} + switch t := v.goValue.(type) { + case nil: + // TODO(b/301606416): Consider supporting typed nulls. + return pbValue, nil + case bool: + pbValue.Value = &crpb.Value_BooleanValue{BooleanValue: t} + case string: + pbValue.Value = &crpb.Value_StringValue{StringValue: t} + case int32: + pbValue.Value = &crpb.Value_IntegerValue{IntegerValue: t} + case int64: + pbValue.Value = &crpb.Value_LongValue{LongValue: t} + case float64: + pbValue.Value = &crpb.Value_DecimalValue{DecimalValue: t} + case Quantity: + pbValue.Value = &crpb.Value_QuantityValue{QuantityValue: t.Proto()} + case Ratio: + pbValue.Value = &crpb.Value_RatioValue{RatioValue: t.Proto()} + case Date: + pb, err := t.Proto() + if err != nil { + return nil, err + } + pbValue.Value = &crpb.Value_DateValue{DateValue: pb} + case DateTime: + pb, err := t.Proto() + if err != nil { + return nil, err + } + pbValue.Value = &crpb.Value_DateTimeValue{DateTimeValue: pb} + case Time: + pb, err := t.Proto() + if err != nil { + return nil, err + } + pbValue.Value = &crpb.Value_TimeValue{TimeValue: pb} + case Interval: + pb, err := t.Proto() + if err != nil { + return nil, err + } + pbValue.Value = &crpb.Value_IntervalValue{IntervalValue: pb} + case List: + pb, err := t.Proto() + if err != nil { + return nil, err + } + pbValue.Value = &crpb.Value_ListValue{ListValue: pb} + case Named: + pb, err := t.Proto() + if err != nil { + return nil, err + } + pbValue.Value = &crpb.Value_NamedValue{NamedValue: pb} + case Tuple: + pb, err := t.Proto() + if err != nil { + return nil, err + } + pbValue.Value = &crpb.Value_TupleValue{TupleValue: pb} + case CodeSystem: + pbValue.Value = &crpb.Value_CodeSystemValue{CodeSystemValue: t.Proto()} + case ValueSet: + pbValue.Value = &crpb.Value_ValueSetValue{ValueSetValue: t.Proto()} + case Concept: + pbValue.Value = &crpb.Value_ConceptValue{ConceptValue: t.Proto()} + case Code: + pbValue.Value = &crpb.Value_CodeValue{CodeValue: t.Proto()} + } + return pbValue, nil +} + +// Equal is our custom implementation of equality used primarily by cmp.Diff in tests. This is not +// CQL equality. Equal only compares the GolangValue and RuntimeType, ignoring SourceExpression and +// SourceValues. +func (v Value) Equal(a Value) bool { + if !v.RuntimeType().Equal(a.RuntimeType()) { + return false + } + + switch t := v.goValue.(type) { + case Date: + vDate, ok := a.GolangValue().(Date) + if !ok { + return false + } + return t.Equal(vDate) + case DateTime: + vDateTime, ok := a.GolangValue().(DateTime) + if !ok { + return false + } + return t.Equal(vDateTime) + case Time: + vTime, ok := a.GolangValue().(Time) + if !ok { + return false + } + return t.Equal(vTime) + case Interval: + vInterval, ok := a.GolangValue().(Interval) + if !ok { + return false + } + return t.Equal(vInterval) + case List: + vList, ok := a.GolangValue().(List) + if !ok { + return false + } + return t.Equal(vList) + case Tuple: + vTuple, ok := a.GolangValue().(Tuple) + if !ok { + return false + } + return t.Equal(vTuple) + case Named: + vProto, ok := a.GolangValue().(Named) + if !ok { + return false + } + return t.Equal(vProto) + case ValueSet: + vValueSet, ok := a.GolangValue().(ValueSet) + if !ok { + return false + } + return t.Equal(vValueSet) + case Concept: + vConcept, ok := a.GolangValue().(Concept) + if !ok { + return false + } + return t.Equal(vConcept) + default: + return v.GolangValue() == a.GolangValue() + } +} + +var errUnsupportedType = errors.New("unsupported type") + +// New converts Golang values to CQL values. This function should be used when creating values from +// call sites where the supporting sources are not know, and to be added with the WithSources() +// function later. Call sites with the needed sources are encouraged to use NewWithSources below. +// Specifically: +// Golang bool converts to CQL Boolean +// Golang string converts to CQL String +// Golang int32 converts to CQL Integer +// Golang int64 converts to CQL Long +// Golang float64 converts to CQL Decimal +// Golang Quantity struct converts to CQL Quantity +// Golang Ratio struct converts to CQL Ratio +// Golang Date struct converts to CQL Date +// Golang DateTime struct converts to CQL DateTime +// Golang Time struct converts to CQL Time +// Golang Interval struct converts to CQL Interval +// Golang []Value converts to CQL List +// Golang map[string]Value converts to CQL Tuple +// Golang proto.Message (a type defined in the data model) converts to CQL Named +// Golang CodeSystem struct converts to CQL CodeSystem +// Golang ValueSet struct converts to CQL ValueSet +// Golang Concept struct converts to CQL Concept +// Golang Code struct converts to CQL Code +func New(val any) (Value, error) { + if val == nil { + return Value{runtimeType: types.Any, goValue: nil}, nil + } + switch v := val.(type) { + case int: + return Value{runtimeType: types.Integer, goValue: int32(v)}, nil + case int32: + return Value{runtimeType: types.Integer, goValue: v}, nil + case int64: + return Value{runtimeType: types.Long, goValue: v}, nil + case float64: + return Value{runtimeType: types.Decimal, goValue: v}, nil + case Quantity: + return Value{runtimeType: types.Quantity, goValue: v}, nil + case Ratio: + return Value{runtimeType: types.Ratio, goValue: v}, nil + case bool: + return Value{runtimeType: types.Boolean, goValue: v}, nil + case string: + return Value{runtimeType: types.String, goValue: v}, nil + case Date: + switch v.Precision { + case model.YEAR, model.MONTH, model.DAY, model.UNSETDATETIMEPRECISION: + return Value{runtimeType: types.Date, goValue: v}, nil + } + return Value{}, fmt.Errorf("unsupported precision in Date with value %v %w", v.Precision, datehelpers.ErrUnsupportedPrecision) + case DateTime: + switch v.Precision { + case model.YEAR, + model.MONTH, + model.DAY, + model.HOUR, + model.MINUTE, + model.SECOND, + model.MILLISECOND, + model.UNSETDATETIMEPRECISION: + return Value{runtimeType: types.DateTime, goValue: v}, nil + } + return Value{}, fmt.Errorf("unsupported precision in DateTime with value %v %w", v.Precision, datehelpers.ErrUnsupportedPrecision) + case Time: + switch v.Precision { + case model.HOUR, model.MINUTE, model.SECOND, model.MILLISECOND, model.UNSETDATETIMEPRECISION: + if v.Date.Year() != 0 || v.Date.Month() != 1 || v.Date.Day() != 1 { + return Value{}, fmt.Errorf("internal error - Time must be Year 0000, Month 01, Day 01, instead got %v", v.Date) + } + return Value{runtimeType: types.Time, goValue: v}, nil + } + return Value{}, fmt.Errorf("unsupported precision in Time with value %v %w", v.Precision, datehelpers.ErrUnsupportedPrecision) + case Interval: + // RuntimeType is not set here because it is inferred at RuntimeType() is called. + return Value{goValue: v}, nil + case List: + // RuntimeType is not set here because it is inferred when RuntimeType() is called. + return Value{goValue: v}, nil + case Named: + return Value{runtimeType: v.RuntimeType, goValue: v}, nil + case Tuple: + return Value{runtimeType: v.RuntimeType, goValue: v}, nil + case CodeSystem: + if v.ID == "" { + return Value{}, fmt.Errorf("%v must have an ID", types.CodeSystem) + } + return Value{runtimeType: types.CodeSystem, goValue: v}, nil + case Concept: + if len(v.Codes) == 0 { + return Value{}, fmt.Errorf("%v must have at least one %v", types.Concept, types.Code) + } + return Value{runtimeType: types.Concept, goValue: v}, nil + case ValueSet: + if v.ID == "" { + return Value{}, fmt.Errorf("%v must have an ID", types.ValueSet) + } + return Value{runtimeType: types.ValueSet, goValue: v}, nil + case Code: + if v.Code == "" { + return Value{}, fmt.Errorf("%v must have a Code", types.Code) + } + return Value{runtimeType: types.Code, goValue: v}, nil + default: + return Value{}, fmt.Errorf("%T %w", v, errUnsupportedType) + } +} + +// NewWithSources converts Golang values to CQL values when the sources are known. See New() +// function for full documentation. +func NewWithSources(val any, sourceExp model.IExpression, sourceObjs ...Value) (Value, error) { + o, err := New(val) + if err != nil { + return Value{}, err + } + return o.WithSources(sourceExp, sourceObjs...), nil +} + +// WithSources returns a version of the value with the given sources. This function has +// the following semantics to ensure all child values and expressions are recursively preserved +// as values propagate through the evaluation tree: +// +// First, if the value already has sources, this creates a copy of that value with the newly +// provided sources, so the original and its sources are preserved. Therefore an value with +// existing sources is never mutated and can be safely stored or reused across many consuming +// expressions if needed by the engine implementation. +// +// Second, if a caller does not explicitly provide a new set of source values, this function will +// use the existing value this is invoked on as the source. For instance, function implementations +// can do this to propagate a trace up the call stack by simply calling +// `valueToReturn.WithSources(theFunctionExpression)` prior to returning. +func (v Value) WithSources(sourceExp model.IExpression, sourceObjs ...Value) Value { + if v.sourceExpr == nil { + v.sourceExpr = sourceExp + v.sourceVals = sourceObjs + return v + } + + // TODO b/301606416: This does not make a copy of val for lists, tuples and proto types. This is + // ok since we currently don't mutate Values after they are created. + if len(sourceObjs) == 0 { + return Value{runtimeType: v.runtimeType, goValue: v.goValue, sourceExpr: sourceExp, sourceVals: []Value{v}} + } + return Value{runtimeType: v.runtimeType, goValue: v.goValue, sourceExpr: sourceExp, sourceVals: sourceObjs} +} + +// NewFromProto converts a proto to a Value. The source expression and source values are dropped. +func NewFromProto(pb *crpb.Value) (Value, error) { + switch t := pb.GetValue().(type) { + case nil: + return New(nil) + case *crpb.Value_BooleanValue: + return New(t.BooleanValue) + case *crpb.Value_StringValue: + return New(t.StringValue) + case *crpb.Value_IntegerValue: + return New(t.IntegerValue) + case *crpb.Value_LongValue: + return New(t.LongValue) + case *crpb.Value_DecimalValue: + return New(t.DecimalValue) + case *crpb.Value_QuantityValue: + return New(QuantityFromProto(t.QuantityValue)) + case *crpb.Value_RatioValue: + return New(RatioFromProto(t.RatioValue)) + case *crpb.Value_DateValue: + v, err := DateFromProto(t.DateValue) + if err != nil { + return Value{}, err + } + return New(v) + case *crpb.Value_DateTimeValue: + v, err := DateTimeFromProto(t.DateTimeValue) + if err != nil { + return Value{}, err + } + return New(v) + case *crpb.Value_TimeValue: + v, err := TimeFromProto(t.TimeValue) + if err != nil { + return Value{}, err + } + return New(v) + case *crpb.Value_IntervalValue: + v, err := IntervalFromProto(t.IntervalValue) + if err != nil { + return Value{}, err + } + return New(v) + case *crpb.Value_ListValue: + v, err := ListFromProto(t.ListValue) + if err != nil { + return Value{}, err + } + return New(v) + case *crpb.Value_TupleValue: + v, err := TupleFromProto(t.TupleValue) + if err != nil { + return Value{}, err + } + return New(v) + case *crpb.Value_NamedValue: + v, err := NamedFromProto(t.NamedValue) + if err != nil { + return Value{}, err + } + return New(v) + case *crpb.Value_CodeSystemValue: + return New(CodeSystemFromProto(t.CodeSystemValue)) + case *crpb.Value_ValueSetValue: + return New(ValueSetFromProto(t.ValueSetValue)) + case *crpb.Value_ConceptValue: + return New(ConceptFromProto(t.ConceptValue)) + case *crpb.Value_CodeValue: + return New(CodeFromProto(t.CodeValue)) + default: + return Value{}, fmt.Errorf("%T %w", pb, errUnsupportedType) + } +} + +// Quantity represents a decimal value with an associated unit string. +type Quantity struct { + Value float64 + Unit model.Unit +} + +// Proto converts Quantity to a proto. +func (q Quantity) Proto() *crpb.Quantity { + return &crpb.Quantity{ + Value: proto.Float64(q.Value), + Unit: proto.String(string(q.Unit)), + } +} + +// QuantityFromProto converts a proto to a Quantity. +func QuantityFromProto(pb *crpb.Quantity) Quantity { + return Quantity{Value: pb.GetValue(), Unit: model.Unit(pb.GetUnit())} +} + +func (q Quantity) marshalJSON(t json.RawMessage) ([]byte, error) { + return json.Marshal(struct { + Type json.RawMessage `json:"@type"` + Value float64 `json:"value"` + Unit string `json:"unit"` + }{ + Type: t, + Value: q.Value, + Unit: string(q.Unit), + }) +} + +// Ratio represents a ratio of two quantities. +type Ratio struct { + Numerator Quantity + Denominator Quantity +} + +// RatioFromProto converts a proto to a Ratio. +func RatioFromProto(pb *crpb.Ratio) Ratio { + return Ratio{Numerator: QuantityFromProto(pb.Numerator), Denominator: QuantityFromProto(pb.Denominator)} +} + +// Proto converts Ratio to a proto. +func (r Ratio) Proto() *crpb.Ratio { + return &crpb.Ratio{ + Numerator: r.Numerator.Proto(), + Denominator: r.Denominator.Proto(), + } +} + +func (r Ratio) marshalJSON(t json.RawMessage) ([]byte, error) { + quantityType, err := types.Quantity.MarshalJSON() + if err != nil { + return nil, err + } + marshalledNumerator, err := r.Numerator.marshalJSON(quantityType) + if err != nil { + return nil, err + } + marshalledDenominator, err := r.Denominator.marshalJSON(quantityType) + if err != nil { + return nil, err + } + return json.Marshal(struct { + Type json.RawMessage `json:"@type"` + Numerator json.RawMessage `json:"numerator"` + Denominator json.RawMessage `json:"denominator"` + }{ + Type: t, + Numerator: marshalledNumerator, + Denominator: marshalledDenominator, + }) +} + +// Date is the Golang representation of a CQL Date. CQL Dates do not have timezone offsets, but +// Golang time.Time requires a location. The time.Time should always have the offset of the +// evaluation timestamp. The precision will be is Year, Month or Day. +type Date DateTime + +// Equal returns true if this Date matches the provided one, otherwise false. +func (d Date) Equal(v Date) bool { + return DateTime(d).Equal(DateTime(v)) +} + +// Proto converts Date to a proto. +func (d Date) Proto() (*crpb.Date, error) { + pbCQLDate := &crpb.Date{} + pbDate := &datepb.Date{} + switch d.Precision { + case model.YEAR: + pbDate.Year = int32(d.Date.Year()) + pbCQLDate.Precision = crpb.Date_PRECISION_YEAR.Enum() + case model.MONTH: + pbDate.Year = int32(d.Date.Year()) + pbDate.Month = int32(d.Date.Month()) + pbCQLDate.Precision = crpb.Date_PRECISION_MONTH.Enum() + case model.DAY: + pbDate.Year = int32(d.Date.Year()) + pbDate.Month = int32(d.Date.Month()) + pbDate.Day = int32(d.Date.Day()) + pbCQLDate.Precision = crpb.Date_PRECISION_DAY.Enum() + default: + return nil, fmt.Errorf("unsupported precision in Date with value %v %w", d.Precision, datehelpers.ErrUnsupportedPrecision) + } + + pbCQLDate.Date = pbDate + return pbCQLDate, nil +} + +// DateFromProto converts a proto to a Date. +func DateFromProto(pb *crpb.Date) (Date, error) { + var modelPrecision model.DateTimePrecision + switch pb.GetPrecision() { + case crpb.Date_PRECISION_YEAR: + modelPrecision = model.YEAR + case crpb.Date_PRECISION_MONTH: + modelPrecision = model.MONTH + case crpb.Date_PRECISION_DAY: + modelPrecision = model.DAY + default: + return Date{}, fmt.Errorf("unsupported precision converting proto to Date %v %w", pb.GetPrecision(), datehelpers.ErrUnsupportedPrecision) + } + return Date{Date: time.Date(int(pb.Date.GetYear()), time.Month(pb.Date.GetMonth()), int(pb.Date.GetDay()), 0, 0, 0, 0, time.UTC), Precision: modelPrecision}, nil +} + +// DateTime is the Golang representation of a CQL DateTime. The time.Time may have different +// offsets. The precision will be anything from Year to Millisecond. +type DateTime struct { + Date time.Time + Precision model.DateTimePrecision +} + +// Proto converts DateTime to a proto. +func (d DateTime) Proto() (*crpb.DateTime, error) { + pbCQLDateTime := &crpb.DateTime{} + switch d.Precision { + case model.YEAR: + pbCQLDateTime.Precision = crpb.DateTime_PRECISION_YEAR.Enum() + case model.MONTH: + pbCQLDateTime.Precision = crpb.DateTime_PRECISION_MONTH.Enum() + case model.DAY: + pbCQLDateTime.Precision = crpb.DateTime_PRECISION_DAY.Enum() + case model.HOUR: + pbCQLDateTime.Precision = crpb.DateTime_PRECISION_HOUR.Enum() + case model.MINUTE: + pbCQLDateTime.Precision = crpb.DateTime_PRECISION_MINUTE.Enum() + case model.SECOND: + pbCQLDateTime.Precision = crpb.DateTime_PRECISION_SECOND.Enum() + case model.MILLISECOND: + pbCQLDateTime.Precision = crpb.DateTime_PRECISION_MILLISECOND.Enum() + default: + return nil, fmt.Errorf("unsupported precision in DateTime with value %v %w", d.Precision, datehelpers.ErrUnsupportedPrecision) + } + + pbCQLDateTime.Date = timestamppb.New(d.Date) + return pbCQLDateTime, nil +} + +// DateTimeFromProto converts a proto to a DateTime. +func DateTimeFromProto(pb *crpb.DateTime) (DateTime, error) { + var modelPrecision model.DateTimePrecision + switch pb.GetPrecision() { + case crpb.DateTime_PRECISION_YEAR: + modelPrecision = model.YEAR + case crpb.DateTime_PRECISION_MONTH: + modelPrecision = model.MONTH + case crpb.DateTime_PRECISION_DAY: + modelPrecision = model.DAY + case crpb.DateTime_PRECISION_HOUR: + modelPrecision = model.HOUR + case crpb.DateTime_PRECISION_MINUTE: + modelPrecision = model.MINUTE + case crpb.DateTime_PRECISION_SECOND: + modelPrecision = model.SECOND + case crpb.DateTime_PRECISION_MILLISECOND: + modelPrecision = model.MILLISECOND + default: + return DateTime{}, fmt.Errorf("unsupported precision converting Proto to DateTime %v %w", pb.GetPrecision(), datehelpers.ErrUnsupportedPrecision) + } + return DateTime{Date: pb.Date.AsTime(), Precision: modelPrecision}, nil +} + +// Equal returns true if this DateTime matches the provided one, otherwise false. +func (d DateTime) Equal(v DateTime) bool { + if !d.Date.Equal(v.Date) { + return false + } + if d.Precision != v.Precision { + return false + } + return true +} + +// Time is the Golang representation of a CQL Time. CQL Times do not have year, month, days or a +// timezone but Golang time.Time does. We use the date 0000-01-01 and timezone UTC for all golang +// time.Time. The precision will be between Hour and Millisecond. +type Time DateTime + +// Equal returns true if this Time matches the provided one, otherwise false. +func (t Time) Equal(v Time) bool { + return DateTime(t).Equal(DateTime(v)) +} + +// Proto converts Time to a proto. +func (t Time) Proto() (*crpb.Time, error) { + pbCQLTime := &crpb.Time{} + pbTime := &timeofdaypb.TimeOfDay{} + switch t.Precision { + case model.HOUR: + pbTime.Hours = int32(t.Date.Hour()) + pbCQLTime.Precision = crpb.Time_PRECISION_HOUR.Enum() + case model.MINUTE: + pbTime.Hours = int32(t.Date.Hour()) + pbTime.Minutes = int32(t.Date.Minute()) + pbCQLTime.Precision = crpb.Time_PRECISION_MINUTE.Enum() + case model.SECOND: + pbTime.Hours = int32(t.Date.Hour()) + pbTime.Minutes = int32(t.Date.Minute()) + pbTime.Seconds = int32(t.Date.Second()) + pbCQLTime.Precision = crpb.Time_PRECISION_SECOND.Enum() + case model.MILLISECOND: + pbTime.Hours = int32(t.Date.Hour()) + pbTime.Minutes = int32(t.Date.Minute()) + pbTime.Seconds = int32(t.Date.Second()) + pbTime.Nanos = int32(t.Date.Nanosecond()) + pbCQLTime.Precision = crpb.Time_PRECISION_MILLISECOND.Enum() + default: + return nil, fmt.Errorf("unsupported precision converting proto to Time %v %w", t.Precision, datehelpers.ErrUnsupportedPrecision) + } + + pbCQLTime.Date = pbTime + return pbCQLTime, nil +} + +// TimeFromProto converts a proto to a Time. +func TimeFromProto(pb *crpb.Time) (Time, error) { + var modelPrecision model.DateTimePrecision + switch pb.GetPrecision() { + case crpb.Time_PRECISION_HOUR: + modelPrecision = model.HOUR + case crpb.Time_PRECISION_MINUTE: + modelPrecision = model.MINUTE + case crpb.Time_PRECISION_SECOND: + modelPrecision = model.SECOND + case crpb.Time_PRECISION_MILLISECOND: + modelPrecision = model.MILLISECOND + default: + return Time{}, fmt.Errorf("unsupported precision in Time with value %v %w", pb.GetPrecision(), datehelpers.ErrUnsupportedPrecision) + } + pbDate := pb.GetDate() + return Time{Date: time.Date(0, time.January, 1, int(pbDate.GetHours()), int(pbDate.GetMinutes()), int(pbDate.GetSeconds()), int(pbDate.GetNanos()), time.UTC), Precision: modelPrecision}, nil +} + +// Interval is the Golang representation of a CQL Interval. +type Interval struct { + Low Value + High Value + LowInclusive bool + HighInclusive bool + // StaticType is used for the RuntimeType() of the interval when the interval contains + // only runtime nulls (meaning the runtime type cannot be reliably inferred). + StaticType *types.Interval // Field not exported. +} + +// Equal returns true if this Interval matches the provided one, otherwise false. +func (i Interval) Equal(v Interval) bool { + if !i.StaticType.Equal(v.StaticType) { + return false + } + if !i.Low.Equal(v.Low) || !i.High.Equal(v.High) || i.LowInclusive != v.LowInclusive || i.HighInclusive != v.HighInclusive || !i.StaticType.Equal(v.StaticType) { + return false + } + return true +} + +// Proto converts Interval to a proto. +func (i Interval) Proto() (*crpb.Interval, error) { + typepb, err := i.StaticType.Proto() + if err != nil { + return nil, err + } + pbInterval := &crpb.Interval{StaticType: typepb} + + pbInterval.Low, err = i.Low.Proto() + if err != nil { + return nil, err + } + pbInterval.High, err = i.High.Proto() + if err != nil { + return nil, err + } + pbInterval.LowInclusive = proto.Bool(i.LowInclusive) + pbInterval.HighInclusive = proto.Bool(i.HighInclusive) + return pbInterval, nil +} + +// IntervalFromProto converts a proto to an Interval. +func IntervalFromProto(pb *crpb.Interval) (Interval, error) { + typ, err := types.IntervalFromProto(pb.GetStaticType()) + if err != nil { + return Interval{}, err + } + + low, err := NewFromProto(pb.Low) + if err != nil { + return Interval{}, err + } + + high, err := NewFromProto(pb.High) + if err != nil { + return Interval{}, err + } + + return Interval{Low: low, High: high, LowInclusive: pb.GetLowInclusive(), HighInclusive: pb.GetHighInclusive(), StaticType: typ}, nil +} + +func inferIntervalType(i Interval) types.IType { + if !IsNull(i.Low) { + return &types.Interval{PointType: i.Low.RuntimeType()} + } + if !IsNull(i.High) { + return &types.Interval{PointType: i.High.RuntimeType()} + } + // Fallback to static type + return i.StaticType +} + +func (i Interval) marshalJSON(t json.RawMessage) ([]byte, error) { + low, err := i.Low.MarshalJSON() + if err != nil { + return nil, err + } + high, err := i.High.MarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(struct { + Type json.RawMessage `json:"@type"` + Low json.RawMessage `json:"low"` + High json.RawMessage `json:"high"` + LowInclusive bool `json:"lowClosed"` + HighInclusive bool `json:"highClosed"` + }{ + Type: t, + Low: low, + High: high, + LowInclusive: i.LowInclusive, + HighInclusive: i.HighInclusive, + }) +} + +// List is the Golang representation of a CQL List. +type List struct { + Value []Value + // StaticType is used for the RuntimeType() of the list when the list is empty. + StaticType *types.List +} + +// Proto converts List to a proto. +func (l List) Proto() (*crpb.List, error) { + typepb, err := l.StaticType.Proto() + if err != nil { + return nil, err + } + pbList := &crpb.List{StaticType: typepb} + for _, v := range l.Value { + pb, err := v.Proto() + if err != nil { + return nil, err + } + pbList.Value = append(pbList.Value, pb) + } + return pbList, nil +} + +// ListFromProto converts a proto to a List. +func ListFromProto(pb *crpb.List) (List, error) { + l := List{Value: make([]Value, 0, len(pb.Value))} + + typ, err := types.ListFromProto(pb.GetStaticType()) + if err != nil { + return List{}, err + } + l.StaticType = typ + + for _, pbv := range pb.Value { + v, err := NewFromProto(pbv) + if err != nil { + return List{}, err + } + l.Value = append(l.Value, v) + } + return l, nil +} + +// Equal returns true if this List matches the provided one, otherwise false. +func (l List) Equal(v List) bool { + if !l.StaticType.Equal(v.StaticType) { + return false + } + if len(l.Value) != len(v.Value) { + return false + } + for idx, obj := range l.Value { + if !obj.Equal(v.Value[idx]) { + return false + } + } + return true +} + +func inferListType(l []Value, staticType types.IType) types.IType { + // The parser should have already done type inference and conversions according to + // https://cql.hl7.org/03-developersguide.html#literals-and-selectors, if necessary for a List + // literal without a type specifier. + // + // At runtime, we simply return the runtime type of the first element, or fall back to the + // static type if the list is empty. + // TODO(b/326277425): support mixed lists that may have a choice result type. + if len(l) == 0 { + // Because we fall back to a static type, this might be a choice type, even though mixed lists + // are not fully supported yet. + return staticType + } + return &types.List{ElementType: l[0].RuntimeType()} +} + +// Named is the Golang representation fo a CQL Class (aka a CQL Named Structured Value). This could +// be any type defined in the Data Model like a FHIR.Encounter. +type Named struct { + Value proto.Message + // RuntimeType is the runtime type of this proto message value. Often times this is just the + // same as the static named type, but in some cases (e.g. Choice types) the caller should resolve + // this to the specific runtime type. + RuntimeType *types.Named +} + +// Proto converts Named to a proto. +func (n Named) Proto() (*crpb.Named, error) { + a, err := anypb.New(n.Value) + if err != nil { + return nil, err + } + typepb, err := n.RuntimeType.Proto() + if err != nil { + return nil, err + } + return &crpb.Named{ + Value: a, + RuntimeType: typepb, + }, nil +} + +// NamedFromProto converts a proto to a Named. +func NamedFromProto(pb *crpb.Named) (Named, error) { + return Named{}, errors.New("currently do not support converting a proto named type back into a golang value") +} + +// Named types aren't called out in the spec yet so we are defining our own representation +// here for now. +func (n Named) marshalJSON(_ json.RawMessage) ([]byte, error) { + v, err := protojson.Marshal(n.Value) + if err != nil { + return nil, err + } + + return json.Marshal(struct { + Type types.IType `json:"@type"` + Value json.RawMessage `json:"value"` + }{ + Type: n.RuntimeType, + Value: v, + }) +} + +// Equal returns true if this Named matches the provided one, otherwise false. +func (n Named) Equal(v Named) bool { + if !n.RuntimeType.Equal(v.RuntimeType) { + return false + } + return proto.Equal(n.Value, v.Value) +} + +// Tuple is the Golang representation of a CQL Tuple (aka a CQL Structured Value). +type Tuple struct { + // Value is the map of element name to CQL Value. + Value map[string]Value + // RuntimeType could be a tuple type or if this was a Class instance could be the class type + // (FHIR.Patient, System.Quantity...). For Choice types this should resolve to the specific + // runtime type. + RuntimeType types.IType +} + +// Proto converts Tuple to a proto. +func (t Tuple) Proto() (*crpb.Tuple, error) { + pbTuple := &crpb.Tuple{ + Value: make(map[string]*crpb.Value), + } + + switch typ := t.RuntimeType.(type) { + case *types.Tuple: + typepb, err := typ.Proto() + if err != nil { + return nil, err + } + pbTuple.RuntimeType = &crpb.Tuple_TupleType{TupleType: typepb} + case *types.Named: + typepb, err := typ.Proto() + if err != nil { + return nil, err + } + pbTuple.RuntimeType = &crpb.Tuple_NamedType{NamedType: typepb} + default: + return nil, fmt.Errorf("converting to proto found unsupported tuple type %v", t.RuntimeType) + } + + for k, v := range t.Value { + pb, err := v.Proto() + if err != nil { + return nil, err + } + pbTuple.Value[k] = pb + } + return pbTuple, nil +} + +// TupleFromProto converts a proto to a Tuple. +func TupleFromProto(pb *crpb.Tuple) (Tuple, error) { + cqlTuple := Tuple{Value: make(map[string]Value)} + switch pbt := pb.RuntimeType.(type) { + case *crpb.Tuple_TupleType: + tupleType, err := types.TupleFromProto(pbt.TupleType) + if err != nil { + return Tuple{}, err + } + cqlTuple.RuntimeType = tupleType + case *crpb.Tuple_NamedType: + namedType, err := types.NamedFromProto(pbt.NamedType) + if err != nil { + return Tuple{}, err + } + cqlTuple.RuntimeType = namedType + default: + return Tuple{}, fmt.Errorf("converting from proto found unsupported tuple type %v", pb.RuntimeType) + } + + for k, v := range pb.Value { + v, err := NewFromProto(v) + if err != nil { + return Tuple{}, err + } + cqlTuple.Value[k] = v + } + return cqlTuple, nil +} + +// Equal returns true if this Tuple matches the provided one, otherwise false. +func (t Tuple) Equal(vTuple Tuple) bool { + if !t.RuntimeType.Equal(vTuple.RuntimeType) { + return false + } + if len(t.Value) != len(vTuple.Value) { + return false + } + for k, v := range t.Value { + if !v.Equal(vTuple.Value[k]) { + return false + } + } + return true +} + +// ValueSet is the Golang representation of a CQL ValueSet. +type ValueSet struct { + ID string // 1..1 + Version string // 0..1 + // Unlike the CQL reference we are not including the local name as it is not considered useful. + CodeSystems []CodeSystem // 0..* +} + +// Equal returns true if this ValueSet matches the provided one, otherwise false. +func (v ValueSet) Equal(a ValueSet) bool { + if v.ID != a.ID || + v.Version != a.Version || + len(v.CodeSystems) != len(a.CodeSystems) { + return false + } + slices.SortFunc(v.CodeSystems, compareCodeSystem) + slices.SortFunc(a.CodeSystems, compareCodeSystem) + for i, c := range a.CodeSystems { + if c != v.CodeSystems[i] { + return false + } + } + return true +} + +// Proto converts ValueSet to a proto. +func (v ValueSet) Proto() *crpb.ValueSet { + pbValueSet := &crpb.ValueSet{ + Id: proto.String(v.ID), + Version: proto.String(v.Version), + CodeSystems: make([]*crpb.CodeSystem, 0, len(v.CodeSystems)), + } + for _, c := range v.CodeSystems { + pbValueSet.CodeSystems = append(pbValueSet.CodeSystems, c.Proto()) + } + return pbValueSet +} + +// ValueSetFromProto converts a proto to a ValueSet. +func ValueSetFromProto(pb *crpb.ValueSet) ValueSet { + codeSystems := make([]CodeSystem, 0, len(pb.CodeSystems)) + for _, c := range pb.CodeSystems { + codeSystems = append(codeSystems, CodeSystemFromProto(c)) + } + return ValueSet{ID: pb.GetId(), Version: pb.GetVersion(), CodeSystems: codeSystems} +} + +// TODO: b/301606416 - Need to be able to output ValueSet name. +func (v ValueSet) marshalJSON(runtimeType json.RawMessage) ([]byte, error) { + var cs []byte + if len(v.CodeSystems) > 0 { + var err error + if cs, err = json.Marshal(v.CodeSystems); err != nil { + return nil, err + } + } + + return json.Marshal(struct { + Type json.RawMessage `json:"@type"` + ID string `json:"id"` + Version string `json:"version,omitempty"` + CodeSystems json.RawMessage `json:"codesystems,omitempty"` + }{ + Type: runtimeType, + ID: v.ID, + Version: v.Version, + CodeSystems: cs, + }) +} + +// CodeSystem is the Golang representation of a CQL CodeSystem. +type CodeSystem struct { + ID string // 1..1 + Version string // 0..1 + // Unlike the CQL reference we are not including the local name as it is not considered useful. +} + +// TODO: b/301606416 - Need to be able to output CodeSystem name. +func (c CodeSystem) marshalJSON(runtimeType json.RawMessage) ([]byte, error) { + return json.Marshal(struct { + Type json.RawMessage `json:"@type"` + ID string `json:"id"` + Version string `json:"version,omitempty"` + }{ + Type: runtimeType, + ID: c.ID, + Version: c.Version, + }) +} + +func compareCodeSystem(a, b CodeSystem) int { + if a.ID != b.ID { + return strings.Compare(a.ID, b.ID) + } + return strings.Compare(a.Version, b.Version) +} + +// Proto converts CodeSystem to a proto. +func (c CodeSystem) Proto() *crpb.CodeSystem { + return &crpb.CodeSystem{ + Id: proto.String(c.ID), + Version: proto.String(c.Version), + } +} + +// CodeSystemFromProto converts a proto to a CodeSystem. +func CodeSystemFromProto(pb *crpb.CodeSystem) CodeSystem { + return CodeSystem{ID: pb.GetId(), Version: pb.GetVersion()} +} + +// Concept is the Golang representation of a CQL Concept. +type Concept struct { + Codes []Code // 1..* + Display string // 0..1 +} + +// Equal returns true if this Concept matches the provided one, otherwise false. +func (c Concept) Equal(v Concept) bool { + if len(c.Codes) != len(v.Codes) || c.Display != v.Display { + return false + } + slices.SortFunc(c.Codes, compareCode) + slices.SortFunc(v.Codes, compareCode) + for i, c := range c.Codes { + if c != v.Codes[i] { + return false + } + } + return true +} + +// Proto converts Concept to a proto. +func (c Concept) Proto() *crpb.Concept { + pbConcept := &crpb.Concept{ + Display: proto.String(c.Display), + Codes: make([]*crpb.Code, 0, len(c.Codes)), + } + for _, code := range c.Codes { + pbConcept.Codes = append(pbConcept.Codes, code.Proto()) + } + return pbConcept +} + +// ConceptFromProto converts a proto to a Concept. +func ConceptFromProto(pb *crpb.Concept) Concept { + codes := make([]Code, 0, len(pb.Codes)) + for _, c := range pb.Codes { + codes = append(codes, CodeFromProto(c)) + } + return Concept{Codes: codes, Display: pb.GetDisplay()} +} + +func (c Concept) marshalJSON(runtimeType json.RawMessage) ([]byte, error) { + codeType, err := types.Code.MarshalJSON() + if err != nil { + return nil, err + } + var codes []json.RawMessage + for _, code := range c.Codes { + code, err := code.marshalJSON(codeType) + if err != nil { + return nil, err + } + codes = append(codes, code) + } + + return json.Marshal(struct { + Type json.RawMessage `json:"@type"` + Codes []json.RawMessage `json:"codes"` + Display string `json:"display,omitempty"` + }{ + Type: runtimeType, + Codes: codes, + Display: c.Display, + }) +} + +// Code is the Golang representation of a CQL Code. +type Code struct { + Code string // 1..1 + Display string // 0..1 + System string // 0..1 + Version string // 0..1 +} + +func (c Code) marshalJSON(runtimeType json.RawMessage) ([]byte, error) { + return json.Marshal(struct { + Type json.RawMessage `json:"@type"` + Code string `json:"code"` + Display string `json:"display,omitempty"` + System string `json:"system"` + Version string `json:"version,omitempty"` + }{ + Type: runtimeType, + Code: c.Code, + Display: c.Display, + System: c.System, + Version: c.Version, + }) +} + +// compareCode is used for sorting for go Equal() implementation. This is different from CQL +// equality where display is ignored. +func compareCode(a, b Code) int { + if a.Code != b.Code { + return strings.Compare(a.Code, b.Code) + } else if a.System != b.System { + return strings.Compare(a.System, b.System) + } else if a.Version != b.Version { + return strings.Compare(a.Version, b.Version) + } + return strings.Compare(a.Display, b.Display) +} + +// Proto converts Code to a proto. +func (c Code) Proto() *crpb.Code { + return &crpb.Code{ + Code: proto.String(c.Code), + Display: proto.String(c.Display), + System: proto.String(c.System), + Version: proto.String(c.Version), + } +} + +// CodeFromProto converts a proto to a Code. +func CodeFromProto(pb *crpb.Code) Code { + return Code{Code: pb.GetCode(), Display: pb.GetDisplay(), System: pb.GetSystem(), Version: pb.GetVersion()} +} diff --git a/result/value_test.go b/result/value_test.go new file mode 100644 index 0000000..f77771a --- /dev/null +++ b/result/value_test.go @@ -0,0 +1,1550 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package result + +import ( + "encoding/json" + "strings" + "testing" + "time" + + anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + datepb "google.golang.org/genproto/googleapis/type/date" + timeofdaypb "google.golang.org/genproto/googleapis/type/timeofday" + + "github.com/google/cql/internal/datehelpers" + "github.com/google/cql/model" + crpb "github.com/google/cql/protos/cql_result_go_proto" + ctpb "github.com/google/cql/protos/cql_types_go_proto" + "github.com/google/cql/types" + d4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" + r4patientpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/patient_go_proto" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestEqual(t *testing.T) { + tests := []struct { + name string + a Value + b Value + wantEqual bool + }{ + { + name: "equal integers", + a: newOrFatal(t, 10), + b: newOrFatal(t, 10), + wantEqual: true, + }, + { + name: "unequal integers", + a: newOrFatal(t, 10), + b: newOrFatal(t, 20), + wantEqual: false, + }, + { + name: "equal bool", + a: newOrFatal(t, true), + b: newOrFatal(t, true), + wantEqual: true, + }, + { + name: "unequal bool", + a: newOrFatal(t, true), + b: newOrFatal(t, false), + wantEqual: false, + }, + { + name: "equal string", + a: newOrFatal(t, "hello"), + b: newOrFatal(t, "hello"), + wantEqual: true, + }, + { + name: "equal string", + a: newOrFatal(t, "hello"), + b: newOrFatal(t, "Hello"), + wantEqual: false, + }, + { + name: "equal long", + a: newOrFatal(t, 10), + b: newOrFatal(t, 10), + wantEqual: true, + }, + { + name: "unequal long", + a: newOrFatal(t, 10), + b: newOrFatal(t, 20), + wantEqual: false, + }, + { + name: "equal decimal", + a: newOrFatal(t, 10.0000001), + b: newOrFatal(t, 10.0000001), + wantEqual: true, + }, + { + name: "unequal decimal", + a: newOrFatal(t, 10.0000001), + b: newOrFatal(t, 10.0000002), + wantEqual: false, + }, + { + name: "equal proto", + a: newOrFatal(t, Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + b: newOrFatal(t, Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + wantEqual: true, + }, + { + name: "unequal proto", + a: newOrFatal(t, Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + b: newOrFatal(t, Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "2"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + wantEqual: false, + }, + { + name: "equal tuples", + a: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + b: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + wantEqual: true, + }, + { + name: "unequal tuples value", + a: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + b: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 20), "Banana": newOrFatal(t, 10)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + wantEqual: false, + }, + { + name: "unequal tuples length", + a: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + b: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 20), "Banana": newOrFatal(t, 10)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + wantEqual: false, + }, + { + name: "unequal tuples type", + a: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 20), "Banana": newOrFatal(t, 10)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + b: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 20), "Banana": newOrFatal(t, 10)}, + RuntimeType: &types.Named{TypeName: "FHIR.Fruit"}, + }), + wantEqual: false, + }, + { + name: "equal list", + a: newOrFatal(t, List{Value: []Value{newOrFatal(t, 10), newOrFatal(t, 20)}, StaticType: &types.List{ElementType: types.Integer}}), + b: newOrFatal(t, List{Value: []Value{newOrFatal(t, 10), newOrFatal(t, 20)}, StaticType: &types.List{ElementType: types.Integer}}), + wantEqual: true, + }, + { + name: "unequal list", + a: newOrFatal(t, List{Value: []Value{newOrFatal(t, 20), newOrFatal(t, 20)}, StaticType: &types.List{ElementType: types.Integer}}), + b: newOrFatal(t, List{Value: []Value{newOrFatal(t, 10), newOrFatal(t, 20)}, StaticType: &types.List{ElementType: types.Integer}}), + wantEqual: false, + }, + { + name: "unequal list length", + a: newOrFatal(t, List{Value: []Value{newOrFatal(t, 20), newOrFatal(t, 20)}, StaticType: &types.List{ElementType: types.Integer}}), + b: newOrFatal(t, List{Value: []Value{newOrFatal(t, 10), newOrFatal(t, 20), newOrFatal(t, 20)}, StaticType: &types.List{ElementType: types.Integer}}), + wantEqual: false, + }, + { + name: "equal null", + a: newOrFatal(t, nil), + b: newOrFatal(t, nil), + wantEqual: true, + }, + { + name: "equal quantity", + a: newOrFatal(t, Quantity{Value: 1, Unit: model.YEARUNIT}), + b: newOrFatal(t, Quantity{Value: 1, Unit: model.YEARUNIT}), + wantEqual: true, + }, + { + name: "unequal quantity with different value", + a: newOrFatal(t, Quantity{Value: 1, Unit: model.YEARUNIT}), + b: newOrFatal(t, Quantity{Value: 2, Unit: model.YEARUNIT}), + wantEqual: false, + }, + { + name: "unequal quantity with different unit", + a: newOrFatal(t, Quantity{Value: 1, Unit: model.YEARUNIT}), + b: newOrFatal(t, Quantity{Value: 1, Unit: model.MONTHUNIT}), + wantEqual: false, + }, + { + name: "equal ratio", + a: newOrFatal(t, Ratio{ + Numerator: Quantity{Value: 1, Unit: model.YEARUNIT}, + Denominator: Quantity{Value: 2, Unit: model.YEARUNIT}, + }, + ), + b: newOrFatal(t, Ratio{ + Numerator: Quantity{Value: 1, Unit: model.YEARUNIT}, + Denominator: Quantity{Value: 2, Unit: model.YEARUNIT}, + }, + ), + wantEqual: true, + }, + { + name: "unequal ratio with different quantity unit", + a: newOrFatal(t, Ratio{ + Numerator: Quantity{Value: 1, Unit: model.MONTHUNIT}, + Denominator: Quantity{Value: 2, Unit: model.MONTHUNIT}, + }, + ), + b: newOrFatal(t, Ratio{ + Numerator: Quantity{Value: 1, Unit: model.YEARUNIT}, + Denominator: Quantity{Value: 2, Unit: model.YEARUNIT}, + }, + ), + wantEqual: false, + }, + { + name: "unequal ratio with different quantity values", + a: newOrFatal(t, Ratio{ + Numerator: Quantity{Value: 44, Unit: model.YEARUNIT}, + Denominator: Quantity{Value: 55, Unit: model.YEARUNIT}, + }, + ), + b: newOrFatal(t, Ratio{ + Numerator: Quantity{Value: 1, Unit: model.YEARUNIT}, + Denominator: Quantity{Value: 2, Unit: model.YEARUNIT}, + }, + ), + wantEqual: false, + }, + { + name: "equal valueset", + a: newOrFatal(t, ValueSet{ID: "ID", Version: "Version"}), + b: newOrFatal(t, ValueSet{ID: "ID", Version: "Version"}), + wantEqual: true, + }, + { + name: "equal valueset with unsorted but equal codesystem", + a: newOrFatal(t, ValueSet{ID: "ID", Version: "Version", CodeSystems: []CodeSystem{CodeSystem{ID: "ID1"}, CodeSystem{ID: "ID2"}}}), + b: newOrFatal(t, ValueSet{ID: "ID", Version: "Version", CodeSystems: []CodeSystem{CodeSystem{ID: "ID2"}, CodeSystem{ID: "ID1"}}}), + wantEqual: true, + }, + { + name: "unequal valueset", + a: newOrFatal(t, ValueSet{ID: "ID", Version: "Version"}), + b: newOrFatal(t, ValueSet{ID: "ID", Version: "Version2"}), + wantEqual: false, + }, + { + name: "unequal valueset with unequal codesystem", + a: newOrFatal(t, ValueSet{ID: "ID", Version: "Version", CodeSystems: []CodeSystem{CodeSystem{ID: "ID"}}}), + b: newOrFatal(t, ValueSet{ID: "ID2", Version: "Version", CodeSystems: []CodeSystem{CodeSystem{ID: "ID", Version: "Version"}}}), + wantEqual: false, + }, + { + name: "unequal valueset with unequal number of codesystem", + a: newOrFatal(t, ValueSet{ID: "ID", Version: "Version", CodeSystems: []CodeSystem{CodeSystem{ID: "ID"}}}), + b: newOrFatal(t, ValueSet{ID: "ID2", Version: "Version", CodeSystems: []CodeSystem{CodeSystem{ID: "ID"}, CodeSystem{ID: "ID2"}}}), + wantEqual: false, + }, + { + name: "equal codesystem", + a: newOrFatal(t, CodeSystem{ID: "ID", Version: "Version"}), + b: newOrFatal(t, CodeSystem{ID: "ID", Version: "Version"}), + wantEqual: true, + }, + { + name: "unequal codesystem", + a: newOrFatal(t, CodeSystem{ID: "ID", Version: "Version"}), + b: newOrFatal(t, CodeSystem{ID: "ID", Version: "Version2"}), + wantEqual: false, + }, + { + name: "equal code", + a: newOrFatal(t, Code{System: "System", Code: "Code"}), + b: newOrFatal(t, Code{System: "System", Code: "Code"}), + wantEqual: true, + }, + { + name: "unequal code", + a: newOrFatal(t, Code{System: "System", Code: "Code"}), + b: newOrFatal(t, Code{System: "System", Code: "Code2"}), + wantEqual: false, + }, + { + name: "equal concept", + a: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code"}}, Display: "BO"}), + b: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code"}}, Display: "BO"}), + wantEqual: true, + }, + { + name: "equal concept with unsorted but equal codes", + a: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code"}, Code{System: "CodeSystem2", Code: "Code2"}}, Display: "BO"}), + b: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem2", Code: "Code2"}, Code{System: "CodeSystem", Code: "Code"}}, Display: "BO"}), + wantEqual: true, + }, + { + name: "unequal concept different displays", + a: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code"}}, Display: "BO"}), + b: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code2"}}, Display: "Deoderant"}), + wantEqual: false, + }, + { + name: "unequal concept", + a: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code"}}, Display: "BO"}), + b: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code"}, Code{System: "CodeSystem2", Code: "Code2"}}, Display: "BO"}), + wantEqual: false, + }, + { + name: "unequal concept", + a: newOrFatal(t, Concept{Codes: []Code{Code{System: "CodeSystem", Code: "Code"}}, Display: "BO"}), + b: newOrFatal(t, Concept{Codes: []Code{Code{System: "CS", Code: "Co"}}, Display: "BO"}), + wantEqual: false, + }, + { + name: "unequal different Value types: protoMessage, integer", + a: newOrFatal(t, Named{Value: &r4patientpb.Patient{}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + b: newOrFatal(t, 10), + wantEqual: false, + }, + { + name: "unequal different Value types: null, protoMessage", + a: newOrFatal(t, nil), + b: newOrFatal(t, Named{Value: &r4patientpb.Patient{}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + wantEqual: false, + }, + { + name: "equal Date", + a: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + b: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + wantEqual: true, + }, + { + name: "Date equal with month granularity", + a: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.MONTH}), + b: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.MONTH}), + wantEqual: true, + }, + { + name: "uneqal Dates", + a: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + b: newOrFatal(t, Date{Date: time.Date(2024, time.March, 5, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + wantEqual: false, + }, + { + name: "unequal Date with differing precision granularity", + a: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + b: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.MONTH}), + wantEqual: false, + }, + { + name: "Date unequal with another type", + a: newOrFatal(t, Date{Date: time.Date(2024, time.March, 1, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + b: newOrFatal(t, 10.0000001), + wantEqual: false, + }, + { + name: "equal DateTimes", + a: newOrFatal(t, DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC)}), + b: newOrFatal(t, DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC)}), + wantEqual: true, + }, + { + name: "DateTime equal with year granularity", + a: newOrFatal(t, DateTime{Date: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), Precision: model.YEAR}), + b: newOrFatal(t, DateTime{Date: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), Precision: model.YEAR}), + wantEqual: true, + }, + { + name: "unequal DateTimes", + a: newOrFatal(t, DateTime{Date: time.Date(2024, time.March, 31, 0, 2, 3, 1e8, time.UTC)}), + b: newOrFatal(t, DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC)}), + wantEqual: false, + }, + { + name: "unequal DateTime with differing precision granularity", + a: newOrFatal(t, DateTime{Date: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), Precision: model.YEAR}), + b: newOrFatal(t, DateTime{Date: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), Precision: model.MONTH}), + wantEqual: false, + }, + { + name: "DateTime unequal with another type", + a: newOrFatal(t, DateTime{Date: time.Date(2024, time.March, 31, 0, 2, 3, 1e8, time.UTC)}), + b: newOrFatal(t, 10), + wantEqual: false, + }, + { + name: "equal Times", + a: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC)}), + b: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC)}), + wantEqual: true, + }, + { + name: "Times equal with hour granularity", + a: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 4, 0, 0, 0, time.UTC), Precision: model.HOUR}), + b: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 4, 0, 0, 0, time.UTC), Precision: model.HOUR}), + wantEqual: true, + }, + { + name: "unequal Times", + a: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 0, 2, 3, 1e8, time.UTC)}), + b: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC)}), + wantEqual: false, + }, + { + name: "unequal Times with differing precision granularity", + a: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 2, 0, 0, 0, time.UTC), Precision: model.HOUR}), + b: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 2, 0, 0, 0, time.UTC), Precision: model.MINUTE}), + wantEqual: false, + }, + { + name: "DateTime unequal with another type", + a: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 0, 2, 3, 1e8, time.UTC)}), + b: newOrFatal(t, 10), + wantEqual: false, + }, + { + name: "equal Intervals", + a: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + b: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + wantEqual: true, + }, + { + name: "unequal Interval Low Value", + a: newOrFatal(t, Interval{ + Low: newOrFatal(t, 15), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + b: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + wantEqual: false, + }, + { + name: "unequal Interval High Value", + a: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 25), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + b: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + wantEqual: false, + }, + { + name: "unequal Interval LowInclusive", + a: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + b: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: false, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + wantEqual: false, + }, + { + name: "unequal Interval HighInclusive", + a: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + b: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: false, + StaticType: &types.Interval{PointType: types.Integer}, + }), + wantEqual: false, + }, + { + name: "unequal Interval value Types", + a: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Long}, + }), + b: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: false, + StaticType: &types.Interval{PointType: types.Integer}, + }), + wantEqual: false, + }, + { + name: "unequal Value Types, with Interval", + a: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: false, + StaticType: &types.Interval{PointType: types.Integer}, + }), + b: newOrFatal(t, 10), + wantEqual: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.a.Equal(tc.b) != tc.wantEqual { + t.Errorf("Equal(%v, %v) = %v, want %v", tc.a, tc.b, tc.a, tc.wantEqual) + } + }) + } +} + +func TestNew(t *testing.T) { + tests := []struct { + name string + input any + want Value + }{ + { + name: "nil", + input: nil, + want: Value{goValue: nil, runtimeType: types.Any}, + }, + { + name: "quantity", + input: Quantity{Value: 1, Unit: model.DAYUNIT}, + want: Value{goValue: Quantity{Value: 1, Unit: model.DAYUNIT}, runtimeType: types.Quantity}, + }, + { + name: "ratio", + input: Ratio{Numerator: Quantity{Value: 1, Unit: model.DAYUNIT}, Denominator: Quantity{Value: 2, Unit: model.DAYUNIT}}, + want: Value{goValue: Ratio{Numerator: Quantity{Value: 1, Unit: model.DAYUNIT}, Denominator: Quantity{Value: 2, Unit: model.DAYUNIT}}, runtimeType: types.Ratio}, + }, + { + name: "bool", + input: true, + want: Value{goValue: true, runtimeType: types.Boolean}, + }, + { + name: "string", + input: "hello", + want: Value{goValue: "hello", runtimeType: types.String}, + }, + { + name: "int", + input: 1, + want: Value{goValue: int32(1), runtimeType: types.Integer}, + }, + { + name: "long", + input: int64(1), + want: Value{goValue: int64(1), runtimeType: types.Long}, + }, + { + name: "decimal", + input: 1.1, + want: Value{goValue: 1.1, runtimeType: types.Decimal}, + }, + { + name: "valueset", + input: ValueSet{ID: "ID", Version: "Version"}, + want: Value{goValue: ValueSet{ID: "ID", Version: "Version"}, runtimeType: types.ValueSet}, + }, + { + name: "codesystem", + input: CodeSystem{ID: "ID", Version: "Version"}, + want: Value{goValue: CodeSystem{ID: "ID", Version: "Version"}, runtimeType: types.CodeSystem}, + }, + { + name: "code", + input: Code{System: "System", Code: "Code"}, + want: Value{goValue: Code{System: "System", Code: "Code"}, runtimeType: types.Code}, + }, + { + name: "concept", + input: Concept{Codes: []Code{Code{System: "System", Code: "Code"}}, Display: "A disease"}, + want: Value{goValue: Concept{Codes: []Code{Code{System: "System", Code: "Code"}}, Display: "A disease"}, runtimeType: types.Concept}, + }, + { + name: "tuple", + input: Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }, + want: Value{ + goValue: Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }, + runtimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}}, + }, + { + name: "list of integer Values", + input: List{Value: []Value{newOrFatal(t, 1)}, StaticType: &types.List{ElementType: types.Integer}}, + want: Value{goValue: List{Value: []Value{newOrFatal(t, 1)}, StaticType: &types.List{ElementType: types.Integer}}, runtimeType: &types.List{ElementType: types.Integer}}, + }, + { + name: "list of integer literals", + input: List{Value: []Value{newOrFatal(t, 1), newOrFatal(t, 2)}, StaticType: &types.List{ElementType: types.Integer}}, + want: Value{goValue: List{Value: []Value{newOrFatal(t, 1), newOrFatal(t, 2)}, StaticType: &types.List{ElementType: types.Integer}}, runtimeType: &types.List{ElementType: types.Integer}}, + }, + { + name: "protoValue", + input: Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}, + want: Value{ + goValue: Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}, + runtimeType: &types.Named{TypeName: "FHIR.Patient"}, + }, + }, + { + name: "DateValue", + input: Date{ + Date: time.Date(2023, time.March, 5, 0, 0, 0, 0, time.UTC), + Precision: model.DAY, + }, + want: Value{ + goValue: Date{ + Date: time.Date(2023, time.March, 5, 0, 0, 0, 0, time.UTC), + Precision: model.DAY, + }, + runtimeType: types.Date, + }, + }, + { + name: "DateTimeValue", + input: DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + want: Value{ + goValue: DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + runtimeType: types.DateTime, + }, + }, + { + name: "TimeValue", + input: Time{ + Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.FixedZone("Fixed", 4*60*60)), + Precision: model.SECOND, + }, + want: Value{ + goValue: Time{ + Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.FixedZone("Fixed", 4*60*60)), + Precision: model.SECOND, + }, + runtimeType: types.Time, + }, + }, + { + name: "IntervalValue", + input: Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }, + want: Value{ + goValue: Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }, + runtimeType: nil, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := New(tc.input) + if err != nil { + t.Errorf("New(%v) returned unexpected error, %v", tc.input, err) + } + if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { + t.Errorf("New(%v) returned unexpected diff (-want +got):\n%s", tc.input, diff) + } + }) + } +} + +func TestNew_Error(t *testing.T) { + tests := []struct { + name string + input any + wantErr string + }{ + { + name: "Date unsupported precision", + input: Date{ + Date: time.Date(2024, time.March, 1, 2, 3, 0, 0, time.UTC), + Precision: model.SECOND, + }, + wantErr: datehelpers.ErrUnsupportedPrecision.Error(), + }, + { + name: "DateTime unsupported precision", + input: DateTime{ + Date: time.Date(2024, time.March, 1, 2, 3, 0, 0, time.UTC), + Precision: model.WEEK, + }, + wantErr: datehelpers.ErrUnsupportedPrecision.Error(), + }, + { + name: "Time unsupported precision", + input: Time{ + Date: time.Date(0, time.January, 1, 2, 3, 0, 0, time.UTC), + Precision: model.DAY, + }, + wantErr: datehelpers.ErrUnsupportedPrecision.Error(), + }, + { + name: "Time unsupported precision", + input: Time{ + Date: time.Date(2024, time.January, 1, 2, 3, 0, 0, time.UTC), + Precision: model.MINUTE, + }, + wantErr: "Time must be Year 0000, Month 01, Day 01", + }, + { + name: "CodeSystem missing ID", + input: CodeSystem{}, + wantErr: "System.CodeSystem must have an ID", + }, + { + name: "Concept must have one code", + input: Concept{}, + wantErr: "System.Concept must have at least one System.Code", + }, + { + name: "ValueSet missing ID", + input: ValueSet{}, + wantErr: "System.ValueSet must have an ID", + }, + { + name: "unsupported type", + input: map[string]string{"test": "test"}, + wantErr: errUnsupportedType.Error(), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := New(tc.input) + if err == nil { + t.Fatalf("New(%v) succeeded, want error", tc.input) + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("Returned error (%s) did not contain expected (%s)", err, tc.wantErr) + } + }) + } +} + +func TestNewWithSources(t *testing.T) { + defaultSourceObs := []Value{newOrFatal(t, "PLACEHOLDER")} + defaultSourceExpr := &model.Add{} + tests := []struct { + name string + input any + want Value + }{ + { + name: "nil", + input: nil, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: nil, runtimeType: types.Any}, + }, + { + name: "bool", + input: true, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: true, runtimeType: types.Boolean}, + }, + { + name: "string", + input: "hello", + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: "hello", runtimeType: types.String}, + }, + { + name: "int", + input: 1, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: int32(1), runtimeType: types.Integer}, + }, + { + name: "long", + input: int64(1), + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: int64(1), runtimeType: types.Long}, + }, + { + name: "decimal", + input: 1.1, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: 1.1, runtimeType: types.Decimal}, + }, + { + name: "quantity", + input: Quantity{Value: 1, Unit: model.DAYUNIT}, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: Quantity{Value: 1, Unit: model.DAYUNIT}, runtimeType: types.Quantity}, + }, + { + name: "ratio", + input: Ratio{Numerator: Quantity{Value: 1, Unit: model.DAYUNIT}, Denominator: Quantity{Value: 2, Unit: model.DAYUNIT}}, + want: Value{ + goValue: Ratio{Numerator: Quantity{Value: 1, Unit: model.DAYUNIT}, Denominator: Quantity{Value: 2, Unit: model.DAYUNIT}}, + runtimeType: types.Ratio, + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + }, + }, + { + name: "valueset", + input: ValueSet{ID: "ID", Version: "Version"}, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: ValueSet{ID: "ID", Version: "Version"}, runtimeType: types.ValueSet}, + }, + { + name: "codesystem", + input: CodeSystem{ID: "ID", Version: "Version"}, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: CodeSystem{ID: "ID", Version: "Version"}, runtimeType: types.CodeSystem}, + }, + { + name: "concept", + input: Concept{Codes: []Code{Code{System: "System", Code: "Code"}}, Display: "A disease"}, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: Concept{Codes: []Code{Code{System: "System", Code: "Code"}}, Display: "A disease"}, runtimeType: types.Concept}, + }, + { + name: "list of integer Values", + input: List{Value: []Value{newOrFatal(t, 1)}, StaticType: &types.List{ElementType: types.Integer}}, + want: Value{sourceExpr: defaultSourceExpr, sourceVals: defaultSourceObs, goValue: List{Value: []Value{newOrFatal(t, 1)}, StaticType: &types.List{ElementType: types.Integer}}, runtimeType: &types.List{ElementType: types.Integer}}, + }, + { + name: "ProtoValue", + input: Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}, + want: Value{ + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + goValue: Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}, + runtimeType: &types.Named{TypeName: "FHIR.Patient"}, + }, + }, + { + name: "tuple", + input: Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }, + want: Value{ + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + goValue: Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }, + runtimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }, + }, + { + name: "ListValue", + input: List{Value: []Value{newOrFatal(t, 1)}, StaticType: &types.List{ElementType: types.Integer}}, + want: Value{ + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + goValue: List{Value: []Value{newOrFatal(t, 1)}, StaticType: &types.List{ElementType: types.Integer}}, + runtimeType: nil, + }, + }, + { + name: "DateValue", + input: Date{ + Date: time.Date(2023, time.March, 5, 0, 0, 0, 0, time.UTC), + Precision: model.DAY, + }, + want: Value{ + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + goValue: Date{ + Date: time.Date(2023, time.March, 5, 0, 0, 0, 0, time.UTC), + Precision: model.DAY, + }, + runtimeType: types.Date, + }, + }, + { + name: "DateTimeValue", + input: DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + want: Value{ + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + goValue: DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }, + runtimeType: types.DateTime, + }, + }, + { + name: "TimeValue", + input: Time{ + Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.FixedZone("Fixed", 4*60*60)), + Precision: model.SECOND, + }, + want: Value{ + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + goValue: Time{ + Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.FixedZone("Fixed", 4*60*60)), + Precision: model.SECOND, + }, + runtimeType: types.Time, + }, + }, + { + name: "IntervalValue", + input: Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }, + want: Value{ + sourceExpr: defaultSourceExpr, + sourceVals: defaultSourceObs, + goValue: Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }, + runtimeType: &types.Interval{PointType: types.Integer}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := NewWithSources(tc.input, defaultSourceExpr, defaultSourceObs...) + if err != nil { + t.Errorf("NewWithSources(%v) returned unexpected error, %v", tc.input, err) + } + if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { + t.Errorf("NewWithSources(%v) returned unexpected diff (-want +got):\n%s", tc.input, diff) + } + + // Test adding sources with a specific source value makes it visible. + wrappedExpr := &model.Subtract{} + wrappedSourceObj := newOrFatal(t, "Wrapper") + wrappedWithSourceObj := got.WithSources(wrappedExpr, wrappedSourceObj) + if diff := cmp.Diff(tc.want.GolangValue(), wrappedWithSourceObj.GolangValue(), protocmp.Transform()); diff != "" { + t.Errorf("Wrapped value for %v did not match the original (-want +got):\n%s", tc.input, diff) + } + if diff := cmp.Diff(wrappedExpr, wrappedWithSourceObj.SourceExpression()); diff != "" { + t.Errorf("Wrapped source expression for %v returned unexpected diff (-want +got):\n%s", tc.input, diff) + } + if diff := cmp.Diff([]Value{wrappedSourceObj}, wrappedWithSourceObj.SourceValues()); diff != "" { + t.Errorf("Wrapped source values for %v returned unexpected diff (-want +got):\n%s", tc.input, diff) + } + + // Test adding sources without a new source value keeps the existing source value. + + wrappedWithoutSourceObj := got.WithSources(wrappedExpr) + if diff := cmp.Diff(tc.want.GolangValue(), wrappedWithoutSourceObj.GolangValue(), protocmp.Transform()); diff != "" { + t.Errorf("Wrapped without new source value for %v did not match the original (-want +got):\n%s", tc.input, diff) + } + if diff := cmp.Diff(wrappedExpr, wrappedWithoutSourceObj.SourceExpression()); diff != "" { + t.Errorf("Wrapped source expression without new source for %v returned unexpected diff (-want +got):\n%s", tc.input, diff) + } + if diff := cmp.Diff([]Value{got}, wrappedWithoutSourceObj.SourceValues()); diff != "" { + t.Errorf("Wrapped source values without new source for %v returned unexpected diff (-want +got):\n%s", tc.input, diff) + } + + // TODO b/301606416: Add tests where we modify the value of the original Value. + if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { + t.Errorf("Original Value for (%v) should not be modified after wrapping but was: (-want +got):\n%s", tc.input, diff) + } + }) + } +} + +func TestMarshalJSON(t *testing.T) { + tests := []struct { + name string + unmarshalled Value + want string + }{ + // Simple types. + { + name: "nil", + unmarshalled: newOrFatal(t, nil), + want: `{"@type":"System.Any","value":null}`, + }, + { + name: "Int", + unmarshalled: newOrFatal(t, 1), + want: `{"@type":"System.Integer","value":1}`, + }, + { + name: "Long", + unmarshalled: newOrFatal(t, int64(1)), + want: `{"@type":"System.Long","value":1}`, + }, + { + name: "Decimal", + unmarshalled: newOrFatal(t, 4.5), + want: `{"@type":"System.Decimal","value":4.5}`, + }, + { + name: "String", + unmarshalled: newOrFatal(t, "hello"), + want: `{"@type":"System.String","value":"hello"}`, + }, + { + name: "Bool", + unmarshalled: newOrFatal(t, true), + want: `{"@type":"System.Boolean","value":true}`, + }, + { + name: "Quantity", + unmarshalled: newOrFatal(t, Quantity{Value: 1, Unit: model.YEARUNIT}), + want: `{"@type":"System.Quantity","value":1,"unit":"year"}`, + }, + { + name: "Ratio", + unmarshalled: newOrFatal(t, + Ratio{Numerator: Quantity{Value: 1, Unit: model.YEARUNIT}, Denominator: Quantity{Value: 2, Unit: model.YEARUNIT}}), + want: `{"@type":"System.Ratio","numerator":{"@type":"System.Quantity","value":1,"unit":"year"},"denominator":{"@type":"System.Quantity","value":2,"unit":"year"}}`, + }, + { + name: "Code", + unmarshalled: newOrFatal(t, Code{System: "foo", Code: "bar", Display: "the foo", Version: "1.0"}), + want: `{"@type":"System.Code","code":"bar","display":"the foo","system":"foo","version":"1.0"}`, + }, + { + name: "Valueset", + unmarshalled: newOrFatal(t, ValueSet{ID: "ID", Version: "Version"}), + want: `{"@type":"System.ValueSet","id":"ID","version":"Version"}`, + }, + { + name: "CodeSystem", + unmarshalled: newOrFatal(t, CodeSystem{ID: "ID", Version: "Version"}), + want: `{"@type":"System.CodeSystem","id":"ID","version":"Version"}`, + }, + { + name: "Concept", + unmarshalled: newOrFatal(t, Concept{Codes: []Code{Code{System: "foo", Code: "bar", Version: "1.0"}}, Display: "A disease"}), + want: `{"@type":"System.Concept","codes":[{"@type":"System.Code","code":"bar","system":"foo","version":"1.0"}],"display":"A disease"}`, + }, + { + name: "Date", + unmarshalled: newOrFatal(t, Date{ + Date: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC), + Precision: model.DAY, + }), + want: `{"@type":"System.Date","value":"@2024-03-31"}`, + }, + { + name: "DateTime", + unmarshalled: newOrFatal(t, DateTime{ + Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }), + want: `{"@type":"System.DateTime","value":"@2024-03-31T01:20:30Z"}`, + }, + { + name: "Time with UTC TimeZone", + unmarshalled: newOrFatal(t, Time{ + Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC), + Precision: model.SECOND, + }), + want: `{"@type":"System.Time","value":"T01:20:30"}`, + }, + { + name: "Time with TimeZone", + unmarshalled: newOrFatal(t, Time{ + Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.FixedZone("Fixed", 4*60*60)), + Precision: model.MILLISECOND, + }), + want: `{"@type":"System.Time","value":"T01:20:30.100"}`, + }, + // Complex types. + { + name: "Interval", + unmarshalled: newOrFatal(t, Interval{ + Low: newOrFatal(t, 10), + High: newOrFatal(t, 20), + LowInclusive: true, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Integer}, + }), + want: `{"@type":"Interval\u003cSystem.Integer\u003e","low":{"@type":"System.Integer","value":10},"high":{"@type":"System.Integer","value":20},"lowClosed":true,"highClosed":true}`, + }, + { + name: "Tuple", + unmarshalled: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + want: `{"Apple":{"@type":"System.Integer","value":10},"Banana":{"@type":"System.Integer","value":20}}`, + }, + { + name: "List", + unmarshalled: newOrFatal(t, List{Value: []Value{newOrFatal(t, 3), newOrFatal(t, 4)}, StaticType: &types.List{ElementType: types.Integer}}), + want: `[{"@type":"System.Integer","value":3},{"@type":"System.Integer","value":4}]`, + }, + { + name: "Proto", + unmarshalled: newOrFatal(t, Named{Value: &r4patientpb.Patient{Active: &d4pb.Boolean{Value: true}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}), + want: `{"@type":"FHIR.Patient","value":{"active":{"value":true}}}`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + got, err := json.Marshal(tc.unmarshalled) + if err != nil { + t.Fatalf("Json marshalling failed %v", err) + } + if diff := cmp.Diff(tc.want, string(got)); diff != "" { + t.Errorf("json.Marshal() returned unexpected diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestRuntimeType(t *testing.T) { + cases := []struct { + name string + input Value + wantRuntimeType types.IType + }{ + { + name: "Empty list falls back to static type", + input: newOrFatal(t, List{Value: []Value{}, StaticType: &types.List{ElementType: types.Integer}}), + wantRuntimeType: &types.List{ElementType: types.Integer}, + }, + { + name: "List runtime type is inferred for a non-empty list", + input: newOrFatal(t, List{Value: []Value{newOrFatal(t, 3), newOrFatal(t, 4)}, StaticType: &types.List{ElementType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}}), + wantRuntimeType: &types.List{ElementType: types.Integer}, + }, + { + name: "Interval with two nulls falls back to static type", + input: newOrFatal(t, Interval{Low: newOrFatal(t, nil), High: newOrFatal(t, nil), StaticType: &types.Interval{PointType: types.Integer}}), + wantRuntimeType: &types.Interval{PointType: types.Integer}, + }, + { + name: "Interval runtime type inferred for non-null values", + input: newOrFatal(t, Interval{Low: newOrFatal(t, 1), High: newOrFatal(t, nil), StaticType: &types.Interval{PointType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.Date}}}}), + wantRuntimeType: &types.Interval{PointType: types.Integer}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := tc.input.RuntimeType() + if !got.Equal(tc.wantRuntimeType) { + t.Errorf("%v RuntimeType() = %v, want %v", tc.input, got, tc.wantRuntimeType) + } + }) + } +} + +func TestProtoAndBack(t *testing.T) { + tests := []struct { + name string + value Value + wantProto *crpb.Value + // wantSameValue is true if the value is expected to be the same as the original value. + wantSameValue bool + // If wantSameValue is false, wantDifferentValue is the value that is expected. + wantDifferentValue Value + }{ + { + name: "Null", + value: newOrFatal(t, nil), + wantProto: &crpb.Value{}, + wantSameValue: true, + }, + { + name: "Boolean", + value: newOrFatal(t, true), + wantProto: &crpb.Value{ + Value: &crpb.Value_BooleanValue{BooleanValue: true}, + }, + wantSameValue: true, + }, + { + name: "String", + value: newOrFatal(t, "hello"), + wantProto: &crpb.Value{ + Value: &crpb.Value_StringValue{StringValue: "hello"}, + }, + wantDifferentValue: newOrFatal(t, "hello"), + }, + { + name: "Integer", + value: newOrFatal(t, 1), + wantProto: &crpb.Value{ + Value: &crpb.Value_IntegerValue{IntegerValue: 1}, + }, + wantSameValue: true, + }, + { + name: "Long", + value: newOrFatal(t, int64(1)), + wantProto: &crpb.Value{ + Value: &crpb.Value_LongValue{LongValue: 1}, + }, + wantSameValue: true, + }, + { + name: "Decimal", + value: newOrFatal(t, 1.1), + wantProto: &crpb.Value{ + Value: &crpb.Value_DecimalValue{DecimalValue: 1.1}, + }, + wantSameValue: true, + }, + { + name: "Quantity", + value: newOrFatal(t, Quantity{Value: 1, Unit: model.YEARUNIT}), + wantProto: &crpb.Value{ + Value: &crpb.Value_QuantityValue{ + QuantityValue: &crpb.Quantity{ + Value: proto.Float64(1), + Unit: proto.String(string(model.YEARUNIT)), + }, + }, + }, + wantSameValue: true, + }, + { + name: "Ratio", + value: newOrFatal(t, Ratio{Numerator: Quantity{Value: 1, Unit: model.YEARUNIT}, Denominator: Quantity{Value: 2, Unit: model.YEARUNIT}}), + wantProto: &crpb.Value{ + Value: &crpb.Value_RatioValue{ + RatioValue: &crpb.Ratio{ + Numerator: &crpb.Quantity{ + Value: proto.Float64(1), + Unit: proto.String(string(model.YEARUNIT)), + }, + Denominator: &crpb.Quantity{ + Value: proto.Float64(2), + Unit: proto.String(string(model.YEARUNIT)), + }, + }, + }, + }, + wantSameValue: true, + }, + { + name: "Date", + value: newOrFatal(t, Date{Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), Precision: model.DAY}), + wantProto: &crpb.Value{ + Value: &crpb.Value_DateValue{ + DateValue: &crpb.Date{ + Date: &datepb.Date{ + Year: 2024, + Month: 3, + Day: 31, + }, + Precision: crpb.Date_PRECISION_DAY.Enum(), + }, + }, + }, + // Hours and lower are dropped due to precision. + wantDifferentValue: newOrFatal(t, Date{Date: time.Date(2024, time.March, 31, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + }, + { + name: "DateTime", + value: newOrFatal(t, DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC), Precision: model.SECOND}), + wantProto: &crpb.Value{ + Value: &crpb.Value_DateTimeValue{ + DateTimeValue: &crpb.DateTime{ + Date: timestamppb.New(time.Date(2024, time.March, 31, 1, 20, 30, 1e8, time.UTC)), + Precision: crpb.DateTime_PRECISION_SECOND.Enum(), + }, + }, + }, + wantSameValue: true, + }, + { + name: "Time", + value: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 1, 20, 30, 1e8, time.UTC), Precision: model.SECOND}), + wantProto: &crpb.Value{ + Value: &crpb.Value_TimeValue{ + TimeValue: &crpb.Time{ + Date: &timeofdaypb.TimeOfDay{ + Hours: 1, + Minutes: 20, + Seconds: 30, + }, + Precision: crpb.Time_PRECISION_SECOND.Enum(), + }, + }, + }, + // Seconds are dropped due to precision. + wantDifferentValue: newOrFatal(t, Time{Date: time.Date(0, time.January, 1, 1, 20, 30, 0, time.UTC), Precision: model.SECOND}), + }, + { + name: "Interval", + value: newOrFatal(t, Interval{Low: newOrFatal(t, 1), High: newOrFatal(t, 2), LowInclusive: true, HighInclusive: true, StaticType: &types.Interval{PointType: types.Integer}}), + wantProto: &crpb.Value{ + Value: &crpb.Value_IntervalValue{ + IntervalValue: &crpb.Interval{ + Low: &crpb.Value{Value: &crpb.Value_IntegerValue{IntegerValue: 1}}, + High: &crpb.Value{Value: &crpb.Value_IntegerValue{IntegerValue: 2}}, + LowInclusive: proto.Bool(true), + HighInclusive: proto.Bool(true), + StaticType: &ctpb.IntervalType{PointType: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}}, + }, + }, + }, + wantSameValue: true, + }, + { + name: "List", + value: newOrFatal(t, List{Value: []Value{newOrFatal(t, 1), newOrFatal(t, 2)}, StaticType: &types.List{ElementType: types.Integer}}), + wantProto: &crpb.Value{ + Value: &crpb.Value_ListValue{ + ListValue: &crpb.List{ + Value: []*crpb.Value{ + &crpb.Value{Value: &crpb.Value_IntegerValue{IntegerValue: 1}}, + &crpb.Value{Value: &crpb.Value_IntegerValue{IntegerValue: 2}}, + }, + StaticType: &ctpb.ListType{ElementType: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}}, + }, + }, + }, + wantSameValue: true, + }, + { + name: "Tuple", + value: newOrFatal(t, Tuple{ + Value: map[string]Value{"Apple": newOrFatal(t, 10), "Banana": newOrFatal(t, 20)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"Apple": types.Integer, "Banana": types.Integer}}, + }), + wantProto: &crpb.Value{ + Value: &crpb.Value_TupleValue{ + TupleValue: &crpb.Tuple{ + Value: map[string]*crpb.Value{ + "Apple": &crpb.Value{Value: &crpb.Value_IntegerValue{IntegerValue: 10}}, + "Banana": &crpb.Value{Value: &crpb.Value_IntegerValue{IntegerValue: 20}}, + }, + RuntimeType: &crpb.Tuple_TupleType{TupleType: &ctpb.TupleType{ElementTypes: map[string]*ctpb.CQLType{ + "Apple": &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}, + "Banana": &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}, + }}}, + }, + }, + }, + wantSameValue: true, + }, + { + name: "CodeSystem", + value: newOrFatal(t, CodeSystem{ID: "ID", Version: "Version"}), + wantProto: &crpb.Value{ + Value: &crpb.Value_CodeSystemValue{ + CodeSystemValue: &crpb.CodeSystem{ + Id: proto.String("ID"), + Version: proto.String("Version"), + }, + }, + }, + wantSameValue: true, + }, + { + name: "ValueSet", + value: newOrFatal(t, ValueSet{ID: "ID", Version: "Version", CodeSystems: []CodeSystem{CodeSystem{ID: "CSID", Version: "CSVersion"}}}), + wantProto: &crpb.Value{ + Value: &crpb.Value_ValueSetValue{ + ValueSetValue: &crpb.ValueSet{ + Id: proto.String("ID"), + Version: proto.String("Version"), + CodeSystems: []*crpb.CodeSystem{ + &crpb.CodeSystem{ + Id: proto.String("CSID"), + Version: proto.String("CSVersion"), + }, + }, + }, + }, + }, + wantSameValue: true, + }, + { + name: "Concept", + value: newOrFatal(t, Concept{Codes: []Code{Code{System: "System", Code: "Code"}}, Display: "A disease"}), + wantProto: &crpb.Value{ + Value: &crpb.Value_ConceptValue{ + ConceptValue: &crpb.Concept{ + Codes: []*crpb.Code{ + &crpb.Code{ + System: proto.String("System"), + Code: proto.String("Code"), + Display: proto.String(""), + Version: proto.String(""), + }, + }, + Display: proto.String("A disease"), + }, + }, + }, + wantSameValue: true, + }, + { + name: "Code", + value: newOrFatal(t, Code{System: "System", Code: "Code", Version: "Version", Display: "A disease"}), + wantProto: &crpb.Value{ + Value: &crpb.Value_CodeValue{ + CodeValue: &crpb.Code{ + System: proto.String("System"), + Code: proto.String("Code"), + Version: proto.String("Version"), + Display: proto.String("A disease"), + }, + }, + }, + wantSameValue: true, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotProto, err := tc.value.Proto() + if err != nil { + t.Errorf("Proto() returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantProto, gotProto, protocmp.Transform()); diff != "" { + t.Errorf("Proto() returned unexpected diff (-want +got):\n%s", diff) + } + + gotValue, err := NewFromProto(gotProto) + if err != nil { + t.Errorf("NewFromProto() returned unexpected error: %v", err) + } + + wantValue := gotValue + if !tc.wantSameValue { + wantValue = tc.wantDifferentValue + } + if diff := cmp.Diff(wantValue, gotValue); diff != "" { + t.Errorf("NewFromProto() returned unexpected diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestProtoAndBack_Named(t *testing.T) { + value := newOrFatal(t, Named{Value: &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}, RuntimeType: &types.Named{TypeName: "FHIR.Patient"}}) + wantProto := &crpb.Value{ + Value: &crpb.Value_NamedValue{ + NamedValue: &crpb.Named{ + Value: anyProtoOrFatal(t, &r4patientpb.Patient{Id: &d4pb.Id{Value: "1"}}), + RuntimeType: &ctpb.NamedType{TypeName: proto.String("FHIR.Patient")}, + }, + }, + } + + gotProto, err := value.Proto() + if err != nil { + t.Errorf("Proto() returned unexpected error: %v", err) + } + if diff := cmp.Diff(wantProto, gotProto, protocmp.Transform()); diff != "" { + t.Errorf("Proto() returned unexpected diff (-want +got):\n%s", diff) + } + + _, err = NewFromProto(gotProto) + if err == nil { + t.Errorf("NewFromProto() succeeded, want error") + } +} + +func newOrFatal(t *testing.T, a any) Value { + t.Helper() + o, err := New(a) + if err != nil { + t.Fatalf("New(%v) returned unexpected error: %v", a, err) + } + return o +} + +func anyProtoOrFatal(t *testing.T, a proto.Message) *anypb.Any { + t.Helper() + o, err := anypb.New(a) + if err != nil { + t.Fatalf("anypb.New(%v) returned unexpected error: %v", a, err) + } + return o +} diff --git a/retriever/gcs/gcs_retriever.go b/retriever/gcs/gcs_retriever.go new file mode 100644 index 0000000..0d9b35f --- /dev/null +++ b/retriever/gcs/gcs_retriever.go @@ -0,0 +1,79 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gcsretriever is an implementation of the Retriever Interface for the +// CQL Engine that pulls bundles from gcs. +package gcsretriever + +import ( + "context" + "fmt" + "io" + + "github.com/google/cql/retriever/local" + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + "github.com/google/bulk_fhir_tools/gcs" +) + +// Retriever implements the Retriever Interface. +type Retriever struct { + resources *local.Retriever + gcsFile string + endpointURL string +} + +// New creates a new Retriever that retrieves bundles from GCS. +func New(gcsFile, endpointURL string) (*Retriever, error) { + r := &Retriever{gcsFile: gcsFile, endpointURL: endpointURL} + resources, err := loadBundle(gcsFile, endpointURL) + if err != nil { + return nil, err + } + r.resources = resources + return r, nil +} + +// Retrieve returns all FHIR resources of type fhirResourceType for the patient. +func (r *Retriever) Retrieve(ctx context.Context, fhirResourceType string) ([]*r4pb.ContainedResource, error) { + return r.resources.Retrieve(ctx, fhirResourceType) +} + +func loadBundle(gcsFile string, endpointURL string) (*local.Retriever, error) { + bucket, object, err := gcs.PathComponents(gcsFile) + if err != nil { + return nil, fmt.Errorf("could not parse GCS path %q: %w", gcsFile, err) + } + ctx := context.Background() + client, err := gcs.NewClient(ctx, bucket, endpointURL) + if err != nil { + return nil, fmt.Errorf("could not connect to a client %v", err) + } + defer client.Close() + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + rc, err := client.GetFileReader(ctx, object) + if err != nil { + return nil, fmt.Errorf("could not access reader for %s/%s: %v", bucket, object, err) + } + defer rc.Close() + + data, err := io.ReadAll(rc) + if err != nil { + return nil, fmt.Errorf("io.ReadAll() failed: %w", err) + } + + return local.NewRetrieverFromR4Bundle(data) +} diff --git a/retriever/gcs/gcs_retriever_test.go b/retriever/gcs/gcs_retriever_test.go new file mode 100644 index 0000000..96dcac3 --- /dev/null +++ b/retriever/gcs/gcs_retriever_test.go @@ -0,0 +1,84 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsretriever + +import ( + "context" + "testing" + + "github.com/google/cql/retriever/local" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + "github.com/google/bulk_fhir_tools/testhelpers" +) + +func TestGCSRetrieve(t *testing.T) { + var bucketID = "TestBucket" + var fileName = "TestFile" + var bundle = `{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Encounter", + "id": "1"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1"} + } + ] + }` + + server := testhelpers.NewGCSServer(t) + server.AddObject(bucketID, fileName, testhelpers.GCSObjectEntry{ + Data: []byte(bundle), + }) + + path := "gs://" + bucketID + "/" + fileName + gcsRetriever, err := New(path, server.URL()) + if err != nil { + t.Fatalf("failed to create GCSRetriever: %v", err) + } + + gcsResult, err := gcsRetriever.Retrieve(context.Background(), "Patient") + if err != nil { + t.Fatalf("failed to retrieve Patient from gcs retriever: %v", err) + } + + localRetriever, err := local.NewRetrieverFromR4Bundle([]byte(bundle)) + if err != nil { + t.Fatalf("failed to create localRetriever: %v", err) + } + localResult, err := localRetriever.Retrieve(context.Background(), "Patient") + if err != nil { + t.Fatalf("failed to retrieve Patient from local retriever: %v", err) + } + + if diff := cmp.Diff(gcsResult, localResult, protocmp.Transform()); diff != "" { + t.Errorf("retrieved Patient from gcs retriever diff from local retriever (-want +got):\n%s", diff) + } + +} diff --git a/retriever/local/local.go b/retriever/local/local.go new file mode 100644 index 0000000..d756548 --- /dev/null +++ b/retriever/local/local.go @@ -0,0 +1,71 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package local is an implementation of the Retriever Interface for the CQL engine. The +// implementation can be initialized from a json FHIR bundle of all the patient's FHIR Resources. +package local + +import ( + "context" + + "github.com/google/cql/internal/resourcewrapper" + "github.com/google/fhir/go/fhirversion" + "github.com/google/fhir/go/jsonformat" + + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" +) + +// Retriever implements the Retriever Interface for the CQL engine. +type Retriever struct { + resources map[string][]*r4pb.ContainedResource +} + +// NewRetrieverFromR4BundleProto initializes a local retriever from a FHIR bundle proto of all +// the patient's FHIR resources. +func NewRetrieverFromR4BundleProto(bundle *r4pb.Bundle) (*Retriever, error) { + r := &Retriever{resources: make(map[string][]*r4pb.ContainedResource)} + for _, e := range bundle.GetEntry() { + rw := resourcewrapper.New(e.GetResource()) + resourceType, err := rw.ResourceType() + if err != nil { + return nil, err + } + r.resources[resourceType] = append(r.resources[resourceType], rw.Resource) + } + + return r, nil +} + +// NewRetrieverFromR4Bundle initializes a local Retriever from a json R4 FHIR bundle of all the +// patient's FHIR Resources. +func NewRetrieverFromR4Bundle(jsonBundle []byte) (*Retriever, error) { + unmarshaller, err := jsonformat.NewUnmarshallerWithoutValidation("UTC", fhirversion.R4) + if err != nil { + return nil, err + } + containedResource, err := unmarshaller.UnmarshalR4(jsonBundle) + if err != nil { + return nil, err + } + bundle := containedResource.GetBundle() + return NewRetrieverFromR4BundleProto(bundle) +} + +// Retrieve returns all FHIR resources of type fhirResourceType for the patient. +func (r *Retriever) Retrieve(ctx context.Context, fhirResourceType string) ([]*r4pb.ContainedResource, error) { + if resources, ok := r.resources[fhirResourceType]; ok { + return resources, nil + } + return []*r4pb.ContainedResource{}, nil +} diff --git a/retriever/local/local_test.go b/retriever/local/local_test.go new file mode 100644 index 0000000..eca075b --- /dev/null +++ b/retriever/local/local_test.go @@ -0,0 +1,163 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package local + +import ( + "context" + "testing" + + "github.com/google/fhir/go/fhirversion" + "github.com/google/fhir/go/jsonformat" + r4datapb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + r4patientpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/patient_go_proto" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestRetrieverFromR4Bundle(t *testing.T) { + tests := []struct { + name string + bundle string + wantResources []*r4pb.ContainedResource + }{ + { + name: "Single Patient", + bundle: `{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Encounter", + "id": "1"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1"} + } + ] + }`, + wantResources: []*r4pb.ContainedResource{ + &r4pb.ContainedResource{ + OneofResource: &r4pb.ContainedResource_Patient{ + Patient: &r4patientpb.Patient{Id: &r4datapb.Id{Value: "1"}}, + }, + }, + }, + }, + { + name: "No Patients Returns Empty Slice", + bundle: `{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1"} + } + ] + }`, + wantResources: []*r4pb.ContainedResource{}, + }, + { + name: "Multiple Patients", + bundle: `{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "2"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1"} + } + ] + }`, + wantResources: []*r4pb.ContainedResource{ + &r4pb.ContainedResource{ + OneofResource: &r4pb.ContainedResource_Patient{ + Patient: &r4patientpb.Patient{Id: &r4datapb.Id{Value: "1"}}, + }, + }, + &r4pb.ContainedResource{ + OneofResource: &r4pb.ContainedResource_Patient{ + Patient: &r4patientpb.Patient{Id: &r4datapb.Id{Value: "2"}}, + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Test retrieve directly from bundles. + r, err := NewRetrieverFromR4Bundle([]byte(tc.bundle)) + if err != nil { + t.Fatalf("NewRetrieverFromR4Bundle() failed: %v", err) + } + gotResources, err := r.Retrieve(context.Background(), "Patient") + if err != nil { + t.Fatalf("Retrieve(ctx, \"Patient\") got err: %v", err) + } + if diff := cmp.Diff(gotResources, tc.wantResources, protocmp.Transform()); diff != "" { + t.Errorf("Retrieve(ctx, \"Patient\") => %v, want %v, (-got +want): %v", gotResources, tc.wantResources, diff) + } + + // Test retrieve from protocol buffers. + unmarshaller, err := jsonformat.NewUnmarshallerWithoutValidation("UTC", fhirversion.R4) + if err != nil { + t.Fatalf("jsonformat.NewUnmarshallerWithoutValidation() failed: %v", err) + } + cr, err := unmarshaller.UnmarshalR4([]byte(tc.bundle)) + if err != nil { + t.Fatalf("UnmarshalR4() failed: %v", err) + } + r, err = NewRetrieverFromR4BundleProto(cr.GetBundle()) + if err != nil { + t.Fatalf("NewRetrieverFromR4BundleProto() failed: %v", err) + } + gotResources, err = r.Retrieve(context.Background(), "Patient") + if err != nil { + t.Fatalf("Retrieve(ctx, \"Patient\") got err: %v", err) + } + if diff := cmp.Diff(gotResources, tc.wantResources, protocmp.Transform()); diff != "" { + t.Errorf("Retrieve(ctx, \"Patient\") => %v, want %v, (-got +want): %v", gotResources, tc.wantResources, diff) + } + }) + } +} diff --git a/retriever/retriever.go b/retriever/retriever.go new file mode 100644 index 0000000..57e8286 --- /dev/null +++ b/retriever/retriever.go @@ -0,0 +1,31 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package retriever defines the interface between the CQL engine and the data source CQL will be +// computed over. Those using the CQL engine must provide an implementation of the Retriever +// Interface. +package retriever + +import ( + "context" + + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" +) + +// Retriever defines the interface between the CQL engine and the data source CQL will be computed +// over. +type Retriever interface { + // Retrieve returns all FHIR resources of type fhirResourceType for the patient. + Retrieve(ctx context.Context, fhirResourceType string) ([]*r4pb.ContainedResource, error) +} diff --git a/terminology/local.go b/terminology/local.go new file mode 100644 index 0000000..75d1b93 --- /dev/null +++ b/terminology/local.go @@ -0,0 +1,361 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package terminology includes various TerminologyProviders for working with medical terminology. +package terminology + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + +) + +var ( + // ErrResourceNotLoaded indicates the resource requested was not loaded. + ErrResourceNotLoaded = errors.New("resource not loaded") + // ErrIncorrectResourceType indicated the Resource was not of the desired type. + ErrIncorrectResourceType = errors.New("incorrect resource type") + // ErrNotInitialized indicates the terminology provider was not initialized. + ErrNotInitialized = errors.New("terminology provider not initialized, so no terminology operations can be performed") +) + +const ( + // codeSystem is the fhir string resourceType for a CodeSystem. + codeSystem string = "CodeSystem" + // valueSet is the fhir string resourceType for a ValueSet + valueSet string = "ValueSet" +) + +// NewLocalFHIRProvider returns a new Local FHIR terminology provider initialized with the input +// directory. If multiple ValueSets in the directory have the same ID and Version, the last one seen +// by the LocalFHIR provider will be the one loaded for use. +// TODO(b/297090333): support loading only certain ValueSets into memory, and FHIR versions if needed. +func NewLocalFHIRProvider(dir string) (*LocalFHIRProvider, error) { + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + + lf := &LocalFHIRProvider{ + codeSystems: make(map[resourceKey]fhirCodeSystem), + valueSets: make(map[resourceKey]fhirValueSet), + latestCodeSystems: make(map[string]fhirCodeSystem), + latestValuesets: make(map[string]fhirValueSet), + } + + for _, file := range files { + // Skip non-JSON files, such as BUILD files, READMEs, or sub directories. + if file.IsDir() || !strings.HasSuffix(file.Name(), ".json") { + continue + } + + f, err := os.Open(filepath.Join(dir, file.Name())) + if err != nil { + return nil, err + } + fr, err := decodeFHIRResource(f) + if err != nil { + return nil, err + } + + switch fr.ResourceType { + case codeSystem: + lf.addCodeSystem(fr) + case valueSet: + lf.addValueSet(fr) + } + } + + return lf, nil +} + +// NewInMemoryFHIRProvider returns a new Local FHIR terminology provider initialized with the JSON +// resources. If multiple ValueSets in the directory have the same ID and Version, the last one seen +// by the LocalFHIR provider will be the one loaded for use. +func NewInMemoryFHIRProvider(jsons []string) (*LocalFHIRProvider, error) { + lf := &LocalFHIRProvider{ + codeSystems: make(map[resourceKey]fhirCodeSystem), + valueSets: make(map[resourceKey]fhirValueSet), + latestCodeSystems: make(map[string]fhirCodeSystem), + latestValuesets: make(map[string]fhirValueSet), + } + + for _, json := range jsons { + fr, err := decodeFHIRResource(strings.NewReader(json)) + if err != nil { + return nil, err + } + + switch fr.ResourceType { + case codeSystem: + lf.addCodeSystem(fr) + case valueSet: + lf.addValueSet(fr) + } + } + + return lf, nil +} + +func (l *LocalFHIRProvider) addCodeSystem(fr *fhirResource) { + cs := buildFHIRCodeSystem(*fr) + l.codeSystems[fr.key()] = cs + + latest, ok := l.latestCodeSystems[fr.URL] + if !ok { + l.latestCodeSystems[fr.URL] = cs + } else if fr.Version > latest.Version { + l.latestCodeSystems[fr.URL] = cs + } +} + +func (l *LocalFHIRProvider) addValueSet(fr *fhirResource) { + vs := buildFHIRValueSet(*fr) + l.valueSets[fr.key()] = vs + + latest, ok := l.latestValuesets[fr.URL] + if !ok { + l.latestValuesets[fr.URL] = vs + } else if fr.Version > latest.Version { + l.latestValuesets[fr.URL] = vs + } +} + +// LocalFHIRProvider is a terminology provider that uses local ValueSet information on the file system. +type LocalFHIRProvider struct { + codeSystems map[resourceKey]fhirCodeSystem + valueSets map[resourceKey]fhirValueSet + latestCodeSystems map[string]fhirCodeSystem + latestValuesets map[string]fhirValueSet +} + +type resourceKey struct { + URL string + Version string +} + +func (l *LocalFHIRProvider) findCodeSystem(codeSystemURL, codeSystemVersion string) (fhirCodeSystem, error) { + var vs fhirCodeSystem + var ok bool + if codeSystemVersion == "" { + vs, ok = l.latestCodeSystems[codeSystemURL] + } else { + vs, ok = l.codeSystems[resourceKey{codeSystemURL, codeSystemVersion}] + } + + if !ok { + return fhirCodeSystem{}, fmt.Errorf("could not find CodeSystem{%s, %s} %w", codeSystemURL, codeSystemVersion, ErrResourceNotLoaded) + } + return vs, nil +} + +func (l *LocalFHIRProvider) findValueSet(valueSetURL, valueSetVersion string) (fhirValueSet, error) { + var vs fhirValueSet + var ok bool + if valueSetVersion == "" { + vs, ok = l.latestValuesets[valueSetURL] + } else { + vs, ok = l.valueSets[resourceKey{valueSetURL, valueSetVersion}] + } + + if !ok { + return fhirValueSet{}, fmt.Errorf("could not find ValueSet{%s, %s} %w", valueSetURL, valueSetVersion, ErrResourceNotLoaded) + } + return vs, nil +} + +// AnyInValueSet returns true if any code is contained within the specified Valueset, otherwise +// false. Code.Display is ignored when making this determination. If the valueSetVersion is an empty +// string, this will use the 'latest' value set version based on a simple version string comparison. +// If the resource type of the found resource does not line up return a error +// https://cql.hl7.org/09-b-cqlreference.html#in-valueset +func (l *LocalFHIRProvider) AnyInValueSet(codes []Code, valuesetURL, valuesetVersion string) (bool, error) { + if l == nil { + return false, ErrNotInitialized + } + + r, err := l.findValueSet(valuesetURL, valuesetVersion) + if err != nil { + // The desired ValueSet didn't exist but found a CodeSystem with this key. + if _, err := l.findCodeSystem(valuesetURL, valuesetVersion); err == nil { + return false, fmt.Errorf("could not find ValueSet{%s, %s} found CodeSystem instead. %w", valuesetURL, valuesetVersion, ErrIncorrectResourceType) + } + return false, err + } + + for _, c := range codes { + foundCode := r.code(c.key()) + if foundCode != nil { + return true, nil + } + } + + return false, nil +} + +// AnyInCodeSystem returns true if any code is contained within the specified CodeSystem, otherwise +// false. Code.Display is ignored when making this determination. If the CodeSystemVersion is an +// empty string, this will use the 'latest' resource version based on a simple version string +// comparison. If the resource type of the found resource does not line up return a error +// https://cql.hl7.org/09-b-cqlreference.html#in-code-system +func (l *LocalFHIRProvider) AnyInCodeSystem(codes []Code, codeSystemURL, codeSystemVersion string) (bool, error) { + if l == nil { + return false, ErrNotInitialized + } + + r, err := l.findCodeSystem(codeSystemURL, codeSystemVersion) + if err != nil { + // The desired CodeSystem didn't exist but found a ValueSet with this key. + if _, err := l.findValueSet(codeSystemURL, codeSystemVersion); err == nil { + return false, fmt.Errorf("could not find CodeSystem{%s, %s} found ValueSet instead. %w", codeSystemURL, codeSystemVersion, ErrIncorrectResourceType) + } + return false, err + } + + for _, c := range codes { + // Retrieve the code from the FHIR resource. + if code := r.code(c.key()); code != nil { + return true, nil + } + } + + return false, nil +} + +// ExpandValueSet returns the expanded codes for the provided ValueSet id and version. If the +// valueSetVersion is an empty string, this will use the 'latest' value set version based on a +// simple version string comparison. +func (l *LocalFHIRProvider) ExpandValueSet(valueSetURL, valueSetVersion string) ([]*Code, error) { + if l == nil { + return nil, ErrNotInitialized + } + + r, err := l.findValueSet(valueSetURL, valueSetVersion) + if err != nil { + return nil, err + } + + return r.codes(), nil +} + +// A base fhirResource that is used to store top level data from parsed json resources. This struct +// exists to perform initial parsing of json resources so we can figure out the type of the resource +// (CodeSystem or ValueSet). +type fhirResource struct { + ResourceType string `json:"resourceType"` + URL string `json:"url"` + Version string `json:"version"` + // Only one of the following two fields should be populated + Concept []*Code `json:"concept"` + Expansion *expansion `json:"expansion"` +} + +func (f *fhirResource) key() resourceKey { + return resourceKey{f.URL, f.Version} +} + +func decodeFHIRResource(i io.Reader) (*fhirResource, error) { + r := fhirResource{} + if err := json.NewDecoder(i).Decode(&r); err != nil { + return nil, err + } + return &r, nil +} + +type fhirValueSet struct { + ResourceType string `json:"resourceType"` + URL string `json:"url"` + Version string `json:"version"` + CodeMap map[codeKey]*Code + Expansion expansion `json:"expansion"` +} + +func (f *fhirValueSet) code(key codeKey) *Code { + return f.CodeMap[key] +} + +func (f *fhirValueSet) key() resourceKey { + return resourceKey{f.URL, f.Version} +} + +func (f *fhirValueSet) codes() []*Code { + return f.Expansion.Codes +} + +type expansion struct { + Codes []*Code `json:"contains"` +} + +type fhirCodeSystem struct { + ResourceType string `json:"resourceType"` + URL string `json:"url"` + Version string `json:"version"` + CodeMap map[codeKey]*Code + Concept []*Code `json:"concept"` +} + +func (f *fhirCodeSystem) key() resourceKey { + return resourceKey{f.URL, f.Version} +} + +func (f *fhirCodeSystem) codes() []*Code { + return f.Concept +} + +// Retrieve a Code from the CodeSystem +func (f *fhirCodeSystem) code(key codeKey) *Code { + if key.System != f.URL { + return nil + } + // CodeSystems don't have System set on their codes. + key.System = "" + return f.CodeMap[key] +} + +func buildFHIRValueSet(fr fhirResource) fhirValueSet { + vs := fhirValueSet{ + ResourceType: fr.ResourceType, + URL: fr.URL, + Version: fr.Version, + CodeMap: make(map[codeKey]*Code), + } + if fr.Expansion != nil { + vs.Expansion = *fr.Expansion + } + + for _, c := range vs.Expansion.Codes { + vs.CodeMap[c.key()] = c + } + return vs +} + +func buildFHIRCodeSystem(fr fhirResource) fhirCodeSystem { + cs := fhirCodeSystem{ + ResourceType: fr.ResourceType, + URL: fr.URL, + Version: fr.Version, + Concept: fr.Concept, + CodeMap: make(map[codeKey]*Code), + } + + for _, c := range cs.Concept { + cs.CodeMap[c.key()] = c + } + return cs +} diff --git a/terminology/local_test.go b/terminology/local_test.go new file mode 100644 index 0000000..092e79a --- /dev/null +++ b/terminology/local_test.go @@ -0,0 +1,499 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package terminology_test + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "github.com/google/cql/internal/testhelpers" + "github.com/google/cql/terminology" + "github.com/google/go-cmp/cmp" +) + +var testJSONResources = []string{` + { + "resourceType": "ValueSet", + "id": "https://test/file1", + "url": "https://test/file1", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "system1", "code": "1" }, + { "system": "system1", "code": "2" }, + { "system": "system2", "code": "3" } + ] + } + } + `, + ` + { + "resourceType": "ValueSet", + "id": "https://test/file2", + "url": "https://test/file2", + "version": "2.0.0", + "expansion": { + "contains": [ + { "system": "system3", "code": "4", "display": "four" }, + { "system": "system3", "code": "5" }, + { "system": "system4", "code": "6" } + ] + } + } + `, + // Should override version 1.0.0 + ` + { + "resourceType": "ValueSet", + "id": "https://test/file1", + "url": "https://test/file1", + "version": "2.0.0", + "expansion": { + "contains": [ + { "system": "system1", "code": "1v2" }, + { "system": "system1", "code": "2v2" }, + { "system": "system2", "code": "3v2" } + ] + } + } + `, + ` + { + "resourceType": "CodeSystem", + "id": "https://test/file3", + "url": "https://test/file3", + "version": "1.0.0", + "concept": [ + { + "code": "sn", + "definition": "The sniffles" + }, + { + "code": "sr", + "display": "SRT", + "definition": "A sore throat" + } + ] + } + `, + // Should override version 1.0.0 + ` + { + "resourceType": "CodeSystem", + "id": "https://test/file3", + "url": "https://test/file3", + "version": "3.0.0", + "concept": [ + { + "code": "snfl", + "definition": "The sniffles" + }, + { + "code": "sre-thrt", + "display": "SRT", + "definition": "A sore throat" + } + ] + } + `, + // The following ValueSet and CodeSystem have the same URL and version. + ` + { + "resourceType": "ValueSet", + "id": "https://test/file4", + "url": "https://test/file4", + "version": "4.0.0", + "expansion": { + "contains": [ + { "system": "system1", "code": "1" }, + { "system": "system1", "code": "2" }, + { "system": "system2", "code": "3" } + ] + } + } + `, + ` + { + "resourceType": "CodeSystem", + "id": "https://test/file4", + "url": "https://test/file4", + "version": "4.0.0", + "concept": [ + { + "code": "snfl", + "definition": "The sniffles" + }, + { "code": "1" }, + { "code": "2" }, + { "code": "3" } + ] + } + `, + // empty valueset + ` + { + "resourceType": "ValueSet", + "id": "https://test/emptyVS", + "url": "https://test/emptyVS", + "version": "1.0.0" + } + `, + // empty codesystem + ` + { + "resourceType": "CodeSystem", + "id": "https://test/emptyCS", + "url": "https://test/emptyCS", + "version": "1.0.0" + } + `, +} + +// writeTestResources writes the standard test FHIR resources to disk, and returns the temporary dir +// where they've been written. +func writeTestResources(t *testing.T) string { + dir := testhelpers.WriteJSONs(t, testJSONResources) + + // Write additional file to ensure the local provider skips it. + readme := []byte("This directory contains ValueSet JSON files.\n") + if err := os.WriteFile(filepath.Join(dir, "README.md"), readme, 0644); err != nil { + t.Fatalf("Unable to write test README: %v", err) + } + return dir +} + +func testExpandValueSet(t *testing.T, lf *terminology.LocalFHIRProvider) { + cases := []struct { + name string + valueSetURL string + valueSetVersion string + wantCodes []*terminology.Code + wantErr error + }{ + { + name: "ValueSet https://test/file2", + valueSetURL: "https://test/file2", + valueSetVersion: "2.0.0", + wantCodes: []*terminology.Code{ + {System: "system3", Code: "4", Display: "four"}, + {System: "system3", Code: "5"}, + {System: "system4", Code: "6"}, + }, + }, + { + name: "ValueSet https://test/file1", + valueSetURL: "https://test/file1", + valueSetVersion: "1.0.0", + wantCodes: []*terminology.Code{ + {System: "system1", Code: "1"}, + {System: "system1", Code: "2"}, + {System: "system2", Code: "3"}, + }, + }, + { + name: "ValueSet https://test/file1", + valueSetURL: "https://test/file1", + valueSetVersion: "2.0.0", + wantCodes: []*terminology.Code{ + {System: "system1", Code: "1v2"}, + {System: "system1", Code: "2v2"}, + {System: "system2", Code: "3v2"}, + }, + }, + { + // Valueset no version specified + name: "ValueSet https://test/file1", + valueSetURL: "https://test/file1", + wantCodes: []*terminology.Code{ + {System: "system1", Code: "1v2"}, + {System: "system1", Code: "2v2"}, + {System: "system2", Code: "3v2"}, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + codes, err := lf.ExpandValueSet(tc.valueSetURL, tc.valueSetVersion) + if !errors.Is(err, tc.wantErr) { + t.Errorf("Expand(%v, %v) unexpected error. got: %v, want: %v", tc.valueSetURL, tc.valueSetVersion, err, tc.wantErr) + } + if diff := cmp.Diff(tc.wantCodes, codes); diff != "" { + t.Errorf("Expand(%v, %v) returned diff (-want +got):\n%s", tc.valueSetURL, tc.valueSetVersion, diff) + } + }) + } +} + +func TestLocalFHIR_Expand(t *testing.T) { + testdir := writeTestResources(t) + + lf, err := terminology.NewLocalFHIRProvider(testdir) + if err != nil { + t.Fatalf("NewLocalFHIRProvider(%v) unexpected error: %v", testdir, err) + } + + testExpandValueSet(t, lf) + +} + +func TestInMemoryFHIR_Expand(t *testing.T) { + imf, err := terminology.NewInMemoryFHIRProvider(testJSONResources) + if err != nil { + t.Fatalf("NewInMemoryFHIRProvider(%v) unexpected error: %v", testJSONResources, err) + } + + testExpandValueSet(t, imf) +} + +func testResourceInCodeSystem(t *testing.T, lf *terminology.LocalFHIRProvider) { + cases := []struct { + name string + URL string + Version string + Codes []terminology.Code + wantIn bool + }{ + { + name: "Code in CodeSystem https://test/file3", + URL: "https://test/file3", + Version: "3.0.0", + Codes: []terminology.Code{{Code: "snfl", System: "https://test/file3"}}, + wantIn: true, + }, + { + name: "One Code in CodeSystem https://test/file3", + URL: "https://test/file3", + Version: "3.0.0", + Codes: []terminology.Code{ + {Code: "asthma", System: "https://test/file3"}, + {Code: "snfl", System: "https://test/file3"}, + }, + wantIn: true, + }, + { + name: "Code not in CodeSystem https://test/file3", + URL: "https://test/file3", + Version: "3.0.0", + Codes: []terminology.Code{{Code: "asthma", System: "https://test/file3"}}, + wantIn: false, + }, + { + name: "Code in CodeSystem https://test/file3 latest", + URL: "https://test/file3", + Codes: []terminology.Code{{Code: "snfl", System: "https://test/file3"}}, + wantIn: true, + }, + { + name: "Code not in CodeSystem https://test/file3 latest", + URL: "https://test/file3", + Codes: []terminology.Code{{Code: "sn", System: "https://test/file3"}}, + wantIn: false, + }, + { + name: "Code in CodeSystem https://test/file4 when ValueSet with same key exists", + URL: "https://test/file4", + Version: "4.0.0", + Codes: []terminology.Code{{Code: "1", System: "https://test/file4"}}, + wantIn: true, + }, + { + name: "Code not in Empty CodeSystem https://test/emptyCS", + URL: "https://test/emptyCS", + Codes: []terminology.Code{{Code: "1", System: "https://test/emptyCS"}}, + wantIn: false, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + in, err := lf.AnyInCodeSystem(tc.Codes, tc.URL, tc.Version) + if err != nil { + t.Errorf("In(%v, %v, %v) returned unexpected error: %v", tc.Codes, tc.URL, tc.Version, err) + } + if !cmp.Equal(tc.wantIn, in) { + t.Errorf("In(%v, %v, %v) incorrect. got: %v, want: %v ", tc.Codes, tc.URL, tc.Version, in, tc.wantIn) + } + }) + } +} + +func testResourceInValueSet(t *testing.T, lf *terminology.LocalFHIRProvider) { + cases := []struct { + name string + URL string + Version string + Codes []terminology.Code + wantIn bool + }{ + { + name: "Code not in ValueSet https://test/file1", + URL: "https://test/file1", + Version: "1.0.0", + Codes: []terminology.Code{{System: "system3", Code: "4"}}, + wantIn: false, + }, + { + name: "Code in ValueSet https://test/file1", + URL: "https://test/file1", + Version: "1.0.0", + Codes: []terminology.Code{{System: "system2", Code: "3"}}, + wantIn: true, + }, + { + name: "One Code in ValueSet https://test/file1", + URL: "https://test/file1", + Version: "1.0.0", + Codes: []terminology.Code{ + {System: "system2", Code: "3"}, + {System: "system3", Code: "4"}, + }, + wantIn: true, + }, + { + name: "Code not in ValueSet https://test/file1 v2", + URL: "https://test/file1", + Version: "2.0.0", + Codes: []terminology.Code{{System: "system2", Code: "3"}}, + wantIn: false, + }, + { + name: "Code not in ValueSet https://test/file1 latest", + URL: "https://test/file1", + Codes: []terminology.Code{{System: "system2", Code: "3"}}, + wantIn: false, + }, + { + name: "Code in ValueSet https://test/file1 latest", + URL: "https://test/file1", + Codes: []terminology.Code{{System: "system2", Code: "3v2"}}, + wantIn: true, + }, + { + name: "Code in ValueSet https://test/file4 when CodeSystem with same key exists", + URL: "https://test/file4", + Version: "4.0.0", + Codes: []terminology.Code{{System: "system1", Code: "1"}}, + wantIn: true, + }, + { + name: "Code not in Empty ValueSet https://test/emptyVS", + URL: "https://test/emptyVS", + Codes: []terminology.Code{{System: "system1", Code: "1"}}, + wantIn: false, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + in, err := lf.AnyInValueSet(tc.Codes, tc.URL, tc.Version) + if err != nil { + t.Errorf("In(%v, %v, %v) unexpected error. got: %v", tc.Codes, tc.URL, tc.Version, err) + } + if !cmp.Equal(tc.wantIn, in) { + t.Errorf("In(%v, %v, %v) incorrect. got: %v, want: %v ", tc.Codes, tc.URL, tc.Version, in, tc.wantIn) + } + }) + } +} + +func TestLocalFHIR_In(t *testing.T) { + testdir := writeTestResources(t) + lf, err := terminology.NewLocalFHIRProvider(testdir) + if err != nil { + t.Fatalf("NewLocalFHIRProvider(%v) unexpected error: %v", testdir, err) + } + testResourceInCodeSystem(t, lf) + testResourceInValueSet(t, lf) +} + +func TestInMemoryFHIR_In(t *testing.T) { + imf, err := terminology.NewInMemoryFHIRProvider(testJSONResources) + if err != nil { + t.Fatalf("NewInMemoryFHIRProvider(%v) unexpected error: %v", testJSONResources, err) + } + + testResourceInCodeSystem(t, imf) + testResourceInValueSet(t, imf) +} + +func testResourceInError(t *testing.T, lf *terminology.LocalFHIRProvider) { + cases := []struct { + name string + ResourceType string + URL string + Version string + Code terminology.Code + wantErr error + }{ + { + name: "ErrResourceNotLoaded_MissingURL", + URL: "https://test/file20", + Version: "1.0.0", + wantErr: terminology.ErrResourceNotLoaded, + }, + { + name: "ErrResourceNotLoaded_MissingVersion", + URL: "https://test/file1", + Version: "4.0.0", + wantErr: terminology.ErrResourceNotLoaded, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := lf.AnyInValueSet([]terminology.Code{tc.Code}, tc.URL, tc.Version) + if !errors.Is(err, tc.wantErr) { + t.Errorf("InValueSet(%v, %v) unexpected error. got: %v, want: %v", tc.URL, tc.Version, err, tc.wantErr) + } + + _, err = lf.AnyInCodeSystem([]terminology.Code{tc.Code}, tc.URL, tc.Version) + if !errors.Is(err, tc.wantErr) { + t.Errorf("InCodeSystem(%v, %v) unexpected error. got: %v, want: %v", tc.URL, tc.Version, err, tc.wantErr) + } + }) + } +} + +func TestLocalFHIR_InError(t *testing.T) { + testdir := writeTestResources(t) + lf, err := terminology.NewLocalFHIRProvider(testdir) + if err != nil { + t.Fatalf("NewLocalFHIRProvider(%v) unexpected error: %v", testdir, err) + } + testResourceInError(t, lf) +} + +func TestInMemoryFHIR_InError(t *testing.T) { + imf, err := terminology.NewInMemoryFHIRProvider(testJSONResources) + if err != nil { + t.Fatalf("NewInMemoryFHIRProvider(%v) unexpected error: %v", testJSONResources, err) + } + + testResourceInError(t, imf) +} + +func TestLocalFHIR_NotInitialized(t *testing.T) { + var tp *terminology.LocalFHIRProvider + + if _, err := tp.AnyInCodeSystem([]terminology.Code{{"", "", ""}}, "", ""); !errors.Is(err, terminology.ErrNotInitialized) { + t.Errorf("In() on nil provider got unexpected error. got: %v, want: %v", err, terminology.ErrNotInitialized) + } + + if _, err := tp.AnyInValueSet([]terminology.Code{{"", "", ""}}, "", ""); !errors.Is(err, terminology.ErrNotInitialized) { + t.Errorf("In() on nil provider got unexpected error. got: %v, want: %v", err, terminology.ErrNotInitialized) + } + if _, err := tp.ExpandValueSet("", ""); !errors.Is(err, terminology.ErrNotInitialized) { + t.Errorf("Expand() on nil provider got unexpected error. got: %v, want: %v", err, terminology.ErrNotInitialized) + } +} diff --git a/terminology/model.go b/terminology/model.go new file mode 100644 index 0000000..732c046 --- /dev/null +++ b/terminology/model.go @@ -0,0 +1,38 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package terminology + +// Code represents a CQL or ELM "Code", which is equivalent to a FHIR Coding. +type Code struct { + // Code is the code value. This is repetitive, but matches the ELM/CQL and FHIR naming. + Code string `json:"code"` + // System is the coding system id. + System string `json:"system"` + // Display is an optional display string that represents this code. + Display string `json:"display"` +} + +// key returns the codingKey that uniquely identifies this Code. +func (c *Code) key() codeKey { + return codeKey{Value: c.Code, System: c.System} +} + +// codeKey contains code value and coding system information that uniquely identifies a Code. +type codeKey struct { + // Value is the code value of this code. + Value string + // System is the coding system id. + System string +} diff --git a/terminology/provider.go b/terminology/provider.go new file mode 100644 index 0000000..2ea21ef --- /dev/null +++ b/terminology/provider.go @@ -0,0 +1,26 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package terminology + +// Provider contains standard APIs to work with healthcare terminologies. +type Provider interface { + // In for CodeSystem and ValueSet returns true if any Code in a list is contained within the + // specified resource, otherwise false. + // Code.Display should be ignored when making this determination. + AnyInCodeSystem(c []Code, codeSystemURL, codeSystemVersion string) (bool, error) + AnyInValueSet(c []Code, valueSetURL, valueSetVersion string) (bool, error) + // ExpandValueSet expands a ValueSet and returns all codes in that resource. + ExpandValueSet(valueSetURL, valueSetVersion string) ([]*Code, error) +} diff --git a/tests/enginetests/README.md b/tests/enginetests/README.md new file mode 100644 index 0000000..586aa59 --- /dev/null +++ b/tests/enginetests/README.md @@ -0,0 +1,11 @@ +# Engine Tests + +Engine Tests holds most of the "unit tests" for the CQL Engine parser and interpreter. The problem with writing typical unit tests for the parser (CQL --> model) and interpreter (model --> result) is the model input to the interpreter tests. When writing hundreds of interpreter unit tests it is easy for developers to miss bugs by writing an invalid model input or a model input that does not match what our parser produces. Instead we decided to have integration tests (CQL --> model --> result) as our primary suite of unit tests. This way the unit tests use a model that our parser actually produces, even as we change and update the parser. + +Although the engine tests are end to end, they should target as specific a behaviour as possible just like a typical unit test. For testing large, realistic CQL expressions use the large tests. We still have some parser (CQL --> model) and interpreter (model --> result) unit tests. For example, if a developer wants to tests a model input to the interpreter that our parser will not produce they should write a typical interpreter (model --> result) unit test. But the majority of interpreter test should be written as engine tests to avoid incorrect or out of date models. + +The cons of this approach are: + +* Typical Golang unit tests are stored in the file next to the code they are testing (foo.go, foo_test.go). This approach breaks that pattern. + +* It encourages coupling of our parser to our interpreter, and may mean our interpreter less robust to inputs from other parsers. For now we are treating our interpreter as an internal detail not meant to be called directly. diff --git a/tests/enginetests/benchmark_test.go b/tests/enginetests/benchmark_test.go new file mode 100644 index 0000000..9107f66 --- /dev/null +++ b/tests/enginetests/benchmark_test.go @@ -0,0 +1,67 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/parser" + "github.com/google/cql/result" +) + +// forceBenchResult ensures the results are used so the compiler does not optimize away the +// EvalLibraries function. +var forceBenchResult result.Libraries + +func BenchmarkInterpreter(b *testing.B) { + benchmarks := []struct { + name string + cql string + }{ + { + name: "Addition", + cql: "1 + 2", + }, + } + + for _, bc := range benchmarks { + p := newFHIRParser(b) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(b, bc.cql), parser.Config{}) + if err != nil { + b.Fatalf("Parse Libraries returned unexpected error: %v", err) + } + + config := interpreter.Config{ + DataModels: p.DataModel(), + Retriever: BuildRetriever(b), + Terminology: buildTerminologyProvider(b), + EvaluationTimestamp: defaultEvalTimestamp, + ReturnPrivateDefs: true} + + b.Run(bc.name, func(b *testing.B) { + var force result.Libraries + for n := 0; n < b.N; n++ { + force, err = interpreter.Eval(context.Background(), parsedLibs, config) + if err != nil { + b.Fatalf("Eval returned unexpected error: %v", err) + } + } + forceBenchResult = force + b.ReportAllocs() + }) + } +} diff --git a/tests/enginetests/conditional_test.go b/tests/enginetests/conditional_test.go new file mode 100644 index 0000000..b383104 --- /dev/null +++ b/tests/enginetests/conditional_test.go @@ -0,0 +1,261 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestIf(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "if 1 = 1 then 2 else 3", + cql: "if 1 = 1 then 2 else 3", + wantModel: &model.IfThenElse{ + Condition: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + Then: model.NewLiteral("2", types.Integer), + Else: model.NewLiteral("3", types.Integer), + Expression: model.ResultType(types.Integer), + }, + wantResult: newOrFatal(t, 2), + }, + { + name: "if 1 = 2 then 2 else 3", + cql: "if 1 = 2 then 2 else 3", + wantResult: newOrFatal(t, 3), + }, + { + name: "if null then 2 else 3", + cql: "if null then 2 else 3", + wantResult: newOrFatal(t, 3), + }, + // Result expression type inference tests. + { + // If the else case can be implicitly converted to the then case it should be wrapped + // in a conversion operator. + name: "if 1 = 2 then 2.0 else 3", + cql: "if 1 = 2 then 2.0 else 3", + wantModel: &model.IfThenElse{ + Condition: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + Then: model.NewLiteral("2.0", types.Decimal), + Else: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("3", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + }, + Expression: model.ResultType(types.Decimal), + }, + wantResult: newOrFatal(t, 3.0), + }, + { + name: "If returns choice type", + cql: "if 1 = 2 then 2 else 'hi there!'", + wantModel: &model.IfThenElse{ + Condition: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + Then: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("2", types.Integer), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + }, + Else: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("hi there!", types.String), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + }, + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}), + }, + wantResult: newOrFatal(t, "hi there!"), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestCase(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "No comparand returns then", + cql: dedent.Dedent(` + case + when false then 9 + when true then 4 + else 5 + end`), + wantModel: &model.Case{ + Comparand: nil, + CaseItem: []*model.CaseItem{ + &model.CaseItem{ + When: model.NewLiteral("false", types.Boolean), + Then: model.NewLiteral("9", types.Integer), + }, + &model.CaseItem{ + When: model.NewLiteral("true", types.Boolean), + Then: model.NewLiteral("4", types.Integer), + }, + }, + Else: model.NewLiteral("5", types.Integer), + Expression: model.ResultType(types.Integer), + }, + wantResult: newOrFatal(t, 4), + }, + { + name: "No comparand, returns else", + cql: dedent.Dedent(` + case + when false then 9 + when false then 4 + else 5 + end`), + wantResult: newOrFatal(t, 5), + }, + { + name: "No comparand, case with null is skipped", + cql: dedent.Dedent(` + case + when null then 9 + when false then 4 + else 5 + end`), + wantResult: newOrFatal(t, 5), + }, + { + name: "Comparand, returns then case", + cql: dedent.Dedent(` + case 4 + when 5 then 9 + when 4 then 6 + else 7 + end`), + wantResult: newOrFatal(t, 6), + }, + { + name: "Comparand, no match returns else", + cql: dedent.Dedent(` + case 4 + when 5 then 9 + when 3 then 6 + else 7 + end`), + wantResult: newOrFatal(t, 7), + }, + { + name: "Comparand, null case skipped", + cql: dedent.Dedent(` + case 5 + when null then 6 + when 5 then 9 + else 7 + end`), + wantResult: newOrFatal(t, 9), + }, + { + name: "Null comparand, returns else", + cql: dedent.Dedent(` + case null + when null then 6 + when 5 then 9 + else 7 + end`), + wantResult: newOrFatal(t, 7), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/expression_test.go b/tests/enginetests/expression_test.go new file mode 100644 index 0000000..791608d --- /dev/null +++ b/tests/enginetests/expression_test.go @@ -0,0 +1,391 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "fmt" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + d4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestExpressions(t *testing.T) { + // TestExpressions suite is for miscellaneous expressions that aren't system operators and don't + // have enough test cases to warrant their own test suite. + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // Message + { + name: "Message returns source value", + cql: "Message(1.2, true, 'Code 100', 'Message', 'Test Message')", + wantModel: &model.Message{ + Expression: model.ResultType(types.Decimal), + Source: model.NewLiteral("1.2", types.Decimal), + Condition: model.NewLiteral("true", types.Boolean), + Code: model.NewLiteral("Code 100", types.String), + Severity: model.NewLiteral("Message", types.String), + Message: model.NewLiteral("Test Message", types.String), + }, + wantResult: newOrFatal(t, 1.2), + }, + { + name: "Message returns source false condition", + cql: "Message(1.2, false, 'Code 100', 'Severity', 'Test Message')", + wantResult: newOrFatal(t, 1.2), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestRetrieves(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Retrieve multiple FHIR resources", + cql: "define TESTRESULT: [Observation]", + wantModel: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Observation", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Observation", + CodeProperty: "code", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: RetrieveFHIRResource(t, "Observation", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Observation"}}), + newOrFatal(t, result.Named{Value: RetrieveFHIRResource(t, "Observation", "2"), RuntimeType: &types.Named{TypeName: "FHIR.Observation"}}), + newOrFatal(t, result.Named{Value: RetrieveFHIRResource(t, "Observation", "3"), RuntimeType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}, + }), + }, + { + name: "Retrieve filtered by valueset", + cql: dedent.Dedent(` + valueset GlucoseVS: 'https://example.com/vs/glucose' + define TESTRESULT: [Observation: GlucoseVS]`), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: RetrieveFHIRResource(t, "Observation", "2"), RuntimeType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}, + }), + }, + { + name: "Retrieve returns empty list", + cql: "define TESTRESULT: [Binary]", + wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Binary"}}}), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := dedent.Dedent(fmt.Sprintf(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + %v`, tc.cql)) + + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestLocalReferences(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Expression Ref", + cql: dedent.Dedent(` + define Foo: 4 + define TESTRESULT: Foo`), + wantModel: &model.ExpressionRef{ + Name: "Foo", + Expression: model.ResultType(types.Integer), + }, + wantResult: newOrFatal(t, 4), + }, + { + name: "Property on Expression Ref", + cql: dedent.Dedent(` + define Foo: [Patient] P + define TESTRESULT: Foo.active`), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, result.Named{Value: &d4pb.Boolean{Value: true}, RuntimeType: &types.Named{TypeName: "FHIR.boolean"}})}, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.boolean"}}}), + }, + { + name: "Parameter Ref", + cql: dedent.Dedent(` + parameter Foo default 4 + define TESTRESULT: Foo`), + wantResult: newOrFatal(t, 4), + }, + { + name: "Property on Alias Ref", + cql: "define TESTRESULT: [Patient] P return P.active", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, result.Named{Value: &d4pb.Boolean{Value: true}, RuntimeType: &types.Named{TypeName: "FHIR.boolean"}})}, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.boolean"}}}), + }, + { + name: "Proto converts to date for or less operator", + cql: dedent.Dedent(` + include FHIRHelpers version '4.0.1' called FHIRHelpers + context Patient + define TESTRESULT: Patient.birthDate 1 year or less on or before end of Interval[@2019, @2022]`), + wantResult: newOrFatal(t, false), + }, + { + name: "ValueSet Ref", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' version '1.0' + define TESTRESULT: VS`), + wantResult: newOrFatal(t, result.ValueSet{ID: "https://example.com/vs/glucose", Version: "1.0"}), + }, + { + name: "CodeSystem Ref", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: CS`), + wantResult: newOrFatal(t, result.CodeSystem{ID: "https://example.com/cs/diagnosis", Version: "1.0"}), + }, + { + name: "Code Ref", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' + code C: '1234' from CS display 'Display' + define TESTRESULT: C`), + wantResult: newOrFatal(t, result.Code{Code: "1234", System: "https://example.com/cs/diagnosis", Display: "Display"}), + }, + { + name: "Concept Ref", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' + code C: '1234' from CS + concept Foo: {C} display 'Concept Display' + define TESTRESULT: Foo`), + wantResult: newOrFatal(t, result.Concept{ + Codes: []result.Code{result.Code{Code: "1234", System: "https://example.com/cs/diagnosis"}}, + Display: "Concept Display", + }), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := dedent.Dedent(fmt.Sprintf(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + %v`, tc.cql)) + + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestGlobalReferences(t *testing.T) { + tests := []struct { + name string + cqlLibs []string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Global Expression Ref", + cqlLibs: []string{ + dedent.Dedent(` + library CQL_Helpers_Library version '1' + define Foo: 4 + `), + dedent.Dedent(` + library TESTLIB version '1.0.0' + include CQL_Helpers_Library version '1' called helpers + define TESTRESULT: helpers.Foo`), + }, + wantModel: &model.ExpressionRef{ + LibraryName: "helpers", + Name: "Foo", + Expression: model.ResultType(types.Integer), + }, + wantResult: newOrFatal(t, 4), + }, + { + name: "Global Parameter Ref", + cqlLibs: []string{ + dedent.Dedent(` + library CQL_Helpers_Library version '1' + parameter Foo default 4 + `), + dedent.Dedent(` + library TESTLIB version '1.0.0' + include CQL_Helpers_Library version '1' called helpers + define TESTRESULT: helpers.Foo`), + }, + wantResult: newOrFatal(t, 4), + }, + { + name: "Global ValueSet Ref", + cqlLibs: []string{ + dedent.Dedent(` + library CQL_Helpers_Library version '1' + valueset Foo: 'https://example.com/cs/diagnosis' version '1.0' + `), + dedent.Dedent(` + library TESTLIB version '1.0.0' + include CQL_Helpers_Library version '1' called helpers + define TESTRESULT: helpers.Foo`), + }, + wantResult: newOrFatal(t, result.ValueSet{ID: "https://example.com/cs/diagnosis", Version: "1.0"}), + }, + { + name: "Global CodeSystem Ref", + cqlLibs: []string{ + dedent.Dedent(` + library CQL_Helpers_Library version '1' + codesystem Foo: 'https://example.com/cs/diagnosis' version '1.0' + `), + dedent.Dedent(` + library TESTLIB version '1.0.0' + include CQL_Helpers_Library version '1' called helpers + define TESTRESULT: helpers.Foo`), + }, + wantResult: newOrFatal(t, result.CodeSystem{ID: "https://example.com/cs/diagnosis", Version: "1.0"}), + }, + { + name: "Global Code Ref", + cqlLibs: []string{ + dedent.Dedent(` + library CQL_Helpers_Library version '1' + codesystem CS: 'https://example.com/cs/diagnosis' + code Foo: '1234' from CS + `), + dedent.Dedent(` + library TESTLIB version '1.0.0' + include CQL_Helpers_Library version '1' called helpers + define TESTRESULT: helpers.Foo`), + }, + wantResult: newOrFatal(t, result.Code{Code: "1234", System: "https://example.com/cs/diagnosis"}), + }, + { + name: "Global Concept Ref", + cqlLibs: []string{ + dedent.Dedent(` + library CQL_Helpers_Library version '1' + codesystem CS: 'https://example.com/cs/diagnosis' + code C: '1234' from CS + concept Foo: {C} display 'Concept Display' + `), + dedent.Dedent(` + library TESTLIB version '1.0.0' + include CQL_Helpers_Library version '1' called helpers + define TESTRESULT: helpers.Foo`), + }, + wantResult: newOrFatal(t, result.Concept{ + Codes: []result.Code{result.Code{Code: "1234", System: "https://example.com/cs/diagnosis"}}, + Display: "Concept Display", + }), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), tc.cqlLibs, parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} diff --git a/tests/enginetests/function_test.go b/tests/enginetests/function_test.go new file mode 100644 index 0000000..6188aa4 --- /dev/null +++ b/tests/enginetests/function_test.go @@ -0,0 +1,329 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestLocalFunctions(t *testing.T) { + // TODO(b/311222838): Add source check to additional expressions to improve coverage. + tests := []struct { + name string + cql string + wantModels []model.IExpressionDef + wantResult result.Value + }{ + { + name: "Local Function no operands", + cql: dedent.Dedent(` + define function FuncNoOperand(): 1 + define TESTRESULT: FuncNoOperand()`), + wantModels: []model.IExpressionDef{ + &model.FunctionDef{ + Operands: []model.OperandDef{}, + ExpressionDef: &model.ExpressionDef{ + Name: "FuncNoOperand", + Expression: model.NewLiteral("1", types.Integer), + Context: "Patient", + AccessLevel: model.Public, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + &model.ExpressionDef{ + Name: "TESTRESULT", + Expression: &model.FunctionRef{ + Name: "FuncNoOperand", + Operands: []model.IExpression{}, + Expression: model.ResultType(types.Integer), + }, + Context: "Patient", + AccessLevel: model.Public, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + wantResult: newOrFatal(t, 1), + }, + { + name: "Local Function with OperandRef", + cql: dedent.Dedent(` + define function FuncWithOperand(a Integer): a + 1 + define TESTRESULT: FuncWithOperand(1)`), + wantModels: []model.IExpressionDef{ + &model.FunctionDef{ + Operands: []model.OperandDef{ + model.OperandDef{Name: "a", Expression: model.ResultType(types.Integer)}, + }, + ExpressionDef: &model.ExpressionDef{ + Name: "FuncWithOperand", + Expression: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.OperandRef{Name: "a", Expression: model.ResultType(types.Integer)}, + model.NewLiteral("1", types.Integer), + }, + }, + }, + Context: "Patient", + AccessLevel: model.Public, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + &model.ExpressionDef{ + Name: "TESTRESULT", + Expression: &model.FunctionRef{ + Name: "FuncWithOperand", + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{model.NewLiteral("1", types.Integer)}, + }, + AccessLevel: model.Public, + Context: "Patient", + Element: &model.Element{ResultType: types.Integer}, + }, + }, + wantResult: newOrFatal(t, 2), + }, + { + name: "Fluent Function", + cql: dedent.Dedent(` + define fluent function FluentFunc(a Integer, b String): a + 1 + define TESTRESULT: 1.FluentFunc('apple')`), + wantModels: []model.IExpressionDef{ + &model.FunctionDef{ + Operands: []model.OperandDef{ + model.OperandDef{Name: "a", Expression: model.ResultType(types.Integer)}, + model.OperandDef{Name: "b", Expression: model.ResultType(types.String)}, + }, + Fluent: true, + ExpressionDef: &model.ExpressionDef{ + Name: "FluentFunc", + Expression: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.OperandRef{Name: "a", Expression: model.ResultType(types.Integer)}, + model.NewLiteral("1", types.Integer), + }, + }, + }, + Context: "Patient", + AccessLevel: model.Public, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + &model.ExpressionDef{ + Name: "TESTRESULT", + Expression: &model.FunctionRef{ + Name: "FluentFunc", + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("apple", types.String), + }, + }, + AccessLevel: model.Public, + Context: "Patient", + Element: &model.Element{ResultType: types.Integer}, + }, + }, + wantResult: newOrFatal(t, 2), + }, + { + name: "Fluent function called on property", + cql: dedent.Dedent(` + define fluent function FluentFunc(a Boolean): a + define TESTRESULT: [Patient] P return P.active.FluentFunc()`), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, true)}, + StaticType: &types.List{ElementType: types.Boolean}}), + }, + { + name: "System operators can be called fluently", + cql: dedent.Dedent(` + define TESTRESULT: 4.Add(4)`), + wantResult: newOrFatal(t, 8), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := dedent.Dedent(fmt.Sprintf(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' + %v`, tc.cql)) + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModels, getTESTLIBModel(t, parsedLibs).Statements.Defs); tc.wantModels != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestGlobalFunctions(t *testing.T) { + tests := []struct { + name string + cqlLibs []string + wantModels []model.IExpressionDef + wantResult result.Value + }{ + { + name: "Global FunctionRef", + cqlLibs: []string{ + dedent.Dedent(` + library CQL_Helpers_Library version '1' + define function PublicFunc(a Integer): a + 1 + define private function PrivateFunc(b Integer): b - 1 + `), + dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + include CQL_Helpers_Library version '1' called helpers + define TESTRESULT: helpers.PublicFunc(1)`), + }, + wantModels: []model.IExpressionDef{ + &model.ExpressionDef{ + Name: "TESTRESULT", + Expression: &model.FunctionRef{ + Name: "PublicFunc", + LibraryName: "helpers", + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{model.NewLiteral("1", types.Integer)}, + }, + Context: "Patient", + AccessLevel: model.Public, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + wantResult: newOrFatal(t, 2), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), tc.cqlLibs, parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModels, getTESTLIBModel(t, parsedLibs).Statements.Defs); tc.wantModels != nil && diff != "" { + t.Errorf("Parse Expression diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval returned diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestFailingFunctions(t *testing.T) { + tests := []struct { + name string + cql string + wantModels []model.IExpressionDef + wantEvalErrContains string + }{ + { + name: "External functions are not supported", + cql: dedent.Dedent(` + define function ExternalFunc(a Integer) returns Integer : external + define ExternalFuncRef: ExternalFunc(4)`), + wantModels: []model.IExpressionDef{ + &model.FunctionDef{ + Operands: []model.OperandDef{ + model.OperandDef{Name: "a", Expression: model.ResultType(types.Integer)}, + }, + External: true, + ExpressionDef: &model.ExpressionDef{ + Name: "ExternalFunc", + Context: "Patient", + AccessLevel: model.Public, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + &model.ExpressionDef{ + Name: "ExternalFuncRef", + Expression: &model.FunctionRef{ + Name: "ExternalFunc", + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + model.NewLiteral("4", types.Integer), + }, + }, + Context: "Patient", + AccessLevel: model.Public, + Element: &model.Element{ResultType: types.Integer}, + }, + }, + wantEvalErrContains: "external functions are not supported", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := dedent.Dedent(fmt.Sprintf(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' + %v`, tc.cql)) + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModels, getTESTLIBModel(t, parsedLibs).Statements.Defs); tc.wantModels != nil && diff != "" { + t.Errorf("Parse Expression diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead") + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents got (%v) want (%v)", err.Error(), tc.wantEvalErrContains) + } + + }) + } +} diff --git a/tests/enginetests/implicit_conversion_test.go b/tests/enginetests/implicit_conversion_test.go new file mode 100644 index 0000000..ab91720 --- /dev/null +++ b/tests/enginetests/implicit_conversion_test.go @@ -0,0 +1,142 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "fmt" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestImplicitConversions(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Exact match over subtype", + cql: dedent.Dedent(` + valueset vs: 'url' version '1.0' + define function Foo(a Vocabulary): a.id + define function Foo(a ValueSet): a.version + define TESTRESULT: Foo(vs)`), + wantResult: newOrFatal(t, "1.0"), + }, + { + name: "Tuple subtype", + cql: dedent.Dedent(` + valueset vs: 'url' version '1.0' + define function Foo(a Tuple{ apple Vocabulary }): a.apple.id + define TESTRESULT: Foo(Tuple { apple: vs})`), + wantResult: newOrFatal(t, "url"), + }, + { + name: "Cast choice", + cql: dedent.Dedent(` + context Patient + define function Foo(a String): a + define TESTRESULT: Foo('hi' as Choice)`), + wantResult: newOrFatal(t, "hi"), + }, + { + name: "Cast choice as null", + cql: dedent.Dedent(` + context Patient + define function Foo(a String): a + define TESTRESULT: Foo(4 as Choice)`), + wantResult: newOrFatal(t, nil), + }, + { + name: "Implicit conversion FHIR.Boolean to System.Boolean", + cql: dedent.Dedent(` + context Patient + define function Foo(a Boolean): a + define TESTRESULT: Foo(Patient.active)`), + wantResult: newOrFatal(t, true), + }, + { + name: "Nested implicit conversion List to List", + cql: dedent.Dedent(` + context Patient + define function Foo(a List): First(a) + define TESTRESULT: Foo({Patient.active})`), + wantResult: newOrFatal(t, true), + }, + { + name: "Class instance subtype allowed property", + cql: dedent.Dedent(` + valueset vs: 'url' version '1.0' + define function Foo(a Vocabulary): a.id + define TESTRESULT: Foo(vs)`), + wantResult: newOrFatal(t, "url"), + }, + { + name: "Generic system operator implicitly converts to same type", + cql: "define TESTRESULT: 4 = 4.5", + wantResult: newOrFatal(t, false), + }, + { + name: "Mixed list converts to same type", + cql: "define TESTRESULT: {4, 4.5}", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 4.0), newOrFatal(t, 4.5)}, + StaticType: &types.List{ElementType: types.Decimal}}), + }, + { + name: "Exact match is not fluent", + cql: dedent.Dedent(` + define fluent function Foo(a Decimal): a + 4 + define function Foo(a Integer): a + 3 + define TESTRESULT: 4.Foo()`), + wantResult: newOrFatal(t, 8.0), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := fmt.Sprintf(dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' + %v`), tc.cql) + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/literal_test.go b/tests/enginetests/literal_test.go new file mode 100644 index 0000000..c5c2db5 --- /dev/null +++ b/tests/enginetests/literal_test.go @@ -0,0 +1,535 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "testing" + "time" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + c4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/codes_go_proto" + r4patientpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/patient_go_proto" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestLiteral(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integer", + cql: "1", + wantModel: &model.Literal{ + Value: "1", + Expression: model.ResultType(types.Integer), + }, + wantResult: newOrFatal(t, 1), + }, + { + name: "Long", + cql: "1L", + wantResult: newOrFatal(t, int64(1)), + }, + { + name: "Decimal", + cql: "1.0", + wantResult: newOrFatal(t, 1.0), + }, + { + name: "Quantity with temporal unit", + cql: "1 'month'", + wantResult: newOrFatal(t, result.Quantity{Value: 1, Unit: model.MONTHUNIT}), + }, + { + name: "Ratio", + cql: "1 'cm':2 'cm'", + wantResult: newOrFatal(t, result.Ratio{Numerator: result.Quantity{Value: 1, Unit: "cm"}, Denominator: result.Quantity{Value: 2, Unit: "cm"}}), + }, + { + name: "Boolean", + cql: "true", + wantResult: newOrFatal(t, true), + }, + { + name: "String", + cql: "'apple'", + wantResult: newOrFatal(t, "apple"), + }, + { + name: "Null", + cql: "null", + wantResult: newOrFatal(t, nil), + }, + { + name: "Date with default timezone", + cql: "@2024-02-20", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, 2, 20, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "DateTime with default timezone", + cql: "@2024-03-31T", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.March, 31, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "DateTime Zulu override", + cql: "@2024-03-31T01:20:30.101Z", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 101e6, time.UTC), Precision: model.MILLISECOND}), + }, + { + name: "DateTime TimeZone override", + cql: "@2024-03-31T01:20:30.101-07:00", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 101e6, time.FixedZone("-07:00", -7*60*60)), Precision: model.MILLISECOND}), + }, + { + name: "Time", + cql: "@T12", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 12, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.HOUR}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIntervalSelector(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integers", + cql: "Interval[1, 2)", + wantModel: &model.Interval{ + Low: model.NewLiteral("1", types.Integer), + High: model.NewLiteral("2", types.Integer), + LowInclusive: true, + HighInclusive: false, + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + }, + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, 1), + High: newOrFatal(t, 2), + LowInclusive: true, + HighInclusive: false, + StaticType: &types.Interval{PointType: types.Integer}, + }), + }, + { + name: "Longs", + cql: "Interval(1L, 3]", + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, int64(1)), + High: newOrFatal(t, int64(3)), + LowInclusive: false, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Long}, + }), + }, + { + name: "Decimals", + cql: "Interval(1.0, 3]", + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, 1.0), + High: newOrFatal(t, 3.0), + LowInclusive: false, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Decimal}, + }), + }, + { + name: "Date", + cql: "Interval(@2024-03, @2024-03]", + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, result.Date{Date: time.Date(2024, 3, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MONTH}), + High: newOrFatal(t, result.Date{Date: time.Date(2024, 3, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MONTH}), + LowInclusive: false, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.Date}, + }), + }, + { + name: "DateTime", + cql: "Interval(@2024-03-31T01:20:30.101Z, @2024-03-31T01:20:30.101Z]", + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 101e6, time.UTC), Precision: model.MILLISECOND}), + High: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 101e6, time.UTC), Precision: model.MILLISECOND}), + LowInclusive: false, + HighInclusive: true, + StaticType: &types.Interval{PointType: types.DateTime}, + }), + }, + { + name: "Left Null", + cql: "Interval[null, 2)", + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, nil), + High: newOrFatal(t, 2), + LowInclusive: true, + HighInclusive: false, + StaticType: &types.Interval{PointType: types.Integer}, + }), + }, + { + name: "Right Null", + cql: "Interval[1, null)", + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, 1), + High: newOrFatal(t, nil), + LowInclusive: true, + HighInclusive: false, + StaticType: &types.Interval{PointType: types.Integer}, + }), + }, + { + name: "Both null (with static types)", + cql: "Interval[null as Integer, null as Integer)", + wantResult: newOrFatal(t, result.Interval{ + Low: newOrFatal(t, nil), + High: newOrFatal(t, nil), + LowInclusive: true, + HighInclusive: false, + StaticType: &types.Interval{PointType: types.Integer}, + }), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestListSelector(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Non mixed", + cql: "{1, 2}", + wantModel: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + }, + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 1), newOrFatal(t, 2)}, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "With type specifier", + cql: "List{1, 2.0}", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 1.0), newOrFatal(t, 2.0)}, + StaticType: &types.List{ElementType: types.Decimal}, + }), + }, + { + name: "Mixed implicitly convertible to same type", + cql: "{1, 2.0}", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 1.0), newOrFatal(t, 2.0)}, + StaticType: &types.List{ElementType: types.Decimal}, + }), + }, + { + name: "Mixed", + cql: "{1, 'hi'}", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 1), newOrFatal(t, "hi")}, + StaticType: &types.List{ElementType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}, + }), + }, + { + name: "Null is converted based on type specifier", + cql: "List{null}", + wantModel: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + }, + }, + }, + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, nil)}, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Empty", + cql: "{}", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{}, + StaticType: &types.List{ElementType: types.Any}, + }), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestCodeSelectors(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Code Selector", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: Code '132' from cs display 'Severed Leg'`), + wantModel: &model.Code{ + Expression: model.ResultType(types.Code), + System: &model.CodeSystemRef{ + Name: "cs", + Expression: model.ResultType(types.CodeSystem), + }, + Code: "132", + Display: "Severed Leg", + }, + wantResult: newOrFatal(t, + result.Code{ + Code: "132", + Display: "Severed Leg", + System: "https://example.com/cs/diagnosis", + Version: "1.0", + }), + }, + { + name: "Code Selector no display", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: Code '132' from cs`), + wantResult: newOrFatal(t, + result.Code{ + Code: "132", + System: "https://example.com/cs/diagnosis", + Version: "1.0", + }), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), []string{tc.cql}, parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestTupleAndInstanceSelector(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Quantity Instance", + cql: "Quantity{value: 4, unit: 'day' }", + wantResult: newOrFatal(t, result.Quantity{Value: 4, Unit: "day"}), + }, + { + name: "Code Instance", + cql: "Code{code: 'foo', system: 'bar', version: '1.0', display: 'severed leg' }", + wantModel: &model.Instance{ + ClassType: types.Code, + Elements: []*model.InstanceElement{ + &model.InstanceElement{Name: "code", Value: model.NewLiteral("foo", types.String)}, + &model.InstanceElement{Name: "system", Value: model.NewLiteral("bar", types.String)}, + &model.InstanceElement{Name: "version", Value: model.NewLiteral("1.0", types.String)}, + &model.InstanceElement{Name: "display", Value: model.NewLiteral("severed leg", types.String)}, + }, + Expression: model.ResultType(types.Code), + }, + wantResult: newOrFatal(t, result.Code{Code: "foo", System: "bar", Display: "severed leg", Version: "1.0"}), + }, + { + name: "CodeSystem Instance", + cql: "CodeSystem{id: 'id', version: '1.0' }", + wantResult: newOrFatal(t, result.CodeSystem{ID: "id", Version: "1.0"}), + }, + { + name: "Concept Instance", + cql: "Concept{codes: {Code{code: 'foo', system: 'bar', version: '1.0' }}, display: 'display' }", + wantResult: newOrFatal(t, result.Concept{ + Codes: []result.Code{result.Code{Code: "foo", System: "bar", Version: "1.0"}}, + Display: "display", + }), + }, + { + name: "ValueSet Instance", + cql: "ValueSet{id: 'id', version: '1.0', codesystems: List{CodeSystem{id: 'id', version: '1.0' }}}", + wantResult: newOrFatal(t, result.ValueSet{ + ID: "id", + Version: "1.0", + CodeSystems: []result.CodeSystem{result.CodeSystem{ID: "id", Version: "1.0"}}, + }), + }, + { + name: "FHIR Instance", + // The test setup wraps the CQL expression in a library which includes context Patient so we + // can use Patient.gender. + cql: "Patient { gender: Patient.gender }", + wantResult: newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{ + "gender": newOrFatal(t, result.Named{ + Value: &r4patientpb.Patient_GenderCode{Value: c4pb.AdministrativeGenderCode_MALE}, + RuntimeType: &types.Named{TypeName: "FHIR.AdministrativeGender"}, + }), + }, + RuntimeType: &types.Named{TypeName: "FHIR.Patient"}, + }), + }, + { + name: "Tuple", + cql: "Tuple { apple: 'red', banana: 4 as Choice }", + wantModel: &model.Tuple{ + Elements: []*model.TupleElement{ + &model.TupleElement{Name: "apple", Value: model.NewLiteral("red", types.String)}, + &model.TupleElement{ + Name: "banana", + Value: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}), + Operand: model.NewLiteral("4", types.Integer), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}, + }, + }, + }, + Expression: model.ResultType(&types.Tuple{ElementTypes: map[string]types.IType{"apple": types.String, "banana": &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}}), + }, + wantResult: newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{ + "apple": newOrFatal(t, "red"), + "banana": newOrFatal(t, 4), + }, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"apple": types.String, "banana": &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}}, + }), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/operator_arithmetic_test.go b/tests/enginetests/operator_arithmetic_test.go new file mode 100644 index 0000000..b1b0704 --- /dev/null +++ b/tests/enginetests/operator_arithmetic_test.go @@ -0,0 +1,1081 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "math" + "strings" + "testing" + "time" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestAdd(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integers", + cql: "1 + 2", + wantResult: newOrFatal(t, 3), + }, + { + name: "Longs", + cql: "1L + 2", + wantModel: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1L", types.Long), + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Long), + Operand: model.NewLiteral("2", types.Integer), + }, + }, + }, + Expression: model.ResultType(types.Long), + }, + }, + wantResult: newOrFatal(t, int64(3)), + }, + { + name: "Decimals", + cql: "1.5 + 2", + wantResult: newOrFatal(t, 3.5), + }, + { + name: "Quantity", + cql: "11 'day' + 9 'day'", + wantResult: newOrFatal(t, result.Quantity{Value: 20, Unit: model.DAYUNIT}), + }, + { + name: "Quantity via class instances", + cql: "Quantity{value: 11, unit: 'day'} + 9 'day'", + wantResult: newOrFatal(t, result.Quantity{Value: 20, Unit: model.DAYUNIT}), + }, + // Tests for Date and Quantity + // TODO(b/301606416): Add more tests for DateTime + Quantity + { + name: "Date Quantity", + cql: "@2014 + 1 'year'", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2015, time.January, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}), + }, + { + name: "Date month precision add year", + cql: "@2014-01 + 1 'year'", + wantModel: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2014-01", types.Date), + &model.Quantity{Value: 1, Unit: model.YEARUNIT, Expression: model.ResultType(types.Quantity)}, + }, + Expression: model.ResultType(types.Date), + }, + }, + wantResult: newOrFatal(t, result.Date{Date: time.Date(2015, time.January, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MONTH}), + }, + { + name: "Date year precision add month", + cql: "@2014 + 12 'month'", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2015, time.January, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}), + }, + { + name: "Date day precision add week to day precision", + cql: "@2014-01-01 + 2 'week'", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2014, time.January, 15, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "Date year precision add 1.6 year truncates to 1", + cql: "@2014-01-01 + 1.6 'year'", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2015, time.January, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "Date year precision add 6 months truncates to 0", + cql: "@2014-01-01 + 6 'month'", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2014, time.July, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "Date time millisecond precision add 1.6 second does not truncate", + cql: "@2014-01-01T00:00:00.000Z + 1.6 'second'", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.January, 1, 0, 0, 1, 600_000_000, time.UTC), Precision: "millisecond"}), + }, + // Tests for Nulls + { + name: "Integer Null", + cql: "1 + null", + wantModel: &model.Add{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Integer, + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Null Integer", + cql: "null + 2", + wantResult: newOrFatal(t, nil), + }, + { + name: "Long Null", + cql: "null + 2L", + wantResult: newOrFatal(t, nil), + }, + { + name: "Null Long", + cql: "1L + null", + wantResult: newOrFatal(t, nil), + }, + { + name: "Decimal Null", + cql: "1.5 + null", + wantResult: newOrFatal(t, nil), + }, + { + name: "Null Decimal", + cql: "null + 1.5", + wantResult: newOrFatal(t, nil), + }, + { + name: "Quantity Null", + cql: "11 'day' + null", + wantResult: newOrFatal(t, nil), + }, + { + name: "Date Null", + cql: "@2014 + null", + wantResult: newOrFatal(t, nil), + }, + { + name: "DateTime Null", + cql: "@2014-01-01T00:00:00.000Z + null", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestAdd_EvalErrors(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "Date month precision add day returns conversion error", + cql: "@2014-01 + 1 'day'", + wantEvalErrContains: "invalid unit conversion", + }, + { + name: "Date month precision add week returns conversion error", + cql: "@2014-01 + 1 'week'", + wantEvalErrContains: "cannot convert from week to a higher precision", + }, + { + name: "Date month precision add minutes returns conversion error", + cql: "@2014-01 + 1 'minute'", + wantEvalErrContains: "invalid unit conversion", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead") + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents got (%v) want (%v)", err.Error(), tc.wantEvalErrContains) + } + }) + } +} + +func TestSubtract(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integers", + cql: "1 - 2", + wantResult: newOrFatal(t, -1), + }, + { + name: "Longs", + cql: "1L - 2", + wantModel: &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1L", types.Long), + &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Long), + Operand: model.NewLiteral("2", types.Integer), + }, + }, + }, + Expression: model.ResultType(types.Long), + }, + }, + wantResult: newOrFatal(t, int64(-1)), + }, + { + name: "Decimals", + cql: "1 - 2.0", + wantResult: newOrFatal(t, -1.0), + }, + { + name: "Quantity", + cql: "10.1 'day' - 1.1 'day'", + wantResult: newOrFatal(t, result.Quantity{Value: 9, Unit: model.DAYUNIT}), + }, + // Tests for Date and Quantity + // TODO(b/301606416): Add more tests for DateTime + Quantity + { + name: "Date Quantity", + cql: "@2014 - 1 'year'", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2013, time.January, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestMultiply(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integers", + cql: "2 * 4", + wantResult: newOrFatal(t, 8), + }, + { + name: "Longs", + cql: "2 * 3L", + wantResult: newOrFatal(t, int64(6)), + }, + { + name: "Decimals", + cql: "2L * 2.0", + wantModel: &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Decimal), + Operand: model.NewLiteral("2L", types.Long), + }, + }, + model.NewLiteral("2.0", types.Decimal), + }, + Expression: model.ResultType(types.Decimal), + }, + }, + wantResult: newOrFatal(t, 4.0), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestTruncatedDivide(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integers", + cql: "2 div 4", + wantModel: &model.TruncatedDivide{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("2", types.Integer), + model.NewLiteral("4", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, 0), + }, + { + name: "Longs", + cql: "2 div 4L", + wantResult: newOrFatal(t, int64(0)), + }, + { + name: "Decimals", + cql: "5 div 2.0", + wantResult: newOrFatal(t, 2.0), + }, + { + name: "Quantity", + cql: "10.1 day div 1.1 day", + wantResult: newOrFatal(t, result.Quantity{Value: 9.0, Unit: model.ONEUNIT}), + }, + { + name: "Divide by zero", + cql: "10 div 0", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestDivide(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integers", + cql: "2 / 4", + wantModel: &model.Divide{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Decimal), + Operand: model.NewLiteral("2", types.Integer), + }, + }, + &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Decimal), + Operand: model.NewLiteral("4", types.Integer), + }, + }, + }, + Expression: model.ResultType(types.Decimal), + }, + }, + wantResult: newOrFatal(t, 0.5), + }, + { + name: "Longs", + cql: "2 / 4L", + wantResult: newOrFatal(t, 0.5), + }, + { + name: "Decimals", + cql: "5 / 2.0", + wantResult: newOrFatal(t, 2.5), + }, + { + name: "Quantity", + cql: "5.0 day / 2.0 day", + wantResult: newOrFatal(t, result.Quantity{Value: 2.5, Unit: model.ONEUNIT}), + }, + { + name: "Divide by zero", + cql: "10 / 0", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestMod(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integers Zero", + cql: "1000 mod 200", + wantResult: newOrFatal(t, 0), + }, + { + name: "Integers", + cql: "5 mod 2", + wantModel: &model.Modulo{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("5", types.Integer), + model.NewLiteral("2", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, 1), + }, + { + name: "Longs Zero", + cql: "1000L mod 200L", + wantResult: newOrFatal(t, int64(0)), + }, + { + name: "Longs", + cql: "5L mod 2L", + wantResult: newOrFatal(t, int64(1)), + }, + { + name: "Decimals", + cql: "10.1111 mod 2.1111", + wantResult: newOrFatal(t, 1.6667000000000005), + }, + { + name: "Another Decimals", + cql: "2.1111 mod 10.1111", + wantResult: newOrFatal(t, 2.1111), + }, + { + name: "Quantity", + cql: "10 'm' mod 2 'm'", + wantResult: newOrFatal(t, result.Quantity{Value: 0, Unit: "m"}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestMaximum(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "maximum Integer", + cql: "maximum Integer", + wantModel: &model.MaxValue{ValueType: types.Integer, Expression: model.ResultType(types.Integer)}, + wantResult: newOrFatal(t, int32(2147483647)), + }, + { + name: "maximum Long", + cql: "maximum Long", + wantResult: newOrFatal(t, int64(9223372036854775807)), + }, + { + name: "maximum Decimal", + cql: "maximum Decimal", + wantResult: newOrFatal(t, float64(99999999999999999999.99999999)), + }, + { + name: "maximum Date", + cql: "maximum Date", + wantResult: newOrFatal(t, result.Date{Date: time.Date(9999, 12, 31, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "maximum DateTime", + cql: "maximum DateTime", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(9999, 12, 31, 23, 59, 59, 999, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "maximum Time", + cql: "maximum Time", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 23, 59, 59, 999000000, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "maximum Quantity", + cql: "maximum Quantity", + wantResult: newOrFatal(t, result.Quantity{Value: float64(99999999999999999999.99999999), Unit: "1"}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestMinimum(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "minimum Integer", + cql: "minimum Integer", + wantModel: &model.MinValue{ValueType: types.Integer, Expression: model.ResultType(types.Integer)}, + wantResult: newOrFatal(t, int32(-2147483648)), + }, + { + name: "minimum Long", + cql: "minimum Long", + wantResult: newOrFatal(t, int64(-9223372036854775808)), + }, + { + name: "minimum Decimal", + cql: "minimum Decimal", + wantResult: newOrFatal(t, float64(-99999999999999999999.99999999)), + }, + { + name: "minimum Date", + cql: "minimum Date", + wantResult: newOrFatal(t, result.Date{Date: time.Date(1, 1, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "minimum DateTime", + cql: "minimum DateTime", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(1, 1, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "minimum Time", + cql: "minimum Time", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "minimum Quantity", + cql: "minimum Quantity", + wantResult: newOrFatal(t, result.Quantity{Value: float64(-99999999999999999999.99999999), Unit: "1"}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestPredecessor(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "predecessor of 1", + cql: "predecessor of 1", + wantModel: &model.Predecessor{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("1", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, 0), + }, + { + name: "predecessor of 1000000000000L", + cql: "predecessor of 1000000000000L", + wantResult: newOrFatal(t, int64(999999999999)), + }, + { + name: "predecessor of 1.0", + cql: "predecessor of 1.0", + wantResult: newOrFatal(t, float64(0.99999999)), + }, + { + name: "predecessor of @2024-01-02", + cql: "predecessor of @2024-01-02", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, 1, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "predecessor of @2024-01-01T00:00:00.001Z", + cql: "predecessor of @2024-01-01T00:00:00.001Z", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + }, + { + name: "predecessor of @T12:00:00.000", + cql: "predecessor of @T12:00:00.000", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 11, 59, 59, 999000000, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "predecessor of 1.0'cm'", + cql: "predecessor of 1.0'cm'", + wantResult: newOrFatal(t, result.Quantity{Value: float64(0.99999999), Unit: "cm"}), + }, + { + name: "predecessor of (4 as Choice)", + cql: "predecessor of (4 as Choice)", + wantResult: newOrFatal(t, int32(3)), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestSuccessor(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "successor of 1", + cql: "successor of 1", + wantModel: &model.Successor{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("1", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, 2), + }, + { + name: "successor of 1000000000000L", + cql: "successor of 1000000000000L", + wantResult: newOrFatal(t, int64(1000000000001)), + }, + { + name: "successor of 1.0", + cql: "successor of 1.0", + wantResult: newOrFatal(t, float64(1.00000001)), + }, + { + name: "successor of @2024-01-01", + cql: "successor of @2024-01-01", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, 1, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "successor of @2024-01-01T00:00:00.999Z", + cql: "successor of @2024-01-01T00:00:00.999Z", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, 1, 1, 0, 0, 1, 0, time.UTC), Precision: model.MILLISECOND}), + }, + { + name: "successor of @T11:59:59.999", + cql: "successor of @T11:59:59.999", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 12, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "successor of 1.0'cm'", + cql: "successor of 1.0'cm'", + wantResult: newOrFatal(t, result.Quantity{Value: float64(1.00000001), Unit: "cm"}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestSuccessorPredecessor_EvalErrors(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "successor of max value", + cql: "successor of maximum Integer", + wantEvalErrContains: "tried to compute successor for value that is already a max", + }, + // Date tests + { + name: "successor of maximum Date", + cql: "successor of maximum Date", + wantEvalErrContains: "tried to compute successor for value that is already a max", + }, + { + name: "successor of max date for year precision", + cql: "successor of @9999", + wantEvalErrContains: "tried to compute successor for System.Date that is already a max", + }, + { + name: "predecessor of minimum Date", + cql: "predecessor of minimum Date", + wantEvalErrContains: "tried to compute predecessor for value that is already a min", + }, + { + name: "predecessor of min date for year precision", + cql: "predecessor of @0001", + wantEvalErrContains: "tried to compute predecessor for System.Date that is already a min", + }, + // DateTime tests + { + name: "successor of maximum DateTime", + cql: "successor of maximum DateTime", + wantEvalErrContains: "tried to compute successor for value that is already a max", + }, + { + name: "successor of max date for day precision", + cql: "successor of @9999-12-31T", + wantEvalErrContains: "tried to compute successor for System.DateTime that is already a max", + }, + { + name: "predecessor of minimum DateTime", + cql: "predecessor of minimum DateTime", + wantEvalErrContains: "tried to compute predecessor for value that is already a min", + }, + { + name: "predecessor of min date for day precision", + cql: "predecessor of @0001-01-01T", + wantEvalErrContains: "tried to compute predecessor for System.DateTime that is already a min", + }, + // Time tests + { + name: "successor of maximum Time", + cql: "successor of maximum Time", + wantEvalErrContains: "tried to compute successor for value that is already a max", + }, + { + name: "successor of max time for minute precision", + cql: "successor of @T23:59", + wantEvalErrContains: "tried to compute successor for System.Time that is already a max", + }, + { + name: "predecessor of minimum Time", + cql: "predecessor of minimum Time", + wantEvalErrContains: "tried to compute predecessor for value that is already a min", + }, + { + name: "predecessor of min time for minute precision", + cql: "predecessor of @T00:00", + wantEvalErrContains: "tried to compute predecessor for System.Time that is already a min", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead") + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents got (%v) want (%v)", err.Error(), tc.wantEvalErrContains) + } + }) + } +} + +func TestNegate(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Integer", + cql: "-4", + wantModel: &model.Negate{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, -4), + }, + { + name: "Minimum Integer", + cql: "-2147483648", + wantResult: newOrFatal(t, int32(math.MinInt32)), + }, + { + name: "Long", + cql: "-4L", + wantResult: newOrFatal(t, int64(-4)), + }, + { + name: "Minimum Long", + cql: "-9223372036854775808L", + wantResult: newOrFatal(t, int64(math.MinInt64)), + }, + { + name: "Decimal", + cql: "-1.0", + wantResult: newOrFatal(t, float64(-1.0)), + }, + { + name: "Minimum Decimal", + cql: "-99999999999999999999.99999999", + wantResult: newOrFatal(t, float64(-99999999999999999999.99999999)), + }, + { + name: "Quantity", + cql: "-1.0 'day'", + wantResult: newOrFatal(t, result.Quantity{Value: -1.0, Unit: model.DAYUNIT}), + }, + { + name: "Null", + cql: "-(null as Integer)", + wantResult: newOrFatal(t, nil), + }, + { + name: "negate minimum Integer", + cql: "Negate(minimum Integer)", + wantResult: newOrFatal(t, nil), + }, + { + name: "negate minimum Long", + cql: "Negate(minimum Long)", + wantResult: newOrFatal(t, nil), + }, + { + name: "negate minimum Decimal", + cql: "Negate(minimum Decimal)", + wantResult: newOrFatal(t, nil), + }, + { + name: "negate minimum Quantity", + cql: "Negate(minimum Quantity)", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/operator_clinical_test.go b/tests/enginetests/operator_clinical_test.go new file mode 100644 index 0000000..41c9bbb --- /dev/null +++ b/tests/enginetests/operator_clinical_test.go @@ -0,0 +1,363 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "fmt" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestCalculateAgeAt(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // AgeAt() + // The test patient's birthday is 1950-01-01. Note AgeAt is translated into CalculateAgeAt + // by the parser, so although the syntax is different much of the interpreter logic is shared. + { + name: "AgeAt Date", + cql: "AgeInYearsAt(@2023-06-14)", + wantModel: &model.CalculateAgeAt{ + Precision: model.YEAR, + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.Property{ + Expression: model.ResultType(types.Date), + Source: &model.Property{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.date"}), + Source: &model.ExpressionRef{ + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + Name: "Patient", + }, + Path: "birthDate", + }, + Path: "value", + }, + model.NewLiteral("@2023-06-14", types.Date), + }, + }, + }, + wantResult: newOrFatal(t, 73), + }, + { + name: "AgeAt DateTime", + cql: "AgeInYearsAt(@2023-06-15T10:01:01.000Z)", + wantResult: newOrFatal(t, 73), + }, + // CalculateAgeAt() + { + name: "Left Null", + cql: "CalculateAgeInYearsAt(null, @2023-06-14)", + wantModel: &model.CalculateAgeAt{ + Precision: model.YEAR, + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Date), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Date, + }, + model.NewLiteral("@2023-06-14", types.Date), + }, + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Right Null", + cql: "CalculateAgeInYearsAt(@1981-06-15, null)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Years precision Dates", + cql: "CalculateAgeInYearsAt(@1981-06-15, @2023-06-14)", + wantResult: newOrFatal(t, 41), + }, + { + name: "Years precision DateTimes", + cql: "CalculateAgeInYearsAt(@1981-06-15, @2023-06-15T10:01:01.000Z)", + wantResult: newOrFatal(t, 42), + }, + { + name: "Years precision day short", + cql: "CalculateAgeInYearsAt(@1981-06-15, @2023-06-14)", + wantResult: newOrFatal(t, 41), + }, + { + name: "Months precision", + cql: "CalculateAgeInMonthsAt(@2022-06-15, @2023-06-14)", + wantResult: newOrFatal(t, 11), + }, + { + name: "Months precision day short", + cql: "CalculateAgeInMonthsAt(@2022-06-15, @2023-06-14)", + wantResult: newOrFatal(t, 11), + }, + { + name: "Weeks precision", + cql: "CalculateAgeInWeeksAt(@2023-06-01, @2023-06-14)", + wantResult: newOrFatal(t, 1), + }, + { + name: "Days precision", + cql: "CalculateAgeInDaysAt(@2023-06-01, @2023-06-15)", + wantResult: newOrFatal(t, 14), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestInValueSetAndCodeSystem(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // ValueSet tests + { + name: "Code In ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' version '1.0.0' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code C: 'gluc' from CS + define TESTRESULT: C in VS`), + wantResult: newOrFatal(t, true), + }, + { + name: "Code In unversioned ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code C: 'gluc' from CS + define TESTRESULT: C in VS`), + wantResult: newOrFatal(t, true), + }, + { + name: "Code Not In ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' version '1.0.0' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code C: 'NotInValueSet' from CS + define TESTRESULT: C in VS`), + wantResult: newOrFatal(t, false), + }, + { + name: "One Code from List In unversioned ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code ExistsCode: 'gluc' from CS + code NonexistantCode: 'NotInValueSet' from CS + define TESTRESULT: { NonexistantCode, ExistsCode } in VS`), + wantResult: newOrFatal(t, true), + }, + { + name: "List Not In unversioned ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code NonexistantCode: 'NotInValueSet' from CS + code NonexistantCode2: 'NotInValueSet2' from CS + define TESTRESULT: { NonexistantCode, NonexistantCode2 } in VS`), + wantResult: newOrFatal(t, false), + }, + { + name: "One Code from Concept In unversioned ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code ExistsCode: 'gluc' from CS + code NonexistantCode: 'NotInValueSet' from CS + concept Con: { NonexistantCode, ExistsCode } + define TESTRESULT: Con in VS`), + wantResult: newOrFatal(t, true), + }, + { + name: "Concept Not In unversioned ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code NonexistantCode: 'NotInValueSet' from CS + code NonexistantCode2: 'NotInValueSet2' from CS + concept Con: { NonexistantCode, NonexistantCode2 } + define TESTRESULT: Con in VS`), + wantResult: newOrFatal(t, false), + }, + { + name: "One Code from List In unversioned ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code ExistsCode: 'gluc' from CS + code NonexistantCode: 'NotInValueSet' from CS + concept ConWithValidCode: { ExistsCode } + concept ConNoValidCode: { NonexistantCode } + define TESTRESULT: { ConNoValidCode, ConWithValidCode } in VS`), + wantResult: newOrFatal(t, true), + }, + { + name: "List Not In unversioned ValueSet", + cql: dedent.Dedent(` + valueset VS: 'https://example.com/vs/glucose' + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code NonexistantCode: 'NotInValueSet' from CS + code NonexistantCode2: 'NotInValueSet2' from CS + concept ConNoValidCode: { NonexistantCode } + concept ConNoValidCode2: { NonexistantCode2 } + define TESTRESULT: { ConNoValidCode, ConNoValidCode2 } in VS`), + wantResult: newOrFatal(t, false), + }, + // CodeSystem tests + { + name: "Code In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code C: 'snfl' from CS + define TESTRESULT: C in CS`), + wantResult: newOrFatal(t, true), + }, + { + name: "Code Not In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code C: 'NotInCodeSystem' from CS + define TESTRESULT: C in CS`), + wantResult: newOrFatal(t, false), + }, + { + name: "One Code from List In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code ExistsCode: 'snfl' from CS + code NonexistantCode: 'NotInCodeSystem' from CS + define TESTRESULT: { NonexistantCode, ExistsCode } in CS`), + wantResult: newOrFatal(t, true), + }, + { + name: "List Not In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code NonexistantCode: 'NotInCodeSystem' from CS + code NonexistantCode2: 'NotInCodeSystem2' from CS + define TESTRESULT: { NonexistantCode, NonexistantCode2 } in CS`), + wantResult: newOrFatal(t, false), + }, + { + name: "One Code from Concept In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code ExistsCode: 'snfl' from CS + code NonexistantCode: 'NotInCodeSystem' from CS + concept Con: { NonexistantCode, ExistsCode } + define TESTRESULT: Con in CS`), + wantResult: newOrFatal(t, true), + }, + { + name: "Concept Not In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code NonexistantCode: 'NotInCodeSystem' from CS + code NonexistantCode2: 'NotInCodeSystem2' from CS + concept Con: { NonexistantCode, NonexistantCode2 } + define TESTRESULT: Con in CS`), + wantResult: newOrFatal(t, false), + }, + { + name: "One Code from List In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code ExistsCode: 'snfl' from CS + code NonexistantCode: 'NotInCodeSystem' from CS + concept ConWithValidCode: { ExistsCode } + concept ConNoValidCode: { NonexistantCode } + define TESTRESULT: { ConNoValidCode, ConWithValidCode } in CS`), + wantResult: newOrFatal(t, true), + }, + { + name: "Concept Not In Code System", + cql: dedent.Dedent(` + codesystem CS: 'https://example.com/cs/diagnosis' version '1.0.0' + code NonexistantCode: 'NotInCodeSystem' from CS + code NonexistantCode2: 'NotInCodeSystem2' from CS + concept ConNoValidCode: { NonexistantCode } + concept ConNoValidCode2: { NonexistantCode2 } + define TESTRESULT: { ConNoValidCode, ConNoValidCode2 } in CS`), + wantResult: newOrFatal(t, false), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := dedent.Dedent(fmt.Sprintf(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + %v`, tc.cql)) + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} diff --git a/tests/enginetests/operator_comparison_test.go b/tests/enginetests/operator_comparison_test.go new file mode 100644 index 0000000..37dd208 --- /dev/null +++ b/tests/enginetests/operator_comparison_test.go @@ -0,0 +1,1389 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "strings" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestEqual(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "null = true", + cql: "null = true", + wantModel: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Boolean, + }, + model.NewLiteral("true", types.Boolean), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "false = null", + cql: "false = null", + wantResult: newOrFatal(t, nil), + }, + { + name: "true = true", + cql: "true = true", + wantResult: newOrFatal(t, true), + }, + { + name: "false = false", + cql: "false = false", + wantResult: newOrFatal(t, true), + }, + { + name: "false = true", + cql: "false = true", + wantResult: newOrFatal(t, false), + }, + { + name: "true = false", + cql: "true = false", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTimes equal", + cql: "@2024-02-29T01:20:30.101-07:00 = @2024-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTimes not equal", + cql: "@2024-02-29T01:20:30.101-07:00 = @2028-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, false), + }, + { + name: "Equal DateTimes until differing precision is null", + cql: "@2024-02-29T01:20:30.101-07:00 = @2024-02-29T", + wantResult: newOrFatal(t, nil), + }, + { + name: "Dates equal", + cql: "@2024-02-29 = @2024-02-29", + wantResult: newOrFatal(t, true), + }, + { + name: "Dates not equal", + cql: "@2024-02-29 = @2028-02-29", + wantResult: newOrFatal(t, false), + }, + { + name: "Equal Dates until differing precision is null", + cql: "@2024-02-29 = @2024-02", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestNotEqual(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "null != true", + cql: "null != true", + wantModel: &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: &model.Equal{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Boolean, + }, + model.NewLiteral("true", types.Boolean), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "false != null", + cql: "false != null", + wantResult: newOrFatal(t, nil), + }, + { + name: "true != true", + cql: "true != true", + wantResult: newOrFatal(t, false), + }, + { + name: "false != false", + cql: "false != false", + wantResult: newOrFatal(t, false), + }, + { + name: "false != true", + cql: "false != true", + wantResult: newOrFatal(t, true), + }, + { + name: "true != false", + cql: "true != false", + wantResult: newOrFatal(t, true), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestEquivalent(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "null ~ true", + cql: "null ~ true", + wantModel: &model.Equivalent{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Boolean, + }, + model.NewLiteral("true", types.Boolean), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "Boolean nulls are equivalent", + cql: "null as Boolean ~ null as Boolean", + wantResult: newOrFatal(t, true), + }, + { + name: "true ~ true", + cql: "true ~ true", + wantResult: newOrFatal(t, true), + }, + { + name: "false ~ false", + cql: "false ~ false", + wantResult: newOrFatal(t, true), + }, + { + name: "false ~ true", + cql: "false ~ true", + wantResult: newOrFatal(t, false), + }, + { + name: "true ~ false", + cql: "true ~ false", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTimes equivalent", + cql: "@2024-02-29T01:20:30.101-07:00 ~ @2024-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTimes not equivalent", + cql: "@2024-02-29T01:20:30.101-07:00 ~ @2028-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTime nulls are equivalent", + cql: "null as DateTime ~ null as DateTime", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime equivalent to null is false", + cql: "@2024-02-29T01:20:30.101-07:00 ~ null", + wantResult: newOrFatal(t, false), + }, + { + name: "Equal DateTimes until differing precision is false", + cql: "@2024-02-29T01:20:30.101-07:00 ~ @2024-02-29T", + wantResult: newOrFatal(t, false), + }, + { + name: "Dates equivalent", + cql: "@2024-02-29 ~ @2024-02-29", + wantResult: newOrFatal(t, true), + }, + { + name: "Dates not equivalent", + cql: "@2024-02-29 ~ @2028-02-29", + wantResult: newOrFatal(t, false), + }, + { + name: "Date nulls are equivalent", + cql: "null as Date ~ null as Date", + wantResult: newOrFatal(t, true), + }, + { + name: "Date equivalent to null is false", + cql: "@2024-02-29 ~ null", + wantResult: newOrFatal(t, false), + }, + { + name: "Equal Dates until differing precision is false", + cql: "@2024-02-29 ~ @2024-02", + wantResult: newOrFatal(t, false), + }, + { + name: "Equivalent integers", + cql: "1 ~ 1", + wantResult: newOrFatal(t, true), + }, + { + name: "Not equivalent integers", + cql: "1 ~ 2", + wantResult: newOrFatal(t, false), + }, + { + name: "Integer nulls are equivalent", + cql: "null as Integer ~ null as Integer", + wantResult: newOrFatal(t, true), + }, + { + name: "Integer equivalent to null is false", + cql: "1 ~ null", + wantResult: newOrFatal(t, false), + }, + { + name: "Equivalent Long", + cql: "1L ~ 1L", + wantResult: newOrFatal(t, true), + }, + { + name: "Not equivalent Long", + cql: "1L ~ 2L", + wantResult: newOrFatal(t, false), + }, + { + name: "Equivalent empty Lists", + cql: "{} ~ {}", + wantResult: newOrFatal(t, true), + }, + { + name: "Long nulls are equivalent", + cql: "null as Long ~ null as Long", + wantResult: newOrFatal(t, true), + }, + { + name: "Date equivalent to null is false", + cql: "1L ~ null", + wantResult: newOrFatal(t, false), + }, + { + name: "Equivalent Lists", + cql: "{1, 2} ~ {1, 2}", + wantResult: newOrFatal(t, true), + }, + { + name: "Not equivalent Lists", + cql: "{1} ~ {2}", + wantResult: newOrFatal(t, false), + }, + { + name: "Not equivalent Lists of different length", + cql: "{1} ~ {2, 3}", + wantResult: newOrFatal(t, false), + }, + { + name: "{1, 2} ~ null = false", + cql: "{1} ~ null", + wantResult: newOrFatal(t, false), + }, + { + name: "null as List ~ null as List", + cql: "null as List ~ null as List", + wantResult: newOrFatal(t, true), + }, + { + name: "Equivalent lists with implicit conversions", + cql: "{1, 2L} ~ {1L, 2L}", + wantResult: newOrFatal(t, true), + }, + { + name: "Equivalent Intervals", + cql: "Interval[1, 2] ~ Interval[1, 2]", + wantResult: newOrFatal(t, true), + }, + { + name: "Not equivalent Intervals", + cql: "Interval[1, 4] ~ Interval[1, 2]", + wantResult: newOrFatal(t, false), + }, + { + name: "Equivalent intervals with nulls", + cql: "Interval(null, 4] ~ Interval(null, 4]", + wantResult: newOrFatal(t, true), + }, + { + name: "Non equivalent intervals with nulls", + cql: "null ~ Interval[1, 4]", + wantResult: newOrFatal(t, false), + }, + { + name: "Equivalent null intervals", + cql: "null as Interval ~ null as Interval", + wantResult: newOrFatal(t, true), + }, + { + name: "Equivalent intervals that require implicit conversions", + cql: "Interval[1, 4L] ~ Interval[1L, 4L]", + wantResult: newOrFatal(t, true), + }, + { + name: "Equivalent Strings", + cql: "'a' ~ 'a'", + wantResult: newOrFatal(t, true), + }, + { + name: "Equivalent Strings with differing case", + cql: "'abc' ~ 'Abc'", + wantResult: newOrFatal(t, true), + }, + { + name: "Equivalent Strings with different whitespace characters", + // This is "'a b' ~ 'ab'" + cql: "'a b' ~ 'a\tb'", + wantResult: newOrFatal(t, true), + }, + { + name: "Not Equivalent Strings", + cql: "'abc' ~ 'zbc'", + wantResult: newOrFatal(t, false), + }, + { + name: "Non equivalent Strings with null", + cql: "'a' ~ null", + wantResult: newOrFatal(t, false), + }, + { + name: "Equivalent null strings", + cql: "null as String ~ null as String", + wantResult: newOrFatal(t, true), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestEquivalent_Errors(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "Unsupported mixed lists equivalent", + cql: "List{1, 'str', 1} ~ List{1, 'str', 1.0}", + wantEvalErrContains: "unable to match Equivalent overload for elements in a list, this is likely because our engine does not fully support mixed type lists yet", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned containing %q, got nil instead", tc.wantEvalErrContains) + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents got (%v) want (%v)", err.Error(), tc.wantEvalErrContains) + } + }) + } +} + +func TestNotEquivalent(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "null !~ true", + cql: "null !~ true", + wantModel: &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: &model.Equivalent{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Boolean, + }, + model.NewLiteral("true", types.Boolean), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "true !~ true", + cql: "true !~ true", + wantResult: newOrFatal(t, false), + }, + { + name: "false !~ false", + cql: "false !~ false", + wantResult: newOrFatal(t, false), + }, + { + name: "false !~ true", + cql: "false !~ true", + wantResult: newOrFatal(t, true), + }, + { + name: "true !~ false", + cql: "true !~ false", + wantResult: newOrFatal(t, true), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestEquivalentCodes(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // Code equivalency tests, per https://cql.hl7.org/09-b-cqlreference.html#equivalent-3 + { + name: `null ~ Code`, + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: null ~ Code 'code1' from "cs" display 'display1'`), + wantModel: &model.Equivalent{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Code), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Code, + }, + &model.Code{ + System: &model.CodeSystemRef{Name: "cs", Expression: model.ResultType(types.CodeSystem)}, + Code: "code1", + Display: "display1", + Expression: model.ResultType(types.Code), + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: `Equivalent codes`, + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: Code 'code1' from "cs" display 'display1' ~ Code 'code1' from "cs" display 'display1'`), + wantResult: newOrFatal(t, true), + }, + { + name: `Codes with different displays still true`, + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: Code 'code1' from "cs" display 'display1' ~ Code 'code1' from "cs" display 'display2'`), + wantResult: newOrFatal(t, true), + }, + { + name: `Codes with different systems`, + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + codesystem cs2: 'url' version '1.0' + define TESTRESULT: Code 'code1' from "cs" display 'display1' ~ Code 'code1' from "cs2" display 'display1'`), + wantResult: newOrFatal(t, false), + }, + { + name: `Codes with different codes`, + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: Code 'code1' from "cs" display 'display1' ~ Code 'code2' from "cs" display 'display1'`), + wantResult: newOrFatal(t, false), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), []string{tc.cql}, parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestNotEquivalentCodes(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // Code equivalency tests, per https://cql.hl7.org/09-b-cqlreference.html#equivalent-3 + { + name: `Not Equivalent codes`, + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + code c1: 'code1' from "cs" + code c2: 'code2' from "cs" display 'display1' + define TESTRESULT: c1 !~ c2`), + wantResult: newOrFatal(t, true), + }, + { + name: `Not Equivalent on equivalent codes`, + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + code c1: 'code1' from "cs" + code c2: 'code1' from "cs" display 'display1' + define TESTRESULT: c1 !~ c2`), + wantResult: newOrFatal(t, false), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), []string{tc.cql}, parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestGreater(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "2 > 1", + cql: "2 > 1", + wantModel: &model.Greater{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("2", types.Integer), + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "1 > 2", + cql: "1 > 2", + wantResult: newOrFatal(t, false), + }, + { + name: "1 > 1", + cql: "1 > 1", + wantResult: newOrFatal(t, false), + }, + { + name: "1 > null", + cql: "1 > null", + wantResult: newOrFatal(t, nil), + }, + { + name: "2L > 1L", + cql: "2L > 1L", + wantResult: newOrFatal(t, true), + }, + { + name: "1L > 2L", + cql: "1L > 2L", + wantResult: newOrFatal(t, false), + }, + { + name: "1L > 1L", + cql: "1L > 1L", + wantResult: newOrFatal(t, false), + }, + { + name: "1L > null", + cql: "1L > null", + wantResult: newOrFatal(t, nil), + }, + { + name: "1.1 > 1.0", + cql: "1.1 > 1.0", + wantResult: newOrFatal(t, true), + }, + { + name: "1.0 > 2.0", + cql: "1.0 > 2.0", + wantResult: newOrFatal(t, false), + }, + { + name: "1.0 > 1.0", + cql: "1.0 > 1.0", + wantResult: newOrFatal(t, false), + }, + { + name: "2.0 > null", + cql: "2.0 > null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020 > @2019", + cql: "@2020 > @2019", + wantResult: newOrFatal(t, true), + }, + { + name: "@2019 > @2020", + cql: "@2019 > @2020", + wantResult: newOrFatal(t, false), + }, + { + name: "@2019 > @2019", + cql: "@2019 > @2019", + wantResult: newOrFatal(t, false), + }, + { + name: "@2019 > null", + cql: "@2019 > null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01 > @2020 left has greater precision", + cql: "@2020-01 > @2020", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01-02T02 > @2020-01-02T01", + cql: "@2020-01-02T02:01:00.000Z > @2020-01-02T01:01:00.000Z", + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-01-02T01 > @2020-01-02T02", + cql: "@2020-01-02T01:01:00.000Z > @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, false), + }, + { + name: "@2020-01-02T02 > @2020-01-02T02", + cql: "@2020-01-02T02:01:00.000Z > @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, false), + }, + { + name: "@2020-01-02T01 > null", + cql: "@2020-01-02T01:01:00.000Z > null", + wantResult: newOrFatal(t, nil), + }, + { + name: "'ab' > 'aa'", + cql: "'ab' > 'aa'", + wantResult: newOrFatal(t, true), + }, + { + name: "'aa' > 'ab'", + cql: "'aa' > 'ab'", + wantResult: newOrFatal(t, false), + }, + { + name: "'aa' > 'aa'", + cql: "'aa' > 'aa'", + wantResult: newOrFatal(t, false), + }, + { + name: "'ab' > null", + cql: "'ab' > null", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestGreaterOrEqual(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "2 >= 1", + cql: "2 >= 1", + wantModel: &model.GreaterOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("2", types.Integer), + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "1 >= 2", + cql: "1 >= 2", + wantResult: newOrFatal(t, false), + }, + { + name: "1 >= 1", + cql: "1 >= 1", + wantResult: newOrFatal(t, true), + }, + { + name: "1 >= null", + cql: "1 >= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "2L >= 1L", + cql: "2L >= 1L", + wantResult: newOrFatal(t, true), + }, + { + name: "1L >= 2L", + cql: "1L >= 2L", + wantResult: newOrFatal(t, false), + }, + { + name: "1L >= 1L", + cql: "1L >= 1L", + wantResult: newOrFatal(t, true), + }, + { + name: "1L >= null", + cql: "1L >= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "1.1 >= 1.0", + cql: "1.1 >= 1.0", + wantResult: newOrFatal(t, true), + }, + { + name: "1.0 >= 2.0", + cql: "1.0 >= 2.0", + wantResult: newOrFatal(t, false), + }, + { + name: "1.0 >= 1.0", + cql: "1.0 >= 1.0", + wantResult: newOrFatal(t, true), + }, + { + name: "2.0 >= null", + cql: "2.0 >= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020 >= @2019", + cql: "@2020 >= @2019", + wantResult: newOrFatal(t, true), + }, + { + name: "@2019 >= @2020", + cql: "@2019 >= @2020", + wantResult: newOrFatal(t, false), + }, + { + name: "@2019 >= @2019", + cql: "@2019 >= @2019", + wantResult: newOrFatal(t, true), + }, + { + name: "@2019 >= null", + cql: "@2019 >= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01 >= @2020 left has greater precision", + cql: "@2020-01 >= @2020", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01-02T02 >= @2020-01-02T01", + cql: "@2020-01-02T02:01:00.000Z >= @2020-01-02T01:01:00.000Z", + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-01-02T01 >= @2020-01-02T02", + cql: "@2020-01-02T01:01:00.000Z >= @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, false), + }, + { + name: "@2020-01-02T02 >= @2020-01-02T02", + cql: "@2020-01-02T02:01:00.000Z >= @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-01-02T01 >= null", + cql: "@2020-01-02T01:01:00.000Z >= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "'ab' >= 'aa'", + cql: "'ab' >= 'aa'", + wantResult: newOrFatal(t, true), + }, + { + name: "'aa' >= 'ab'", + cql: "'aa' >= 'ab'", + wantResult: newOrFatal(t, false), + }, + { + name: "'aa' >= 'aa'", + cql: "'aa' >= 'aa'", + wantResult: newOrFatal(t, true), + }, + { + name: "'ab' >= null", + cql: "'ab' >= null", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestLess(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "2 < 1", + cql: "2 < 1", + wantModel: &model.Less{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("2", types.Integer), + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "1 < 2", + cql: "1 < 2", + wantResult: newOrFatal(t, true), + }, + { + name: "1 < 1", + cql: "1 < 1", + wantResult: newOrFatal(t, false), + }, + { + name: "1 < null", + cql: "1 < null", + wantResult: newOrFatal(t, nil), + }, + { + name: "2L < 1L", + cql: "2L < 1L", + wantResult: newOrFatal(t, false), + }, + { + name: "1L < 2L", + cql: "1L < 2L", + wantResult: newOrFatal(t, true), + }, + { + name: "1L < 1L", + cql: "1L < 1L", + wantResult: newOrFatal(t, false), + }, + { + name: "1L < null", + cql: "1L < null", + wantResult: newOrFatal(t, nil), + }, + { + name: "1.1 < 1.0", + cql: "1.1 < 1.0", + wantResult: newOrFatal(t, false), + }, + { + name: "1.0 < 2.0", + cql: "1.0 < 2.0", + wantResult: newOrFatal(t, true), + }, + { + name: "1.0 < 1.0", + cql: "1.0 < 1.0", + wantResult: newOrFatal(t, false), + }, + { + name: "2.0 < null", + cql: "2.0 < null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020 < @2019", + cql: "@2020 < @2019", + wantResult: newOrFatal(t, false), + }, + { + name: "@2019 < @2020", + cql: "@2019 < @2020", + wantResult: newOrFatal(t, true), + }, + { + name: "@2019 < @2019", + cql: "@2019 < @2019", + wantResult: newOrFatal(t, false), + }, + { + name: "@2019 < null", + cql: "@2019 < null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01 < @2020 left has greater precision", + cql: "@2020-01 < @2020", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01-02T02 < @2020-01-02T01", + cql: "@2020-01-02T02:01:00.000Z < @2020-01-02T01:01:00.000Z", + wantResult: newOrFatal(t, false), + }, + { + name: "@2020-01-02T01 < @2020-01-02T02", + cql: "@2020-01-02T01:01:00.000Z < @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-01-02T02 < @2020-01-02T02", + cql: "@2020-01-02T02:01:00.000Z < @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, false), + }, + { + name: "@2020-01-02T01 < null", + cql: "@2020-01-02T01:01:00.000Z < null", + wantResult: newOrFatal(t, nil), + }, + { + name: "'ab' < 'aa'", + cql: "'ab' < 'aa'", + wantResult: newOrFatal(t, false), + }, + { + name: "'aa' < 'ab'", + cql: "'aa' < 'ab'", + wantResult: newOrFatal(t, true), + }, + { + name: "'aa' < 'aa'", + cql: "'aa' < 'aa'", + wantResult: newOrFatal(t, false), + }, + { + name: "'ab' < null", + cql: "'ab' < null", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestLessOrEqual(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "2 <= 1", + cql: "2 <= 1", + wantModel: &model.LessOrEqual{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("2", types.Integer), + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "1 <= 2", + cql: "1 <= 2", + wantResult: newOrFatal(t, true), + }, + { + name: "1 <= 1", + cql: "1 <= 1", + wantResult: newOrFatal(t, true), + }, + { + name: "1 <= null", + cql: "1 <= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "2L <= 1L", + cql: "2L <= 1L", + wantResult: newOrFatal(t, false), + }, + { + name: "1L <= 2L", + cql: "1L <= 2L", + wantResult: newOrFatal(t, true), + }, + { + name: "1L <= 1L", + cql: "1L <= 1L", + wantResult: newOrFatal(t, true), + }, + { + name: "1L <= null", + cql: "1L <= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "1.1 <= 1.0", + cql: "1.1 <= 1.0", + wantResult: newOrFatal(t, false), + }, + { + name: "1.0 <= 2.0", + cql: "1.0 <= 2.0", + wantResult: newOrFatal(t, true), + }, + { + name: "1.0 <= 1.0", + cql: "1.0 <= 1.0", + wantResult: newOrFatal(t, true), + }, + { + name: "2.0 <= null", + cql: "2.0 <= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020 <= @2019", + cql: "@2020 <= @2019", + wantResult: newOrFatal(t, false), + }, + { + name: "@2019 <= @2020", + cql: "@2019 <= @2020", + wantResult: newOrFatal(t, true), + }, + { + name: "@2019 <= @2019", + cql: "@2019 <= @2019", + wantResult: newOrFatal(t, true), + }, + { + name: "@2019 <= null", + cql: "@2019 <= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01 <= @2020 left has greater precision", + cql: "@2020-01 <= @2020", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-01-02T02 <= @2020-01-02T01", + cql: "@2020-01-02T02:01:00.000Z <= @2020-01-02T01:01:00.000Z", + wantResult: newOrFatal(t, false), + }, + { + name: "@2020-01-02T01 <= @2020-01-02T02", + cql: "@2020-01-02T01:01:00.000Z <= @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-01-02T02 <= @2020-01-02T02", + cql: "@2020-01-02T02:01:00.000Z <= @2020-01-02T02:01:00.000Z", + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-01-02T01 <= null", + cql: "@2020-01-02T01:01:00.000Z <= null", + wantResult: newOrFatal(t, nil), + }, + { + name: "'ab' <= 'aa'", + cql: "'ab' <= 'aa'", + wantResult: newOrFatal(t, false), + }, + { + name: "'aa' <= 'ab'", + cql: "'aa' <= 'ab'", + wantResult: newOrFatal(t, true), + }, + { + name: "'aa' <= 'aa'", + cql: "'aa' <= 'aa'", + wantResult: newOrFatal(t, true), + }, + { + name: "'ab' <= null", + cql: "'ab' <= null", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/operator_datetime_test.go b/tests/enginetests/operator_datetime_test.go new file mode 100644 index 0000000..4568a5f --- /dev/null +++ b/tests/enginetests/operator_datetime_test.go @@ -0,0 +1,1071 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestCanConvertQuantity(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "CanConvertQuantity(1 year, null) returns null", + cql: "CanConvertQuantity(1 year, null)", + wantModel: &model.CanConvertQuantity{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.Quantity{ + Value: 1, + Unit: "year", + Expression: model.ResultType(types.Quantity), + }, + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.String), + }, + AsTypeSpecifier: types.String, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "CanConvertQuantity(null, 'y') returns null", + cql: "CanConvertQuantity(null, 'y')", + wantResult: newOrFatal(t, nil), + }, + // CanConvertQuantity is unsupported, should return false for all cases. + { + name: "CanConvertQuantity(1 year, 'mo')", + cql: "CanConvertQuantity(1 year, 'mo')", + wantResult: newOrFatal(t, false), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestDateTimeOperatorBefore(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "True", + cql: "@2020-03-01 before day of @2020-03-02", + wantModel: &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-01", types.Date), + model.NewLiteral("@2020-03-02", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "False", + cql: "@2020-03-04 before day of @2020-03-02", + wantResult: newOrFatal(t, false), + }, + { + name: "Equal dates false", + cql: "@2020-03-01 before day of @2020-03-01", + wantResult: newOrFatal(t, false), + }, + { + name: "Left null", + // Ambiguous match without casting. + cql: "(null as Date) before day of @2020-03-02", + wantResult: newOrFatal(t, nil), + }, + { + name: "Right null", + // Ambiguous match without casting. + cql: "@2020 before day of (null as Date)", + wantResult: newOrFatal(t, nil), + }, + { + name: "No precision", + cql: "@2014-01-01 before @2015-01-01", + wantResult: newOrFatal(t, true), + }, + // DateTime tests + { + name: "Equal datetimes false", + cql: "@2024-02-29T01:20:30.101-07:00 before day of @2024-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, false), + }, + { + name: "Same Year Different Date at Year Precision is False", + cql: "@2024-02-29T01:20:30.101-07:00 before year of @2024-02-28T01:20:30.101-07:00", + wantResult: newOrFatal(t, false), + }, + { + name: "Month Precision Before True Even Though Day Precision is Before", + cql: "@2024-02-29T01:20:30.101-07:00 before month of @2024-03-31T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Timezone offset taken into account", + cql: "@2024-02-19T01:20-04:00 before hour of @2024-02-19T01:20-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Timezone not applied for day precision", + // If timezones were normalized this would be @2024-02-19TZ before day of @2024-02-19TZ + // which would be false. + cql: "@2024-02-18T-04:00 before day of @2024-02-19TZ", + wantResult: newOrFatal(t, true), + }, + { + name: "Timezone applied because the left DateTime is hour precision", + cql: "@2024-02-18T23-04:00 before day of @2024-02-19TZ", + wantResult: newOrFatal(t, false), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestDateTimeOperatorBefore_Error(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "Invalid Precision Date", + cql: "@2020-03-01 before second of @2020-03-02", + wantModel: &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-01", types.Date), + model.NewLiteral("@2020-03-02", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.SECOND, + }, + wantEvalErrContains: "precision must be one of", + }, + { + name: "Invalid Precision DateTime", + cql: "@2024-02-29T01:20:30.101-07:00 before week of @2024-03-31T01:20:30.101-07:00", + wantEvalErrContains: "precision must be one of", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead for cql: %s", tc.cql) + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents. got: %v, want contains: %v", err.Error(), tc.wantEvalErrContains) + } + }) + } +} + +func TestDateTimeOperatorAfter(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "@2020-03-04 after day of @2020-03-02", + cql: "@2020-03-04 after day of @2020-03-02", + wantModel: &model.After{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-04", types.Date), + model.NewLiteral("@2020-03-02", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-03-01 after day of @2020-03-02 returns false", + cql: "@2020-03-01 after day of @2020-03-02", + wantResult: newOrFatal(t, false), + }, + { + name: "null after day of @2020-03-02 returns null", + // Ambiguous match without casting. + cql: "(null as Date) after day of @2020-03-02", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020 after day of null returns null", + // Ambiguous match without casting. + cql: "@2020 after day of (null as Date)", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020-03-01 after day of @2020-03-01 returns false", + cql: "@2020-03-01 after day of @2020-03-01", + wantResult: newOrFatal(t, false), + }, + // DateTime tests + { + name: "@2024-02-29T01:20:30.101-07:00 after day of @2024-02-29T01:20:30.101-07:00 returns false", + cql: "@2024-02-29T01:20:30.101-07:00 after day of @2024-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, false), + }, + { + name: "Same Year Different Date at Year Precision is False", + cql: "@2024-05-29T01:20:30.101-07:00 after year of @2024-03-31T01:20:30.101-07:00", + wantResult: newOrFatal(t, false), + }, + { + name: "Month Precision After True Even Though Day Precision is Before", + cql: "@2024-05-29T01:20:30.101-07:00 after month of @2024-03-31T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Timezone offset taken into account", + cql: "@2024-02-19T01:20+04:00 after hour of @2024-02-19T01:20+07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Timezone not applied for day precision", + // If timezones were normalized this would be @2024-02-17TZ after day of @2024-02-17TZ which + // would be false. + cql: "@2024-02-18T+04:00 after day of @2024-02-17TZ", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + gotResult := getTESTRESULT(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestDateTimeOperatorDifferenceBetween(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "difference in years between @2020 and @2022", + cql: "difference in years between @2020 and @2022", + wantModel: &model.DifferenceBetween{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020", types.Date), + model.NewLiteral("@2022", types.Date), + }, + Expression: model.ResultType(types.Integer), + }, + Precision: model.YEAR, + }, + wantResult: newOrFatal(t, 2), + }, + { + name: "difference in years between @2022 and @2020", + cql: "difference in years between @2022 and @2020", + wantResult: newOrFatal(t, -2), + }, + { + name: "difference in years between null and @2022 returns null", + cql: "difference in years between null and @2022", + wantResult: newOrFatal(t, nil), + }, + { + name: "difference in years between @2020 and null returns null", + cql: "difference in years between @2020 and null", + wantResult: newOrFatal(t, nil), + }, + { + name: "difference in months between @2020-10 and @2022-02", + cql: "difference in months between @2020-10 and @2022-02", + wantResult: newOrFatal(t, 16), + }, + { + name: "difference in weeks from monday to saturday returns zero", + cql: "difference in weeks between @2023-11-20 and @2023-11-25", + wantResult: newOrFatal(t, 0), + }, + { + name: "difference in weeks from monday to sunday returns 1", + cql: "difference in weeks between @2023-11-20 and @2023-11-26", + wantResult: newOrFatal(t, 1), + }, + { + name: "difference in weeks from saturday to saturday returns 1", + cql: "difference in weeks between @2023-11-25 and @2023-12-02", + wantResult: newOrFatal(t, 1), + }, + { + name: "difference in weeks from saturday to saturday a week later returns 2", + cql: "difference in weeks between @2023-11-25 and @2023-12-03", + wantResult: newOrFatal(t, 2), + }, + { + name: "difference in days from saturday to monday returns 2", + cql: "difference in days between @2023-11-25 and @2023-11-27", + wantResult: newOrFatal(t, 2), + }, + // DateTime tests + { + name: "difference in years between @2022-02-22T01:20:30.101-07:00 and @2024-02-22T01:20:30.101-07:00", + cql: "difference in years between @2022-02-22T01:20:30.101-07:00 and @2024-02-22T01:20:30.101-07:00", + wantResult: newOrFatal(t, 2), + }, + { + name: "difference in hours between @2014-01-01T01:01:00.000Z and @2014-01-02T01:01:00.000Z", + cql: "difference in hours between @2014-01-01T01:01:00.000Z and @2014-01-02T01:01:00.000Z", + wantResult: newOrFatal(t, 24), + }, + { + name: "difference in months between @2014-01-01T01:01:00.000Z and @2014-02", + cql: "difference in months between @2014-01-01T01:01:00.000Z and @2014-02", + wantResult: newOrFatal(t, 1), + }, + { + name: "difference in milliseconds between @2022-02-22T01:20:30.101-07:00 and @2022-02-22T01:20:30.105-07:00", + cql: "difference in milliseconds between @2022-02-22T01:20:30.101-07:00 and @2022-02-22T01:20:30.105-07:00", + wantResult: newOrFatal(t, 4), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + gotResult := getTESTRESULT(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestDateTimeOperatorDifferencebetween_Error(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "difference in months between 2014 and 2016 return error invalid precision", + cql: "difference in months between @2014 and @2016", + wantModel: &model.DifferenceBetween{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2014", types.Date), + model.NewLiteral("@2016", types.Date), + }, + Expression: model.ResultType(types.Integer), + }, + Precision: model.MONTH, + }, + wantEvalErrContains: "difference between specified a precision greater than argument precision", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead for cql: %s", tc.cql) + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents. got: %v, want contains: %v", err.Error(), tc.wantEvalErrContains) + } + }) + } +} + +func TestDateTimeOperatorSameOrAfter(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // same or after syntax + { + name: "@2020-03-04 same day or after @2020-03-02", + cql: "@2020-03-04 same day or after @2020-03-02", + wantModel: &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-04", types.Date), + model.NewLiteral("@2020-03-02", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-03-01 same day or after @2020-03-02 returns false", + cql: "@2020-03-01 same day or after @2020-03-02", + wantResult: newOrFatal(t, false), + }, + // on or after operator + { + name: "@2020-03-04 on or after day of @2020-03-02", + cql: "@2020-03-04 on or after day of @2020-03-02", + wantModel: &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-04", types.Date), + model.NewLiteral("@2020-03-02", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-03-01 on or after day of @2020-03-02 returns false", + cql: "@2020-03-01 on or after day of @2020-03-02", + wantResult: newOrFatal(t, false), + }, + { + name: "null on or after year of @2020 returns null", + // Ambiguous match without casting. + cql: "(null as Date) on or after year of @2020", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020 on or after year of null returns null", + // Ambiguous match without casting. + cql: "@2020 on or after year of (null as Date)", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2024-02-29T01:20:30.101-07:00 on or after day of @2024-02-29T01:20:30.101-07:00", + cql: "@2024-02-29T01:20:30.101-07:00 on or after day of @2024-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Same Year Different Date at Year Precision is True", + cql: "@2024-03-30T01:20:30.101-07:00 on or after year of @2024-02-28T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Month Precision After True Even Though Day Precision is Before", + cql: "@2024-05-29T01:20:30.101-07:00 on or after month of @2024-03-31T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestDateTimeOperatorSameOrBefore(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // same or before syntax + { + name: "@2020-03-01 same day or before @2020-03-02", + cql: "@2020-03-01 same day or before @2020-03-02", + wantModel: &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-01", types.Date), + model.NewLiteral("@2020-03-02", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-03-04 same day or before @2020-03-02 returns false", + cql: "@2020-03-04 same day or before @2020-03-02", + wantResult: newOrFatal(t, false), + }, + // on or before operator + { + name: "@2020-03-01 on or before day of @2020-03-02", + cql: "@2020-03-01 on or before day of @2020-03-02", + wantModel: &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-01", types.Date), + model.NewLiteral("@2020-03-02", types.Date), + }, + Expression: model.ResultType(types.Boolean), + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "@2020-03-04 on or before day of @2020-03-02 returns false", + cql: "@2020-03-04 on or before day of @2020-03-02", + wantResult: newOrFatal(t, false), + }, + { + name: "null on or before year of @2020 returns null", + // Ambiguous match without casting. + cql: "(null as Date) on or before year of @2020", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2020 on or before year of null returns null", + // Ambiguous match without casting. + cql: "@2020 on or before year of (null as Date)", + wantResult: newOrFatal(t, nil), + }, + { + name: "@2024-02-29T01:20:30.101-07:00 on or before day of @2024-02-29T01:20:30.101-07:00", + cql: "@2024-02-29T01:20:30.101-07:00 on or before day of @2024-02-29T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Same Year Different Date at Year Precision is True", + cql: "@2024-02-29T01:20:30.101-07:00 on or before year of @2024-03-31T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "Month Precision Before True Even Though Day Precision is After", + cql: "@2024-02-29T01:20:30.101-07:00 on or before month of @2024-03-31T01:20:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestEvaluationTimestamp(t *testing.T) { + tests := []struct { + name string + cql string + evaluationTimestamp time.Time + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Now returns passed evaluation timestamp", + cql: "define TESTRESULT: Now()", + evaluationTimestamp: time.Date(2024, time.January, 1, 0, 0, 0, 1, time.UTC), + wantModel: &model.Now{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{}, + Expression: model.ResultType(types.DateTime), + }, + }, + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.January, 1, 0, 0, 0, 1, time.UTC), Precision: model.MILLISECOND}), + }, + { + name: "Time returns passed evaluation timestamp time components", + cql: "define TESTRESULT: TimeOfDay()", + evaluationTimestamp: time.Date(0, time.January, 1, 1, 2, 3, 4, time.UTC), + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 1, 2, 3, 4, time.UTC), Precision: model.MILLISECOND}), + }, + { + name: "Today truncates time values", + cql: "define TESTRESULT: Today()", + evaluationTimestamp: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := fmt.Sprintf(dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + %v`), tc.cql) + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + config := defaultInterpreterConfig(t, p) + config.EvaluationTimestamp = tc.evaluationTimestamp + results, err := interpreter.Eval(context.Background(), parsedLibs, config) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestDate(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Year", + cql: "Date(2014)", + wantModel: &model.Date{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("2014", types.Integer), + }, + Expression: model.ResultType(types.Date), + }, + }, + wantResult: newOrFatal(t, result.Date{Date: time.Date(2014, time.January, 1, 0, 0, 0, 0, time.UTC), Precision: model.YEAR}), + }, + { + name: "Month", + cql: "Date(2014, 9)", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2014, time.September, 1, 0, 0, 0, 0, time.UTC), Precision: model.MONTH}), + }, + { + name: "Day", + cql: "Date(2014, 9, 4)", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2014, time.September, 4, 0, 0, 0, 0, time.UTC), Precision: model.DAY}), + }, + { + name: "Functional and string constructors equal", + cql: "Date(2014, 9, 4) = @2014-09-04", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestDateTime(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Year", + cql: "DateTime(2014)", + wantModel: &model.DateTime{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("2014", types.Integer), + }, + Expression: model.ResultType(types.DateTime), + }, + }, + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.January, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}), + }, + { + name: "Month", + cql: "DateTime(2014, 9)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.September, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MONTH}), + }, + { + name: "Day", + cql: "DateTime(2014, 9, 4)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.September, 4, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "Hour", + cql: "DateTime(2014, 9, 4, 12)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.September, 4, 12, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.HOUR}), + }, + { + name: "Minute", + cql: "DateTime(2014, 9, 4, 12, 30)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.September, 4, 12, 30, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MINUTE}), + }, + { + name: "Second", + cql: "DateTime(2014, 9, 4, 12, 30, 30)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.September, 4, 12, 30, 30, 0, defaultEvalTimestamp.Location()), Precision: model.SECOND}), + }, + { + name: "Millisecond", + cql: "DateTime(2014, 9, 4, 12, 30, 30, 100)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.September, 4, 12, 30, 30, 100*1000000, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "Timezone", + cql: "DateTime(2014, 9, 4, 12, 30, 30, 100, -7)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.September, 4, 12, 30, 30, 100*1000000, time.FixedZone("-7", -7*60*60)), Precision: model.MILLISECOND}), + }, + { + name: "Functional and string constructors equal", + cql: "DateTime(2014, 9, 4, 12, 30, 30, 101) = @2014-09-04T12:30:30.101", + wantResult: newOrFatal(t, true), + }, + { + name: "Functional and string constructors equal with timezone", + cql: "DateTime(2014, 9, 4, 12, 30, 30, 101, -7) = @2014-09-04T12:30:30.101-07:00", + wantResult: newOrFatal(t, true), + }, + { + name: "All null arguments", + cql: "DateTime(null, null, null, null, null, null, null, null)", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestTime(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Hour", + cql: "Time(21)", + wantModel: &model.Time{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("21", types.Integer), + }, + Expression: model.ResultType(types.Time), + }, + }, + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 21, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.HOUR}), + }, + { + name: "Minute", + cql: "Time(12, 30)", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 12, 30, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MINUTE}), + }, + { + name: "Second", + cql: "Time(12, 30, 30)", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 12, 30, 30, 0, defaultEvalTimestamp.Location()), Precision: model.SECOND}), + }, + { + name: "Millisecond", + cql: "Time(12, 30, 30, 100)", + wantResult: newOrFatal(t, result.Time{Date: time.Date(0, time.January, 1, 12, 30, 30, 100*1000000, defaultEvalTimestamp.Location()), Precision: model.MILLISECOND}), + }, + { + name: "Functional and string constructors equal", + cql: "Time(12, 30, 30, 101) = @T12:30:30.101", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestDateTimeConstructor_Errors(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantErr string + }{ + { + name: "Date val after null", + cql: "Date(2014, null, 15)", + wantErr: "when constructing Date precision day had value 15, even though a higher precision was null", + }, + { + name: "Date starts with null", + cql: "Date(null)", + wantErr: "in Date year cannot be null", + }, + { + name: "Date year outside range", + cql: "Date(-1)", + wantErr: "year -1 is out of range", + }, + { + name: "Date day outside range", + cql: "Date(2014, 5, 32)", + wantErr: "day 32 is out of range", + }, + { + name: "Time val after null", + cql: "Time(12, null, 30)", + wantErr: "when constructing Time precision second had value 30, even though a higher precision was null", + }, + { + name: "Time starts with null", + cql: "Time(null)", + wantErr: "in Time hour cannot be null", + }, + { + name: "Time hour outside range", + cql: "Time(25)", + wantErr: "hour 25 is out of range", + }, + { + name: "Time millisecond outside range", + cql: "Time(12, 30, 30, -100)", + wantErr: "millisecond -100 is out of range", + }, + { + name: "DateTime year outside range", + cql: "DateTime(99999999)", + wantErr: "year 99999999 is out of range", + }, + { + name: "DateTime millisecond outside range", + cql: "DateTime(2014, 9, 4, 12, 30, 30, -101, -7)", + wantErr: "millisecond -101 is out of range", + }, + { + name: "DateTime timezone our of range", + cql: "DateTime(2014, 9, 4, 12, 30, 30, 101, -14.1)", + wantErr: "timezone offset -14.1 is out of range", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatal("Evaluate Expression expected an error to be returned, got nil instead") + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("Unexpected evaluation error contents. got (%v), want (%v)", err.Error(), tc.wantErr) + } + }) + } +} diff --git a/tests/enginetests/operator_interval_test.go b/tests/enginetests/operator_interval_test.go new file mode 100644 index 0000000..19d7622 --- /dev/null +++ b/tests/enginetests/operator_interval_test.go @@ -0,0 +1,1559 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestEnd(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "High inclusive", + cql: "end of Interval[1, 2]", + wantModel: &model.End{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.Interval{ + Low: model.NewLiteral("1", types.Integer), + High: model.NewLiteral("2", types.Integer), + LowInclusive: true, + HighInclusive: true, + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + }, + }, + }, + wantResult: newOrFatal(t, 2), + }, + { + name: "High exclusive null", + cql: "end of Interval(5, null)", + wantModel: &model.End{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.Interval{ + Low: model.NewLiteral("5", types.Integer), + High: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Integer, + }, + LowInclusive: false, + HighInclusive: false, + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + }, + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Null", + cql: "end of null", + wantResult: newOrFatal(t, nil), + }, + { + name: "High inclusive null", + cql: "end of Interval[2, null]", + wantResult: newOrFatal(t, int32(2147483647)), + }, + { + name: "High inclusive with all runtime nulls", + cql: "end of Interval[null as Integer, null as Integer]", + wantResult: newOrFatal(t, int32(2147483647)), + }, + { + name: "High exclusive returns predecessor integer", + cql: "end of Interval[2, 43)", + wantResult: newOrFatal(t, int32(42)), + }, + { + name: "High exclusive returns predecessor date", + cql: "end of Interval[@2012-01, @2013-01)", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2012, time.December, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MONTH}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestStart(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Low inclusive", + cql: "start of Interval[1, 2]", + wantModel: &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.Interval{ + Low: model.NewLiteral("1", types.Integer), + High: model.NewLiteral("2", types.Integer), + LowInclusive: true, + HighInclusive: true, + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + }, + }, + }, + wantResult: newOrFatal(t, 1), + }, + { + name: "Low exclusive null", + cql: "start of Interval(null, 2)", + wantModel: &model.Start{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.Interval{ + Low: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Integer, + }, + High: model.NewLiteral("2", types.Integer), + LowInclusive: false, + HighInclusive: false, + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + }, + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Null", + cql: "start of null", + wantResult: newOrFatal(t, nil), + }, + { + name: "Low inclusive null", + cql: "start of Interval[null, 2)", + wantResult: newOrFatal(t, int32(-2147483648)), + }, + { + name: "Low inclusive with all runtime nulls", + cql: "start of Interval[null as Integer, null as Integer)", + wantResult: newOrFatal(t, int32(-2147483648)), + }, + { + name: "Low exclusive returns predecessor integer", + cql: "start of Interval(41, 50]", + wantResult: newOrFatal(t, int32(42)), + }, + { + name: "Low exclusive returns predecessor date", + cql: "start of Interval(@2012-11, @2013-01]", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2012, time.December, 1, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.MONTH}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Evaluate diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIntervalBefore(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // Date, Date overloads: + { + name: "Date before interval", + cql: "@2020-03-04 before day of Interval[@2020-03-05, @2020-03-07]", + wantModel: &model.Before{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-04", types.Date), + &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + Low: model.NewLiteral("@2020-03-05", types.Date), + High: model.NewLiteral("@2020-03-07", types.Date), + LowInclusive: true, + HighInclusive: true, + }, + }, + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "Date before null Interval", + cql: "@2024-02-28 before null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null Date before Interval", + cql: "null as Date before Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Date before interval without precision", + cql: "@2020-03-04 before Interval[@2020-03-05, @2020-03-07]", + wantResult: newOrFatal(t, true), + }, + { + name: "Date day matches interval start", + cql: "@2020-03-05 before day of Interval[@2020-03-05, @2020-03-07]", + wantResult: newOrFatal(t, false), + }, + { + name: "Date Interval right null", + cql: "@2020-03-04 before day of Interval[@2020-03-05, null]", + wantResult: newOrFatal(t, true), + }, + { + name: "Date Interval left null", + cql: "@2020-03-04 before day of Interval[null, @2020-03-07]", + wantResult: newOrFatal(t, false), + }, + // DateTime, DateTime overloads: + { + name: "DateTime before null Interval", + cql: "@2024-02-28T01:20:30.101-07:00 before null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null DateTime before Interval", + cql: "null as DateTime before Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, nil), + }, + { + name: "DateTime before interval", + cql: "@2024-02-28T01:20:30.101-07:00 before day of Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime before interval without precision", + cql: "@2024-02-28T01:20:30.101-07:00 before Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime matches closed interval start", + cql: "@2024-02-29T01:20:30.101-07:00 before day of Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTime Interval right null", + cql: "@2020-03-04T before day of Interval[@2020-03-05T, null]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime Interval left null", + cql: "@2020-03-04T before day of Interval[null, @2020-03-07T]", + wantResult: newOrFatal(t, false), + }, + // Interval, Interval overloads: + { + name: "Interval before null as Interval", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00] before null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null as Interval before Interval", + cql: "null as Interval before Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Interval before Interval at seconds precision", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:10.101-07:00] before second of Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval before Interval without precision", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00] before Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval end matches closed right interval start", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] before Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, false), + }, + { + name: "Left Interval end matches open right interval start", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] before Interval(@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval before right Interval with null end", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-28T01:20:30.101-07:00] before Interval[@2024-02-29T01:20:30.101-07:00, null]", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval before right Interval with null start", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-28T01:20:30.101-07:00] before Interval[null, @2024-02-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, false), + }, + // Sanity check some Interval, Interval overloads + { + name: "Interval before Interval at day precision", + cql: "Interval[@2024-01-25, @2024-02-28] before day of Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval before Interval without precision", + cql: "Interval[@2024-01-25, @2024-02-28] before Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval end matches closed right interval start", + cql: "Interval[@2024-01-25, @2024-02-29] before Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, false), + }, + { + name: "Left Interval end matches open right interval start", + cql: "Interval[@2024-01-25, @2024-02-29] before Interval(@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, true), + }, + // starts or ends before syntax: + { + name: "Interval starts before another", + cql: "Interval[@2024, @2026] starts before Interval[@2027, @2027]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not start before another", + cql: "Interval[@2024, @2026] starts before Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "null Interval starts before Interval", + cql: "null as Interval starts before Interval[@2024, @2027]", + wantResult: newOrFatal(t, nil), + }, + { + name: "null low bound starts before Interval", + cql: "Interval[null, @2027] starts before Interval[@2024, @2027]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval ends after another", + cql: "Interval[@2024, @2028] ends after Interval[@2027, @2027]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not end after another", + cql: "Interval[@2024, @2026] ends after Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "null Interval ends after Interval", + cql: "null as Interval ends after Interval[@2024, @2027]", + wantResult: newOrFatal(t, nil), + }, + { + name: "null high bound ends after Interval", + cql: "Interval[@2024, null] ends after Interval[@2024, @2027]", + wantResult: newOrFatal(t, true), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIntervalAfter(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // Date, Date overloads: + { + name: "Day precision true", + cql: "@2020-03-09 after day of Interval[@2020-03-05, @2020-03-07]", + wantModel: &model.After{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-09", types.Date), + &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + Low: model.NewLiteral("@2020-03-05", types.Date), + High: model.NewLiteral("@2020-03-07", types.Date), + LowInclusive: true, + HighInclusive: true, + }, + }, + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "Date after null Interval", + cql: "@2024-02-28 after null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null Date after Interval", + cql: "null as Date after Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Same day is false", + cql: "@2020-03-07 after day of Interval[@2020-03-05, @2020-03-07]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval right null", + cql: "@2020-03-09 after day of Interval[@2020-03-05, null]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval left null", + cql: "@2020-03-09 after day of Interval[null, @2020-03-07]", + wantResult: newOrFatal(t, true), + }, + // DateTime, DateTime overloads: + { + name: "DateTime after null Interval", + cql: "@2024-02-28T01:20:30.101-07:00 after null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null DateTime after Interval", + cql: "null as DateTime after Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, nil), + }, + { + name: "DateTime after interval", + cql: "@2024-04-28T01:20:30.101-07:00 after day of Interval[@2024-02-28T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime after interval without precision", + cql: "@2024-04-28T01:20:30.101-07:00 after Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime equals end of closed interval", + cql: "@2024-03-28T01:20:30.101-07:00 after day of Interval[@2024-02-28T01:20:30.101-07:00, @2024-03-28T01:20:30.101-07:00]", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTime Interval right null", + cql: "@2024-02-28T01:20:30.101-07:00 after day of Interval[@2024-01-28T01:20:30.101-07:00, null]", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTime after Interval with left null", + cql: "@2024-02-29T01:20:30.101-07:00 after day of Interval[null, @2024-02-28T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + // Interval, Interval overloads: + { + name: "Interval after null as Interval", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00] after null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null as Interval after Interval", + cql: "null as Interval after Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Interval after Interval at seconds precision", + cql: "Interval[@2024-02-10T01:20:45.101-07:00, @2024-02-29T01:20:10.101-07:00] after second of Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-10T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval after Interval with no precision", + cql: "Interval[@2024-02-12T01:20:30.101-07:00, @2024-02-29T01:20:10.101-07:00] after Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-10T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval start matches closed right Interval end", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] after day of Interval[@2024-01-20T01:20:30.101-07:00, @2024-01-25T01:20:30.101-07:00]", + wantResult: newOrFatal(t, false), + }, + { + name: "Left Interval start matches open right Interval end", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] after Interval[@2024-01-20T01:20:30.101-07:00, @2024-01-25T01:20:30.101-07:00)", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval after right Interval with null end", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-28T01:20:30.101-07:00] after Interval[@2024-02-29T01:20:30.101-07:00, null]", + wantResult: newOrFatal(t, false), + }, + { + name: "Left Interval after right Interval with null start", + cql: "Interval[@2024-04-25T01:20:30.101-07:00, @2024-05-28T01:20:30.101-07:00] after Interval[null, @2024-02-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + // Sanity check some Interval, Interval overloads + { + name: "Interval after Interval at day precision", + cql: "Interval[@2024-02-29, @2024-03-29] after day of Interval[@2024-01-29, @2024-02-28]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval after Interval without precision", + cql: "Interval[@2024-01-25, @2024-02-28] after Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, false), + }, + // starts or ends after syntax: + { + name: "Interval starts after another", + cql: "Interval[@2028, @2029] starts after Interval[@2027, @2027]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not start after another", + cql: "Interval[@2021, @2026] starts after Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "null Interval starts after Interval", + cql: "null as Interval starts after Interval[@2024, @2027]", + wantResult: newOrFatal(t, nil), + }, + { + name: "null low bound starts after Interval", + cql: "Interval[null, @2027] starts after Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval ends after another", + cql: "Interval[@2024, @2028] ends after Interval[@2027, @2027]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not end after another", + cql: "Interval[@2024, @2026] ends after Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "null Interval ends after Interval", + cql: "null as Interval ends after Interval[@2024, @2027]", + wantResult: newOrFatal(t, nil), + }, + { + name: "null high bound ends after Interval", + cql: "Interval[@2024, null] ends after Interval[@2024, @2027]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval starts after another", + cql: "Interval[@2028T, @2029T] starts after Interval[@2027T, @2027T]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not start after another", + cql: "Interval[@2021T, @2026T] starts after Interval[@2024T, @2027T]", + wantResult: newOrFatal(t, false), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIntervalSameOrBefore(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Day precision true", + cql: "@2020-03-04 on or before day of Interval[@2020-03-05, @2020-03-07]", + wantModel: &model.SameOrBefore{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-04", types.Date), + &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + Low: model.NewLiteral("@2020-03-05", types.Date), + High: model.NewLiteral("@2020-03-07", types.Date), + LowInclusive: true, + HighInclusive: true, + }, + }, + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "Date on or before null as Interval", + cql: "@2020-03-04 on or before null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "Same day is true", + cql: "@2020-03-05 on or before day of Interval[@2020-03-05, @2020-03-07]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval right null", + cql: "@2020-03-04 on or before day of Interval[@2020-03-05, null]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval left null", + cql: "@2020-03-04 on or before day of Interval[null, @2020-03-07]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval left null with minimum Date", + // TODO(b/329322517): swap to minimum date literal when we can represent it with functional + // syntax (does not currently parse). + cql: "@0001-01-01 on or before day of Interval[null, @2020-03-07]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime before interval", + cql: "@2024-02-28T01:20:30.101-07:00 on or before day of Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime before interval without precision", + cql: "@2024-02-28T01:20:30.101-07:00 on or before Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime matches closed interval start", + cql: "@2024-02-29T01:20:30.101-07:00 on or before day of Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime Interval right null", + cql: "@2020-03-04T on or before day of Interval[@2020-03-05T, null]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime Interval left null", + cql: "@2020-03-04T on or before day of Interval[null, @2020-03-07T]", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTime Interval left null with minimum DateTime", + // TODO(b/329322517): swap to minimum date literal when we can represent it with functional + // syntax (does not currently parse). + cql: "minimum DateTime on or before day of Interval[null, @2020-03-07T]", + wantResult: newOrFatal(t, true), + }, + // Interval, Interval overloads: + { + name: "Interval on or before null as Interval", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00] on or before null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null as Interval on or before Interval", + cql: "null as Interval on or before Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Interval on or before Interval at seconds precision", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:10.101-07:00] on or before second of Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval on or before Interval without precision", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00] on or before Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + // For "on or before" this is true, whereas for "before" this is false + name: "Left Interval end equals closed right interval start", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] on or before Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval end equals open right interval start", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] on or before Interval(@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + // Sanity check some Interval, Interval overloads + { + name: "Left Interval end equals closed right interval start", + cql: "Interval[@2024-01-25, @2024-02-28] on or before Interval[@2024-02-28, @2024-03-29]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval before Interval at day precision", + cql: "Interval[@2024-01-25, @2024-02-28] on or before day of Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, true), + }, + // starts or ends before syntax with Dates: + { + name: "Interval starts on or before another", + // For "on or before" this is true, whereas for "before" this is false + cql: "Interval[@2024, @2026] starts on or before Interval[@2024, @2027]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not start on or before another", + cql: "Interval[@2025, @2026] starts on or before Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval ends on or before another", + // For "on or before" this is true, whereas for "before" this is false + cql: "Interval[@2024, @2028] ends on or before Interval[@2028, @2029]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not end on or before another", + cql: "Interval[@2025, @2026] ends on or before Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval starts on or before another", + // For "on or before" this is true, whereas for "before" this is false + cql: "Interval[@2024T, @2026T] starts on or before Interval[@2024T, @2027T]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval ends on or before another", + // For "on or before" this is true, whereas for "before" this is false + cql: "Interval[@2024T, @2028T] ends on or before Interval[@2028T, @2029T]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval starts on or before another with open bounds", + cql: "Interval[@2024, @2026] starts on or before Interval(@2024, @2027)", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval starts on or before another with open bounds both sides", + cql: "Interval(@2024, @2026] starts on or before Interval(@2024, @2027)", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not start on or before another with open bounds", + cql: "Interval(@2024, @2026] starts on or before Interval(@2023, @2027)", + wantResult: newOrFatal(t, false), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIntervalSameOrAfter(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Day precision true", + cql: "@2020-03-09 on or after day of Interval[@2020-03-05, @2020-03-07]", + wantModel: &model.SameOrAfter{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("@2020-03-09", types.Date), + &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + Low: model.NewLiteral("@2020-03-05", types.Date), + High: model.NewLiteral("@2020-03-07", types.Date), + LowInclusive: true, + HighInclusive: true, + }, + }, + }, + Precision: model.DAY, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "Date on or after null as Interval", + cql: "@2020-03-04 on or after null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "Same day is true", + cql: "@2020-03-07 on or after day of Interval[@2020-03-05, @2020-03-07]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval right null", + cql: "@2020-03-09 on or after day of Interval[@2020-03-05, null]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval right null with max date", + cql: "@9999-12-31 on or after day of Interval[@2020-03-05, null]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval left null", + cql: "@2020-03-09 on or after day of Interval[null, @2020-03-07]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime after interval", + cql: "@2024-04-28T01:20:30.101-07:00 on or after day of Interval[@2024-02-28T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime after interval without precision", + cql: "@2024-04-28T01:20:30.101-07:00 on or after Interval[@2024-02-29T01:20:30.101-07:00, @2024-03-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime equals end of closed interval", + cql: "@2024-03-28T01:20:30.101-07:00 on or after day of Interval[@2024-02-28T01:20:30.101-07:00, @2024-03-28T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime Interval right null", + cql: "@2024-02-28T01:20:30.101-07:00 on or after day of Interval[@2024-01-28T01:20:30.101-07:00, null]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval right null with max DateTime", + cql: "maximum DateTime on or after day of Interval[@2020-03-05T, null]", + wantResult: newOrFatal(t, true), + }, + { + name: "DateTime after Interval with left null", + cql: "@2024-02-29T01:20:30.101-07:00 on or after day of Interval[null, @2024-02-28T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + // Interval, Interval overloads: + { + name: "Interval on or after null as Interval", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00] on or after null as Interval", + wantResult: newOrFatal(t, nil), + }, + { + name: "null as Interval on or after Interval", + cql: "null as Interval on or after Interval[@2024-01-25T01:20:30.101-07:00, @2024-01-29T01:20:30.101-07:00]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Interval on or after Interval at seconds precision", + cql: "Interval[@2024-02-10T01:20:45.101-07:00, @2024-02-29T01:20:10.101-07:00] on or after second of Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-10T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval on or after Interval with no precision", + cql: "Interval[@2024-02-12T01:20:30.101-07:00, @2024-02-29T01:20:10.101-07:00] on or after Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-10T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + // 'on or after' returns true here, whereas 'after' would return false + name: "Left Interval start matches closed right Interval end", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] on or after Interval[@2024-01-20T01:20:30.101-07:00, @2024-01-25T01:20:30.101-07:00]", + wantResult: newOrFatal(t, true), + }, + { + name: "Left Interval start matches open right Interval end", + cql: "Interval[@2024-01-25T01:20:30.101-07:00, @2024-02-29T01:20:30.101-07:00] after Interval[@2024-01-20T01:20:30.101-07:00, @2024-01-25T01:20:30.101-07:00)", + wantResult: newOrFatal(t, true), + }, + // Sanity check some Interval, Interval overloads + { + name: "Interval on or after Interval at day precision", + cql: "Interval[@2024-02-29, @2024-03-29] on or after day of Interval[@2024-01-29, @2024-02-28]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval after Interval without precision", + cql: "Interval[@2024-01-25, @2024-02-28] on or after Interval[@2024-02-29, @2024-03-29]", + wantResult: newOrFatal(t, false), + }, + // starts or ends after syntax: + { + name: "Interval starts on or after another", + // 'on or after' returns true here, whereas 'after' would return false + cql: "Interval[@2029, @2030] starts on or after Interval[@2028, @2029]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not start on or after another", + cql: "Interval[@2021, @2026] starts on or after Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval ends on or after another", + // 'on or after' returns true here, whereas 'after' would return false + cql: "Interval[@2024, @2028] ends on or after Interval[@2027, @2028]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not end after another", + cql: "Interval[@2024, @2026] ends on or after Interval[@2024, @2027]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval starts on or after another with open bounds", + cql: "Interval[@2027, @2028] starts on or after Interval(@2024, @2027)", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval starts on or after another with open bounds both sides", + cql: "Interval(@2027, @2028] starts on or after Interval(@2024, @2027)", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval does not start on or after another with open bounds", + cql: "Interval(@2024, @2026] starts on or after Interval(@2023, @2027)", + wantResult: newOrFatal(t, false), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +// TestIntervalRelativeOffsetBefore test cases where 'or more' or 'or less' operator appears. +func TestIntervalRelativeOffsetBefore(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Interval ends 1 year or less on or before end of another", + cql: "Interval[@2015, @2020] ends 1 year or less on or before end of Interval[@2019, @2022]", + wantModel: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2015", "@2020", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + &model.Interval{ + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + Low: &model.Subtract{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2019", "@2022", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + &model.Quantity{Value: 1, Unit: "year", Expression: model.ResultType(types.Quantity)}, + }, + Expression: model.ResultType(types.Date), + }, + }, + High: &model.End{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewInclusiveInterval("@2019", "@2022", types.Date), + Expression: model.ResultType(types.Date), + }, + }, + LowInclusive: true, + HighInclusive: true, + }, + }, + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "Interval ends 1 year or less on or before end of another", + cql: "Interval[@2015, @2021] ends 1 year or less on or before end of Interval[@2019, @2022]", + wantResult: newOrFatal(t, true), + }, + { + name: "Date 1 year or more on or before start of Interval", + cql: "@2015 1 year or more on or before start of Interval[@2019, @2022]", + wantResult: newOrFatal(t, true), + }, + { + name: "Date 1 year or more on or before end of Interval", + cql: "@2030 1 year or more on or after end of Interval[@2019, @2022]", + wantResult: newOrFatal(t, true), + }, + { + name: "Date 1 year or more on or after end of Interval", + cql: "@2022 1 year or more on or after end of Interval[@2019, @2022]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval ends 1'1' or less on or before end of another", + cql: "Interval[1, 3] ends 1 '1' or less on or before end of Interval[3, 4]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval ends 1'1' or less on or after end of another", + cql: "Interval[5, 8] starts 1 '1' or less on or after end of Interval[3, 4]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval ends 1'1' or more on or before start of another", + cql: "Interval[0, 1] ends 1 '1' or more on or before start of Interval[3, 4]", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval starts 1'1' or more on or after start of another", + cql: "Interval[6, 8] starts 1 '1' or more on or after start of Interval[3, 4]", + wantResult: newOrFatal(t, true), + }, + // Overload matching tests that the or less operator properly applies conversions on manually + // inserted models in the parser. + { + name: "Interval 1 year or less on or before Interval", + cql: "Interval[@2015, @2020] ends 1 year or less on or before Interval[@2019, @2022]", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval 1 year or less on or before DateTime", + cql: "Interval[@2015, @2016] ends 1 year or less on or before @2019-06-06T", + wantResult: newOrFatal(t, false), + }, + { + name: "Date 1 year or less on or before DateTime", + cql: "@2016 1 year or less on or before @2019-06-06T", + wantResult: newOrFatal(t, false), + }, + { + name: "DateTime 1 year or less on or before Date", + cql: "@2016-06-06T 1 year or less on or before @2019", + wantResult: newOrFatal(t, false), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIntervalIn(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Point is null date", + cql: "null in year of Interval[@2020, @2022]", + wantModel: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Date), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Date, + }, + model.NewInclusiveInterval("@2020", "@2022", types.Date), + }, + }, + Precision: model.YEAR, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Point is null datetime", + cql: "null in year of Interval[@2024-03-31T00:00:00.000Z, @2024-03-31T00:00:00.000Z]", + wantResult: newOrFatal(t, nil), + }, + { + name: "On inclusive bound date", + cql: "@2020-03 in month of Interval[@2020-03-25, @2022-04)", + wantResult: newOrFatal(t, true), + }, + { + name: "On inclusive bound datetime", + cql: "@2024-03-31T00:00:00.000Z in month of Interval[@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z)", + wantResult: newOrFatal(t, true), + }, + { + name: "On exclusive bound date", + cql: "@2020-03 in month of Interval(@2020-03-25, @2022-04)", + wantResult: newOrFatal(t, false), + }, + { + name: "On exclusive bound datetime", + cql: "@2024-03-31T00:00:00.000Z in month of Interval(@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z)", + wantResult: newOrFatal(t, false), + }, + { + name: "On exclusive and inclusive bound", + cql: "@2020-03 in month of Interval(@2020-03, @2022-03]", + wantResult: newOrFatal(t, false), + }, + { + name: "Insufficient precision date", + cql: "@2020-03 in day of Interval[@2020-03-25, @2020-04]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Insufficient precision datetime", + cql: "@2024-03 in day of Interval[@2024-03-28T00:00:00.000Z, @2024-03-31T00:00:00.000Z]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Insufficient precision but for sure false", + cql: "@2028-03 in day of Interval[@2020-03-25, @2020-04]", + wantResult: newOrFatal(t, false), + }, + { + name: "Null inclusive bound is true", + cql: "@2020-03 in month of Interval[null, @2022-04)", + wantResult: newOrFatal(t, true), + }, + { + name: "Null inclusive bound but this is for sure false", + cql: "@2025-03 in month of Interval[null, @2022-04)", + wantResult: newOrFatal(t, false), + }, + { + name: "Null exclusive bound is null", + cql: "@2021-03 in month of Interval(null, @2022-04)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Null exclusive bound but this is for sure false", + cql: "@2025-03 in month of Interval(null, @2022-04)", + wantResult: newOrFatal(t, false), + }, + { + name: "No in operator precision: On inclusive bound date", + cql: "@2020-03-25 in Interval[@2020-03-25, @2022-04)", + wantResult: newOrFatal(t, true), + }, + { + name: "No in operator precision: On inclusive bound datetime", + cql: "@2024-03-31T00:00:00.000Z in Interval[@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z)", + wantResult: newOrFatal(t, true), + }, + { + name: "No in operator precision: On exclusive bound date", + cql: "@2020-03-25 in Interval(@2020-03-25, @2022-04)", + wantResult: newOrFatal(t, false), + }, + { + name: "No in operator precision: On exclusive bound datetime", + cql: "@2024-03-31T00:00:00.000Z in Interval(@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z)", + wantResult: newOrFatal(t, false), + }, + { + name: "No in operator precision with differing operand precision", + cql: "@2020-03 in Interval[@2020-03-25, @2022-04-25)", + wantResult: newOrFatal(t, nil), + }, + { + name: "integer in interval", + cql: "42 in Interval[0, 100]", + wantResult: newOrFatal(t, true), + }, + { + name: "integer not in interval", + cql: "42 in Interval[0, 25]", + wantResult: newOrFatal(t, false), + }, + { + name: "integer in interval on bounds", + cql: "25 in Interval[0, 25]", + wantResult: newOrFatal(t, true), + }, + { + name: "integer in interval on bounds not inclusive", + cql: "25 in Interval[0, 25)", + wantResult: newOrFatal(t, false), + }, + { + name: "integer before interval bounds not inclusive", + cql: "24 in Interval[0, 25)", + wantResult: newOrFatal(t, true), + }, + { + name: "integer in interval on bounds", + cql: "25 in Interval[0, 25)", + wantResult: newOrFatal(t, false), + }, + { + name: "integer in interval on bounds", + cql: "25 in Interval[0, null)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Double in interval on upper bounds", + cql: "1.5 in Interval[1.0, 1.5]", + wantResult: newOrFatal(t, true), + }, + { + name: "Long not in interval", + cql: "0L in Interval[1L, 2L]", + wantResult: newOrFatal(t, false), + }, + { + name: "Quantity in interval on bounds", + cql: "1'cm' in Interval[1'cm', 2'cm')", + wantResult: newOrFatal(t, true), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Evaluate diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIntervalIncludedIn(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // TODO: b/331225778 - Null support handling for Included In operator + { + name: "On inclusive bound date", + cql: "@2020-03 included in month of Interval[@2020-03-25, @2022-04)", + // Model should implicitly be converted to model.In when left operator is point type. + wantModel: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("@2020-03", types.Date), + &model.Interval{ + Low: model.NewLiteral("@2020-03-25", types.Date), + High: model.NewLiteral("@2022-04", types.Date), + LowInclusive: true, + HighInclusive: false, + Expression: model.ResultType(&types.Interval{PointType: types.Date}), + }, + }, + }, + Precision: model.MONTH, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "On right arg null", + cql: "@2020-03 included in month of null", + wantResult: newOrFatal(t, false), + }, + { + name: "During syntax, inclusive bound date", + cql: "@2020-03 during month of Interval[@2020-03-25, @2022-04)", + wantResult: newOrFatal(t, true), + }, + { + name: "On inclusive bound datetime", + cql: "@2024-03-31T00:00:00.000Z included in month of Interval[@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z)", + wantResult: newOrFatal(t, true), + }, + { + name: "On exclusive bound date", + cql: "@2020-03 included in month of Interval(@2020-03-25, @2022-04)", + wantResult: newOrFatal(t, false), + }, + { + name: "On exclusive bound datetime", + cql: "@2024-03-31T00:00:00.000Z included in month of Interval(@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z)", + wantResult: newOrFatal(t, false), + }, + { + name: "On inclusive bound datetime second precision", + cql: "@2024-03-31T00:00:00.000Z included in second of Interval[@2024-03-31T00:00:00.000Z, @2024-03-31T00:00:05.000Z]", + wantResult: newOrFatal(t, true), + }, + { + name: "On exclusive and inclusive bound", + cql: "@2020-03 included in month of Interval(@2020-03, @2022-03]", + wantResult: newOrFatal(t, false), + }, + { + name: "Insufficient precision date", + cql: "@2020-03 included in day of Interval[@2020-03-25, @2020-04]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Insufficient precision datetime", + cql: "@2024-03 included in day of Interval[@2024-03-28T00:00:00.000Z, @2024-03-31T00:00:00.000Z]", + wantResult: newOrFatal(t, nil), + }, + { + name: "Insufficient precision but for sure false", + cql: "@2028-03 included in day of Interval[@2020-03-25, @2020-04]", + wantResult: newOrFatal(t, false), + }, + { + name: "Null inclusive bound is true", + cql: "@2020-03 included in month of Interval[null, @2022-04)", + wantResult: newOrFatal(t, true), + }, + { + name: "Null inclusive bound but this is for sure false", + cql: "@2025-03 included in month of Interval[null, @2022-04)", + wantResult: newOrFatal(t, false), + }, + { + name: "Null exclusive bound is null", + cql: "@2021-03 included in month of Interval(null, @2022-04)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Null exclusive bound but this is for sure false", + cql: "@2025-03 included in month of Interval(null, @2022-04)", + wantResult: newOrFatal(t, false), + }, + // No precision + { + name: "No included in operator precision: On inclusive bound date", + cql: "@2020-03-25 included in Interval[@2020-03-25, @2022-04)", + wantResult: newOrFatal(t, true), + }, + { + name: "No included in operator precision: On exclusive bound datetime", + cql: "@2024-03-31T00:00:00.000Z included in Interval(@2024-03-31T00:00:00.000Z, @2025-03-31T00:00:00.000Z)", + wantResult: newOrFatal(t, false), + }, + { + name: "No included in operator precision with differing operand precision", + cql: "@2020-03 included in Interval[@2020-03-25, @2022-04-25)", + wantResult: newOrFatal(t, nil), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestComparison_Error(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "Quantity in interval with not matching lower unit", + cql: "1'cm' in Interval[1'm', 2'cm']", + wantEvalErrContains: "in operator recieved Quantities with differing unit values", + }, + { + name: "Quantity in interval with not matching lower unit", + cql: "1'cm' in Interval[1'cm', 2'm']", + wantEvalErrContains: "in operator recieved Quantities with differing unit values", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatal("Eval succeeded, wanted error") + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents. got (%v), want contains (%v)", err.Error(), tc.wantEvalErrContains) + } + }) + } +} diff --git a/tests/enginetests/operator_list_test.go b/tests/enginetests/operator_list_test.go new file mode 100644 index 0000000..27e3fc5 --- /dev/null +++ b/tests/enginetests/operator_list_test.go @@ -0,0 +1,469 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "strings" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestExists(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Non Empty List", + cql: "exists({1})", + wantModel: &model.Exists{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: &model.List{ + List: []model.IExpression{model.NewLiteral("1", types.Integer)}, + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "Empty List", + cql: "exists({})", + wantResult: newOrFatal(t, false), + }, + { + name: "List Of Nulls", + cql: "exists({null})", + wantResult: newOrFatal(t, false), + }, + { + name: "Null", + cql: "exists(null)", + wantModel: &model.Exists{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Boolean), + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(&types.List{ElementType: types.Any}), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: &types.List{ElementType: types.Any}, + }, + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "Non Function Syntax", + cql: "exists {1}", + wantModel: &model.Exists{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestInList(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "In List", + cql: "1 in {1, 2}", + wantModel: &model.In{ + BinaryExpression: &model.BinaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("1", types.Integer), + &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + }, + }, + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "null in list", + cql: "null in {1, null}", + wantResult: newOrFatal(t, true), + }, + { + name: "Operand not in list", + cql: "3 in {1, 2}", + wantResult: newOrFatal(t, false), + }, + { + name: "Functional syntax: In list", + cql: "In(1, {1, 2})", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func TestFirst(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + wantSourceExpression model.IExpression + wantSourceValues []result.Value + }{ + { + name: "First({1, 2}) = 1", + cql: "First({1, 2})", + wantModel: &model.First{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + }, + }, + }, + wantResult: newOrFatal(t, int32(1)), + wantSourceExpression: &model.First{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + }, + }, + }, + wantSourceValues: []result.Value{ + newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}), + }, + }, + { + name: "First({}) = null", + cql: "First({})", + wantResult: newOrFatal(t, nil), + }, + { + name: "First(null) = null", + cql: "First(null)", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + gotResult := getTESTRESULTWithSources(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceExpression, gotResult.SourceExpression(), protocmp.Transform()); tc.wantSourceExpression != nil && diff != "" { + t.Errorf("Eval SourceExpression diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceValues, gotResult.SourceValues(), protocmp.Transform()); tc.wantSourceValues != nil && diff != "" { + t.Errorf("Eval SourceValues diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestLast(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + wantSourceExpression model.IExpression + wantSourceValues []result.Value + }{ + { + name: "Last({1, 2}) = 2", + cql: "Last({1, 2})", + wantModel: &model.Last{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + }, + }, + }, + wantResult: newOrFatal(t, int32(2)), + wantSourceExpression: &model.Last{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + model.NewLiteral("2", types.Integer), + }, + }, + }, + }, + wantSourceValues: []result.Value{ + newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}), + }, + }, + { + name: "Last({}) = null", + cql: "Last({})", + wantResult: newOrFatal(t, nil), + }, + { + name: "Last(null) = null", + cql: "Last(null)", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + gotResult := getTESTRESULTWithSources(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval returned diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceExpression, gotResult.SourceExpression(), protocmp.Transform()); tc.wantSourceExpression != nil && diff != "" { + t.Errorf("Eval SourceExpression diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceValues, gotResult.SourceValues(), protocmp.Transform()); tc.wantSourceValues != nil && diff != "" { + t.Errorf("Eval SourceValues diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestSingletonFrom(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "singleton from {1} = 1", + cql: "singleton from {1}", + wantModel: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, 1), + }, + { + name: "Functional syntax: SingletonFrom({1}) = 1", + cql: "SingletonFrom({1})", + wantModel: &model.SingletonFrom{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.List{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + List: []model.IExpression{ + model.NewLiteral("1", types.Integer), + }, + }, + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, 1), + }, + { + name: "Empty list input returns null", + cql: "singleton from {}", + wantResult: newOrFatal(t, nil), + }, + { + name: "Functional syntax with empty list", + cql: "SingletonFrom({})", + wantResult: newOrFatal(t, nil), + }, + { + name: "Null input returns null", + cql: "singleton from null", + wantResult: newOrFatal(t, nil), + }, + { + name: "Functional syntax with null", + cql: "SingletonFrom(null)", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + gotResult := getTESTRESULT(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestListOperatorSingletonFrom_Error(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "Length Greater Than 1", + cql: "singleton from {1, 2}", + wantEvalErrContains: "length 0 or 1", + }, + { + name: "Length Greater Than 1 with functional syntax", + cql: "SingletonFrom({1, 2})", + wantEvalErrContains: "length 0 or 1", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead") + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents. got: %v, want contains: %v", err.Error(), tc.wantEvalErrContains) + } + }) + } +} diff --git a/tests/enginetests/operator_logic_test.go b/tests/enginetests/operator_logic_test.go new file mode 100644 index 0000000..a8671bb --- /dev/null +++ b/tests/enginetests/operator_logic_test.go @@ -0,0 +1,334 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestLogicOperators(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // And + { + name: "true and true", + cql: "true and true", + wantModel: &model.And{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "true and false", + cql: "true and false", + wantResult: newOrFatal(t, false), + }, + { + name: "true and null", + cql: "true and null", + wantResult: newOrFatal(t, nil), + }, + { + name: "null and true", + cql: "null and true", + wantResult: newOrFatal(t, nil), + }, + { + name: "false and true", + cql: "false and true", + wantResult: newOrFatal(t, false), + }, + { + name: "false and false", + cql: "false and false", + wantResult: newOrFatal(t, false), + }, + { + name: "false and null", + cql: "false and null", + wantResult: newOrFatal(t, false), + }, + { + name: "null and false", + cql: "null and false", + wantResult: newOrFatal(t, false), + }, + { + name: "null and null", + cql: "null and null", + wantResult: newOrFatal(t, nil), + }, + // Or + { + name: "true or true", + cql: "true or true", + wantModel: &model.Or{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "true or false", + cql: "true or false", + wantResult: newOrFatal(t, true), + }, + { + name: "true or null", + cql: "true or null", + wantResult: newOrFatal(t, true), + }, + { + name: "null or true", + cql: "null or true", + wantResult: newOrFatal(t, true), + }, + { + name: "false or true", + cql: "false or true", + wantResult: newOrFatal(t, true), + }, + { + name: "false or false", + cql: "false or false", + wantResult: newOrFatal(t, false), + }, + { + name: "false or null", + cql: "false or null", + wantResult: newOrFatal(t, nil), + }, + { + name: "null or false", + cql: "null or false", + wantResult: newOrFatal(t, nil), + }, + { + name: "null or null", + cql: "null or null", + wantResult: newOrFatal(t, nil), + }, + // Xor + { + name: "true xor true", + cql: "true xor true", + wantModel: &model.XOr{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "true xor false", + cql: "true xor false", + wantResult: newOrFatal(t, true), + }, + { + name: "true xor null", + cql: "true xor null", + wantResult: newOrFatal(t, nil), + }, + { + name: "null xor true", + cql: "null xor true", + wantResult: newOrFatal(t, nil), + }, + { + name: "false xor true", + cql: "false xor true", + wantResult: newOrFatal(t, true), + }, + { + name: "false xor false", + cql: "false xor false", + wantResult: newOrFatal(t, false), + }, + { + name: "false xor null", + cql: "false xor null", + wantResult: newOrFatal(t, nil), + }, + { + name: "null xor false", + cql: "null xor false", + wantResult: newOrFatal(t, nil), + }, + { + name: "null xor null", + cql: "null xor null", + wantResult: newOrFatal(t, nil), + }, + // Implies + { + name: "true implies true", + cql: "true implies true", + wantModel: &model.Implies{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Boolean), + Operands: []model.IExpression{ + model.NewLiteral("true", types.Boolean), + model.NewLiteral("true", types.Boolean), + }, + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "true implies false", + cql: "true implies false", + wantResult: newOrFatal(t, false), + }, + { + name: "true implies null", + cql: "true implies null", + wantResult: newOrFatal(t, nil), + }, + { + name: "null implies true", + cql: "null implies true", + wantResult: newOrFatal(t, true), + }, + { + name: "false implies true", + cql: "false implies true", + wantResult: newOrFatal(t, true), + }, + { + name: "false implies false", + cql: "false implies false", + wantResult: newOrFatal(t, true), + }, + { + name: "false implies null", + cql: "false implies null", + wantResult: newOrFatal(t, true), + }, + { + name: "null implies false", + cql: "null implies false", + wantResult: newOrFatal(t, nil), + }, + { + name: "null implies null", + cql: "null implies null", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestNot(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "not true", + cql: "not true", + wantModel: &model.Not{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("true", types.Boolean), + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "not false", + cql: "not false", + wantResult: newOrFatal(t, true), + }, + { + name: "not null", + cql: "not null", + wantResult: newOrFatal(t, nil), + }, + { + name: "Not(true) functional form", + cql: "Not(true)", + wantResult: newOrFatal(t, false), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/operator_nullological_test.go b/tests/enginetests/operator_nullological_test.go new file mode 100644 index 0000000..b3c43e5 --- /dev/null +++ b/tests/enginetests/operator_nullological_test.go @@ -0,0 +1,232 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestCoalesce(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Coalesce(null, 1)", + cql: "Coalesce(null, 1)", + wantModel: &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + &model.As{ + UnaryExpression: &model.UnaryExpression{ + Expression: model.ResultType(types.Integer), + Operand: model.NewLiteral("null", types.Any), + }, + AsTypeSpecifier: types.Integer, + }, + model.NewLiteral("1", types.Integer), + }, + Expression: model.ResultType(types.Integer), + }, + }, + wantResult: newOrFatal(t, 1), + }, + { + name: "Coalesce(1, 2)", + cql: "Coalesce(1, 2)", + wantResult: newOrFatal(t, 1), + }, + { + name: "Coalesce(null, null)", + cql: "Coalesce(null, null)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Coalesce(null, null, 2)", + cql: "Coalesce(null, null, 2)", + wantResult: newOrFatal(t, 2), + }, + { + name: "Coalesce(null, null, null)", + cql: "Coalesce(null, null, null)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Coalesce(null, null, null, 2)", + cql: "Coalesce(null, null, null, 2)", + wantResult: newOrFatal(t, 2), + }, + { + name: "Coalesce(null, null, null, null, null)", + cql: "Coalesce(null, null, null, null, null)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Coalesce(null, null, null, null, 2)", + cql: "Coalesce(null, null, null, null, 2)", + wantResult: newOrFatal(t, 2), + }, + { + name: "Coalesce(null, null, null, null, null)", + cql: "Coalesce(null, null, null, null, null)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Coalesce({})", + cql: "Coalesce({})", + wantResult: newOrFatal(t, nil), + }, + { + name: "Coalesce({null, 1})", + cql: "Coalesce({null, 1})", + wantResult: newOrFatal(t, 1), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse Expression diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Evaluate Expression returned diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestIsNullogical(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "1 is null", + cql: "1 is null", + wantModel: &model.IsNull{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("1", types.Integer), + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "null is null", + cql: "null is null", + wantResult: newOrFatal(t, true), + }, + { + name: "IsNull(null)", + cql: "IsNull(null)", + wantResult: newOrFatal(t, true), + }, + { + name: "true is true", + cql: "true is true", + wantModel: &model.IsTrue{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("true", types.Boolean), + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "false is true", + cql: "false is true", + wantResult: newOrFatal(t, false), + }, + { + name: "null is true", + cql: "null is true", + wantResult: newOrFatal(t, false), + }, + { + name: "IsTrue(true)", + cql: "IsTrue(true)", + wantResult: newOrFatal(t, true), + }, + { + name: "true is false", + cql: "true is false", + wantModel: &model.IsFalse{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("true", types.Boolean), + Expression: model.ResultType(types.Boolean), + }, + }, + wantResult: newOrFatal(t, false), + }, + { + name: "false is false", + cql: "false is false", + wantResult: newOrFatal(t, true), + }, + { + name: "null is false", + cql: "null is false", + wantResult: newOrFatal(t, false), + }, + { + name: "IsFalse(false)", + cql: "IsFalse(false)", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} diff --git a/tests/enginetests/operator_string_test.go b/tests/enginetests/operator_string_test.go new file mode 100644 index 0000000..655ab72 --- /dev/null +++ b/tests/enginetests/operator_string_test.go @@ -0,0 +1,121 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "testing" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestConcatenate(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "'a' + 'b'", + cql: "'a' + 'b'", + wantModel: &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + model.NewLiteral("a", types.String), + model.NewLiteral("b", types.String), + }, + Expression: model.ResultType(types.String), + }, + }, + wantResult: newOrFatal(t, "ab"), + }, + { + name: "'a' + 'b' + 'c'", + cql: "'a' + 'b' + 'c'", + wantResult: newOrFatal(t, "abc"), + }, + { + name: "'a' + null", + cql: "'a' + null", + wantResult: newOrFatal(t, nil), + }, + { + name: "null + 'a'", + cql: "null + 'a'", + wantResult: newOrFatal(t, nil), + }, + { + name: "concatenate with & operator", + cql: "'a' & 'b'", + wantModel: &model.Concatenate{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{ + &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{model.NewLiteral("a", types.String), model.NewLiteral("", types.String)}, + Expression: model.ResultType(types.String), + }, + }, + &model.Coalesce{ + NaryExpression: &model.NaryExpression{ + Operands: []model.IExpression{model.NewLiteral("b", types.String), model.NewLiteral("", types.String)}, + Expression: model.ResultType(types.String), + }, + }, + }, + Expression: model.ResultType(types.String), + }, + }, + wantResult: newOrFatal(t, "ab"), + }, + { + name: "concatenate using & treats null as empty string, when null is second input", + cql: "'a' & null", + wantResult: newOrFatal(t, "a"), + }, + { + name: "concatenate using & treats null as empty string, when null is first input", + cql: "null & 'a'", + wantResult: newOrFatal(t, "a"), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/operator_type_test.go b/tests/enginetests/operator_type_test.go new file mode 100644 index 0000000..eaacc27 --- /dev/null +++ b/tests/enginetests/operator_type_test.go @@ -0,0 +1,818 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestAs(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + // TODO(b/301606416): Add a test case for subtypes (ex Procedure As ImagingProcedure). This is + // not possible until Named types or Vocabulary type are supported. + { + name: "Null", + cql: "null as Integer", + wantModel: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Integer As Integer", + cql: "4 as Integer", + wantResult: newOrFatal(t, 4), + }, + { + name: "Integer As Any", + cql: "4 as Any", + wantResult: newOrFatal(t, 4), + }, + { + name: "Integer As Choice", + cql: "4 as Choice", + wantResult: newOrFatal(t, 4), + }, + { + name: "Choice As Integer", + cql: "4 as Choice as Integer", + wantModel: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(&types.Choice{ChoiceTypes: []types.IType{types.String, types.Integer}}), + }, + AsTypeSpecifier: &types.Choice{ChoiceTypes: []types.IType{types.String, types.Integer}}, + }, + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer}, + wantResult: newOrFatal(t, 4), + }, + { + name: "Integer As Decimal Null", + cql: "4 as Decimal", + wantResult: newOrFatal(t, nil), + }, + { + name: "Integer As Choice Null", + cql: "4 as Choice", + wantResult: newOrFatal(t, nil), + }, + { + name: "Strict cast Integer as Any", + cql: "cast 4 as Any", + wantModel: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Any), + }, + Strict: true, + AsTypeSpecifier: types.Any, + }, + wantResult: newOrFatal(t, 4), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestAs_EvalErrors(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantEvalErrContains string + }{ + { + name: "Strict Integer as Decimal", + cql: "cast 4 as Decimal", + wantModel: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("4", types.Integer), + Expression: model.ResultType(types.Decimal), + }, + Strict: true, + AsTypeSpecifier: types.Decimal, + }, + wantEvalErrContains: "cannot strict cast", + }, + { + name: "Strict Integer As Choice", + cql: "cast 4 as Choice", + wantEvalErrContains: "cannot strict cast", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead") + } + if !strings.Contains(err.Error(), tc.wantEvalErrContains) { + t.Errorf("Unexpected evaluation error contents got (%v) want (%v)", err.Error(), tc.wantEvalErrContains) + } + + }) + } +} + +func TestToDate(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "DateTime", + cql: "ToDate(@2024-03-31T01:20:30.101-07:00)", + wantModel: &model.ToDate{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("@2024-03-31T01:20:30.101-07:00", types.DateTime), + Expression: model.ResultType(types.Date), + }, + }, + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, time.March, 31, 1, 20, 30, 101e6, time.FixedZone("", -25200)), Precision: model.DAY}), + }, + { + name: "String", + cql: "ToDate('2024-03-31')", + wantModel: &model.ToDate{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("2024-03-31", types.String), + Expression: model.ResultType(types.Date), + }, + }, + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, time.March, 31, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "Date", + cql: "ToDate(@2024-03-31)", + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, time.March, 31, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "Null", + cql: "ToDate(null as String)", // as String necessary to prevent ambiguous match. + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestToDateTime(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Date", + cql: "ToDateTime(@2012-12-02)", + wantModel: &model.ToDateTime{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("@2012-12-02", types.Date), + Expression: model.ResultType(types.DateTime), + }, + }, + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2012, time.December, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + { + name: "Null", + cql: "ToDateTime(null as Date)", + wantResult: newOrFatal(t, nil), + }, + { + name: "String", + cql: "ToDateTime('2024-03-31T01:20:30.101-07:00')", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 101e6, time.FixedZone("", -25200)), Precision: model.MILLISECOND}), + }, + { + name: "DateTime", + cql: "ToDateTime(@2024-03-31T01:20:30.101-07:00)", + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.March, 31, 1, 20, 30, 101e6, time.FixedZone("", -25200)), Precision: model.MILLISECOND}), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestToDecimal(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Null to Decimal", + cql: "ToDecimal(null as Integer)", + wantModel: &model.ToDecimal{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + }, + Expression: model.ResultType(types.Decimal), + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Decimal to Decimal", + cql: "ToDecimal(412.2)", + wantResult: newOrFatal(t, 412.2), + }, + { + name: "Long to Decimal", + cql: "ToDecimal(412L)", + wantResult: newOrFatal(t, 412.0), + }, + { + name: "Integer to Decimal", + cql: "ToDecimal(412)", + wantResult: newOrFatal(t, 412.0), + }, + { + name: "True to Decimal", + cql: "ToDecimal(true)", + wantResult: newOrFatal(t, 1.0), + }, + { + name: "False to Decimal", + cql: "ToDecimal(false)", + wantResult: newOrFatal(t, 0.0), + }, + { + name: "Negative String to Decimal", + cql: "ToDecimal('-412.2')", + wantResult: newOrFatal(t, -412.2), + }, + { + name: "Positive String to Decimal", + cql: "ToDecimal('+412')", + wantResult: newOrFatal(t, 412.0), + }, + { + name: "No Sign String", + cql: "ToDecimal('412.00')", + wantResult: newOrFatal(t, 412.0), + }, + { + name: "Invalid String Additional Character", + cql: "ToDecimal('123.4a')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing Digit", + cql: "ToDecimal('-.4')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid Empty String", + cql: "ToDecimal('')", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestToLong(t *testing.T) { + tests := []struct { + cql string + name string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Null to Long", + cql: "ToLong(null as Integer)", + wantModel: &model.ToLong{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + }, + Expression: model.ResultType(types.Long), + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Long to Long", + cql: "ToLong(412L)", + wantResult: newOrFatal(t, int64(412)), + }, + { + name: "Integer to Long", + cql: "ToLong(412)", + wantResult: newOrFatal(t, int64(412)), + }, + { + name: "True to Long", + cql: "ToLong(true)", + wantResult: newOrFatal(t, int64(1)), + }, + { + name: "False to Long", + cql: "ToLong(false)", + wantResult: newOrFatal(t, int64(0)), + }, + { + name: "Negative String to Long", + cql: "ToLong('-412')", + wantResult: newOrFatal(t, int64(-412)), + }, + { + name: "Positive String to Long", + cql: "ToLong('+412')", + wantResult: newOrFatal(t, int64(412)), + }, + { + name: "No Sign String", + cql: "ToLong('412')", + wantResult: newOrFatal(t, int64(412)), + }, + { + name: "Invalid String Additional Character", + cql: "ToLong('1234a')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing Digit", + cql: "ToLong('-')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid Empty String", + cql: "ToLong('')", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestToQuantity(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "Null to Quantity", + cql: "ToQuantity(null as Integer)", + wantModel: &model.ToQuantity{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Integer), + }, + AsTypeSpecifier: types.Integer, + }, + Expression: model.ResultType(types.Quantity), + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "Integer to Quantity", + cql: "ToQuantity(412)", + wantResult: newOrFatal(t, result.Quantity{Value: 412.0, Unit: "1"}), + }, + { + name: "Decimal to Quantity", + cql: "ToQuantity(412.2)", + wantResult: newOrFatal(t, result.Quantity{Value: 412.2, Unit: "1"}), + }, + // String ToQuantity tests + { + name: "Negative String to Quantity", + cql: "ToQuantity('-412.2')", + wantResult: newOrFatal(t, result.Quantity{Value: -412.2, Unit: "1"}), + }, + { + name: "Positive String to Quantity", + cql: "ToQuantity('+412.2')", + wantResult: newOrFatal(t, result.Quantity{Value: 412.2, Unit: "1"}), + }, + { + name: "String decimal only to Quantity", + cql: "ToQuantity('412.2')", + wantResult: newOrFatal(t, result.Quantity{Value: 412.2, Unit: "1"}), + }, + { + name: "String decimal and unit to Quantity", + cql: "ToQuantity('412.2 \\'cm\\'')", + wantModel: &model.ToQuantity{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("412.2 'cm'", types.String), + Expression: model.ResultType(types.Quantity), + }, + }, + wantResult: newOrFatal(t, result.Quantity{Value: 412.2, Unit: "cm"}), + }, + { + name: "String decimal and unit no spaces to Quantity", + cql: "ToQuantity('412.2\\'cm\\'')", + wantResult: newOrFatal(t, result.Quantity{Value: 412.2, Unit: "cm"}), + }, + // Invalid String tests + { + name: "Invalid Empty String to Quantity", + cql: "ToQuantity('')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing Digit and Unit to Quantity", + cql: "ToQuantity('-')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing Digit to Quantity", + cql: "ToQuantity('-.4')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing right quotation to Quantity", + cql: "ToQuantity('-1.4 \\'cm')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing left quotation to Quantity", + cql: "ToQuantity('-1.4 cm\\')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing both quotations to Quantity", + cql: "ToQuantity('-1.4 cm')", + wantResult: newOrFatal(t, nil), + }, + { + name: "Invalid String Missing only unit to Quantity", + cql: "ToQuantity('\\'cm\\')", + wantResult: newOrFatal(t, nil), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestToConcept(t *testing.T) { + tests := []struct { + cql string + name string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "null Code", + cql: "define TESTRESULT: ToConcept(null as Code)", + wantModel: &model.ToConcept{ + UnaryExpression: &model.UnaryExpression{ + Operand: &model.As{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("null", types.Any), + Expression: model.ResultType(types.Code), + }, + AsTypeSpecifier: types.Code, + }, + Expression: model.ResultType(types.Concept), + }, + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "null List", + cql: "define TESTRESULT: ToConcept(null as List)", + wantResult: newOrFatal(t, nil), + }, + { + name: "Code no display", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: ToConcept(Code '132' from cs)`), + wantResult: newOrFatal(t, result.Concept{ + Codes: []result.Code{ + result.Code{ + Code: "132", + System: "https://example.com/cs/diagnosis", + Version: "1.0", + }, + }}), + }, + { + name: "Code with display", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: ToConcept(Code '132' from cs display 'Severed Leg')`), + wantResult: newOrFatal(t, result.Concept{ + Display: "Severed Leg", + Codes: []result.Code{ + result.Code{ + Code: "132", + System: "https://example.com/cs/diagnosis", + Version: "1.0", + Display: "Severed Leg", + }, + }}), + }, + { + name: "List of codes", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: ToConcept({Code '132' from cs display 'Severed Leg', Code '444' from cs display 'Burnt Cranium'})`), + wantResult: newOrFatal(t, result.Concept{ + Codes: []result.Code{ + result.Code{ + Code: "132", + System: "https://example.com/cs/diagnosis", + Version: "1.0", + Display: "Severed Leg", + }, + result.Code{ + Code: "444", + System: "https://example.com/cs/diagnosis", + Version: "1.0", + Display: "Burnt Cranium", + }, + }}), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), []string{tc.cql}, parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + }) + } +} + +func TestToConcept_Error(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantError string + }{ + { + name: "Concept must have a Code", + cql: "ToConcept(List{null})", + wantError: "System.Concept must have at least one System.Code", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + _, err = interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err == nil { + t.Fatalf("Evaluate Expression expected an error to be returned, got nil instead") + } + if !strings.Contains(err.Error(), tc.wantError) { + t.Errorf("Unexpected evaluation error contents got (%v) want (%v)", err.Error(), tc.wantError) + } + }) + } +} + +func TestIs(t *testing.T) { + // Note that cases for "is true", "is null", "is false", are in operator_nullological_test.go + // since they are considered nullological operators in CQL: + // https://cql.hl7.org/09-b-cqlreference.html#nullological-operators-3 + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + }{ + { + name: "1 is Integer", + cql: "1 is Integer", + wantModel: &model.Is{ + UnaryExpression: &model.UnaryExpression{ + Operand: model.NewLiteral("1", types.Integer), + Expression: model.ResultType(types.Boolean), + }, + IsTypeSpecifier: types.Integer, + }, + wantResult: newOrFatal(t, true), + }, + { + name: "1 is String", + cql: "1 is String", + wantResult: newOrFatal(t, false), + }, + { + name: "1 is Any (Any is subtype of Integer)", + cql: "1 is Any", + wantResult: newOrFatal(t, true), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} diff --git a/tests/enginetests/property_test.go b/tests/enginetests/property_test.go new file mode 100644 index 0000000..47ee2aa --- /dev/null +++ b/tests/enginetests/property_test.go @@ -0,0 +1,544 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/google/cql/retriever" + "github.com/google/cql/types" + c4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/codes_go_proto" + d4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/datatypes_go_proto" + r4pb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/bundle_and_contained_resource_go_proto" + r4encounterpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/encounter_go_proto" + r4observationpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/observation_go_proto" + r4patientpb "github.com/google/fhir/go/proto/google/fhir/proto/r4/core/resources/patient_go_proto" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestProperty(t *testing.T) { + tests := []struct { + name string + cql string + resources []*r4pb.ContainedResource + wantModel model.IExpression + wantResult result.Value + }{ + // Literals + { + name: "property on null", + cql: "define TESTRESULT: null.test", + wantModel: &model.Property{ + Source: model.NewLiteral("null", types.Any), + Path: "test", + Expression: model.ResultType(types.Any), + }, + wantResult: newOrFatal(t, nil), + }, + { + name: "property on empty list", + cql: "define TESTRESULT: {}.test", + wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: types.Any}}), + }, + { + name: "Interval[4, 5].low return 4", + cql: "define TESTRESULT: Interval[4, 5].low", + wantModel: &model.Property{ + Source: &model.Interval{ + Low: model.NewLiteral("4", types.Integer), + High: model.NewLiteral("5", types.Integer), + LowInclusive: true, + HighInclusive: true, + Expression: model.ResultType(&types.Interval{PointType: types.Integer}), + }, + Path: "low", + Expression: model.ResultType(types.Integer), + }, + wantResult: newOrFatal(t, 4), + }, + { + name: "Interval[4, 5].high returns 5", + cql: "define TESTRESULT: Interval[4, 5].high", + wantResult: newOrFatal(t, 5), + }, + { + name: "Interval[4, 5].lowClosed returns true", + cql: "define TESTRESULT: Interval[4, 5].lowClosed", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval[4, 5].highClosed returns true", + cql: "define TESTRESULT: Interval[4, 5].highClosed", + wantResult: newOrFatal(t, true), + }, + { + name: "Interval(4, 5).lowClosed returns false", + cql: "define TESTRESULT: Interval(4, 5).lowClosed", + wantResult: newOrFatal(t, false), + }, + { + name: "Interval(4, 5).highClosed returns false", + cql: "define TESTRESULT: Interval(4, 5).highClosed", + wantResult: newOrFatal(t, false), + }, + { + name: "Quantity.unit", + cql: dedent.Dedent(` + define Q: 1 month + define TESTRESULT: Q.unit`), + wantResult: newOrFatal(t, "month"), + }, + { + name: "Code.system", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define C: Code '132' from cs display 'Severed Leg' + define TESTRESULT: C.system`), + wantResult: newOrFatal(t, "https://example.com/cs/diagnosis"), + }, + { + name: "ValueSet.version", + cql: dedent.Dedent(` + valueset vs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: vs.version`), + wantResult: newOrFatal(t, "1.0"), + }, + { + name: "CodeSystem.version", + cql: dedent.Dedent(` + codesystem cs: 'https://example.com/cs/diagnosis' version '1.0' + define TESTRESULT: cs.version`), + wantResult: newOrFatal(t, "1.0"), + }, + // TODO(b/301606416): Add tests for concept once concept refs are supported. + // Tuples and Instance + { + name: "System Instance", + cql: "define TESTRESULT: Code{code: 'foo', system: 'bar', display: 'the foo', version: '1.0'}.code", + wantResult: newOrFatal(t, "foo"), + }, + { + name: "FHIR Instance", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient { gender: Patient.gender }.gender`), + wantResult: newOrFatal(t, result.Named{ + Value: &r4patientpb.Patient_GenderCode{Value: c4pb.AdministrativeGenderCode_MALE}, + RuntimeType: &types.Named{TypeName: "FHIR.AdministrativeGender"}, + }), + }, + { + name: "Tuple", + cql: "define TESTRESULT: Tuple { apple: 'red', banana: 4 }.apple", + wantResult: newOrFatal(t, "red"), + }, + { + name: "Tuple Choice", + cql: dedent.Dedent(` + define C: 4 as Choice + define TESTRESULT: Tuple { apple : C }.apple`), + wantResult: newOrFatal(t, 4), + }, + // FHIR Patient + { + name: "protomessage boolean returns boolean proto", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.active`), + wantModel: &model.Property{ + Source: &model.ExpressionRef{ + Name: "Patient", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.Patient"}), + }, + Path: "active", + Expression: model.ResultType(&types.Named{TypeName: "FHIR.boolean"}), + }, + wantResult: newOrFatal(t, result.Named{Value: &d4pb.Boolean{Value: true}, RuntimeType: &types.Named{TypeName: "FHIR.boolean"}}), + }, + { + name: "property.value on boolean proto returns System.Boolean", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.active.value`), + wantResult: newOrFatal(t, true), + }, + { + name: "can call nested properties", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.name.family`), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "FamilyName"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.string"}}}), + }, + { + name: "property for enum returns a protomessage", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.gender`), + wantResult: newOrFatal(t, result.Named{ + Value: &r4patientpb.Patient_GenderCode{Value: c4pb.AdministrativeGenderCode_MALE}, + RuntimeType: &types.Named{TypeName: "FHIR.AdministrativeGender"}, + }), + }, + { + name: "property on repeated field returns list", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.name`), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal( + t, + result.Named{ + Value: &d4pb.HumanName{ + Given: []*d4pb.String{ + &d4pb.String{Value: "GivenName"}, + }, + Family: &d4pb.String{Value: "FamilyName"}}, RuntimeType: &types.Named{TypeName: "FHIR.HumanName"}}, + ), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.HumanName"}}, + }), + }, + { + name: "property for unset non-repeated field is null", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.birthDate`), + resources: []*r4pb.ContainedResource{containedFromPatient(&r4patientpb.Patient{})}, + wantResult: newOrFatal(t, nil), + }, + { + name: "primitive property.value is null if parent proto is unset", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.active.value`), + resources: []*r4pb.ContainedResource{containedFromPatient(&r4patientpb.Patient{})}, + wantResult: newOrFatal(t, nil), + }, + { + name: "property for unset repeated field returns empty list", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.name`), + resources: []*r4pb.ContainedResource{containedFromPatient(&r4patientpb.Patient{})}, + wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.HumanName"}}}), + }, + { + name: "property retrieve on list of protomessages is flattened", + cql: "define TESTRESULT: ([Patient]).name.family", + resources: []*r4pb.ContainedResource{ + containedFromPatient(&r4patientpb.Patient{Name: []*d4pb.HumanName{ + &d4pb.HumanName{Family: &d4pb.String{Value: "John"}}, + &d4pb.HumanName{Family: &d4pb.String{Value: "Jim"}}, + }}), + containedFromPatient(&r4patientpb.Patient{Name: []*d4pb.HumanName{ + &d4pb.HumanName{Family: &d4pb.String{Value: "Dave"}}, + &d4pb.HumanName{Family: &d4pb.String{Value: "Dan"}}, + }}), + }, + wantResult: newOrFatal( + t, + result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "John"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "Jim"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "Dave"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "Dan"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + }, + StaticType: &types.List{ + ElementType: &types.Named{TypeName: "FHIR.string"}, + }, + }, + ), + }, + { + name: "property retrieve on list of protomessages alternate syntax", + cql: dedent.Dedent(` + define PatientRetrieve: [Patient] + define TESTRESULT: PatientRetrieve.name.family`), + resources: []*r4pb.ContainedResource{ + containedFromPatient(&r4patientpb.Patient{Name: []*d4pb.HumanName{ + &d4pb.HumanName{Family: &d4pb.String{Value: "John"}}, + &d4pb.HumanName{Family: &d4pb.String{Value: "Jim"}}, + }}), + containedFromPatient(&r4patientpb.Patient{Name: []*d4pb.HumanName{ + &d4pb.HumanName{Family: &d4pb.String{Value: "Dave"}}, + &d4pb.HumanName{Family: &d4pb.String{Value: "Dan"}}, + }}), + }, + wantResult: newOrFatal( + t, + result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "John"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "Jim"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "Dave"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + newOrFatal(t, result.Named{Value: &d4pb.String{Value: "Dan"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + }, + StaticType: &types.List{ + ElementType: &types.Named{TypeName: "FHIR.string"}, + }, + }, + ), + }, + // Properties on Observations + { + name: "unset oneof returns nil", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.value`), + resources: []*r4pb.ContainedResource{containedFromObservation(&r4observationpb.Observation{})}, + wantResult: newOrFatal(t, nil), + }, + { + name: "integer inside oneof returns integer proto", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{Value: &r4observationpb.Observation_ValueX{Choice: &r4observationpb.Observation_ValueX_Integer{Integer: &d4pb.Integer{Value: 4}}}}), + }, + wantResult: newOrFatal(t, result.Named{Value: &d4pb.Integer{Value: 4}, RuntimeType: &types.Named{TypeName: "FHIR.integer"}}), + }, + { + name: "string inside oneof returns string proto", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{ + Value: &r4observationpb.Observation_ValueX{Choice: &r4observationpb.Observation_ValueX_StringValue{StringValue: &d4pb.String{Value: "obsValue"}}}, + }), + }, + wantResult: newOrFatal(t, result.Named{Value: &d4pb.String{Value: "obsValue"}, RuntimeType: &types.Named{TypeName: "FHIR.string"}}), + }, + { + name: "dateTime inside oneof returns dateTime proto", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.effective`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{ + Effective: &r4observationpb.Observation_EffectiveX{ + Choice: &r4observationpb.Observation_EffectiveX_DateTime{DateTime: &d4pb.DateTime{ValueUs: 1711929600000000, Precision: d4pb.DateTime_SECOND, Timezone: "UTC"}}, + }, + }), + }, + wantResult: newOrFatal(t, result.Named{Value: &d4pb.DateTime{ValueUs: 1711929600000000, Precision: d4pb.DateTime_SECOND, Timezone: "UTC"}, RuntimeType: &types.Named{TypeName: "FHIR.dateTime"}}), + }, + { + name: "proto message with capital result type inside oneof", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{Value: &r4observationpb.Observation_ValueX{Choice: &r4observationpb.Observation_ValueX_SampledData{SampledData: &d4pb.SampledData{Id: &d4pb.String{Value: "myID"}}}}}), + }, + wantResult: newOrFatal(t, result.Named{ + Value: &d4pb.SampledData{Id: &d4pb.String{Value: "myID"}}, + RuntimeType: &types.Named{TypeName: "FHIR.SampledData"}, // Note that the result type is set correctly (and is not a Choice type). + }), + }, + { + name: "proto message with lowercase result type inside oneof", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{Value: &r4observationpb.Observation_ValueX{Choice: &r4observationpb.Observation_ValueX_Time{Time: &d4pb.Time{ValueUs: 1711929600000000}}}}), + }, + wantResult: newOrFatal(t, result.Named{ + Value: &d4pb.Time{ValueUs: 1711929600000000}, + RuntimeType: &types.Named{TypeName: "FHIR.time"}, // Note that the result type is set correctly (and is not a Choice type). + }), + }, + { + name: "enums are wrapped", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.status`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{Status: &r4observationpb.Observation_StatusCode{Value: c4pb.ObservationStatusCode_FINAL}}), + }, + wantResult: newOrFatal(t, result.Named{Value: &r4observationpb.Observation_StatusCode{Value: c4pb.ObservationStatusCode_FINAL}, RuntimeType: &types.Named{TypeName: "FHIR.ObservationStatus"}}), + }, + { + name: "enum.value returns string", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.status.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{Status: &r4observationpb.Observation_StatusCode{Value: c4pb.ObservationStatusCode_ENTERED_IN_ERROR}}), + }, + wantResult: newOrFatal(t, "entered-in-error"), + }, + { + name: "FHIR.dateTime.value returns System.DateTime", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.effective.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{ + Effective: &r4observationpb.Observation_EffectiveX{ + Choice: &r4observationpb.Observation_EffectiveX_DateTime{DateTime: &d4pb.DateTime{ValueUs: 1711929600000000, Precision: d4pb.DateTime_SECOND, Timezone: "UTC"}}, + }, + }), + }, + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.April, 1, 0, 0, 0, 0, time.UTC), Precision: model.SECOND}), + }, + { + name: "FHIR.date.value returns System.Date", + cql: dedent.Dedent(` + context Patient + define TESTRESULT: Patient.birthDate.value`), + resources: []*r4pb.ContainedResource{containedFromPatient(&r4patientpb.Patient{ + Gender: &r4patientpb.Patient_GenderCode{Value: c4pb.AdministrativeGenderCode_MALE}, + BirthDate: &d4pb.Date{ValueUs: 1711929600000000, Precision: d4pb.Date_DAY, Timezone: "UTC"}, + })}, + wantResult: newOrFatal(t, result.Date{Date: time.Date(2024, time.April, 1, 0, 0, 0, 0, time.FixedZone("", 4*60*60)), Precision: model.DAY}), + }, + { + name: "FHIR.dateTime.value returns System.DateTime with microsecond precision mapped to millisecond", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.effective.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{ + Effective: &r4observationpb.Observation_EffectiveX{ + Choice: &r4observationpb.Observation_EffectiveX_DateTime{DateTime: &d4pb.DateTime{ValueUs: 1711929600000000, Precision: d4pb.DateTime_MICROSECOND, Timezone: "UTC"}}, + }, + }), + }, + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.April, 1, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + }, + { + name: "FHIR.dateTime.value returns System.DateTime with correct unknown precision mapping", + cql: dedent.Dedent(` + define FirstObservation: First([Observation]) + define TESTRESULT: FirstObservation.effective.value`), + resources: []*r4pb.ContainedResource{ + containedFromObservation(&r4observationpb.Observation{ + Effective: &r4observationpb.Observation_EffectiveX{ + Choice: &r4observationpb.Observation_EffectiveX_DateTime{DateTime: &d4pb.DateTime{ValueUs: 1711929600000000, Precision: d4pb.DateTime_PRECISION_UNSPECIFIED, Timezone: "UTC"}}, + }, + }), + }, + wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2024, time.April, 1, 0, 0, 0, 0, time.UTC), Precision: model.UNSETDATETIMEPRECISION}), + }, + { + name: "Encounter.class has a different json and proto field name", + cql: dedent.Dedent(` + define TESTRESULT: First([Encounter]).class`), + resources: []*r4pb.ContainedResource{ + &r4pb.ContainedResource{ + OneofResource: &r4pb.ContainedResource_Encounter{ + Encounter: &r4encounterpb.Encounter{ + ClassValue: &d4pb.Coding{Display: &d4pb.String{Value: "Display"}}, + }, + }, + }, + }, + wantResult: newOrFatal(t, result.Named{Value: &d4pb.Coding{Display: &d4pb.String{Value: "Display"}}, RuntimeType: &types.Named{TypeName: "FHIR.Coding"}}), + }, + { + name: "Ensure camelCase json properties work correctly: Encounter.serviceType", + cql: dedent.Dedent(` + define TESTRESULT: First([Encounter]).serviceType`), + resources: []*r4pb.ContainedResource{ + &r4pb.ContainedResource{ + OneofResource: &r4pb.ContainedResource_Encounter{ + Encounter: &r4encounterpb.Encounter{ + ServiceType: &d4pb.CodeableConcept{Text: &d4pb.String{Value: "ServiceType"}}, + }, + }, + }, + }, + wantResult: newOrFatal(t, result.Named{Value: &d4pb.CodeableConcept{Text: &d4pb.String{Value: "ServiceType"}}, RuntimeType: &types.Named{TypeName: "FHIR.CodeableConcept"}}), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + testCQL := fmt.Sprintf(dedent.Dedent(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + %v`), tc.cql) + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, testCQL), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + config := defaultInterpreterConfig(t, p) + if tc.resources != nil { + config.Retriever = newRetrieverFromProtosOrFatal(t, tc.resources) + } + results, err := interpreter.Eval(context.Background(), parsedLibs, config) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + + }) + } +} + +func newRetrieverFromProtosOrFatal(t *testing.T, crs []*r4pb.ContainedResource) retriever.Retriever { + t.Helper() + bundle := &r4pb.Bundle{} + for _, cr := range crs { + bundle.Entry = append(bundle.Entry, &r4pb.Bundle_Entry{Resource: cr}) + } + ret, err := local.NewRetrieverFromR4BundleProto(bundle) + if err != nil { + t.Fatalf("local.NewRetrieverFromR4BundleProto() failed: %v", err) + } + return ret +} + +func containedFromObservation(o *r4observationpb.Observation) *r4pb.ContainedResource { + return &r4pb.ContainedResource{ + OneofResource: &r4pb.ContainedResource_Observation{ + Observation: o, + }, + } +} + +func containedFromPatient(p *r4patientpb.Patient) *r4pb.ContainedResource { + return &r4pb.ContainedResource{ + OneofResource: &r4pb.ContainedResource_Patient{ + Patient: p, + }, + } +} diff --git a/tests/enginetests/query_test.go b/tests/enginetests/query_test.go new file mode 100644 index 0000000..3ae4e81 --- /dev/null +++ b/tests/enginetests/query_test.go @@ -0,0 +1,544 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package enginetests + +import ( + "context" + "testing" + "time" + + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/types" + "github.com/google/go-cmp/cmp" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestQuery(t *testing.T) { + tests := []struct { + name string + cql string + wantModel model.IExpression + wantResult result.Value + wantSourceExpression model.IExpression + wantSourceValues []result.Value + }{ + { + name: "Without where", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define TESTRESULT: [Encounter] E`), + wantModel: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "E", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Encounter", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Encounter", + CodeProperty: "type", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + }, + }, + }, + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: RetrieveFHIRResource(t, "Encounter", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}, + }), + wantSourceExpression: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "E", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + Source: &model.Retrieve{ + DataType: "{http://hl7.org/fhir}Encounter", + TemplateID: "http://hl7.org/fhir/StructureDefinition/Encounter", + CodeProperty: "type", + Expression: model.ResultType(&types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + }, + }, + }, + wantSourceValues: []result.Value{ + newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, result.Named{Value: RetrieveFHIRResource(t, "Encounter", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Encounter"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Encounter"}}, + }), + }, + }, + { + name: "Let", + cql: "define TESTRESULT: ({1, 2, 3}) A let B: 4, C: 5 return A + B + C", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 10), + newOrFatal(t, 11), + newOrFatal(t, 12), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Let list is not unpacked", + cql: "define TESTRESULT: ({1, 2, 3}) A let B: {4, 5} return A + First(B)", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 5), + newOrFatal(t, 6), + newOrFatal(t, 7), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Nested Let", + cql: "define TESTRESULT: ({1, 2}) A let B: 3 return (4) C let D: 5 return D + B", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 8), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "With where", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' called FHIRHelpers + + define TESTRESULT: [Observation] O where O.id = '1'`), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Named{Value: RetrieveFHIRResource(t, "Observation", "1"), RuntimeType: &types.Named{TypeName: "FHIR.Observation"}}), + }, + StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}, + }), + }, + { + name: "Where filters everything", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + include FHIRHelpers version '4.0.1' called FHIRHelpers + + define TESTRESULT: [Observation] O where O.id = 'apple'`), + wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}), + }, + { + name: "Where returns null", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define TESTRESULT: [Observation] O where null`), + wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Observation"}}}), + }, + { + name: "Query retrieves empy list", + cql: dedent.Dedent(` + using FHIR version '4.0.1' + define TESTRESULT: [Procedure] P`), + wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: &types.Named{TypeName: "FHIR.Procedure"}}}), + }, + { + name: "Sort descending", + cql: "define TESTRESULT: ({@2013-01-02T00:00:00.000Z, @2014-01-02T00:00:00.000Z, @2015-01-02T00:00:00.000Z}) l sort desc", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, result.DateTime{Date: time.Date(2015, time.January, 2, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + newOrFatal(t, result.DateTime{Date: time.Date(2014, time.January, 2, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + newOrFatal(t, result.DateTime{Date: time.Date(2013, time.January, 2, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + }, + StaticType: &types.List{ElementType: types.DateTime}}), + }, + { + name: "Sort ascending", + cql: "define TESTRESULT: ({@2013-01-02T00:00:00.000Z, @2015-01-02T00:00:00.000Z, @2014-01-02T00:00:00.000Z}) l sort ascending", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, result.DateTime{Date: time.Date(2013, time.January, 2, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + newOrFatal(t, result.DateTime{Date: time.Date(2014, time.January, 2, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + newOrFatal(t, result.DateTime{Date: time.Date(2015, time.January, 2, 0, 0, 0, 0, time.UTC), Precision: model.MILLISECOND}), + }, + StaticType: &types.List{ElementType: types.DateTime}}), + }, + { + name: "Sort descending date", + cql: "define TESTRESULT: ({@2013-01-02, @2014-01-02, @2015-01-02}) l sort desc", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, result.Date{Date: time.Date(2015, time.January, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + newOrFatal(t, result.Date{Date: time.Date(2014, time.January, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + newOrFatal(t, result.Date{Date: time.Date(2013, time.January, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + StaticType: &types.List{ElementType: types.Date}}), + }, + { + name: "Sort ascending date", + cql: "define TESTRESULT: ({@2013-01-02, @2015-01-02, @2014-01-02}) l sort ascending", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, result.Date{Date: time.Date(2013, time.January, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + newOrFatal(t, result.Date{Date: time.Date(2014, time.January, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + newOrFatal(t, result.Date{Date: time.Date(2015, time.January, 2, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.DAY}), + }, + StaticType: &types.List{ElementType: types.Date}}), + }, + { + name: "Sort descending int", + cql: "define TESTRESULT: ({1, 3, 2}) l sort desc", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, 3), + newOrFatal(t, 2), + newOrFatal(t, 1), + }, + StaticType: &types.List{ElementType: types.Integer}}), + }, + { + name: "Sort ascending int", + cql: "define TESTRESULT: ({1, 3, 2}) l sort ascending", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, 1), + newOrFatal(t, 2), + newOrFatal(t, 3), + }, + StaticType: &types.List{ElementType: types.Integer}}), + }, + { + name: "Sort descending decimal", + cql: "define TESTRESULT: ({1.3, 3.2, 2.1}) l sort desc", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, 3.2), + newOrFatal(t, 2.1), + newOrFatal(t, 1.3), + }, + StaticType: &types.List{ElementType: types.Decimal}}), + }, + { + name: "Sort ascending decimal", + cql: "define TESTRESULT: ({1.3, 3.2, 2.1}) l sort ascending", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, 1.3), + newOrFatal(t, 2.1), + newOrFatal(t, 3.2), + }, + StaticType: &types.List{ElementType: types.Decimal}}), + }, + { + name: "Sort descending string", + cql: "define TESTRESULT: ({'apple', 'Bat', 'cat', 'Dog'}) l sort desc", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, "cat"), + newOrFatal(t, "apple"), + newOrFatal(t, "Dog"), + newOrFatal(t, "Bat"), + }, + StaticType: &types.List{ElementType: types.String}}), + }, + { + name: "Sort ascending string", + cql: "define TESTRESULT: ({'Dog', 'cat', 'Bat', 'apple'}) l sort ascending", + wantResult: newOrFatal(t, result.List{Value: []result.Value{ + newOrFatal(t, "Bat"), + newOrFatal(t, "Dog"), + newOrFatal(t, "apple"), + newOrFatal(t, "cat"), + }, + StaticType: &types.List{ElementType: types.String}}), + }, + { + name: "Aggregate", + cql: "define TESTRESULT: ({1, 2, 3, 3, 4}) L aggregate A starting 1: A * L", + wantResult: newOrFatal(t, 72), + }, + { + name: "Aggregate all", + cql: "define TESTRESULT: ({1, 2, 3, 3, 4}) L aggregate all A starting 1: A * L", + wantResult: newOrFatal(t, 72), + }, + { + name: "Aggregate distinct", + cql: "define TESTRESULT: ({1, 2, 3, 3, 4}) L aggregate distinct A starting 1: A * L", + wantResult: newOrFatal(t, 24), + }, + { + name: "Aggregate no starting expression", + cql: "define TESTRESULT: ({1, 2, 3}) L aggregate A : A * L", + wantResult: newOrFatal(t, nil), + }, + { + name: "Aggregate with multi-source", + cql: "define TESTRESULT: from ({1, 2, 3}) B, (4) C aggregate A : A + B + C", + wantResult: newOrFatal(t, nil), + }, + { + name: "List query with all return", + cql: "define TESTRESULT: ({2, 2, 3}) l return all (l*2)", + wantModel: &model.Query{ + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + Source: []*model.AliasedSource{ + &model.AliasedSource{ + Alias: "l", + Source: &model.List{ + List: []model.IExpression{ + model.NewLiteral("2", types.Integer), + model.NewLiteral("2", types.Integer), + model.NewLiteral("3", types.Integer), + }, + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + Expression: model.ResultType(&types.List{ElementType: types.Integer}), + }, + }, + Return: &model.ReturnClause{ + Element: &model.Element{ResultType: types.Integer}, + Expression: &model.Multiply{ + BinaryExpression: &model.BinaryExpression{ + Expression: model.ResultType(types.Integer), + Operands: []model.IExpression{ + &model.AliasRef{ + Name: "l", + Expression: model.ResultType(types.Integer), + }, + model.NewLiteral("2", types.Integer), + }, + }, + }, + }, + }, + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 4), newOrFatal(t, 4), newOrFatal(t, 6)}, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "List query with distinct return", + cql: "define TESTRESULT: ({2, 2, 3}) l return (l*2)", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 4), newOrFatal(t, 6)}, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "List query with where clause and all return", + cql: "define TESTRESULT: ({2, 2, 3}) l where l > 2 return all (l*2)", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 6)}, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Empty list query with return", + cql: "define TESTRESULT: (List{}) l return all (l*2)", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{}, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "List query that has a different result type than the input list", + cql: "define TESTRESULT: ({2, 3}) l return all ToDecimal(l)", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{newOrFatal(t, 2.0), newOrFatal(t, 3.0)}, + StaticType: &types.List{ElementType: types.Decimal}, + }), + }, + { + name: "Non list source", + cql: "define TESTRESULT: (4) l", + wantResult: newOrFatal(t, 4), + }, + { + name: "Non list source with return", + cql: "define TESTRESULT: (4) l return 'hi'", + wantResult: newOrFatal(t, "hi"), + }, + { + name: "Multiple sources", + cql: "define TESTRESULT: from ({2, 3}) A, ({5, 6}) B, (7) C", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 2), "B": newOrFatal(t, 5), "C": newOrFatal(t, 7)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer, "C": types.Integer}}, + }), + newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 2), "B": newOrFatal(t, 6), "C": newOrFatal(t, 7)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer, "C": types.Integer}}, + }), + newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 3), "B": newOrFatal(t, 5), "C": newOrFatal(t, 7)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer, "C": types.Integer}}, + }), + newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 3), "B": newOrFatal(t, 6), "C": newOrFatal(t, 7)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer, "C": types.Integer}}, + }), + }, + StaticType: &types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer, "C": types.Integer}}}, + }), + }, + { + name: "Multiple sources all non lists", + cql: "define TESTRESULT: from (3) A, (5) B, (7) C", + wantResult: newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 3), "B": newOrFatal(t, 5), "C": newOrFatal(t, 7)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer, "C": types.Integer}}, + }), + }, + { + name: "Multiple sources with return", + cql: "define TESTRESULT: from ({2, 3}) A, ({5, 6, 7}) B return all A", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 2), + newOrFatal(t, 2), + newOrFatal(t, 2), + newOrFatal(t, 3), + newOrFatal(t, 3), + newOrFatal(t, 3), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Multiple sources mixed list", + cql: "define TESTRESULT: from ({2, 'hi'}) A, ({5, 6, 7}) B return all A", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 2), + newOrFatal(t, 2), + newOrFatal(t, 2), + newOrFatal(t, "hi"), + newOrFatal(t, "hi"), + newOrFatal(t, "hi"), + }, + StaticType: &types.List{ElementType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}, + }), + }, + { + name: "Multiple sources mixed list", + cql: "define TESTRESULT: from ({2, 'hi'}) A, ({5, 6, 7}) B return all A", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 2), + newOrFatal(t, 2), + newOrFatal(t, 2), + newOrFatal(t, "hi"), + newOrFatal(t, "hi"), + newOrFatal(t, "hi"), + }, + StaticType: &types.List{ElementType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}, + }), + }, + { + name: "Relationship clause with", + cql: "define TESTRESULT: ({1, 2, 3, 4}) A with ({2, 3}) B such that A + B >= 5", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 2), + newOrFatal(t, 3), + newOrFatal(t, 4), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Relationship clause without", + cql: "define TESTRESULT: ({1, 2, 3, 4}) A without ({2, 3}) B such that A + B >= 5", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 1), + newOrFatal(t, 2), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Multiple relationship clauses", + cql: dedent.Dedent(` + define TESTRESULT: ({1, 2, 3, 4}) A + with ({2, 3}) B such that A + B >= 5 + with ({2, 3}) C such that A = 4 or A = 1`), + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 4), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Relationship clause non list source", + cql: "define TESTRESULT: ({1, 2, 3, 4}) A with (2) B such that A + B >= 5", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, 3), + newOrFatal(t, 4), + }, + StaticType: &types.List{ElementType: types.Integer}, + }), + }, + { + name: "Relationship clause on multi-source query", + cql: "define TESTRESULT: from ({1, 2, 3}) A, ({4, 5}) B with ({2, 3}) C such that A + B + C >= 10", + wantResult: newOrFatal(t, result.List{ + Value: []result.Value{ + newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 2), "B": newOrFatal(t, 5)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer}}, + }), + newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 3), "B": newOrFatal(t, 4)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer}}, + }), + newOrFatal(t, result.Tuple{ + Value: map[string]result.Value{"A": newOrFatal(t, 3), "B": newOrFatal(t, 5)}, + RuntimeType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer}}, + }), + }, + StaticType: &types.List{ElementType: &types.Tuple{ElementTypes: map[string]types.IType{"A": types.Integer, "B": types.Integer}}}, + }), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := newFHIRParser(t) + parsedLibs, err := p.Libraries(context.Background(), addFHIRHelpersLib(t, tc.cql), parser.Config{}) + if err != nil { + t.Fatalf("Parse returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" { + t.Errorf("Parse diff (-want +got):\n%s", diff) + } + + results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p)) + if err != nil { + t.Fatalf("Eval returned unexpected error: %v", err) + } + gotResult := getTESTRESULTWithSources(t, results) + if diff := cmp.Diff(tc.wantResult, gotResult, protocmp.Transform()); diff != "" { + t.Errorf("Eval diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceExpression, gotResult.SourceExpression(), protocmp.Transform()); tc.wantSourceExpression != nil && diff != "" { + t.Errorf("Eval SourceExpression diff (-want +got)\n%v", diff) + } + if diff := cmp.Diff(tc.wantSourceValues, gotResult.SourceValues(), protocmp.Transform()); tc.wantSourceValues != nil && diff != "" { + t.Errorf("Eval SourceValues diff (-want +got)\n%v", diff) + } + }) + } +} diff --git a/tests/enginetests/setup.go b/tests/enginetests/setup.go new file mode 100644 index 0000000..9abf26e --- /dev/null +++ b/tests/enginetests/setup.go @@ -0,0 +1,235 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package enginetests holds the most of the unit tests for the CQL Engine parser and interpreter. +package enginetests + +import ( + "context" + "embed" + "fmt" + "testing" + "time" + + "github.com/google/cql/internal/embeddata" + "github.com/google/cql/internal/resourcewrapper" + "github.com/google/cql/interpreter" + "github.com/google/cql/model" + "github.com/google/cql/parser" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/google/cql/terminology" + "github.com/google/fhir/go/fhirversion" + "github.com/google/fhir/go/jsonformat" + "github.com/lithammer/dedent" + "google.golang.org/protobuf/proto" +) + +//go:embed testdata/patient_bundle.json +var patientBundle []byte + +//go:embed testdata/terminology/*.json +var terminologyDir embed.FS + +var defaultEvalTimestamp = time.Date(2024, 1, 1, 0, 0, 0, 0, time.FixedZone("Fixed", 4*60*60)) + +// BuildRetriever returns a new retriever.Retriever from the embedded patient_bundle.json. +func BuildRetriever(t testing.TB) *local.Retriever { + t.Helper() + ret, err := local.NewRetrieverFromR4Bundle(patientBundle) + if err != nil { + t.Fatalf("Failed to create retriever: %v", err) + } + return ret +} + +// RetrieveFHIRResource retrieves the FHIR resource with the given type and ID from the embedded +// patient_bundle.json. +func RetrieveFHIRResource(t testing.TB, resourceType string, resourceID string) proto.Message { + t.Helper() + unmarshaller, err := jsonformat.NewUnmarshallerWithoutValidation("UTC", fhirversion.R4) + if err != nil { + t.Fatalf("Failed to retrieve FHIR Resource: %v", err) + } + containedResource, err := unmarshaller.UnmarshalR4(patientBundle) + if err != nil { + t.Fatalf("Failed to retrieve FHIR Resource: %v", err) + } + + for _, e := range containedResource.GetBundle().GetEntry() { + rw := resourcewrapper.New(e.GetResource()) + rt, err := rw.ResourceType() + if err != nil { + t.Fatalf("Failed to retrieve FHIR Resource: %v", err) + } + rid, err := rw.ResourceID() + if err != nil { + t.Fatalf("Failed to retrieve FHIR Resource: %v", err) + } + + if rt == resourceType && rid == resourceID { + msg, err := rw.ResourceMessageField() + if err != nil { + t.Fatalf("Failed to retrieve FHIR Resource: %v", err) + } + return msg + } + } + t.Fatalf("Failed to retrieve FHIR Resource: could not find resource with type %v and id %v", resourceType, resourceID) + return nil +} + +func buildTerminologyProvider(t testing.TB) terminology.Provider { + t.Helper() + files, err := terminologyDir.ReadDir("testdata/terminology") + if err != nil { + t.Fatalf("Failed to read terminology folder: %v", err) + } + + var jsons []string + for _, f := range files { + json, err := terminologyDir.ReadFile("testdata/terminology/" + f.Name()) + if err != nil { + t.Fatalf("Failed to read terminology file: %v", err) + } + jsons = append(jsons, string(json)) + } + + tp, err := terminology.NewInMemoryFHIRProvider(jsons) + if err != nil { + t.Fatalf("Failed to create terminology provider: %v", err) + } + return tp +} + +func newFHIRParser(t testing.TB) *parser.Parser { + t.Helper() + fhirMI, err := embeddata.ModelInfos.ReadFile("third_party/cqframework/fhir-modelinfo-4.0.1.xml") + if err != nil { + t.Fatalf("internal error - could not read fhir-modelinfo-4.0.1.xml: %v", err) + } + p, err := parser.New(context.Background(), [][]byte{fhirMI}) + if err != nil { + t.Fatal("Could not create Parser: ", err) + } + return p +} + +// wrapInLib wraps the cql expression in a library and expression definition. +func wrapInLib(t testing.TB, cql string) []string { + t.Helper() + cqlLib := dedent.Dedent(fmt.Sprintf(` + library TESTLIB version '1.0.0' + using FHIR version '4.0.1' + context Patient + define TESTRESULT: %v`, cql)) + + return addFHIRHelpersLib(t, cqlLib) +} + +func addFHIRHelpersLib(t testing.TB, lib string) []string { + fhirHelpers, err := embeddata.FHIRHelpers.ReadFile("third_party/cqframework/FHIRHelpers-4.0.1.cql") + if err != nil { + t.Fatalf("internal error - could not read FHIRHelpers-4.0.1.cql: %v", err) + } + return []string{lib, string(fhirHelpers)} +} + +func defaultInterpreterConfig(t testing.TB, p *parser.Parser) interpreter.Config { + return interpreter.Config{ + DataModels: p.DataModel(), + Retriever: BuildRetriever(t), + Terminology: buildTerminologyProvider(t), + EvaluationTimestamp: defaultEvalTimestamp, + ReturnPrivateDefs: true} +} + +func getTESTLIBModel(t testing.TB, parsedLibs []*model.Library) model.Library { + t.Helper() + for _, lib := range parsedLibs { + if lib.Identifier.Qualified == "TESTLIB" { + return *lib + } + } + t.Fatalf("Could not find TESTLIB library") + return model.Library{} +} + +// getTESTRESULTModel finds the first TESTRESULT definition in any library and returns the model. +func getTESTRESULTModel(t testing.TB, parsedLibs []*model.Library) model.IExpression { + t.Helper() + + for _, parsedLib := range parsedLibs { + if parsedLib.Statements == nil { + continue + } + for _, def := range parsedLib.Statements.Defs { + if def.GetName() == "TESTRESULT" { + return def.GetExpression() + } + } + } + + t.Fatalf("Could not find TESTRESULT expression definition") + return nil +} + +// getTESTRESULT finds the first TESTRESULT definition in any library and returns the result without +// sources. The result package implementation of Equal ignores sources and is used by tests, however +// it still shows up in diffs. Using getTESTRESULT strips the sources so it doesn't show up in +// cmp.Diff. +func getTESTRESULT(t testing.TB, resultLibs result.Libraries) result.Value { + t.Helper() + res := getTESTRESULTWithSources(t, resultLibs) + gotWithoutMeta, err := result.New(res.GolangValue()) + if err != nil { + t.Fatalf("Could not find remove Meta from TESTRESULT, %v", err) + } + return gotWithoutMeta +} + +// getTESTRESULTWithSources finds the first TESTRESULT definition in any library and returns the +// result with sources. +func getTESTRESULTWithSources(t testing.TB, resultLibs result.Libraries) result.Value { + t.Helper() + + for _, resultLib := range resultLibs { + for name, res := range resultLib { + if name == "TESTRESULT" { + return res + } + } + } + + t.Fatalf("Could not find TESTRESULT expression definition") + return result.Value{} +} + +func newOrFatal(t testing.TB, a any) result.Value { + t.Helper() + o, err := result.New(a) + if err != nil { + t.Fatalf("New(%v) returned unexpected error: %v", a, err) + } + return o +} + +func newWithSourcesOrFatal(t testing.TB, a any, expr model.IExpression, obj []result.Value) result.Value { + t.Helper() + o, err := result.NewWithSources(a, expr, obj...) + if err != nil { + t.Fatalf("New(%v) returned unexpected error: %v", a, err) + } + return o +} diff --git a/tests/enginetests/testdata/patient_bundle.json b/tests/enginetests/testdata/patient_bundle.json new file mode 100644 index 0000000..6129d36 --- /dev/null +++ b/tests/enginetests/testdata/patient_bundle.json @@ -0,0 +1,304 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1", + "extension": [ + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", + "extension": [{ "url": "text", "valueString": "Unknown" }] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", + "extension": [{ "url": "text", "valueString": "Unknown" }] + } + ], + "name": [{ "family": "FamilyName", "given": ["GivenName"] }], + "active": true, + "gender": "male", + "communication": [{ "language": { "text": "English" } }], + "managingOrganization": { "display": "EXAMPLE_ORGANIZATION" }, + "birthDate": "1950-01-01" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Encounter", + "id": "1", + "status": "finished", + "class": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "AMB", + "display": "ambulatory" + }, + "serviceType": { "text": "Medicine" }, + "subject": { "reference": "Patient/1" }, + "period": { + "start": "2018-11-13T11:21:26+00:00", + "end": "2018-11-13T12:39:19+00:00" + }, + "location": [{ "location": { "reference": "Location/1" } }], + "serviceProvider": { + "reference": "Organization/1", + "display": "RetailClinic" + } + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1", + "status": "amended", + "category": [ + { + "coding": [ + { + "system": "https://example.com/cs/procedure", + "code": "vitls", + "display": "Vital Signs" + } + ] + }, + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "vital-signs", + "display": "VitalSigns" + } + ] + } + ], + "code": { + "coding": [ + { + "system": "https://example.com/cs/procedure", + "code": "sys-bld-prs", + "display": "InvasiveSystolicbloodpressure" + } + ], + "text": "InvasiveSystolicbloodpressure" + }, + "subject": { "reference": "Patient/1" }, + "encounter": { "reference": "Encounter/1" }, + "effectiveDateTime": "2018-11-13T12:30:19+00:00", + "valueQuantity": { + "value": 149.746, + "unit": "MMHG", + "system": "http://example.com", + "code": "MMHG" + }, + "method": { + "coding": [ + { + "system": "https://example.com/cs/procedure", + "code": "invs", + "display": "Invasive" + } + ] + }, + "referenceRange": [ + { + "low": { + "value": 90, + "unit": "MMHG", + "system": "http://example.com", + "code": "MMHG" + }, + "high": { + "value": 120, + "unit": "MMHG", + "system": "http://example.com", + "code": "MMHG" + }, + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/referencerange-meaning", + "code": "normal", + "display": "NormalRange" + } + ], + "text": "NormalRange" + }, + "text": "90-120" + } + ] + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "2", + "code": { + "coding": [ + { + "system": "https://example.com/cs/diagnosis", + "code": "gluc", + "display": "Glucose in Blood" + } + ] + } + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "3" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "ObservationDefinition", + "id": "1", + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "vital-signs", + "display": "VitalSigns" + } + ] + } + ], + "code": { + "coding": [{ "system": "https://example.com/cs/procedure", "code": "sys-bld-prs" }], + "text": "InvasiveSystolicbloodpressure" + }, + "method": { + "coding": [ + { + "system": "https://example.com/cs/procedure", + "code": "invs", + "display": "Invasive" + } + ] + } + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Organization", + "id": "1", + "active": true, + "type": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/organization-type", + "code": "prov", + "display": "HealthcareProvider" + } + ], + "text": "HealthcareProvider" + } + ], + "name": "RetailClinic" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Practitioner", + "id": "1", + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "NPI" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-npi", + "value": "1" + } + ], + "active": true, + "name": [{ "family": "PracFamilyName", "given": ["PracGivenName"] }], + "gender": "female", + "qualification": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0360|2.7", + "code": "MD", + "display": "DoctorofMedicine" + } + ], + "text": "doctor" + } + } + ] + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "PractitionerRole", + "id": "1", + "active": true, + "practitioner": { "reference": "Practitioner/1" }, + "organization": { "display": "HospitalOrganizationName" }, + "specialty": [ + { + "coding": [ + { + "system": "https://example.com/cs/procedure", + "code": "crdgy", + "display": "Cardiology" + } + ], + "text": "Cardiology" + } + ] + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Location", + "id": "1", + "status": "active", + "name": "Hospital-Cardio", + "alias": ["Cardioalias"], + "description": "Cardiologyward", + "type": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "ECHO", + "display": "ECHOCARDIOGRAPHYLAB" + } + ], + "text": "ECHOCARDIOGRAPHYLAB" + } + ], + "telecom": [ + { "system": "phone", "value": "111-111-1111", "use": "work" }, + { "system": "fax", "value": "+11111111111", "use": "work" } + ], + "address": { + "city": "LosAngeles", + "state": "California", + "postalCode": "29111" + }, + "managingOrganization": { "reference": "Organization/1" } + } + } + ] +} diff --git a/tests/enginetests/testdata/terminology/diagnosis_codesystem.json b/tests/enginetests/testdata/terminology/diagnosis_codesystem.json new file mode 100644 index 0000000..e6b88d2 --- /dev/null +++ b/tests/enginetests/testdata/terminology/diagnosis_codesystem.json @@ -0,0 +1,33 @@ +{ + "resourceType": "CodeSystem", + "name": "Diagnosis Codesystem", + "title": "Diagnosis Codesystem", + "id": "https://example.com/cs/diagnosis", + "url": "https://example.com/cs/diagnosis", + "version": "1.0.0", + "identifier": [], + "status": "draft", + "date": "2024-02-22", + "description": "Various codes relates to diagnosing conditions.", + "content": "complete", + "concept": [ + { + "code": "gluc", + "definition": "Glucose in Blood" + }, + { + "code": "snfl", + "definition": "The Sniffles" + }, + { + "code": "sre-thrt", + "display": "A Sore Throat", + "definition": "A Sore Throat" + }, + { + "code": "bld-prs", + "display": "Blood Pressure", + "definition": "Blood Pressure" + } + ] +} \ No newline at end of file diff --git a/tests/enginetests/testdata/terminology/glucose_valueset.json b/tests/enginetests/testdata/terminology/glucose_valueset.json new file mode 100644 index 0000000..bf80e60 --- /dev/null +++ b/tests/enginetests/testdata/terminology/glucose_valueset.json @@ -0,0 +1,15 @@ +{ + "resourceType": "ValueSet", + "id": "https://example.com/vs/glucose", + "url": "https://example.com/vs/glucose", + "version": "1.0.0", + "expansion": { + "contains": [ + { + "system": "https://example.com/cs/diagnosis", + "code": "gluc", + "display": "Glucose In Blood" + } + ] + } +} diff --git a/tests/enginetests/testdata/terminology/procedure_codesystem.json b/tests/enginetests/testdata/terminology/procedure_codesystem.json new file mode 100644 index 0000000..a6dfbf5 --- /dev/null +++ b/tests/enginetests/testdata/terminology/procedure_codesystem.json @@ -0,0 +1,32 @@ +{ + "resourceType": "CodeSystem", + "name": "Procedure Codesystem", + "title": "Procedure Codesystem", + "id": "https://example.com/cs/procedure", + "url": "https://example.com/cs/procedure", + "version": "1.0.0", + "identifier": [], + "status": "draft", + "date": "2024-02-22", + "description": "Various codes relates to procedures and operations.", + "content": "complete", + "concept": [ + { + "code": "vitls", + "definition": "Vital Signs" + }, + { + "code": "sys-bld-prs", + "display": "Systolic Blood Pressure", + "definition": "Systolic Blood Pressure" + }, + { + "code": "invs", + "definition": "Invasive" + }, + { + "code": "crdgy", + "definition": "Cardiology Department" + } + ] +} \ No newline at end of file diff --git a/tests/largetests/README.md b/tests/largetests/README.md new file mode 100644 index 0000000..07649d3 --- /dev/null +++ b/tests/largetests/README.md @@ -0,0 +1,40 @@ +# CQL Engine Large End-to-End Tests + +These tests are set up to run like fixture or screenshot tests, where we want coverage and regression detection over large test outputs. For example, all of the expression definition results of a large CQL measure. + +To add a new test case: + +1. Add your CQL, FHIR Bundle, and Valueset files to the `tests/` directory in + the pattern laid out below. +2. Update `buildAllTestCases` in large_test.go to add your test case, for example: + +``` +{ + Name: "example - Most recent systolic bp with a valid status", + CQLFile: "tests/example/main.cql", + BundleFile: "tests/example/data.json", + WantFile: "tests/example/output.json", +}, +``` +3. The first time the test is run it will fail and print out the interpreter output in json. The printed output can be copied and pasted into `output.json` and the test should pass. + +Simply running `TestLarge` will run all of the tests, the same as how normal Go tests operate. + +Preferred test data layout: + +* `valuesets/`: add your Valueset JSONs here, one JSON file per valueset. All + valuesets are always loaded for every test. +* `tests/` + * `your_test_name/` + * `main.cql`: the main CQL to be executed. Multiple libraries not supported + yet. + * `data.json`: A FHIR bundle of the available resources for the test + execution. + * `output.json`: The expected output of evaluating `main.cql` over + `data.json`. + * `your_test_with_subtests/`: if you want to run single CQL over multiple data + bundles: + * `main.cql` + * `subtest1/` + * `data.json` + * `output.json` diff --git a/tests/largetests/largetests_test.go b/tests/largetests/largetests_test.go new file mode 100644 index 0000000..f03961e --- /dev/null +++ b/tests/largetests/largetests_test.go @@ -0,0 +1,230 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package largetests_test + +import ( + "context" + "embed" + "encoding/json" + "errors" + "io/fs" + "path/filepath" + "testing" + "time" + + "github.com/google/cql" + "github.com/google/cql/result" + "github.com/google/cql/retriever/local" + "github.com/google/cql/terminology" + "github.com/google/go-cmp/cmp" +) + +// valuesetDir is the directory containing all the valuesets across all tests. +var valuesetDir = "valuesets" + +//go:embed tests valuesets +var testdata embed.FS + +var defaultEvalTimestamp = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + +// TestConfig represents a test configuration for a single end-to-end test. TestConfigs can be +// generated by arbitrary logic as well in the future. +type TestConfig struct { + // Name of the test. + Name string + // Description is an optional longer text description of the test. + Description string + // TODO(b/319503337): support multiple CQL file input. + // CQLFile is the path to the input CQL file. + CQLFile string + // BundleFile is the path to the input bundle file. + BundleFile string + // WantFile is the path to the expected output file. + WantFile string +} + +func buildAllTestCases() []TestConfig { + // Future cases can be generated with helpers if needed. + return []TestConfig{ + // Register new test cases here: + { + Name: "example - Most recent systolic bp with a valid status", + CQLFile: "tests/example/main.cql", + BundleFile: "tests/example/data.json", + WantFile: "tests/example/output.json", + }, + { + Name: "CoronaryHeartDiseaseMeasure - Patient in Numerator and Denominator", + Description: "Patient is above 80 years old, has coronary heart disease, most recent blood pressure is below 150", + CQLFile: "tests/CoronaryHeartDiseaseMeasure/main.cql", + BundleFile: "tests/CoronaryHeartDiseaseMeasure/included_patient/data.json", + WantFile: "tests/CoronaryHeartDiseaseMeasure/included_patient/output.json", + }, + { + Name: "CoronaryHeartDiseaseMeasure - Patient not in Numerator or Denominator, due to coronary heart disease after start of measurement period", + Description: "Patient is above 80 years old, has coronary heart disease but _after_ start of measurement period, most recent blood pressure is below 150", + CQLFile: "tests/CoronaryHeartDiseaseMeasure/main.cql", + BundleFile: "tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/data.json", + WantFile: "tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/output.json", + }, + } +} + +func TestLarge(t *testing.T) { + for _, test := range buildAllTestCases() { + t.Run(test.Name, func(t *testing.T) { + cqlFileData, err := testdata.ReadFile(test.CQLFile) + if err != nil { + t.Fatalf("Failed to read cql file: %v", err) + } + fhirHelpers, err := cql.FHIRHelpersLib("4.0.1") + if err != nil { + t.Fatal("FHIRHelpersLib returned error: ", err) + } + fhirDM, err := cql.FHIRDataModel("4.0.1") + if err != nil { + t.Fatal("FHIRDataModel returned error: ", err) + } + + bundleFileData, err := testdata.ReadFile(test.BundleFile) + if err != nil { + t.Fatalf("Failed to read bundle file: %v", err) + } + retriever, err := local.NewRetrieverFromR4Bundle(bundleFileData) + if err != nil { + t.Fatalf("Failed to create retriever: %v", err) + } + terminology := getTerminologyProvider(t, valuesetDir) + + elm, err := cql.Parse(context.Background(), []string{string(fhirHelpers), string(cqlFileData)}, cql.ParseConfig{DataModels: [][]byte{fhirDM}}) + if err != nil { + t.Fatalf("Failed to create engine: %v", err) + } + + results, err := elm.Eval(context.Background(), retriever, cql.EvalConfig{Terminology: terminology, EvaluationTimestamp: defaultEvalTimestamp}) + if err != nil { + t.Errorf("Failed to run engine: %v", err) + } + + jsonResults, err := json.MarshalIndent(results, "", " ") + if err != nil { + t.Errorf("Failed to marshal results: %v", err) + } + + // Diff results + outputFileData, err := testdata.ReadFile(test.WantFile) + if errors.Is(err, fs.ErrNotExist) { + // First time running this test. We're going to fail the test and print the current + // output to the test log. + t.Errorf("Test %s failed.\nTest Description: %s\n", test.Name, test.Description) + t.Errorf("The output file %s doesn't exist. Here are the current output contents to copy and paste:\n", test.WantFile) + t.Errorf("Got results:") + t.Errorf("\n%s", jsonResults) + t.FailNow() + } + if err != nil { + t.Errorf("Failed to read existing output file: %v", err) + } + + for _, l := range results { + for exp, expVal := range l { + if exp == "Fecal Occult Blood Test Performed" { + t.Logf("Expression: %v", exp) + t.Logf("Value: %v", expVal) + lval, _ := result.ToSlice(expVal) + t.Logf("List Val: %v", lval) + } + } + } + + if diff := cmp.Diff(outputFileData, jsonResults); diff != "" { + t.Errorf("Test %s failed.\nTest Description: %s\n", test.Name, test.Description) + t.Errorf("For %s: Diff found in output file (%s) (-want +got). After the diff, the entire got file is pasted for easy copying and pasting:\n%s", test.Name, test.WantFile, diff) + t.Errorf("Got file:") + t.Errorf("%s", jsonResults) + } + }) + } +} + +// forceBenchResult ensures the results are used so the compiler does not optimize away the +// EvalLibraries function. +var forceBenchResult result.Libraries + +func BenchmarkInterpreter(b *testing.B) { + for _, test := range buildAllTestCases() { + cqlFileData, err := testdata.ReadFile(test.CQLFile) + if err != nil { + b.Fatalf("Failed to read cql file: %v", err) + } + fhirHelpers, err := cql.FHIRHelpersLib("4.0.1") + if err != nil { + b.Fatal("FHIRHelpersLib returned error: ", err) + } + fhirDM, err := cql.FHIRDataModel("4.0.1") + if err != nil { + b.Fatal("FHIRDataModel returned error: ", err) + } + + bundleFileData, err := testdata.ReadFile(test.BundleFile) + if err != nil { + b.Fatalf("Failed to read bundle file: %v", err) + } + retriever, err := local.NewRetrieverFromR4Bundle(bundleFileData) + if err != nil { + b.Fatalf("Failed to create retriever: %v", err) + } + terminology := getTerminologyProvider(b, valuesetDir) + + elm, err := cql.Parse(context.Background(), []string{string(fhirHelpers), string(cqlFileData)}, cql.ParseConfig{DataModels: [][]byte{fhirDM}}) + if err != nil { + b.Fatalf("Failed to create engine: %v", err) + } + + b.Run(test.Name, func(b *testing.B) { + var force result.Libraries + for n := 0; n < b.N; n++ { + force, err = elm.Eval(context.Background(), retriever, cql.EvalConfig{Terminology: terminology, EvaluationTimestamp: defaultEvalTimestamp}) + if err != nil { + b.Fatalf("Failed to run engine: %v", err) + } + } + forceBenchResult = force + b.ReportAllocs() + }) + } +} + +func getTerminologyProvider(t testing.TB, dir string) terminology.Provider { + t.Helper() + entries, err := testdata.ReadDir(dir) + if err != nil { + t.Fatalf("Failed to read valueset directory: %v", err) + } + + var valuesets = make([]string, 0, len(entries)) + for _, entry := range entries { + eData, err := testdata.ReadFile(filepath.Join(dir, entry.Name())) + if err != nil { + t.Fatalf("Failed to read valueset file: %v", err) + } + valuesets = append(valuesets, string(eData)) + } + tp, err := terminology.NewInMemoryFHIRProvider(valuesets) + if err != nil { + t.Fatalf("Failed to create terminology provider: %v", err) + } + return tp +} diff --git a/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/data.json b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/data.json new file mode 100644 index 0000000..ce45ae0 --- /dev/null +++ b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/data.json @@ -0,0 +1,88 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1", + "gender": "male", + "birthDate": "1920-01-01"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1", + "status": "cancelled", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "8480-6", + "display": "Systolic Blood Pressure" + } + ] + }, + "valueInteger": 151, + "effectiveDateTime": "2014-01-01" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "2", + "status": "final", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "8480-6", + "display": "Systolic Blood Pressure" + } + ] + }, + "valueInteger": 140, + "effectiveDateTime": "2015-02-02" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "3", + "status": "final", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "612", + "display": "Not blood pressure" + } + ] + }, + "valueInteger": 100, + "effectiveDateTime": "2016-03-03" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Condition", + "id": "8", + "code": { + "coding": [ + { + "system": "http://hl7.org/fhir/sid/icd-10", + "code": "I25.10", + "display": "Coronary heart disease" + } + ] + }, + "onsetDateTime": "2009-02-02" + } + } + ] +} \ No newline at end of file diff --git a/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/description.md b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/description.md new file mode 100644 index 0000000..0346edf --- /dev/null +++ b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/description.md @@ -0,0 +1,13 @@ +This test case is a patient with the following attributes: + +* Above age 80 by start of measurement period +* Has coronary heart disease +* Most recent systolic blood pressure reading is under 150 + +We expect this patient to be in the numerator, initial population, and +denominator. + +TODO(b/323418402): allow comments in JSON to include descriptions in the data +file directly. + +TODO(b/319503337): support config based data generation options. \ No newline at end of file diff --git a/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/output.json b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/output.json new file mode 100644 index 0000000..c01d13e --- /dev/null +++ b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/included_patient/output.json @@ -0,0 +1,89 @@ +[ + { + "libName": "M2", + "libVersion": "0.0.1", + "expressionDefinitions": { + "Coronary arteriosclerosis": { + "@type": "System.ValueSet", + "id": "url-valueset-coronary-arteriosclerosis", + "version": "1.0.0" + }, + "Denominator": { + "@type": "System.Boolean", + "value": true + }, + "Has coronary heart disease": { + "@type": "System.Boolean", + "value": true + }, + "Initial Population": { + "@type": "System.Boolean", + "value": true + }, + "Measurement Period": { + "@type": "Interval\u003cSystem.DateTime\u003e", + "low": { + "@type": "System.DateTime", + "value": "@2010-04-01T00:00:00.000Z" + }, + "high": { + "@type": "System.DateTime", + "value": "@2024-03-31T00:00:00.000Z" + }, + "lowClosed": true, + "highClosed": false + }, + "Most recent blood pressure reading": { + "@type": "FHIR.Observation", + "value": { + "id": { + "value": "2" + }, + "status": { + "value": "FINAL" + }, + "code": { + "coding": [ + { + "system": { + "value": "http://example.com" + }, + "code": { + "value": "8480-6" + }, + "display": { + "value": "Systolic Blood Pressure" + } + } + ] + }, + "effective": { + "dateTime": { + "valueUs": "1422835200000000", + "timezone": "UTC", + "precision": "DAY" + } + }, + "value": { + "integer": { + "value": 140 + } + } + } + }, + "Most recent blood pressure reading below 150": { + "@type": "System.Boolean", + "value": true + }, + "Numerator": { + "@type": "System.Boolean", + "value": true + }, + "Systolic blood pressure": { + "@type": "System.ValueSet", + "id": "valueset-systolic-blood-pressure", + "version": "1.0.0" + } + } + } +] \ No newline at end of file diff --git a/tests/largetests/tests/CoronaryHeartDiseaseMeasure/main.cql b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/main.cql new file mode 100644 index 0000000..46b42cc --- /dev/null +++ b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/main.cql @@ -0,0 +1,41 @@ +library M2 version '0.0.1' +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' called FHIRHelpers + +valueset "Coronary arteriosclerosis": 'url-valueset-coronary-arteriosclerosis' version '1.0.0' +valueset "Systolic blood pressure": 'valueset-systolic-blood-pressure' version '1.0.0' + +parameter "Measurement Period" Interval + default Interval[@2010-04-01T00:00:00.000Z, @2024-03-31T00:00:00.000Z) + +context Patient + +define "Has coronary heart disease": + exists ( + [Condition: "Coronary arteriosclerosis"] chd + where chd.onset as FHIR.dateTime before day of start of "Measurement Period" + ) + +define "Most recent blood pressure reading": + Last( + [Observation: "Systolic blood pressure"] bp + where bp.status in {'final', 'amended', 'corrected'} + and bp.effective in day of "Measurement Period" + sort by effective desc + ) + +define "Most recent blood pressure reading below 150": + "Most recent blood pressure reading".value < 150 + +define "Initial Population": + AgeInYearsAt(start of "Measurement Period") > 80 + +define "Denominator": + "Initial Population" + and "Has coronary heart disease" + +define "Numerator": + "Initial Population" + and "Denominator" + and "Most recent blood pressure reading below 150" \ No newline at end of file diff --git a/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/data.json b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/data.json new file mode 100644 index 0000000..5bb0aef --- /dev/null +++ b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/data.json @@ -0,0 +1,88 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1", + "gender": "male", + "birthDate": "1920-01-01"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1", + "status": "cancelled", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "8480-6", + "display": "Systolic Blood Pressure" + } + ] + }, + "valueInteger": 151, + "effectiveDateTime": "2014-01-01" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "2", + "status": "final", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "8480-6", + "display": "Systolic Blood Pressure" + } + ] + }, + "valueInteger": 140, + "effectiveDateTime": "2015-02-02" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "3", + "status": "final", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "612", + "display": "Not blood pressure" + } + ] + }, + "valueInteger": 100, + "effectiveDateTime": "2016-03-03" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Condition", + "id": "8", + "code": { + "coding": [ + { + "system": "http://hl7.org/fhir/sid/icd-10", + "code": "I25.10", + "display": "Coronary heart disease" + } + ] + }, + "onsetDateTime": "2023-02-02" + } + } + ] +} \ No newline at end of file diff --git a/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/description.md b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/description.md new file mode 100644 index 0000000..1d4bb7c --- /dev/null +++ b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/description.md @@ -0,0 +1,13 @@ +This test case is a patient with the following attributes: + +* Above age 80 by start of measurement period +* Has coronary heart disease but onset _after_ measurement period +* Most recent systolic blood pressure reading is under 150 + +We expect this patient to be in the initial population, but not the +numerator or denominator. + +TODO(b/323418402): allow comments in JSON to include descriptions in the data +file directly. + +TODO(b/319503337): support config based data generation options. \ No newline at end of file diff --git a/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/output.json b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/output.json new file mode 100644 index 0000000..0545cda --- /dev/null +++ b/tests/largetests/tests/CoronaryHeartDiseaseMeasure/patient_not_in_numerator_or_denominator/output.json @@ -0,0 +1,89 @@ +[ + { + "libName": "M2", + "libVersion": "0.0.1", + "expressionDefinitions": { + "Coronary arteriosclerosis": { + "@type": "System.ValueSet", + "id": "url-valueset-coronary-arteriosclerosis", + "version": "1.0.0" + }, + "Denominator": { + "@type": "System.Boolean", + "value": false + }, + "Has coronary heart disease": { + "@type": "System.Boolean", + "value": false + }, + "Initial Population": { + "@type": "System.Boolean", + "value": true + }, + "Measurement Period": { + "@type": "Interval\u003cSystem.DateTime\u003e", + "low": { + "@type": "System.DateTime", + "value": "@2010-04-01T00:00:00.000Z" + }, + "high": { + "@type": "System.DateTime", + "value": "@2024-03-31T00:00:00.000Z" + }, + "lowClosed": true, + "highClosed": false + }, + "Most recent blood pressure reading": { + "@type": "FHIR.Observation", + "value": { + "id": { + "value": "2" + }, + "status": { + "value": "FINAL" + }, + "code": { + "coding": [ + { + "system": { + "value": "http://example.com" + }, + "code": { + "value": "8480-6" + }, + "display": { + "value": "Systolic Blood Pressure" + } + } + ] + }, + "effective": { + "dateTime": { + "valueUs": "1422835200000000", + "timezone": "UTC", + "precision": "DAY" + } + }, + "value": { + "integer": { + "value": 140 + } + } + } + }, + "Most recent blood pressure reading below 150": { + "@type": "System.Boolean", + "value": true + }, + "Numerator": { + "@type": "System.Boolean", + "value": false + }, + "Systolic blood pressure": { + "@type": "System.ValueSet", + "id": "valueset-systolic-blood-pressure", + "version": "1.0.0" + } + } + } +] \ No newline at end of file diff --git a/tests/largetests/tests/example/data.json b/tests/largetests/tests/example/data.json new file mode 100644 index 0000000..19a4687 --- /dev/null +++ b/tests/largetests/tests/example/data.json @@ -0,0 +1,68 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Patient", + "id": "1", + "gender": "male", + "birthDate": "1950-01-01"} + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "1", + "status": "cancelled", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "8480-6", + "display": "Systolic Blood Pressure" + } + ] + }, + "effectiveDateTime": "2014-01-01" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "2", + "status": "final", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "8480-6", + "display": "Systolic Blood Pressure" + } + ] + }, + "effectiveDateTime": "2015-02-02" + } + }, + { + "fullUrl": "fullUrl", + "resource": { + "resourceType": "Observation", + "id": "3", + "status": "final", + "code": { + "coding": [ + { + "system": "http://example.com", + "code": "612", + "display": "Not blood pressure" + } + ] + }, + "effectiveDateTime": "2016-03-03" + } + } + ] +} \ No newline at end of file diff --git a/tests/largetests/tests/example/main.cql b/tests/largetests/tests/example/main.cql new file mode 100644 index 0000000..b45d080 --- /dev/null +++ b/tests/largetests/tests/example/main.cql @@ -0,0 +1,12 @@ +library main version '0.0.1' +using FHIR version '4.0.1' + +valueset "Systolic blood pressure": 'valueset-systolic-blood-pressure' version '1.0.0' + +define "Valid systolic blood pressures sorted by effective": + [Observation: "Systolic blood pressure"] bp + where bp.status.value in {'final', 'amended', 'corrected'} + sort by effective desc + +define "Most recent systolic blood pressure": + Last("Valid systolic blood pressures sorted by effective") \ No newline at end of file diff --git a/tests/largetests/tests/example/output.json b/tests/largetests/tests/example/output.json new file mode 100644 index 0000000..805ca90 --- /dev/null +++ b/tests/largetests/tests/example/output.json @@ -0,0 +1,81 @@ +[ + { + "libName": "main", + "libVersion": "0.0.1", + "expressionDefinitions": { + "Most recent systolic blood pressure": { + "@type": "FHIR.Observation", + "value": { + "id": { + "value": "2" + }, + "status": { + "value": "FINAL" + }, + "code": { + "coding": [ + { + "system": { + "value": "http://example.com" + }, + "code": { + "value": "8480-6" + }, + "display": { + "value": "Systolic Blood Pressure" + } + } + ] + }, + "effective": { + "dateTime": { + "valueUs": "1422835200000000", + "timezone": "UTC", + "precision": "DAY" + } + } + } + }, + "Systolic blood pressure": { + "@type": "System.ValueSet", + "id": "valueset-systolic-blood-pressure", + "version": "1.0.0" + }, + "Valid systolic blood pressures sorted by effective": [ + { + "@type": "FHIR.Observation", + "value": { + "id": { + "value": "2" + }, + "status": { + "value": "FINAL" + }, + "code": { + "coding": [ + { + "system": { + "value": "http://example.com" + }, + "code": { + "value": "8480-6" + }, + "display": { + "value": "Systolic Blood Pressure" + } + } + ] + }, + "effective": { + "dateTime": { + "valueUs": "1422835200000000", + "timezone": "UTC", + "precision": "DAY" + } + } + } + } + ] + } + } +] \ No newline at end of file diff --git a/tests/largetests/valuesets/url-valueset-coronary-arteriosclerosis.json b/tests/largetests/valuesets/url-valueset-coronary-arteriosclerosis.json new file mode 100644 index 0000000..f059588 --- /dev/null +++ b/tests/largetests/valuesets/url-valueset-coronary-arteriosclerosis.json @@ -0,0 +1,11 @@ +{ + "resourceType": "ValueSet", + "id": "url-valueset-coronary-arteriosclerosis", + "url": "url-valueset-coronary-arteriosclerosis", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "http://hl7.org/fhir/sid/icd-10", "code": "I25.10"} + ] + } +} \ No newline at end of file diff --git a/tests/largetests/valuesets/valueset-systolic-blood-pressure.json b/tests/largetests/valuesets/valueset-systolic-blood-pressure.json new file mode 100644 index 0000000..54be400 --- /dev/null +++ b/tests/largetests/valuesets/valueset-systolic-blood-pressure.json @@ -0,0 +1,11 @@ +{ + "resourceType": "ValueSet", + "id": "valueset-systolic-blood-pressure", + "url": "valueset-systolic-blood-pressure", + "version": "1.0.0", + "expansion": { + "contains": [ + { "system": "http://example.com", "code": "8480-6"} + ] + } +} \ No newline at end of file diff --git a/tests/spectests/README.md b/tests/spectests/README.md new file mode 100644 index 0000000..74d18a7 --- /dev/null +++ b/tests/spectests/README.md @@ -0,0 +1,37 @@ +# XML tests + +Uses XML test files imported from https://github.com/cqframework/cql-tests. + +XML tests constructs a CQL expression for each test in the following manner from +the input XML file: + +``` +define _: + () ~ +``` + +The CQL strings are then evaluated and checked to see if they return `true`. +If `true`, then the test passes otherwise the test fails. + +The input XML file is expected to match the specifications defined in + `cql/tests/spectests/cqltests/testSchema.xsd`. + +## How to generate `model.go` + +* Run +``` +git clone https://github.com/GoComply/xsd2go.git +``` +* Change the directory +``` +cd go_modules/xsd2go/cli +``` +* Run +``` +go run ./gocomply_xsd2go convert path/to/testSchema.xsd ./scap path/to/output/dir +``` + +## Coverage Stats + +To output coverage stats of these XML tests navigate to +[cmd/analyzer](cmd/analyzer) for a CLI analysis tool. \ No newline at end of file diff --git a/tests/spectests/cmd/analyzer/README.md b/tests/spectests/cmd/analyzer/README.md new file mode 100644 index 0000000..f922a0f --- /dev/null +++ b/tests/spectests/cmd/analyzer/README.md @@ -0,0 +1,16 @@ +# CQL Test Analyzer + +A simple CLI tool for analyzing the current coverage of the golang CQL engine +of the external XML tests. + +// TODO: b/346997754 - set this up as a github action. + +## Build & Run + +``` +go build cql/tests/spectests/cmd/analyzer/analyzer.go + +./analyzer +``` + +This will output totals and per stat coverage of the XML stats. \ No newline at end of file diff --git a/tests/spectests/cmd/analyzer/analyzer.go b/tests/spectests/cmd/analyzer/analyzer.go new file mode 100644 index 0000000..77f98f7 --- /dev/null +++ b/tests/spectests/cmd/analyzer/analyzer.go @@ -0,0 +1,209 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// XML Analyzer is a CLI for analyzing engine capabilities vs the external XML tests. +// This is a temporary tool which could eventually be converted to a PresubmitService but for now +// is just a CLI which appends it's findings to the CL argument. +// +package main + +import ( + "context" + "encoding/xml" + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/google/cql/tests/spectests/third_party/cqltests" + "github.com/google/cql/tests/spectests/exclusions" + "github.com/google/cql/tests/spectests/models" + "github.com/lithammer/dedent" +) + +type cliConfig struct { +} + +func (cfg *cliConfig) RegisterFlags(fs *flag.FlagSet) { +} + +const usageMessage = "A CLI for analyzing the CQL engine's ability to run the external XML tests." + +var errMissingFlag = errors.New("missing required flag") + +// The config which is populated by the CLI input flags. +var config cliConfig + +func init() { + config.RegisterFlags(flag.CommandLine) + defaultUsage := flag.Usage + flag.Usage = func() { + fmt.Fprintln(os.Stderr, usageMessage) + defaultUsage() + } +} + +func main() { + flag.Parse() + + ctx := context.Background() + if err := mainWrapper(ctx, config); err != nil { + log.Fatalf("CQL XML Analyzer failed with an error: %v", err) + } +} + +func mainWrapper(ctx context.Context, cfg cliConfig) error { + message, err := compileXMLTestStats() + if err != nil { + return err + } + log.Println(message) + + return nil +} + +// compileXMLTestStats compiles the XML test stats from the current workspace. +// Uses the exclusions module to label the tests as excluded or not and then build the stats. +// TODO: b/342065376 - Add a breakdown of the tests that were skipped by file and by groupings. +func compileXMLTestStats() (string, error) { + testDir := "." + testExclusions := exclusions.XMLTestFileExclusionDefinitions() + + files, err := cqltests.XMLTests.ReadDir(testDir) + if err != nil { + return "", fmt.Errorf("failed to read cql directory: %v", err) + } + if len(files) == 0 { + return "", fmt.Errorf("no xml files found in %s, %v", testDir, cqltests.XMLTests) + } + + totalMetrics := testMetrics{Name: "Totals"} + perFileMetrics := map[string]*testMetrics{} + for _, f := range files { + if !strings.HasSuffix(f.Name(), ".xml") { + continue + } + src := filepath.Join(testDir, f.Name()) + data, err := cqltests.XMLTests.ReadFile(src) + if err != nil { + return "", fmt.Errorf("failed to read XML file: %v", err) + } + + var cqlTests []cqlTest + xmlTests, err := parseXML(data) + if err != nil { + return "", fmt.Errorf("failed to parse XML file %s: %v", f.Name(), err) + } + cqlTests = createCQLTests(xmlTests) + + perFileMetrics[f.Name()] = &testMetrics{Name: f.Name()} + currExclusions, ok := testExclusions[f.Name()] + if !ok { + currExclusions = exclusions.XMLTestFileExclusions{GroupExcludes: []string{}, NamesExcludes: []string{}} + } + + for _, tc := range cqlTests { + if slices.Contains(currExclusions.GroupExcludes, tc.Group) { + totalMetrics.SkippedTests++ + perFileMetrics[f.Name()].SkippedTests++ + continue + } + if slices.Contains(currExclusions.NamesExcludes, tc.Name) { + totalMetrics.SkippedTests++ + perFileMetrics[f.Name()].SkippedTests++ + continue + } + if tc.Skip { + totalMetrics.SkippedTests++ + perFileMetrics[f.Name()].SkippedTests++ + continue + } + totalMetrics.ValidTests++ + perFileMetrics[f.Name()].ValidTests++ + } + } + + r := fmt.Sprintf(` + CQL XML Text stats: + %s + + ==================================== + + Per File Stats:`, totalMetrics.toString()) + for _, m := range perFileMetrics { + r += "\n" + m.toString() + } + return dedent.Dedent(r), nil +} + +type cqlTest struct { + Group string + Name string + Skip bool + SkipReason string +} + +type testMetrics struct { + Name string + SkippedTests int + ValidTests int +} + +func (t testMetrics) percentageSkipped() float64 { + return (float64(t.SkippedTests) / float64(t.totalTests())) * 100.0 +} + +func (t testMetrics) percentageValid() float64 { + return (float64(t.ValidTests) / float64(t.totalTests())) * 100.0 +} + +func (t testMetrics) totalTests() int { + return t.SkippedTests + t.ValidTests +} + +func (t testMetrics) toString() string { + return fmt.Sprintf(` + %s: + %d Skipped (%.2f%%) + %d Not Skipped (%.2f%%) + %d Total (%.2f%%)`, t.Name, t.SkippedTests, t.percentageSkipped(), t.ValidTests, t.percentageValid(), t.totalTests(), 100.0) +} + +func parseXML(raw []byte) (models.Tests, error) { + var testCase models.Tests + if err := xml.Unmarshal(raw, &testCase); err != nil { + return models.Tests{}, err + } + + return testCase, nil +} + +func createCQLTests(test models.Tests) []cqlTest { + cqlTests := []cqlTest{} + for _, g := range test.Group { + for _, tc := range g.Test { + newTest := cqlTest{Group: g.Name, Name: tc.Name} + if len(tc.Output) == 0 { + newTest.Skip = true + newTest.SkipReason = "no output defined for this test case" + } + cqlTests = append(cqlTests, newTest) + } + } + return cqlTests +} diff --git a/tests/spectests/exclusions/exclusions.go b/tests/spectests/exclusions/exclusions.go new file mode 100644 index 0000000..7ee12b8 --- /dev/null +++ b/tests/spectests/exclusions/exclusions.go @@ -0,0 +1,446 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package exclusions contains the test group and test name exclusions for the XML tests. +package exclusions + +// Exclusions for the XML tests. + +// XMLTestFileExclusions contains the test group and test name exclusions for a given XML test file. +type XMLTestFileExclusions struct { + GroupExcludes []string + NamesExcludes []string +} + +// XMLTestFileExclusionDefinitions returns all of the test group and test name exclusions. A TODO +// should be included for each set of skipped tests. +func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions { + return map[string]XMLTestFileExclusions{ + "CqlAggregateFunctionsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "AllTrue", + "AnyTrue", + "Avg", + "Count", + "Max", + "Median", + "Min", + "Mode", + "PopulationStdDev", + "PopulationVariance", + "StdDev", + "Sum", + "Variance", + }, + NamesExcludes: []string{}, + }, + "CqlAggregateTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{}, + NamesExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "RolledOutIntervals", + }, + }, + "CqlArithmeticFunctionsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "Abs", + "Ceiling", + "Floor", + "Exp", + "HighBoundary", + "Log", + "LowBoundary", + "Ln", + "Precision", + "Power", + "Round", + "Truncate", + }, + NamesExcludes: []string{ + // TODO: b/342061715 - Unsupported operator. + "Divide103", + "Multiply1CMBy2CM", + // TODO: b/342061606 - Unit conversion is not supported. + "Divide1Q1", + "Divide10Q5I", + // TODO: b/342061783 - Got unexpected result. + "Subtract2And11D", + "TruncatedDivide10d1ByNeg3D1Quantity", + // TODO: b/344002938 - xml test is wrong, asserts with a time zone. + "DateTimeMinValue", + "DateTimeMaxValue", + }, + }, + "CqlComparisonOperatorsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{}, + NamesExcludes: []string{ + // TODO: b/342061715 - Unsupported operator. + "BetweenIntTrue", + "DateTimeDayCompare", + "GreaterCM0CM0", + "GreaterCM0CM1", + "GreaterCM0NegCM1", + "GreaterM1CM1", + "GreaterM1CM10", + "TimeGreaterTrue", + "TimeGreaterFalse", + "GreaterOrEqualCM0CM0", + "GreaterOrEqualCM0CM1", + "GreaterOrEqualCM0NegCM1", + "GreaterOrEqualM1CM1", + "GreaterOrEqualM1CM10", + "TimeGreaterEqTrue", + "TimeGreaterEqTrue2", + "TimeGreaterEqFalse", + "LessCM0CM0", + "LessCM0CM1", + "LessCM0NegCM1", + "LessM1CM1", + "LessM1CM10", + "TimeLessTrue", + "TimeLessFalse", + "LessOrEqualCM0CM0", + "LessOrEqualCM0CM1", + "LessOrEqualCM0NegCM1", + "LessOrEqualM1CM1", + "LessOrEqualM1CM10", + "TimeLessEqTrue", + "TimeLessEqTrue2", + "TimeLessEqFalse", + "EquivFloat1Float1", + "EquivFloat1Float2", + "EquivFloat1Int1", + "EquivFloat1Int2", + "EquivEqCM1CM1", + "EquivEqCM1M01", + "EquivTupleJohnJohn", + "EquivTupleJohnJohnWithNulls", + "EquivTupleJohnJane", + "EquivTupleJohn1John2", + "EquivTime10A10A", + "EquivTime10A10P", + // TODO: b/342061783 - Got unexpected result. + "QuantityEqCM1M01", + "TupleEqJohn1John1WithNullName", + "QuantityNotEqCM1M01", + "TupleNotEqJohn1John1WithNullName", + }, + }, + "CqlDateTimeOperatorsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "Duration", + // TODO: b/342064491 - runtime error: invalid memory address or nil pointer dereference. + "SameAs", + }, + NamesExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "DateTimeComponentFromYear", + "DateTimeComponentFromMonth", + "DateTimeComponentFromMonthMinBoundary", + "DateTimeComponentFromDay", + "DateTimeComponentFromHour", + "DateTimeComponentFromMinute", + "DateTimeComponentFromSecond", + "DateTimeComponentFromMillisecond", + "DateTimeComponentFromTimezone", + "TimeComponentFromHour", + "TimeComponentFromMinute", + "TimeComponentFromSecond", + "TimeComponentFromMilli", + "TimeAdd5Hours", + "TimeAdd1Minute", + "TimeAdd1Second", + "TimeAdd1Millisecond", + "TimeAdd5Hours1Minute", + "TimeAdd5hoursByMinute", + "TimeAfterHourTrue", + "TimeAfterHourFalse", + "TimeAfterMinuteTrue", + "TimeAfterMinuteFalse", + "TimeAfterSecondTrue", + "TimeAfterSecondFalse", + "TimeAfterMillisecondTrue", + "TimeAfterMillisecondFalse", + "TimeAfterTimeCstor", + "TimeBeforeHourTrue", + "TimeBeforeHourFalse", + "TimeBeforeMinuteTrue", + "TimeBeforeMinuteFalse", + "TimeBeforeSecondTrue", + "TimeBeforeSecondFalse", + "TimeBeforeMillisecondTrue", + "TimeBeforeMillisecondFalse", + "TimeDifferenceHour", + "TimeDifferenceMinute", + "TimeDifferenceSecond", + "TimeDifferenceMillis", + "TimeSameOrAfterHourTrue1", + "TimeSameOrAfterHourTrue2", + "TimeSameOrAfterHourFalse", + "TimeSameOrAfterMinuteTrue1", + "TimeSameOrAfterMinuteTrue2", + "TimeSameOrAfterMinuteFalse", + "TimeSameOrAfterSecondTrue1", + "TimeSameOrAfterSecondTrue2", + "TimeSameOrAfterSecondFalse", + "TimeSameOrAfterMillisTrue1", + "TimeSameOrAfterMillisTrue2", + "TimeSameOrAfterMillisFalse", + "TimeSameOrBeforeHourTrue1", + "TimeSameOrBeforeHourTrue2", + "TimeSameOrBeforeHourFalse", + "TimeSameOrBeforeMinuteTrue1", + "TimeSameOrBeforeMinuteFalse0", + "TimeSameOrBeforeMinuteFalse", + "TimeSameOrBeforeSecondTrue1", + "TimeSameOrBeforeSecondFalse0", + "TimeSameOrBeforeSecondFalse", + "TimeSameOrBeforeMillisTrue1", + "TimeSameOrBeforeMillisFalse0", + "TimeSameOrBeforeMillisFalse", + "TimeSubtract5Hours", + "TimeSubtract1Minute", + "TimeSubtract1Second", + "TimeSubtract1Millisecond", + "TimeSubtract5Hours1Minute", + "TimeSubtract5hoursByMinute", + // TODO: b/342064803 - Invalid unit conversion. + "DateTimeAdd2YearsByDays", + "DateTimeAdd2YearsByDaysRem5Days", + // TODO: b/342064012 - Uncertain result. + "DateTimeDurationBetweenUncertainInterval", + "DateTimeDurationBetweenUncertainInterval2", + "DateTimeDurationBetweenUncertainAdd", + "DateTimeDurationBetweenUncertainSubtract", + "DateTimeDurationBetweenUncertainMultiply", + "DateTimeDurationBetweenUncertainDiv", + "DateTimeDurationBetweenMonthUncertain", + "DateTimeDurationBetweenMonthUncertain2", + "DateTimeDurationBetweenMonthUncertain3", + "DateTimeDurationBetweenMonthUncertain4", + "DateTimeDurationBetweenMonthUncertain5", + "DateTimeDurationBetweenMonthUncertain6", + "DateTimeDurationBetweenMonthUncertain7", + "DurationInYears", + "DurationInWeeks", + "DurationInWeeks2", + "DurationInWeeks3", + "TimeDurationBetweenHour", + "TimeDurationBetweenHourDiffPrecision", + "TimeDurationBetweenMinute", + "TimeDurationBetweenSecond", + "TimeDurationBetweenMillis", + "DurationInHoursA", + "DurationInMinutesA", + "DurationInDaysA", + "DurationInHoursAA", + "DurationInMinutesAA", + "DurationInDaysAA", + "DateTimeDifferenceUncertain", + // TODO: b/343800835 - Error in output date comparison based on execution timestamp logic. + "DateTimeComponentFromDate", + // TODO: b/342061783 - Got unexpected result. + "DateTimeAddLeapYear", + }, + }, + "CqlIntervalOperatorsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "After", + "Before", + "Collapse", + "Expand", + "Contains", + "Ends", + "Except", + "Includes", + "Included In", + "Intersect", + "Meets", + "MeetsBefore", + "MeetsAfter", + "Overlaps", + "OverlapsBefore", + "OverlapsAfter", + "PointFrom", + "ProperContains", + "ProperIn", + "ProperlyIncludes", + "ProperlyIncludedIn", + "Starts", + "Union", + "Width", + }, + NamesExcludes: []string{ + // TODO: b/342061783 - Got unexpected result. + "TimeInTrue", + "TimeInFalse", + "TimeInNull", + "Issue32Interval", + "DecimalIntervalEquivalentTrue", + "DecimalIntervalEquivalentFalse", + "QuantityIntervalEquivalentTrue", + "QuantityIntervalEquivalentFalse", + "TimeEquivalentTrue", + "TimeEquivalentFalse", + "TestOnOrAfterDateTrue", + "TestOnOrAfterTimeTrue", + "TestOnOrAfterTimeFalse", + "TestOnOrAfterIntegerTrue", + "TestOnOrAfterDecimalFalse", + "TestOnOrAfterQuantityTrue", + "TestOnOrBeforeDateTrue", + "TestOnOrBeforeTimeTrue", + "TestOnOrBeforeTimeFalse", + "TestOnOrBeforeIntegerTrue", + "TestOnOrBeforeDecimalFalse", + "TestOnOrBeforeQuantityTrue", + // TODO: b/342064453 - Ambiguous match. + "TestEqualNull", + "TestInNullBoundaries", + }, + }, + "CqlListOperatorsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "Contains", + "Descendents", + "Distinct", + "Except", + "Flatten", + "Includes", + "IncludedIn", + "Indexer", + "IndexOf", + "Intersect", + "Length", + "ProperContains", + "ProperIn", + "ProperlyIncludes", + "ProperlyIncludedIn", + "Skip", + "Tail", + "Take", + "Union", + }, + NamesExcludes: []string{ + // TODO: b/342061715 - unsupported operator. + "In1Null", + "EquivalentABCAnd123", + "Equivalent123AndABC", + "Equivalent123AndString123", + "EquivalentTimeTrue", + "EquivalentTimeFalse", + "NotEqualABCAnd123", + "NotEqual123AndABC", + "NotEqual123AndString123", + // TODO: b/342061783 - Got unexpected result. + "EqualNullNull", + }, + }, + "CqlQueryTests.xml": XMLTestFileExclusions{ + GroupExcludes: []string{}, + NamesExcludes: []string{}, + }, + "CqlNullologicalOperatorsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{}, + NamesExcludes: []string{}, + }, + "CqlStringOperatorsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "Combine", + "EndsWith", + "Indexer", + "LastPositionOf", + "Length", + "Lower", + "Matches", + "PositionOf", + "ReplaceMatches", + "Split", + "StartsWith", + "Substring", + "Upper", + "toString tests", + }, + NamesExcludes: []string{}, + }, + "CqlTypesTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{}, + NamesExcludes: []string{ + // TODO: b/342064012 - Uncertain result. + "DateTimeUncertain", + // TODO: b/342061715 - unsupported operators. + "DateTimeTimeUnspecified", + // TODO: b/343515613 - fails with unexpected result. Technically not supported. + "StringUnicodeTest", + // TODO: b/343515819 - fails with unexpected result. + "StringTestEscapeQuotes", + }, + }, + "CqlTypeOperatorsTest.xml": XMLTestFileExclusions{ + GroupExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "Convert", + "ToBoolean", + "ToConcept", + "ToInteger", + "ToString", + "ToTime", + }, + NamesExcludes: []string{ + // TODO: b/342061715 - unsupported operators. + "ToDateTimeTimeUnspecified", + }, + }, + "ValueLiteralsAndSelectors.xml": XMLTestFileExclusions{ + GroupExcludes: []string{}, + NamesExcludes: []string{ + // TODO: b/342061715 - unsupported operator. + "Integer10Pow9", + "IntegerPos10Pow9", + "IntegerNeg10Pow9", + "Integer2Pow31ToZero1IntegerMaxValue", + "IntegerPos2Pow31ToZero1IntegerMaxValue", + "IntegerNeg2Pow31ToZero1", + "IntegerNeg2Pow31IntegerMinValue", + "Decimal10Pow9", + "DecimalPos10Pow9", + "DecimalNeg10Pow9", + "Decimal2Pow31ToZero1", + "DecimalPos2Pow31ToZero1", + "DecimalNeg2Pow31ToZero1", + "Decimal2Pow31", + "DecimalPos2Pow31", + "DecimalNeg2Pow31", + "Decimal2Pow31ToInf1", + "DecimalPos2Pow31ToInf1", + "DecimalNeg2Pow31ToInf1", + "DecimalOneStep", + "DecimalPosOneStep", + "DecimalNegOneStep", + "DecimalTwoStep", + "DecimalPosTwoStep", + "DecimalNegTwoStep", + "DecimalTenStep", + "DecimalPosTenStep", + "DecimalNegTenStep", + }, + }, + } +} diff --git a/tests/spectests/models/models.go b/tests/spectests/models/models.go new file mode 100644 index 0000000..b06f37d --- /dev/null +++ b/tests/spectests/models/models.go @@ -0,0 +1,138 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by https://github.com/gocomply/xsd2go; DO NOT EDIT. +// Models for http://hl7.org/fhirpath/tests +package models + +import ( + "encoding/xml" +) + +// Element +type Tests struct { + XMLName xml.Name `xml:"tests"` + + Name string `xml:"name,attr"` + + Version string `xml:"version,attr"` + + Description string `xml:"description,attr"` + + Reference string `xml:"reference,attr"` + + Notes string `xml:"notes"` + + Group []Group `xml:"group"` +} + +// XSD ComplexType declarations + +type Group struct { + XMLName xml.Name + + Name string `xml:"name,attr"` + + Version string `xml:"version,attr"` + + Description string `xml:"description,attr"` + + Reference string `xml:"reference,attr"` + + Notes string `xml:"notes"` + + Test []Test `xml:"test"` +} + +type Test struct { + XMLName xml.Name + + Name string `xml:"name,attr"` + + Version string `xml:"version,attr"` + + Description string `xml:"description,attr"` + + Reference string `xml:"reference,attr"` + + Inputfile string `xml:"inputfile,attr"` + + Predicate bool `xml:"predicate,attr"` + + Mode ModeType `xml:"mode,attr"` + + Ordered bool `xml:"ordered,attr"` + + CheckOrderedFunctions bool `xml:"checkOrderedFunctions,attr"` + + Expression Expression `xml:"expression"` + + Output []Output `xml:"output"` + + Notes string `xml:"notes"` +} + +type Expression struct { + XMLName xml.Name + + Invalid InvalidType `xml:"invalid,attr"` + + Text string `xml:",chardata"` +} + +type Output struct { + XMLName xml.Name + + Type OutputType `xml:"type,attr,omitempty"` + + Text string `xml:",chardata"` +} + +// XSD SimpleType declarations + +type OutputType string + +const OutputTypeBoolean OutputType = "boolean" + +const OutputTypeCode OutputType = "code" + +const OutputTypeDate OutputType = "date" + +const OutputTypeDatetime OutputType = "dateTime" + +const OutputTypeDecimal OutputType = "decimal" + +const OutputTypeInteger OutputType = "integer" + +const OutputTypeLong OutputType = "long" + +const OutputTypeQuantity OutputType = "quantity" + +const OutputTypeString OutputType = "string" + +const OutputTypeTime OutputType = "time" + +type InvalidType string + +const InvalidTypeFalse InvalidType = "false" + +const InvalidTypeSemantic InvalidType = "semantic" + +const InvalidTypeTrue InvalidType = "true" + +type ModeType string + +const ModeTypeStrict ModeType = "strict" + +const ModeTypeLoose ModeType = "loose" diff --git a/tests/spectests/third_party/cqltests/CqlAggregateFunctionsTest.xml b/tests/spectests/third_party/cqltests/CqlAggregateFunctionsTest.xml new file mode 100644 index 0000000..f1489af --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlAggregateFunctionsTest.xml @@ -0,0 +1,188 @@ + + + + + AllTrue({true,true}) + true + + + AllTrue({true,false}) + false + + + AllTrue({false,true}) + false + + + AllTrue({true,false,true}) + false + + + AllTrue({false,true,false}) + false + + + AllTrue({null,true,true}) + true + + + AllTrue({}) + true + + + + + AnyTrue({true,true}) + true + + + AnyTrue({false,false}) + false + + + AnyTrue({true,false,true}) + true + + + AnyTrue({false,true,false}) + true + + + AnyTrue({true,false}) + true + + + AnyTrue({false,true}) + true + + + AnyTrue({null,true}) + true + + + AnyTrue({null,false}) + false + + + AnyTrue({}) + false + + + + + Avg({ 1.0, 2.0, 3.0, 6.0 }) + 3.0 + + + + + Count({ 15, 5, 99, null, 1 }) + 4 + + + Count({ DateTime(2014), DateTime(2001), DateTime(2010) }) + 3 + + + Count({ @T15:59:59.999, @T05:59:59.999, @T20:59:59.999 }) + 3 + + + Count({}) + 0 + + + + + Max({ 5, 12, 1, 15, 0, 4, 90, 44 }) + 90 + + + Max({ 'hi', 'bye', 'zebra' }) + 'zebra' + + + Max({ DateTime(2012, 10, 5), DateTime(2012, 9, 5), DateTime(2012, 10, 6) }) + DateTime(2012, 10, 6) + + + Max({ @T15:59:59.999, @T05:59:59.999, @T20:59:59.999 }) + @T20:59:59.999 + + + + + Median({6.0, 5.0, 4.0, 3.0, 2.0, 1.0}) + 3.5 + + + + + Min({5, 12, 1, 15, 0, 4, 90, 44}) + 0 + + + Min({'hi', 'bye', 'zebra'}) + 'bye' + + + Min({ DateTime(2012, 10, 5), DateTime(2012, 9, 5), DateTime(2012, 10, 6) }) + DateTime(2012, 9, 5) + + + Min({ @T15:59:59.999, @T05:59:59.999, @T20:59:59.999 }) + @T05:59:59.999 + + + + + Mode({ 2, 1, 8, 2, 9, 1, 9, 9 }) + 9 + + + Mode({ DateTime(2012, 10, 5), DateTime(2012, 9, 5), DateTime(2012, 10, 6), DateTime(2012, 9, 5) }) + DateTime(2012, 9, 5) + + + Mode({ @T15:59:59.999, @T05:59:59.999, @T20:59:59.999, @T05:59:59.999 }) + @T05:59:59.999 + + + + + PopulationStdDev({ 1.0, 2.0, 3.0, 4.0, 5.0 }) + 1.41421356 + + + + + + PopulationVariance({ 1.0, 2.0, 3.0, 4.0, 5.0 }) + 2.0 + + + + + StdDev({ 1.0, 2.0, 3.0, 4.0, 5.0 }) + 1.58113883 + + + + + + Sum({ 6.0, 2.0, 3.0, 4.0, 5.0 }) + 20.0 + + + Sum({ null, 1, null }) + 1 + + + + + Variance({ 1.0, 2.0, 3.0, 4.0, 5.0 }) + 2.5 + + + diff --git a/tests/spectests/third_party/cqltests/CqlAggregateTest.xml b/tests/spectests/third_party/cqltests/CqlAggregateTest.xml new file mode 100644 index 0000000..5cca510 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlAggregateTest.xml @@ -0,0 +1,21 @@ + + + + + ({ 1, 2, 3, 4, 5 }) Num aggregate Result starting 1: Result * Num + 120 + + + MedicationRequestIntervals M + aggregate R starting (null as List<Interval<DateTime>>): R union ({ + M X + let S: Max({ end of Last(R) + 1 day, start of X }), + E: S + duration in days of X + return Interval[S, E] + }) + TODO + + + + diff --git a/tests/spectests/third_party/cqltests/CqlArithmeticFunctionsTest.xml b/tests/spectests/third_party/cqltests/CqlArithmeticFunctionsTest.xml new file mode 100644 index 0000000..ff1a881 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlArithmeticFunctionsTest.xml @@ -0,0 +1,833 @@ + + + + + Abs(null as Integer) + null + + + Abs(0) + 0 + + + Abs(-1) + 1 + + + Abs(-1.0) + 1.0 + + + Abs(0.0) + 0.0 + + + Abs(-1.0'cm') + 1.0'cm' + + + Abs(-1L) + 1L + + + + + 1 + null + null + + + 1 + 1 + 2 + + + 1.0 + 1.0 + 2.0 + + + 1'g/cm3' + 1'g/cm3' + 2.0'g/cm3' + + + 1 + 2.0 + 3.0 + + + 1L + 1L + 2L + + + + + Ceiling(null as Decimal) + null + + + Ceiling(1.0) + 1 + + + Ceiling(1.1) + 2 + + + Ceiling(-0.1) + 0 + + + Ceiling(-1.0) + -1 + + + Ceiling(-1.1) + -1 + + + Ceiling(1) + 1 + + + + + 1 / null + null + + + 1 / 0 + null + + + 0 / 1 + 0.0 + + + 1 / 1 + 1.0 + + + 1.0 / 1.0 + 1.0 + + + Round(10 / 3, 8) + 3.33333333 + + + 1'g/cm3' / 1.0 + 1.0'g/cm3' + + + + + 1'g/cm3' / 1'g/cm3' + 1.0'1' + + + + + + + + 10 / 5.0 + 2.0 + + + 10 / 5 + 2.0 + + + 10.0 'g' / 5 + 2.0'g' + + + + + Floor(null as Decimal) + null + + + Floor(1) + 1 + + + Floor(1.0) + 1 + + + Floor(1.1) + 1 + + + Floor(-0.1) + -1 + + + Floor(-1.0) + -1 + + + Floor(-1.1) + -2 + + + Floor(2) + 2 + + + + + Exp(null as Decimal) + null + + + Exp(0) + 1.0 + + + Exp(-0) + 1.0 + + + Round(Exp(1), 8) + 2.71828183 + + + Round(Exp(-1), 8) + 0.36787944 + + + Exp(1000) + + + + Exp(1000.0) + + + + + + HighBoundary(1.587, 8) + 1.58799999 + + + HighBoundary(@2014, 6) + @2014-12 + + + HighBoundary(@2014-01-01T08, 17) + @2014-01-01T08:59:59.999 + + + HighBoundary(@T10:30, 9) + @T10:30:59.999 + + + + + Log(null, null) + null + + + Log(1, null) + null + + + Log(1, 1) + null + + + Log(1, 2) + 0.0 + + + Log(1, 100) + 0.0 + + + Log(16, 2) + 4.0 + + + Log(0.125, 2) + -3.0 + + + + + LowBoundary(1.587, 8) + 1.58700000 + + + LowBoundary(@2014, 6) + @2014-01 + + + LowBoundary(@2014-01-01T08, 17) + @2014-01-01T08:00:00.000 + + + LowBoundary(@T10:30, 9) + @T10:30:00.000 + + + + + Ln(null) + null + + + Ln(0) + + + + Ln(-0) + + + + Ln(1) + 0.0 + + + Ln(-1) + null + + + Round(Ln(1000), 8) + 6.90775528 + + + Round(Ln(1000.0), 8) + 6.90775528 + + + + + minimum Integer + -2147483648 + + + + minimum Long + -9223372036854775808L + + + minimum Decimal + -99999999999999999999.99999999 + + + + minimum DateTime + @0001-01-01T00:00:00.000Z + + + minimum Date + @0001-01-01 + + + minimum Time + @T00:00:00.000 + + + + + maximum Integer + 2147483647 + + + maximum Long + 9223372036854775807L + + + maximum Decimal + 99999999999999999999.99999999 + + + + maximum DateTime + @9999-12-31T23:59:59.999Z + + + maximum Date + @9999-12-31 + + + maximum Time + @T23:59:59.999 + + + + + 1 mod null + null + + + 0 mod 0 + null + + + 4 mod 2 + 0 + + + 4L mod 2L + 0L + + + 4.0 mod 2.0 + 0.0 + + + 10 mod 3 + 1 + + + 10.0 mod 3.0 + 1.0 + + + 10 mod 3.0 + 1.0 + + + 3.5 mod 3 + 0.5 + + + 3.5 'cm' mod 3 'cm' + 0.5 'cm' + + + + + 1 * null + null + + + 1 * 1 + 1 + + + 1.0 * 2.0 + 2.0 + + + 1 * 1L + 1L + + + 1 * 2.0 + 2.0 + + + 1.0 'cm' * 2.0 'cm' + 2.0'cm2' + + + + + + -(null as Integer) + null + + + -0 + 0 + + + -(-0) + 0 + + + -1 + -1 + + + -(-1) + 1 + + + -(-1L) + 1L + + + -(0.0) + 0.0 + + + -(-0.0) + 0.0 + + + -(1.0) + -1.0 + + + -(-1.0) + 1.0 + + + -(1'cm') + -1.0'cm' + + + + + Precision(1.58700) + 5 + + + Precision(@2014) + 4 + + + Precision(@2014-01-05T10:30:00.000) + 17 + + + Precision(@T10:30) + 4 + + + Precision(@T10:30:00.000) + 9 + + + + + predecessor of (null as Integer) + null + + + predecessor of 0 + -1 + + + predecessor of 1 + 0 + + + predecessor of 1L + 0L + + + predecessor of 1.0 + 0.99999999 + + + predecessor of 1.01 + 1.00999999 + + + predecessor of 1.0 'cm' + 0.99999999'cm' + + + predecessor of DateTime(2000,1,1) + @1999-12-31T + + + predecessor of @T12:00:00.000 + @T11:59:59.999 + + + predecessor of DateTime(0001, 1, 1, 0, 0, 0, 0) + + + + predecessor of @T00:00:00.000 + + + + + + Power(null as Integer, null as Integer) + null + + + Power(0, 0) + 1 + + + Power(2, 2) + 4 + + + Power(-2, 2) + 4 + + + Power(2, -2) + 0.25 + + + Power(2L, 2L) + 4L + + + Power(2.0, 2.0) + 4.0 + + + Power(-2.0, 2.0) + 4.0 + + + Power(2.0, -2.0) + 0.25 + + + Power(2.0, 2) + 4.0 + + + Power(2, 2.0) + 4.0 + + + 2^4 + 16 + + + 2.0^4.0 + 16.0 + + + Power(2, -2) ~ 0.25 + true + + + + + Round(null as Decimal) + null + + + Round(1) + 1.0 + + + Round(0.5) + 1.0 + + + Round(0.4) + 0.0 + + + Round(3.14159, 2) + 3.14 + + + Round(-0.5) + 0.0 + + + Round(-0.4) + 0.0 + + + Round(-0.6) + -1.0 + + + Round(-1.1) + -1.0 + + + Round(-1.5) + -1.0 + + + Round(-1.6) + -2.0 + + + + + 1 - null + null + + + 1 - 1 + 0 + + + 1L - 1L + 0L + + + 1.0 - 2.0 + -1.0 + + + 1.0 'cm' - 2.0 'cm' + -1.0'cm' + + + 2 - 1.1 + 0.9 + + + + + successor of (null as Integer) + null + + + successor of 0 + 1 + + + successor of 1 + 2 + + + successor of 1L + 2L + + + successor of 1.0 + 1.00000001 + + + successor of 1.01 + 1.01000001 + + + successor of DateTime(2000,1,1) + @2000-01-02T + + + successor of @T12:00:00.000 + @T12:00:00.001 + + + successor of DateTime(9999, 12, 31, 23, 59, 59, 999) + + + + successor of @T23:59:59.999 + + + + + + Truncate(null as Decimal) + null + + + Truncate(0) + 0 + + + Truncate(0.0) + 0 + + + Truncate(0.1) + 0 + + + Truncate(1) + 1 + + + Truncate(1.0) + 1 + + + Truncate(1.1) + 1 + + + Truncate(1.9) + 1 + + + Truncate(-1) + -1 + + + Truncate(-1.0) + -1 + + + Truncate(-1.1) + -1 + + + Truncate(-1.9) + -1 + + + + + (null as Integer) div (null as Integer) + null + + + 2 div 1 + 2 + + + 10 div 3 + 3 + + + 10L div 3L + 3L + + + 10.1 div 3.1 + 3.0 + + + -2 div -1 + 2 + + + -10 div -3 + 3 + + + -10.1 div -3.1 + 3.0 + + + -2 div 1 + -2 + + + -10 div 3 + -3 + + + -10.1 div 3.1 + -3.0 + + + 2 div -1 + -2 + + + 10 div -3 + -3 + + + 10.1 div -3.1 + -3.0 + + + 10 div 5.0 + 2.0 + + + 10.1 'cm' div -3.1 'cm' + -3.0 'cm' + + + diff --git a/tests/spectests/third_party/cqltests/CqlComparisonOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlComparisonOperatorsTest.xml new file mode 100644 index 0000000..e8407b5 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlComparisonOperatorsTest.xml @@ -0,0 +1,812 @@ + + + + + 4 between 2 and 6 + true + + + + + true = true + true + + + true = false + false + + + false = false + true + + + false = true + false + + + null as String = null + null + + + true = null + null + + + null = true + null + + + 1 = 1 + true + + + 1 = 2 + false + + + 'a' = 'a' + true + + + 'a' = 'b' + false + + + 1.0 = 1.0 + true + + + 1.0 = 2.0 + false + + + 1.0 = 1 + true + + + 1.0 = 2 + false + + + 1'cm' = 1'cm' + true + + + 1'cm' = 0.01'm' + true + + + 2.0'cm' = 2.00'cm' + true + + + Tuple { Id : 1, Name : 'John' } = Tuple { Id : 1, Name : 'John' } + true + + + Tuple { Id : 1, Name : 'John' } = Tuple { Id : 2, Name : 'Jane' } + false + + + Tuple { Id : 1, Name : 'John' } = Tuple { Id : 2, Name : 'John' } + false + + + Tuple { Id : 1, Name : 'John' } = Tuple { Id : 2, Name : null } + false + + + Tuple { Id : null, Name : 'John' } = Tuple { Id : 1, Name : 'James' } + false + + + Tuple { Id : 1, Name : null } = Tuple { Id : 1, Name : null } + true + + + Tuple { Id : null, Name : 'John' } = Tuple { Id : null, Name : 'John' } + true + + + Tuple { Id : 1, Name : 'John' } = Tuple { Id : 1, Name : null } + null + + + Tuple { dateId: 1, Date: DateTime(2012, 10, 5, 0, 0, 0, 0) } = Tuple { dateId: 1, Date: DateTime(2012, 10, 5, 0, 0, 0, 0) } + true + + + Tuple { dateId: 1, Date: DateTime(2012, 10, 5, 0, 0, 0, 0) } = Tuple { dateId: 1, Date: DateTime(2012, 10, 5, 5, 0, 0, 0) } + false + + + Tuple { timeId: 55, TheTime: @T05:15:15.541 } = Tuple { timeId: 55, TheTime: @T05:15:15.541 } + true + + + Tuple { timeId: 55, TheTime: @T05:15:15.541 } = Tuple { timeId: 55, TheTime: @T05:15:15.540 } + false + + + Today() = Today() + true + + + Today() = Today() - 1 days + false + + + DateTime(2014, 1, 5, 5, 0, 0, 0, 0) = DateTime(2014, 1, 5, 5, 0, 0, 0, 0) + true + + + DateTime(2014, 1, 5, 5, 0, 0, 0, 0) = DateTime(2014, 7, 5, 5, 0, 0, 0, 0) + false + + + DateTime(null) = DateTime(null) + null + + + @2014-01-25T14:30:14.559+01:00 = @2014-01-25T14:30:14.559+01:00 + true + + + @2022-02-22T00:00:00.000-05:00 same day as @2022-02-22T04:59:00.000Z + true + + + @T10:00:00.000 = @T10:00:00.000 + true + + + @T10:00:00.000 = @T22:00:00.000 + false + + + + + 0 > 0 + false + + + 0 > 1 + false + + + 0 > -1 + true + + + 0.0 > 0.0 + false + + + 0.0 > 1.0 + false + + + 0.0 > -1.0 + true + + + 1.0 > 2 + false + + + 0'cm' > 0'cm' + false + + + 0'cm' > 1'cm' + false + + + 0'cm' > -1'cm' + true + + + 1'm' > 1'cm' + true + + + 1'm' > 10'cm' + true + + + 'a' > 'a' + false + + + 'a' > 'b' + false + + + 'b' > 'a' + true + + + 'a' > 'aa' + false + + + 'aa' > 'a' + true + + + 'Jack' > 'Jill' + false + + + DateTime(2012, 2, 12) > DateTime(2012, 2, 10) + true + + + DateTime(2012, 2, 12) > DateTime(2012, 2, 13) + false + + + @T10:00:00.001 > @T10:00:00.000 + true + + + @T10:00:00.000 > @T10:00:00.001 + false + + + DateTime(2014) > DateTime(2014, 2, 15) + null + + + DateTime(2015) > DateTime(2014, 2, 15) + true + + + DateTime(2013) > DateTime(2014, 2, 15) + false + + + + + 0 >= 0 + true + + + 0 >= 1 + false + + + 0 >= -1 + true + + + 0.0 >= 0.0 + true + + + 0.0 >= 1.0 + false + + + 0.0 >= -1.0 + true + + + 1.0 >= 2 + false + + + 0'cm' >= 0'cm' + true + + + 0'cm' >= 1'cm' + false + + + 0'cm' >= -1'cm' + true + + + 1'm' >= 1'cm' + true + + + 1'm' >= 10'cm' + true + + + 'a' >= 'a' + true + + + 'a' >= 'b' + false + + + 'b' >= 'a' + true + + + 'a' >= 'aa' + false + + + 'aa' >= 'a' + true + + + 'Jack' >= 'Jill' + false + + + DateTime(2012, 2, 12, 0, 0, 0, 0) >= DateTime(2012, 2, 10, 0, 0, 0, 0) + true + + + DateTime(2012, 2, 12, 0, 0, 0, 0) >= DateTime(2012, 2, 12, 0, 0, 0, 0) + true + + + DateTime(2012, 2, 12, 0, 0, 0, 0) >= DateTime(2012, 2, 13, 0, 0, 0, 0) + false + + + @T10:00:00.001 >= @T10:00:00.000 + true + + + @T10:00:00.000 >= @T10:00:00.000 + true + + + @T10:00:00.000 >= @T10:00:00.001 + false + + + DateTime(2014) >= DateTime(2014, 2, 15) + null + + + DateTime(2015) >= DateTime(2014, 2, 15) + true + + + DateTime(2013) >= DateTime(2014, 2, 15) + false + + + + + 0 < 0 + false + + + 0 < 1 + true + + + 0 < -1 + false + + + 0.0 < 0.0 + false + + + 0.0 < 1.0 + true + + + 0.0 < -1.0 + false + + + 1.0 < 2 + true + + + 0'cm' < 0'cm' + false + + + 0'cm' < 1'cm' + true + + + 0'cm' < -1'cm' + false + + + 1'm' < 1'cm' + false + + + 1'm' < 10'cm' + false + + + 'a' < 'a' + false + + + 'a' < 'b' + true + + + 'b' < 'a' + false + + + 'a' < 'aa' + true + + + 'aa' < 'a' + false + + + 'Jack' < 'Jill' + true + + + DateTime(2012, 2, 9) < DateTime(2012, 2, 10) + true + + + DateTime(2012, 2, 14) < DateTime(2012, 2, 13) + false + + + @T10:00:00.001 < @T10:00:00.002 + true + + + @T10:10:00.000 < @T10:00:00.001 + false + + + DateTime(2014) < DateTime(2014, 2, 15) + null + + + DateTime(2013) < DateTime(2014, 2, 15) + true + + + DateTime(2015) < DateTime(2014, 2, 15) + false + + + + + 0 <= 0 + true + + + 0 <= 1 + true + + + 0 <= -1 + false + + + 0.0 <= 0.0 + true + + + 0.0 <= 1.0 + true + + + 0.0 <= -1.0 + false + + + 1.0 <= 2 + true + + + 0'cm' <= 0'cm' + true + + + 0'cm' <= 1'cm' + true + + + 0'cm' <= -1'cm' + false + + + 1'm' <= 1'cm' + false + + + 1'm' <= 10'cm' + false + + + 'a' <= 'a' + true + + + 'a' <= 'b' + true + + + 'b' <= 'a' + false + + + 'a' <= 'aa' + true + + + 'aa' <= 'a' + false + + + 'Jack' <= 'Jill' + true + + + DateTime(2012, 2, 9, 0, 0, 0, 0) <= DateTime(2012, 2, 10, 0, 0, 0, 0) + true + + + DateTime(2012, 2, 12, 0, 0, 0, 0) <= DateTime(2012, 2, 12, 0, 0, 0, 0) + true + + + DateTime(2012, 2, 12, 1, 0, 0, 0) <= DateTime(2012, 2, 12, 0, 0, 0, 0) + false + + + @T10:00:00.001 <= @T10:00:00.002 + true + + + @T10:00:00.000 <= @T10:00:00.000 + true + + + @T10:00:00.002 <= @T10:00:00.001 + false + + + DateTime(2014) <= DateTime(2014, 2, 15) + null + + + DateTime(2013) <= DateTime(2014, 2, 15) + true + + + DateTime(2015) <= DateTime(2014, 2, 15) + false + + + + + true ~ true + true + + + true ~ false + false + + + false ~ false + true + + + false ~ true + false + + + null as String ~ null + true + + + true ~ null + false + + + null ~ true + false + + + 1 ~ 1 + true + + + 1 ~ 2 + false + + + 'a' ~ 'a' + true + + + 'a' ~ 'b' + false + + + 1.0 ~ 1.0 + true + + + 1.0 ~ 2.0 + false + + + 1.0 ~ 1 + true + + + 1.0 ~ 2 + false + + + 1'cm' ~ 1'cm' + true + + + 1'cm' ~ 0.01'm' + true + + + Tuple { Id : 1, Name : 'John' } ~ Tuple { Id : 1, Name : 'John' } + true + + + Tuple { Id : 1, Name : 'John', Position: null } ~ Tuple { Id : 1, Name : 'John', Position: null } + true + + + Tuple { Id : 1, Name : 'John' } ~ Tuple { Id : 2, Name : 'Jane' } + false + + + Tuple { Id : 1, Name : 'John' } ~ Tuple { Id : 2, Name : 'John' } + false + + + Today() ~ Today() + true + + + Today() ~ Today() - 1 days + false + + + @T10:00:00.000 ~ @T10:00:00.000 + true + + + @T10:00:00.000 ~ @T22:00:00.000 + false + + + + + true != true + false + + + true != false + true + + + false != false + false + + + false != true + true + + + null as String != null + null + + + true != null + null + + + null != true + null + + + 1 != 1 + false + + + 1 != 2 + true + + + 'a' != 'a' + false + + + 'a' != 'b' + true + + + 1.0 != 1.0 + false + + + 1.0 != 2.0 + true + + + 1.0 != 1 + false + + + 1.0 != 2 + true + + + 1'cm' != 1'cm' + false + + + 1'cm' != 0.01'm' + false + + + Tuple{ Id : 1, Name : 'John' } != Tuple{ Id : 1, Name : 'John' } + false + + + Tuple{ Id : 1, Name : 'John' } != Tuple{ Id : 2, Name : 'Jane' } + true + + + Tuple{ Id : 1, Name : 'John' } != Tuple{ Id : 2, Name : 'John' } + true + + + Tuple{ Id : 1, Name : 'John' } != Tuple{ Id : 2, Name : null } + true + + + Tuple{ Id : null, Name : 'John' } != Tuple{ Id : 1, Name : 'Joe' } + true + + + Tuple{ Id : 1, Name : null } != Tuple{ Id : 1, Name : null } + false + + + Tuple{ Id : null, Name : 'John' } != Tuple{ Id : null, Name : 'John' } + false + + + Tuple{ Id : 1, Name : 'John' } != Tuple{ Id : 1, Name : null } + null + + + Today() != Today() + false + + + Today() != Today() - 1 days + true + + + @T10:00:00.000 != @T10:00:00.000 + false + + + @T10:00:00.000 != @T22:00:00.000 + true + + + diff --git a/tests/spectests/third_party/cqltests/CqlConditionalOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlConditionalOperatorsTest.xml new file mode 100644 index 0000000..1da5d0e --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlConditionalOperatorsTest.xml @@ -0,0 +1,82 @@ + + + + + if 10 > 5 then 5 else 10 + 5 + + + if 10 = 5 then 10 + 5 else 10 - 5 + 5 + + + if 10 = null then 5 else 10 + 10 + + + + + + case + when 10 > 5 then 5 + when 5 > 10 then 10 + else null + end + + 5 + + + + case + when 5 > 10 then 5 + 10 + when 5 = 10 then 10 + else 10 - 5 + end + + 5 + + + + case + when null ~ 10 then null + 10 + when null ~ 5 then 5 + else 5 + 10 + end + + 15 + + + + + + case 5 + when 5 then 12 + when 10 then 10 + 5 + else 10 - 5 + end + + 12 + + + + case 10 + when 5 then 12 + when 10 then 10 + 5 + else 10 - 5 + end + + 15 + + + + case 10 + 5 + when 5 then 12 + when 10 then 10 + 5 + else 10 - 5 + end + + 5 + + + diff --git a/tests/spectests/third_party/cqltests/CqlDateTimeOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlDateTimeOperatorsTest.xml new file mode 100644 index 0000000..20fcccc --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlDateTimeOperatorsTest.xml @@ -0,0 +1,1251 @@ + + + + + DateTime(2005, 10, 10) + 5 years + @2010-10-10T + + + DateTime(2005, 10, 10) + 8000 years + + + + DateTime(2005, 5, 10) + 5 months + @2005-10-10T + + + DateTime(2005, 5, 10) + 10 months + @2006-03-10T + + + DateTime(2005, 5, 10) + 5 days + @2005-05-15T + + + DateTime(2016, 6, 10) + 21 days + @2016-07-01T + + + DateTime(2005, 5, 10, 5) + 5 hours + @2005-05-10T10 + + + DateTime(2016, 6, 10, 5) + 19 hours + @2016-06-11T00 + + + DateTime(2005, 5, 10, 5, 5) + 5 minutes + @2005-05-10T05:10 + + + DateTime(2016, 6, 10, 5, 5) + 55 minutes + @2016-06-10T06:00 + + + DateTime(2005, 5, 10, 5, 5, 5) + 5 seconds + @2005-05-10T05:05:10 + + + DateTime(2016, 6, 10, 5, 5, 5) + 55 seconds + @2016-06-10T05:06:00 + + + DateTime(2005, 5, 10, 5, 5, 5, 5) + 5 milliseconds + @2005-05-10T05:05:05.010 + + + DateTime(2016, 6, 10, 5, 5, 5, 5) + 995 milliseconds + @2016-06-10T05:05:06.000 + + + DateTime(2012, 2, 29) + 1 year + @2013-02-28T + + + DateTime(2014) + 24 months + @2016T + + + DateTime(2014) + 730 days + @2016T + + + DateTime(2014) + 735 days + @2016T + + + @T15:59:59.999 + 5 hours + @T20:59:59.999 + + + @T15:59:59.999 + 1 minute + @T16:00:59.999 + + + @T15:59:59.999 + 1 seconds + @T16:00:00.999 + + + @T15:59:59.999 + 1 milliseconds + @T16:00:00.000 + + + @T15:59:59.999 + 5 hours + 1 minutes + @T21:00:59.999 + + + @T15:59:59.999 + 300 minutes + @T20:59:59.999 + + + + + DateTime(2005, 10, 10) after year of DateTime(2004, 10, 10) + true + + + DateTime(2004, 11, 10) after year of DateTime(2004, 10, 10) + false + + + DateTime(2004, 12, 10) after month of DateTime(2004, 11, 10) + true + + + DateTime(2004, 9, 10) after month of DateTime(2004, 10, 10) + false + + + DateTime(2004, 12, 11) after day of DateTime(2004, 10, 10) + true + + + DateTime(2004, 12, 09) after day of DateTime(2003, 10, 10) + true + + + DateTime(2004, 10, 9) after day of DateTime(2004, 10, 10) + false + + + DateTime(2004, 10, 10, 10) after hour of DateTime(2004, 10, 10, 5) + true + + + DateTime(2004, 10, 10, 20) after hour of DateTime(2004, 10, 10, 21) + false + + + DateTime(2004, 10, 10, 20, 30) after minute of DateTime(2004, 10, 10, 20, 29) + true + + + DateTime(2004, 10, 10, 20, 30) after minute of DateTime(2004, 10, 10, 20, 31) + false + + + DateTime(2004, 10, 10, 20, 30, 15) after second of DateTime(2004, 10, 10, 20, 30, 14) + true + + + DateTime(2004, 10, 10, 20, 30, 15) after second of DateTime(2004, 10, 10, 20, 30, 16) + false + + + DateTime(2004, 10, 10, 20, 30, 15, 512) after millisecond of DateTime(2004, 10, 10, 20, 30, 15, 510) + true + + + DateTime(2004, 10, 10, 20, 30, 15, 512) after millisecond of DateTime(2004, 10, 10, 20, 30, 15, 513) + false + + + DateTime(2005, 10, 10) after day of DateTime(2005, 9) + true + + + @2012-03-10T10:20:00.999+07:00 after hour of @2012-03-10T08:20:00.999+06:00 + true + + + @2012-03-10T10:20:00.999+07:00 after hour of @2012-03-10T10:20:00.999+06:00 + false + + + @T15:59:59.999 after hour of @T14:59:59.999 + true + + + @T15:59:59.999 after hour of @T16:59:59.999 + false + + + @T15:59:59.999 after minute of @T15:58:59.999 + true + + + @T15:58:59.999 after minute of @T15:59:59.999 + false + + + @T15:59:59.999 after second of @T15:59:58.999 + true + + + @T15:59:58.999 after second of @T15:59:59.999 + false + + + @T15:59:59.999 after millisecond of @T15:59:59.998 + true + + + @T15:59:59.998 after millisecond of @T15:59:59.999 + false + + + Time(12, 30) after hour of Time(11, 55) + true + + + + + + DateTime(2003) before year of DateTime(2004, 10, 10) + true + + + DateTime(2004, 11, 10) before year of DateTime(2003, 10, 10) + false + + + DateTime(2004, 10, 10) before month of DateTime(2004, 11, 10) + true + + + DateTime(2004, 11, 10) before month of DateTime(2004, 10, 10) + false + + + DateTime(2004, 10, 1) before day of DateTime(2004, 10, 10) + true + + + DateTime(2003, 10, 11) before day of DateTime(2004, 10, 10) + true + + + DateTime(2004, 10, 11) before day of DateTime(2004, 10, 10) + false + + + DateTime(2004, 10, 10, 1) before hour of DateTime(2004, 10, 10, 5) + true + + + DateTime(2004, 10, 10, 23) before hour of DateTime(2004, 10, 10, 21) + false + + + DateTime(2004, 10, 10, 20, 28) before minute of DateTime(2004, 10, 10, 20, 29) + true + + + DateTime(2004, 10, 10, 20, 35) before minute of DateTime(2004, 10, 10, 20, 31) + false + + + DateTime(2004, 10, 10, 20, 30, 12) before second of DateTime(2004, 10, 10, 20, 30, 14) + true + + + DateTime(2004, 10, 10, 20, 30, 55) before second of DateTime(2004, 10, 10, 20, 30, 16) + false + + + DateTime(2004, 10, 10, 20, 30, 15, 508) before millisecond of DateTime(2004, 10, 10, 20, 30, 15, 510) + true + + + DateTime(2004, 10, 10, 20, 30, 15, 599) before millisecond of DateTime(2004, 10, 10, 20, 30, 15, 513) + false + + + @2012-03-10T10:20:00.999+07:00 before hour of @2012-03-10T10:20:00.999+06:00 + true + + + @2012-03-10T10:20:00.999+07:00 before hour of @2012-03-10T09:20:00.999+06:00 + false + + + @T13:59:59.999 before hour of @T14:59:59.999 + true + + + @T16:59:59.999 before hour of @T15:59:59.999 + false + + + @T15:57:59.999 before minute of @T15:58:59.999 + true + + + @T15:59:59.999 before minute of @T15:59:59.999 + false + + + @T15:59:57.999 before second of @T15:59:58.999 + true + + + @T15:59:56.999 before second of @T15:59:55.999 + false + + + @T15:59:59.997 before millisecond of @T15:59:59.998 + true + + + @T15:59:59.998 before millisecond of @T15:59:59.997 + false + + + + + + DateTime(2003) + @2003T + + + DateTime(2003, 10) + @2003-10T + + + DateTime(2003, 10, 29) + @2003-10-29T + + + DateTime(2003, 10, 29, 20) + @2003-10-29T20 + + + DateTime(2003, 10, 29, 20, 50) + @2003-10-29T20:50 + + + DateTime(2003, 10, 29, 20, 50, 33) + @2003-10-29T20:50:33 + + + DateTime(2003, 10, 29, 20, 50, 33, 955) + @2003-10-29T20:50:33.955 + + + + + year from DateTime(2003, 10, 29, 20, 50, 33, 955) + 2003 + + + month from DateTime(2003, 10, 29, 20, 50, 33, 955) + 10 + + + month from DateTime(2003, 01, 29, 20, 50, 33, 955) + 1 + + + day from DateTime(2003, 10, 29, 20, 50, 33, 955) + 29 + + + hour from DateTime(2003, 10, 29, 20, 50, 33, 955) + 20 + + + minute from DateTime(2003, 10, 29, 20, 50, 33, 955) + 50 + + + second from DateTime(2003, 10, 29, 20, 50, 33, 955) + 33 + + + millisecond from DateTime(2003, 10, 29, 20, 50, 33, 955) + 955 + + + timezone from DateTime(2003, 10, 29, 20, 50, 33, 955, 1) + 1.00 + + + + date from DateTime(2003, 10, 29, 20, 50, 33, 955, 1) + @2003-10-29 + + + hour from @T23:20:15.555 + 23 + + + minute from @T23:20:15.555 + 20 + + + second from @T23:20:15.555 + 15 + + + millisecond from @T23:20:15.555 + 555 + + + + + difference in years between DateTime(2000) and DateTime(2005, 12) + 5 + + + difference in months between DateTime(2000, 2) and DateTime(2000, 10) + 8 + + + difference in days between DateTime(2000, 10, 15, 10, 30) and DateTime(2000, 10, 25, 10, 0) + 10 + + + difference in hours between DateTime(2000, 4, 1, 12) and DateTime(2000, 4, 1, 20) + 8 + + + difference in minutes between DateTime(2005, 12, 10, 5, 16) and DateTime(2005, 12, 10, 5, 25) + 9 + + + difference in seconds between DateTime(2000, 10, 10, 10, 5, 45) and DateTime(2000, 10, 10, 10, 5, 50) + 5 + + + difference in milliseconds between DateTime(2000, 10, 10, 10, 5, 45, 500, -6.0) and DateTime(2000, 10, 10, 10, 5, 45, 900, -7.0) + 3600400 + + + difference in weeks between DateTime(2000, 10, 15) and DateTime(2000, 10, 28) + 1 + + + difference in weeks between DateTime(2000, 10, 15) and DateTime(2000, 10, 29) + 2 + + + difference in weeks between @2012-03-10T22:05:09 and @2012-03-24T07:19:33 + 2 + + + difference in years between DateTime(2016) and DateTime(1998) + -18 + + + difference in months between DateTime(2005) and DateTime(2006, 7) > 5 + true + + + difference in hours between @T20 and @T23:25:15.555 + 3 + + + difference in minutes between @T20:20:15.555 and @T20:25:15.555 + 5 + + + difference in seconds between @T20:20:15.555 and @T20:20:20.555 + 5 + + + difference in milliseconds between @T20:20:15.555 and @T20:20:15.550 + -5 + + + + + @2017-03-12T01:00:00-07:00 + @2017-03-12T01:00:00-07:00 + + + DateTime(2017, 3, 12, 1, 0, 0, 0, -7.0) + @2017-03-12T01:00:00.000-07:00 + + + @2017-03-12T03:00:00-06:00 + @2017-03-12T03:00:00-06:00 + + + DateTime(2017, 3, 12, 3, 0, 0, 0, -6.0) + @2017-03-12T03:00:00.000-06:00 + + + @2017-11-05T01:30:00-06:00 + @2017-11-05T01:30:00-06:00 + + + DateTime(2017, 11, 5, 1, 30, 0, 0, -6.0) + @2017-11-05T01:30:00.000-06:00 + + + @2017-11-05T01:15:00-07:00 + @2017-11-05T01:15:00-07:00 + + + DateTime(2017, 11, 5, 1, 15, 0, 0, -7.0) + @2017-11-05T01:15:00.000-07:00 + + + @2017-03-12T00:00:00-07:00 + @2017-03-12T00:00:00-07:00 + + + DateTime(2017, 3, 12, 0, 0, 0, 0, -7.0) + @2017-03-12T00:00:00.000-07:00 + + + @2017-03-13T00:00:00-06:00 + @2017-03-13T00:00:00-06:00 + + + DateTime(2017, 3, 13, 0, 0, 0, 0, -6.0) + @2017-03-13T00:00:00.000-06:00 + + + difference in hours between @2017-03-12T01:00:00-07:00 and @2017-03-12T03:00:00-06:00 + 1 + + + difference in minutes between @2017-11-05T01:30:00-06:00 and @2017-11-05T01:15:00-07:00 + 45 + + + difference in days between @2017-03-12T00:00:00-07:00 and @2017-03-13T00:00:00-06:00 + 1 + + + difference in hours between DateTime(2017, 3, 12, 1, 0, 0, 0, -7.0) and DateTime(2017, 3, 12, 3, 0, 0, 0, -6.0) + 1 + + + difference in minutes between DateTime(2017, 11, 5, 1, 30, 0, 0, -6.0) and DateTime(2017, 11, 5, 1, 15, 0, 0, -7.0) + 45 + + + difference in days between DateTime(2017, 3, 12, 0, 0, 0, 0, -7.0) and DateTime(2017, 3, 13, 0, 0, 0, 0, -6.0) + 1 + + + + + + years between DateTime(2005) and DateTime(2010) + Interval[ 4, 5 ] + + + + years between DateTime(2005, 5) and DateTime(2010, 4) + 4 + + + months between @2014-01-31 and @2014-02-01 + 0 + + + days between DateTime(2010, 10, 12, 12, 5) and DateTime(2008, 8, 15, 8, 8) + -788 + + + + + days between DateTime(2014, 1, 15) and DateTime(2014, 2) + Interval[ 16, 44 ] + + + + months between DateTime(2005) and DateTime(2006, 5) + Interval[ 4, 16 ] + + + + (days between DateTime(2014, 1, 15) and DateTime(2014, 2)) + + (days between DateTime(2014, 1, 15) and DateTime(2014, 2)) + Interval[ 32, 88 ] + + + + (days between DateTime(2014, 1, 15) and DateTime(2014, 2)) + - (months between DateTime(2005) and DateTime(2006, 5)) + Interval[ 0, 40 ] + + + + (days between DateTime(2014, 1, 15) and DateTime(2014, 2)) + * (days between DateTime(2014, 1, 15) and DateTime(2014, 2)) + Interval[ 256, 1936 ] + + + + (days between DateTime(2014, 1, 15) and DateTime(2014, 2)) + div (months between DateTime(2005) and DateTime(2006, 5)) + + + months between DateTime(2005) and DateTime(2006, 7) > 5 + true + + + months between DateTime(2005) and DateTime(2006, 2) > 5 + null + + + months between DateTime(2005) and DateTime(2006, 7) > 25 + false + + + months between DateTime(2005) and DateTime(2006, 7) < 24 + true + + + months between DateTime(2005) and DateTime(2006, 7) = 24 + false + + + months between DateTime(2005) and DateTime(2006, 7) >= 5 + true + + + months between DateTime(2005) and DateTime(2006, 7) <= 24 + true + + + @2012-03-10T10:20:00 + @2012-03-10T10:20:00 + + + @2013-03-10T09:20:00 + @2013-03-10T09:20:00 + + + years between (date from @2012-03-10T10:20:00) and (date from @2013-03-10T09:20:00) + 1 + + + weeks between @2012-03-10T22:05:09 and @2012-03-20T07:19:33 + 1 + + + weeks between @2012-03-10T22:05:09 and @2012-03-24T07:19:33 + 1 + + + weeks between @2012-03-10T06:05:09 and @2012-03-24T07:19:33 + 2 + + + hours between @T20:26:15.555 and @T23:25:15.555 + 2 + + + hours between @T06Z and @T07:00:00Z + 1 + + + + minutes between @T23:20:16.555 and @T23:25:15.555 + 4 + + + seconds between @T23:25:10.556 and @T23:25:15.555 + 4 + + + milliseconds between @T23:25:25.555 and @T23:25:25.560 + 5 + + + + hours between @2017-03-12T01:00:00-07:00 and @2017-03-12T03:00:00-06:00 + 1 + + + minutes between @2017-11-05T01:30:00-06:00 and @2017-11-05T01:15:00-07:00 + 45 + + + days between @2017-03-12T00:00:00-07:00 and @2017-03-13T00:00:00-06:00 + 0 + + + hours between DateTime(2017, 3, 12, 1, 0, 0, 0, -7.0) and DateTime(2017, 3, 12, 3, 0, 0, 0, -6.0) + 1 + + + minutes between DateTime(2017, 11, 5, 1, 30, 0, 0, -6.0) and DateTime(2017, 11, 5, 1, 15, 0, 0, -7.0) + 45 + + + days between DateTime(2017, 3, 12, 0, 0, 0, 0, -7.0) and DateTime(2017, 3, 13, 0, 0, 0, 0, -6.0) + 0 + + + + + Now() = Now() + true + + + + + + DateTime(2014) same year as DateTime(2014) + true + + + DateTime(2013) same year as DateTime(2014) + false + + + DateTime(2014, 12) same month as DateTime(2014, 12) + true + + + DateTime(2014, 12) same month as DateTime(2014, 10) + false + + + DateTime(2014, 12, 10) same day as DateTime(2014, 12, 10) + true + + + DateTime(2014, 10, 10) same day as DateTime(2014, 10, 11) + false + + + DateTime(2014, 12, 10, 20) same hour as DateTime(2014, 12, 10, 20) + true + + + DateTime(2014, 10, 10, 20) same hour as DateTime(2014, 10, 10, 21) + false + + + DateTime(2014, 12, 10, 20, 55) same minute as DateTime(2014, 12, 10, 20, 55) + true + + + DateTime(2014, 10, 10, 20, 55) same minute as DateTime(2014, 10, 10, 21, 56) + false + + + DateTime(2014, 12, 10, 20, 55, 45) same second as DateTime(2014, 12, 10, 20, 55, 45) + true + + + DateTime(2014, 10, 10, 20, 55, 45) same second as DateTime(2014, 10, 10, 21, 55, 44) + false + + + DateTime(2014, 12, 10, 20, 55, 45, 500) same millisecond as DateTime(2014, 12, 10, 20, 55, 45, 500) + true + + + DateTime(2014, 10, 10, 20, 55, 45, 500) same millisecond as DateTime(2014, 10, 10, 21, 55, 45, 501) + false + + + DateTime(2014, 10) same day as DateTime(2014, 10, 12) + null + + + @2012-03-10T10:20:00.999+07:00 same hour as @2012-03-10T09:20:00.999+06:00 + true + + + @2012-03-10T10:20:00.999+07:00 same hour as @2012-03-10T10:20:00.999+06:00 + false + + + @T23:25:25.555 same hour as @T23:55:25.900 + true + + + @T22:25:25.555 same hour as @T23:25:25.555 + false + + + @T23:55:22.555 same minute as @T23:55:25.900 + true + + + @T23:26:25.555 same minute as @T23:25:25.555 + false + + + @T23:55:25.555 same second as @T23:55:25.900 + true + + + @T23:25:35.555 same second as @T23:25:25.555 + false + + + @T23:55:25.555 same millisecond as @T23:55:25.555 + true + + + @T23:25:25.555 same millisecond as @T23:25:25.554 + false + + + + + DateTime(2014) same year or after DateTime(2014) + true + + + DateTime(2016) same year or after DateTime(2014) + true + + + DateTime(2013) same year or after DateTime(2014) + false + + + DateTime(2014, 12) same month or after DateTime(2014, 12) + true + + + DateTime(2014, 10) same month or after DateTime(2014, 9) + true + + + DateTime(2014, 10) same month or after DateTime(2014, 11) + false + + + DateTime(2014, 12, 20) same day or after DateTime(2014, 12, 20) + true + + + DateTime(2014, 10, 25) same day or after DateTime(2014, 10, 20) + true + + + DateTime(2014, 10, 20) same day or after DateTime(2014, 10, 25) + false + + + DateTime(2014, 12, 20, 12) same hour or after DateTime(2014, 12, 20, 12) + true + + + DateTime(2014, 10, 25, 12) same hour or after DateTime(2014, 10, 25, 10) + true + + + DateTime(2014, 10, 25, 12) same hour or after DateTime(2014, 10, 25, 15) + false + + + DateTime(2014, 12, 20, 12, 30) same minute or after DateTime(2014, 12, 20, 12, 30) + true + + + DateTime(2014, 10, 25, 10, 30) same minute or after DateTime(2014, 10, 25, 10, 25) + true + + + DateTime(2014, 10, 25, 15, 30) same minute or after DateTime(2014, 10, 25, 15, 45) + false + + + DateTime(2014, 12, 20, 12, 30, 15) same second or after DateTime(2014, 12, 20, 12, 30, 15) + true + + + DateTime(2014, 10, 25, 10, 25, 25) same second or after DateTime(2014, 10, 25, 10, 25, 20) + true + + + DateTime(2014, 10, 25, 15, 45, 20) same second or after DateTime(2014, 10, 25, 15, 45, 21) + false + + + DateTime(2014, 12, 20, 12, 30, 15, 250) same millisecond or after DateTime(2014, 12, 20, 12, 30, 15, 250) + true + + + DateTime(2014, 10, 25, 10, 25, 20, 500) same millisecond or after DateTime(2014, 10, 25, 10, 25, 20, 499) + true + + + DateTime(2014, 10, 25, 15, 45, 20, 500) same millisecond or after DateTime(2014, 10, 25, 15, 45, 20, 501) + false + + + DateTime(2014, 12, 20) same day or after DateTime(2014, 12) + null + + + @2012-03-10T10:20:00.999+07:00 same hour or after @2012-03-10T09:20:00.999+06:00 + true + + + @2012-03-10T10:20:00.999+07:00 same hour or after @2012-03-10T10:20:00.999+06:00 + false + + + @T23:25:25.555 same hour or after @T23:55:25.900 + true + + + @T23:25:25.555 same hour or after @T22:55:25.900 + true + + + @T22:25:25.555 same hour or after @T23:55:25.900 + false + + + @T23:25:25.555 same minute or after @T23:25:25.900 + true + + + @T23:25:25.555 same minute or after @T22:15:25.900 + true + + + @T23:25:25.555 same minute or after @T23:55:25.900 + false + + + @T23:25:25.555 same second or after @T23:25:25.900 + true + + + @T23:25:35.555 same second or after @T22:25:25.900 + true + + + @T23:55:25.555 same second or after @T23:55:35.900 + false + + + @T23:25:25.555 same millisecond or after @T23:25:25.555 + true + + + @T23:25:25.555 same millisecond or after @T22:25:25.550 + true + + + @T23:55:25.555 same millisecond or after @T23:55:25.900 + false + + + @2017-12-20T11:00:00.000 on or after @2017-12-20T11:00:00.000 + true + + + @2017-12-21T02:00:00.0 same or after @2017-12-20T11:00:00.0 + true + + + + + DateTime(2014) same year or before DateTime(2014) + true + + + DateTime(2013) same year or before DateTime(2014) + true + + + DateTime(2015) same year or before DateTime(2014) + false + + + DateTime(2014, 12) same month or before DateTime(2014, 12) + true + + + DateTime(2014, 8) same month or before DateTime(2014, 9) + true + + + DateTime(2014, 12) same month or before DateTime(2014, 11) + false + + + DateTime(2014, 12, 20) same day or before DateTime(2014, 12, 20) + true + + + DateTime(2014, 10, 15) same day or before DateTime(2014, 10, 20) + true + + + DateTime(2014, 10, 30) same day or before DateTime(2014, 10, 25) + false + + + DateTime(2014, 12, 20, 12) same hour or before DateTime(2014, 12, 20, 12) + true + + + DateTime(2014, 10, 25, 5) same hour or before DateTime(2014, 10, 25, 10) + true + + + DateTime(2014, 10, 25, 20) same hour or before DateTime(2014, 10, 25, 15) + false + + + DateTime(2014, 12, 20, 12, 30) same minute or before DateTime(2014, 12, 20, 12, 30) + true + + + DateTime(2014, 10, 25, 10, 20) same minute or before DateTime(2014, 10, 25, 10, 25) + true + + + DateTime(2014, 10, 25, 15, 55) same minute or before DateTime(2014, 10, 25, 15, 45) + false + + + DateTime(2014, 12, 20, 12, 30, 15) same second or before DateTime(2014, 12, 20, 12, 30, 15) + true + + + DateTime(2014, 10, 25, 10, 25, 15) same second or before DateTime(2014, 10, 25, 10, 25, 20) + true + + + DateTime(2014, 10, 25, 15, 45, 25) same second or before DateTime(2014, 10, 25, 15, 45, 21) + false + + + DateTime(2014, 12, 20, 12, 30, 15, 250) same millisecond or before DateTime(2014, 12, 20, 12, 30, 15, 250) + true + + + DateTime(2014, 10, 25, 10, 25, 20, 450) same millisecond or before DateTime(2014, 10, 25, 10, 25, 20, 499) + true + + + DateTime(2014, 10, 25, 15, 45, 20, 505) same millisecond or before DateTime(2014, 10, 25, 15, 45, 20, 501) + false + + + DateTime(2014, 12, 20) same minute or before DateTime(2014, 12, 20, 15) + null + + + @2012-03-10T09:20:00.999+07:00 same hour or before @2012-03-10T10:20:00.999+06:00 + true + + + @2012-03-10T10:20:00.999+06:00 same hour or before @2012-03-10T10:20:00.999+07:00 + false + + + @T23:25:25.555 same hour or before @T23:55:25.900 + true + + + @T21:25:25.555 same hour or before @T22:55:25.900 + true + + + @T22:25:25.555 same hour or before @T21:55:25.900 + false + + + @T23:25:25.555 same minute or before @T23:25:25.900 + true + + + @T23:10:25.555 same minute or before @T22:15:25.900 + false + + + @T23:56:25.555 same minute or before @T23:55:25.900 + false + + + @T23:25:25.555 same second or before @T23:25:25.900 + true + + + @T23:25:35.555 same second or before @T22:25:45.900 + false + + + @T23:55:45.555 same second or before @T23:55:35.900 + false + + + @T23:25:25.555 same millisecond or before @T23:25:25.555 + true + + + @T23:25:25.200 same millisecond or before @T22:25:25.550 + false + + + @T23:55:25.966 same millisecond or before @T23:55:25.900 + false + + + + + DateTime(2005, 10, 10) - 5 years + @2000-10-10T + + + DateTime(2005, 10, 10) - 2005 years + + + + DateTime(2005, 6, 10) - 5 months + @2005-01-10T + + + DateTime(2005, 5, 10) - 6 months + @2004-11-10T + + + DateTime(2005, 5, 10) - 5 days + @2005-05-05T + + + DateTime(2016, 6, 10) - 11 days + @2016-05-30T + + + DateTime(2005, 5, 10, 10) - 5 hours + @2005-05-10T05 + + + DateTime(2016, 6, 10, 5) - 6 hours + @2016-06-09T23 + + + DateTime(2005, 5, 10, 5, 10) - 5 minutes + @2005-05-10T05:05 + + + DateTime(2016, 6, 10, 5, 5) - 6 minutes + @2016-06-10T04:59 + + + DateTime(2005, 5, 10, 5, 5, 10) - 5 seconds + @2005-05-10T05:05:05 + + + DateTime(2016, 6, 10, 5, 5, 5) - 6 seconds + @2016-06-10T05:04:59 + + + DateTime(2005, 5, 10, 5, 5, 5, 10) - 5 milliseconds + @2005-05-10T05:05:05.005 + + + DateTime(2016, 6, 10, 5, 5, 5, 5) - 6 milliseconds + @2016-06-10T05:05:04.999 + + + DateTime(2014) - 24 months + @2012T + + + DateTime(2014) - 25 months + @2012T + + + @T15:59:59.999 - 5 hours + @T10:59:59.999 + + + @T15:59:59.999 - 1 minutes + @T15:58:59.999 + + + @T15:59:59.999 - 1 seconds + @T15:59:58.999 + + + @T15:59:59.0 - 1 milliseconds + @T15:59:58.999 + + + @T15:59:59.999 - 5 hours - 1 minutes + @T10:58:59.999 + + + @T15:59:59.999 - 300 minutes + @T10:59:59.999 + + + + + @T23:59:59.999 + @T23:59:59.999 + + + + + TimeOfDay() + TimeOfDay() + + + + + Today() same day or before Today() + true + + + Today() same day or before Today() + 1 days + true + + + Today() + 1 years same day or before Today() + false + + + Today() + 1 days > Today() + true + + + Today() + Today() + + + diff --git a/tests/spectests/third_party/cqltests/CqlErrorsAndMessagingOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlErrorsAndMessagingOperatorsTest.xml new file mode 100644 index 0000000..6c6cc03 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlErrorsAndMessagingOperatorsTest.xml @@ -0,0 +1,22 @@ + + + + + Message(1, true, '100', 'Message', 'Test Message') + 1 + + + Message(2, true, '200', 'Warning', 'You have been warned!') + 2 + + + Message({3, 4, 5}, true, '300', 'Trace', 'This is a trace') + {3, 4, 5} + + + Message(3 + 1, true, '400', 'Error', 'This is an error!') + + + + diff --git a/tests/spectests/third_party/cqltests/CqlIntervalOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlIntervalOperatorsTest.xml new file mode 100644 index 0000000..72356c8 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlIntervalOperatorsTest.xml @@ -0,0 +1,1530 @@ + + + + + (null as Integer) after Interval[1, 10] + null + + + Interval[11, 20] after Interval[1, 10] + true + + + Interval[1, 10] after Interval[11, 20] + false + + + 12 after Interval[1, 10] + true + + + 9 after Interval[1, 10] + false + + + Interval[11, 20] after 5 + true + + + Interval[11, 20] after 12 + false + + + Interval[11.0, 20.0] after Interval[1.0, 10.0] + true + + + Interval[1.0, 10.0] after Interval[11.0, 20.0] + false + + + 12.0 after Interval[1.0, 10.0] + true + + + 9.0 after Interval[1.0, 10.0] + false + + + Interval[11.0, 20.0] after 5.0 + true + + + Interval[11.0, 20.0] after 12.0 + false + + + Interval[11.0 'g', 20.0 'g'] after Interval[1.0 'g', 10.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] after Interval[11.0 'g', 20.0 'g'] + false + + + 12.0'g' after Interval[1.0 'g', 10.0 'g'] + true + + + 9.0'g' after Interval[1.0 'g', 10.0 'g'] + false + + + Interval[11.0 'g', 20.0 'g'] after 5.0'g' + true + + + Interval[11.0 'g', 20.0 'g'] after 12.0'g' + false + + + Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)] after DateTime(2011, 12, 31) + true + + + Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)] after DateTime(2012, 12, 31) + false + + + Interval[@T15:59:59.999, @T20:59:59.999] after @T12:59:59.999 + true + + + Interval[@T15:59:59.999, @T20:59:59.999] after @T17:59:59.999 + false + + + + + (null as Integer) before Interval[1, 10] + null + + + Interval[11, 20] before Interval[1, 10] + false + + + Interval[1, 10] before Interval[11, 20] + true + + + 9 before Interval[11, 20] + true + + + 9 before Interval[1, 10] + false + + + Interval[1, 10] before 11 + true + + + Interval[1, 10] before 8 + false + + + Interval[11.0, 20.0] before Interval[1.0, 10.0] + false + + + Interval[1.0, 10.0] before Interval[11.0, 20.0] + true + + + 9.0 before Interval[11.0, 20.0] + true + + + 9.0 before Interval[1.0, 10.0] + false + + + Interval[1.0, 10.0] before 11.0 + true + + + Interval[1.0, 10.0] before 8.0 + false + + + Interval[1.0 'g', 10.0 'g'] before Interval[11.0 'g', 20.0 'g'] + true + + + Interval[11.0 'g', 20.0 'g'] before Interval[1.0 'g', 10.0 'g'] + false + + + Interval[1.0 'g', 10.0 'g'] before 12.0'g' + true + + + Interval[1.0 'g', 10.0 'g'] before 9.0'g' + false + + + 5.0'g' before Interval[11.0 'g', 20.0 'g'] + true + + + 12.0'g' before Interval[11.0 'g', 20.0 'g'] + false + + + Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)] before DateTime(2012, 2, 27) + true + + + Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)] before DateTime(2011, 12, 31) + false + + + Interval[@T15:59:59.999, @T20:59:59.999] before @T22:59:59.999 + true + + + Interval[@T15:59:59.999, @T20:59:59.999] before @T10:59:59.999 + false + + + + + collapse {Interval(null, null)} + { } + + + collapse { Interval[1,5], Interval[3,7], Interval[12,19], Interval[7,10] } + {Interval [ 1, 10 ], Interval [ 12, 19 ]} + + + collapse { Interval[1,2], Interval[3,7], Interval[10,19], Interval[7,10] } + {Interval [ 1, 19 ]} + + + collapse { Interval[4,6], Interval[7,8] } + {Interval [ 4, 8 ]} + + + collapse { Interval[1.0,5.0], Interval[3.0,7.0], Interval[12.0,19.0], Interval[7.0,10.0] } + {Interval [ 1.0, 10.0 ], Interval [ 12.0, 19.0 ]} + + + collapse { Interval[4.0,6.0], Interval[6.00000001,8.0] } + {Interval [ 4.0, 8.0 ]} + + + collapse { Interval[1.0 'g',5.0 'g'], Interval[3.0 'g',7.0 'g'], Interval[12.0 'g',19.0 'g'], Interval[7.0 'g',10.0 'g'] } + {Interval [ 1.0 'g', 10.0 'g' ], Interval [ 12.0 'g', 19.0 'g' ]} + + + collapse { Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)], Interval[DateTime(2012, 1, 10), DateTime(2012, 1, 25)], Interval[DateTime(2012, 5, 10), DateTime(2012, 5, 25)], Interval[DateTime(2012, 5, 20), DateTime(2012, 5, 30)] } + {Interval [ @2012-01-01T, @2012-01-25T ], Interval [ @2012-05-10T, @2012-05-30T ]} + + + collapse { Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)], Interval[DateTime(2012, 1, 16), DateTime(2012, 5, 25)] } + {Interval [ @2012-01-01T, @2012-05-25T ]} + + + collapse { Interval[@T01:59:59.999, @T10:59:59.999], Interval[@T08:59:59.999, @T15:59:59.999], Interval[@T17:59:59.999, @T20:59:59.999], Interval[@T18:59:59.999, @T22:59:59.999] } + {Interval [ @T01:59:59.999, @T15:59:59.999 ], Interval [ @T17:59:59.999, @T22:59:59.999 ]} + + + collapse { Interval[@T01:59:59.999, @T10:59:59.999], Interval[@T11:00:00.000, @T15:59:59.999] } + {Interval [ @T01:59:59.999, @T15:59:59.999 ]} + + + + + expand { Interval[@2018-01-01, @2018-01-04] } per day + { Interval[@2018-01-01, @2018-01-01], Interval[@2018-01-02, @2018-01-02], Interval[@2018-01-03, @2018-01-03], Interval[@2018-01-04, @2018-01-04] } + + + expand { Interval[@2018-01-01, @2018-01-04] } per 2 days + { Interval[@2018-01-01, @2018-01-02], Interval[@2018-01-03, @2018-01-04] } + + + expand { Interval[@T10:00, @T12:30] } per hour + { Interval[@T10:00, @T11:00), Interval[@T11:00, @T12:00) } + + + expand { Interval[10.0, 12.5] } per 1 + { Interval[10, 10], Interval[11, 11], Interval[12, 12] } + + + + expand { Interval[@T10, @T10] } per minute + { } + + + expand { Interval[10, 10] } per 0.1 + { Interval[10.0, 10.0], Interval[10.1, 10.1], Interval[10.2, 10.2], Interval[10.3, 10.3], Interval[10.4, 10.4], Interval[10.5, 10.5], Interval[10.6, 10.6], Interval[10.7, 10.7], Interval[10.8, 10.8], Interval[10.9, 10.9] } + + + + expand { Interval[1, 10] } + { Interval[1, 1], Interval[2, 2], Interval[3, 3], Interval[4, 4], Interval[5, 5], Interval[6, 6], Interval[7, 7], Interval[8, 8], Interval[9, 9], Interval[10, 10] } + + + expand { Interval[1, 10] } per 2 + { Interval[1, 2], Interval[3, 4], Interval[5, 6], Interval[7, 8], Interval[9, 10] } + + + + + Interval[1, 10] contains null + null + + + null contains 5 + false + + + Interval[null, 5] contains 10 + false + + + Interval[1, 10] contains 5 + true + + + Interval[1, 10] contains 25 + false + + + Interval[1.0, 10.0] contains 8.0 + true + + + Interval[1.0, 10.0] contains 255.0 + false + + + Interval[1.0 'g', 10.0 'g'] contains 2.0 'g' + true + + + Interval[1.0 'g', 10.0 'g'] contains 100.0 'g' + false + + + Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)] contains DateTime(2012, 1, 10) + true + + + Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)] contains DateTime(2012, 1, 16) + false + + + Interval[@T01:59:59.999, @T10:59:59.999] contains @T05:59:59.999 + true + + + Interval[@T01:59:59.999, @T10:59:59.999] contains @T15:59:59.999 + false + + + + + end of Interval[1, 10] + 10 + + + end of Interval[1.0, 10.0] + 10.0 + + + end of Interval[1.0 'g', 10.0 'g'] + 10.0'g' + + + end of Interval[@2016-05-01T00:00:00.000, @2016-05-02T00:00:00.000] + @2016-05-02T00:00:00.000 + + + end of Interval[@T00:00:00.000, @T23:59:59.599] + @T23:59:59.599 + + + + + Interval[1, 10] ends Interval(null, null) + null + + + Interval[4, 10] ends Interval[1, 10] + true + + + Interval[44, 50] ends Interval[1, 10] + false + + + Interval[4.0, 10.0] ends Interval[1.0, 10.0] + true + + + Interval[11.0, 20.0] ends Interval[1.0, 10.0] + false + + + Interval[5.0 'g', 10.0 'g'] ends Interval[1.0 'g', 10.0 'g'] + true + + + Interval[11.0 'g', 20.0 'g'] ends Interval[1.0 'g', 10.0 'g'] + false + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] ends Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 15)] + true + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] ends Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 16)] + false + + + Interval[@T05:59:59.999, @T10:59:59.999] ends Interval[@T01:59:59.999, @T10:59:59.999] + true + + + Interval[@T05:59:59.999, @T10:59:59.999] ends Interval[@T01:59:59.999, @T11:59:59.999] + false + + + + + Interval[1, 10] = Interval(null, null) + null + + + Interval[1, 10] = Interval[1, 10] + true + + + Interval[1, 10] = Interval[11, 20] + false + + + Interval[1.0, 10.0] = Interval[1.0, 10.0] + true + + + Interval[1.0, 10.0] = Interval[11.0, 20.0] + false + + + Interval[1.0 'g', 10.0 'g'] = Interval[1.0 'g', 10.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] = Interval[11.0 'g', 20.0 'g'] + false + + + Interval[DateTime(2012, 1, 5, 0, 0, 0, 0), DateTime(2012, 1, 15, 0, 0, 0, 0)] = Interval[DateTime(2012, 1, 5, 0, 0, 0, 0), DateTime(2012, 1, 15, 0, 0, 0, 0)] + true + + + Interval[DateTime(2012, 1, 5, 0, 0, 0, 0), DateTime(2012, 1, 15, 0, 0, 0, 0)] = Interval[DateTime(2012, 1, 5, 0, 0, 0, 0), DateTime(2012, 1, 16, 0, 0, 0, 0)] + false + + + Interval[@T05:59:59.999, @T10:59:59.999] = Interval[@T05:59:59.999, @T10:59:59.999] + true + + + Interval[@T05:59:59.999, @T10:59:59.999] = Interval[@T05:59:59.999, @T10:58:59.999] + false + + + + + Interval[null, null] + null + + + Interval[null, null] except Interval[null, null] + null + + + Interval[1, 10] except Interval[4, 10] + Interval [ 1, 3 ] + + + Interval[1, 10] except Interval[3, 7] + null + + + Interval[1.0, 10.0] except Interval[4.0, 10.0] + Interval [ 1.0, 3.99999999 ] + + + Interval[1.0, 10.0] except Interval[3.0, 7.0] + null + + + Interval[1.0 'g', 10.0 'g'] except Interval[5.0 'g', 10.0 'g'] + Interval [ 1.0 'g', 4.99999999 'g' ] + + + Interval[1, 4] except Interval[3, 6] + Interval [ 1, 2 ] + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] except Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 15)] + Interval [ @2012-01-05T, @2012-01-06T ] + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 16)] except Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 12)] + Interval [ @2012-01-13T, @2012-01-16T ] + + + Interval[@T05:59:59.999, @T10:59:59.999] except Interval[@T08:59:59.999, @T10:59:59.999] + Interval [ @T05:59:59.999, @T08:59:59.998 ] + + + Interval[@T08:59:59.999, @T11:59:59.999] except Interval[@T05:59:59.999, @T10:59:59.999] + Interval [ @T11:00:00.000, @T11:59:59.999 ] + + + + + 5 in Interval[null, null] + false + + + 5 in Interval[1, 10] + true + + + 500 in Interval[1, 10] + false + + + 9.0 in Interval[1.0, 10.0] + true + + + -2.0 in Interval[1.0, 10.0] + false + + + 1.0 'g' in Interval[1.0 'g', 10.0 'g'] + true + + + 55.0 'g' in Interval[1.0 'g', 10.0 'g'] + false + + + DateTime(2012, 1, 7) in Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] + true + + + DateTime(2012, 1, 17) in Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] + false + + + DateTime(2012, 1, 7) in Interval[DateTime(2012, 1, 5), null] + true + + + @T07:59:59.999 in Interval[@T05:59:59.999, @T10:59:59.999] + true + + + @T17:59:59.999 in Interval[@T05:59:59.999, @T10:59:59.999] + false + + + null in Interval[@T05:59:59.999, @T10:59:59.999] + null + + + Interval[@2017-12-20T11:00:00, @2017-12-21T21:00:00] + Interval [ @2017-12-20T11:00:00, @2017-12-21T21:00:00 ] + + + Interval[@2017-12-20T10:30:00, @2017-12-20T12:00:00] + Interval [ @2017-12-20T10:30:00, @2017-12-20T12:00:00 ] + + + + Interval[@2017-12-20T10:30:00, @2017-12-20T12:00:00] + starts 1 day or less on or after day of start of + Interval[@2017-12-20T11:00:00, @2017-12-21T21:00:00] + + true + + + + + Interval[1, 10] includes null + null + + + Interval[1, 10] includes Interval[4, 10] + true + + + Interval[1, 10] includes Interval[44, 50] + false + + + Interval[1.0, 10.0] includes Interval[4.0, 10.0] + true + + + Interval[1.0, 10.0] includes Interval[11.0, 20.0] + false + + + Interval[1.0 'g', 10.0 'g'] includes Interval[5.0 'g', 10.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] includes Interval[11.0 'g', 20.0 'g'] + false + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] includes Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] + true + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] includes Interval[DateTime(2012, 1, 4), DateTime(2012, 1, 14)] + false + + + Interval[@T05:59:59.999, @T10:59:59.999] includes Interval[@T06:59:59.999, @T09:59:59.999] + true + + + Interval[@T05:59:59.999, @T10:59:59.999] includes Interval[@T04:59:59.999, @T09:59:59.999] + false + + + + + null included in Interval[1, 10] + null + + + Interval[4, 10] included in Interval[1, 10] + true + + + Interval[44, 50] included in Interval[1, 10] + false + + + Interval[4.0, 10.0] included in Interval[1.0, 10.0] + true + + + Interval[11.0, 20.0] included in Interval[1.0, 10.0] + false + + + Interval[5.0 'g', 10.0 'g'] included in Interval[1.0 'g', 10.0 'g'] + true + + + Interval[11.0 'g', 20.0 'g'] included in Interval[1.0 'g', 10.0 'g'] + false + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] included in Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] + true + + + Interval[DateTime(2012, 1, 4), DateTime(2012, 1, 14)] included in Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 15)] + false + + + Interval[@T06:59:59.999, @T09:59:59.999] included in Interval[@T05:59:59.999, @T10:59:59.999] + true + + + Interval[@T04:59:59.999, @T09:59:59.999] included in Interval[@T05:59:59.999, @T10:59:59.999] + false + + + Interval [@2017-09-01T00:00:00, @2017-09-01T00:00:00] included in Interval [@2017-09-01T00:00:00.000, @2017-12-30T23:59:59.999] + null + + + Interval [@2017-09-01T00:00:00, @2017-09-01T00:00:00] included in day of Interval [@2017-09-01T00:00:00.000, @2017-12-30T23:59:59.999] + true + + + Interval [@2017-09-01T00:00:00, @2017-09-01T00:00:00] included in millisecond of Interval [@2017-09-01T00:00:00.000, @2017-12-30T23:59:59.999] + null + + + + + Interval[1, 10] intersect Interval[5, null) + Interval[5, null) + + + start of (Interval[1, 10] intersect Interval[5, null)) <= 10 + true + + + start of (Interval[1, 10] intersect Interval[5, null)) >= 5 + true + + + start of (Interval[1, 10] intersect Interval[5, null)) > 10 + false + + + start of (Interval[1, 10] intersect Interval[5, null)) < 5 + false + + + Interval[1, 10] intersect Interval[4, 10] + Interval [ 4, 10 ] + + + Interval[1, 10] intersect Interval[11, 20] + null + + + Interval[1.0, 10.0] intersect Interval[4.0, 10.0] + Interval [ 4.0, 10.0 ] + + + Interval[1.0, 10.0] intersect Interval[11.0, 20.0] + null + + + Interval[1.0 'g', 10.0 'g'] intersect Interval[5.0 'g', 10.0 'g'] + Interval [ 5.0 'g', 10.0 'g' ] + + + Interval[1.0 'g', 10.0 'g'] intersect Interval[11.0 'g', 20.0 'g'] + null + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] intersect Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 10)] + Interval [ @2012-01-07T, @2012-01-10T ] + + + Interval[@T04:59:59.999, @T09:59:59.999] intersect Interval[@T04:59:59.999, @T06:59:59.999] + Interval [ @T04:59:59.999, @T06:59:59.999 ] + + + + + Interval[1, 10] ~ Interval[1, 10] + true + + + Interval[44, 50] ~ Interval[1, 10] + false + + + Interval[1.0, 10.0] ~ Interval[1.0, 10.0] + true + + + Interval[11.0, 20.0] ~ Interval[1.0, 10.0] + false + + + Interval[1.0 'g', 10.0 'g'] ~ Interval[1.0 'g', 10.0 'g'] + true + + + Interval[11.0 'g', 20.0 'g'] ~ Interval[1.0 'g', 10.0 'g'] + false + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] ~ Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] + true + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] ~ Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 15)] + false + + + Interval[@T04:59:59.999, @T09:59:59.999] ~ Interval[@T04:59:59.999, @T09:59:59.999] + true + + + Interval[@T04:59:59.999, @T09:59:59.999] ~ Interval[@T04:58:59.999, @T09:59:59.999] + false + + + + + Interval(null, 5] meets Interval(null, 15) + null + + + Interval[1, 10] meets Interval[11, 20] + true + + + Interval[1, 10] meets Interval[44, 50] + false + + + Interval[3.01, 5.00000001] meets Interval[5.00000002, 8.50] + true + + + Interval[3.01, 5.00000001] meets Interval[5.5, 8.50] + false + + + Interval[3.01 'g', 5.00000001 'g'] meets Interval[5.00000002 'g', 8.50 'g'] + true + + + Interval[3.01 'g', 5.00000001 'g'] meets Interval[5.5 'g', 8.50 'g'] + false + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] meets Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 25)] + true + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] meets Interval[DateTime(2012, 1, 20), DateTime(2012, 1, 25)] + false + + + Interval[@T04:59:59.999, @T09:59:59.999] meets Interval[@T10:00:00.000, @T19:59:59.999] + true + + + Interval[@T04:59:59.999, @T09:59:59.999] meets Interval[@T10:12:00.000, @T19:59:59.999] + false + + + + + Interval(null, 5] meets before Interval(null, 25] + null + + + Interval[1, 10] meets before Interval[11, 20] + true + + + Interval[1, 10] meets before Interval[44, 50] + false + + + Interval[3.50000001, 5.00000011] meets before Interval[5.00000012, 8.50] + true + + + Interval[8.01, 15.00000001] meets before Interval[15.00000000, 18.50] + false + + + Interval[3.50000001 'g', 5.00000011 'g'] meets before Interval[5.00000012 'g', 8.50 'g'] + true + + + Interval[8.01 'g', 15.00000001 'g'] meets before Interval[15.00000000 'g', 18.50 'g'] + false + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] meets Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 25)] + true + + + Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] meets Interval[DateTime(2012, 1, 20), DateTime(2012, 1, 25)] + false + + + Interval[@T04:59:59.999, @T09:59:59.999] meets Interval[@T10:00:00.000, @T19:59:59.999] + true + + + Interval[@T04:59:59.999, @T09:59:59.999] meets Interval[@T10:12:00.000, @T19:59:59.999] + false + + + + + Interval(null, 5] meets after Interval[11, null) + false + + + Interval[11, 20] meets after Interval[1, 10] + true + + + Interval[44, 50] meets after Interval[1, 10] + false + + + Interval[55.00000123, 128.032156] meets after Interval[12.00258, 55.00000122] + true + + + Interval[55.00000124, 150.222222] meets after Interval[12.00258, 55.00000122] + false + + + Interval[55.00000123 'g', 128.032156 'g'] meets after Interval[12.00258 'g', 55.00000122 'g'] + true + + + Interval[55.00000124 'g', 150.222222 'g'] meets after Interval[12.00258 'g', 55.00000122 'g'] + false + + + Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 25)] meets Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] + true + + + Interval[DateTime(2012, 1, 20), DateTime(2012, 1, 25)] meets Interval[DateTime(2012, 1, 7), DateTime(2012, 1, 14)] + false + + + Interval[@T10:00:00.000, @T19:59:59.999] meets Interval[@T04:59:59.999, @T09:59:59.999] + true + + + Interval[@T10:12:00.000, @T19:59:59.999] meets Interval[@T04:59:59.999, @T09:59:59.999] + false + + + + + Interval[1, 10] != Interval[11, 20] + true + + + Interval[1, 10] != Interval[1, 10] + false + + + Interval[1.0, 10.0] != Interval[11.0, 20.0] + true + + + Interval[1.0, 10.0] != Interval[1.0, 10.0] + false + + + Interval[1.0 'g', 10.0 'g'] != Interval[11.0 'g', 20.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] != Interval[1.0 'g', 10.0 'g'] + false + + + Interval[DateTime(2012, 1, 15, 0, 0, 0, 0), DateTime(2012, 1, 25, 0, 0, 0, 0)] != Interval[DateTime(2012, 1, 15, 0, 0, 0, 0), DateTime(2012, 1, 25, 0, 0, 0, 22)] + true + + + Interval[DateTime(2012, 1, 15, 0, 0, 0, 0), DateTime(2012, 1, 25, 0, 0, 0, 0)] != Interval[DateTime(2012, 1, 15, 0, 0, 0, 0), DateTime(2012, 1, 25, 0, 0, 0, 0)] + false + + + Interval[@T10:00:00.000, @T19:59:59.999] != Interval[@T10:10:00.000, @T19:59:59.999] + true + + + Interval[@T10:00:00.000, @T19:59:59.999] != Interval[@T10:00:00.000, @T19:59:59.999] + false + + + + + Interval[@2012-12-01, @2013-12-01] on or after (null as Interval<Date>) + null + + + Interval[@2012-12-01, @2013-12-01] on or after month of @2012-11-15 + true + + + @2012-11-15 on or after month of Interval[@2012-12-01, @2013-12-01] + false + + + Interval[@T10:00:00.000, @T19:59:59.999] on or after hour of Interval[@T08:00:00.000, @T09:59:59.999] + true + + + Interval[@T10:00:00.000, @T19:59:59.999] on or after hour of Interval[@T08:00:00.000, @T11:59:59.999] + false + + + Interval[6, 10] on or after 6 + true + + + 2.5 on or after Interval[1.666, 2.50000001] + false + + + 2.5 'mg' on or after Interval[1.666 'mg', 2.50000000 'mg'] + true + + + + + Interval[@2012-12-01, @2013-12-01] on or before (null as Interval<Date>) + null + + + Interval[@2012-10-01, @2012-11-01] on or before month of @2012-11-15 + true + + + @2012-11-15 on or before month of Interval[@2012-10-01, @2013-12-01] + false + + + Interval[@T05:00:00.000, @T07:59:59.999] on or before hour of Interval[@T08:00:00.000, @T09:59:59.999] + true + + + Interval[@T10:00:00.000, @T19:59:59.999] on or before hour of Interval[@T08:00:00.000, @T11:59:59.999] + false + + + Interval[4, 6] on or before 6 + true + + + 1.6667 on or before Interval[1.666, 2.50000001] + false + + + 1.666 'mg' on or before Interval[1.666 'mg', 2.50000000 'mg'] + true + + + + + Interval[null, null] overlaps Interval[1, 10] + null + + + Interval[1, 10] overlaps Interval[4, 10] + true + + + Interval[1, 10] overlaps Interval[11, 20] + false + + + Interval[1.0, 10.0] overlaps Interval[4.0, 10.0] + true + + + Interval[1.0, 10.0] overlaps Interval[11.0, 20.0] + false + + + Interval[1.0 'g', 10.0 'g'] overlaps Interval[5.0 'g', 10.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] overlaps Interval[11.0 'g', 20.0 'g'] + false + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] overlaps Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 28)] + true + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] overlaps Interval[DateTime(2012, 1, 26), DateTime(2012, 1, 28)] + false + + + Interval[@T10:00:00.000, @T19:59:59.999] overlaps Interval[@T12:00:00.000, @T21:59:59.999] + true + + + Interval[@T10:00:00.000, @T19:59:59.999] overlaps Interval[@T20:00:00.000, @T21:59:59.999] + false + + + + + Interval[null, null] overlaps before Interval[1, 10] + null + + + Interval[1, 10] overlaps before Interval[4, 10] + true + + + Interval[4, 10] overlaps before Interval[1, 10] + false + + + Interval[1.0, 10.0] overlaps before Interval[4.0, 10.0] + true + + + Interval[4.0, 10.0] overlaps before Interval[1.0, 10.0] + false + + + Interval[1.0 'g', 10.0 'g'] overlaps before Interval[5.0 'g', 10.0 'g'] + true + + + Interval[5.0 'g', 10.0 'g'] overlaps before Interval[1.0 'g', 10.0 'g'] + false + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] overlaps Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 28)] + true + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] overlaps Interval[DateTime(2012, 1, 26), DateTime(2012, 1, 28)] + false + + + Interval[@T10:00:00.000, @T19:59:59.999] overlaps Interval[@T12:00:00.000, @T21:59:59.999] + true + + + Interval[@T10:00:00.000, @T19:59:59.999] overlaps Interval[@T20:00:00.000, @T21:59:59.999] + false + + + + + Interval[null, null] overlaps after Interval[1, 10] + null + + + Interval[4, 15] overlaps after Interval[1, 10] + true + + + Interval[4, 10] overlaps after Interval[1, 10] + false + + + Interval[4.0, 15.0] overlaps after Interval[1.0, 10.0] + true + + + Interval[4.0, 10.0] overlaps after Interval[1.0, 10.0] + false + + + Interval[5.0 'g', 15.0 'g'] overlaps after Interval[1.0 'g', 10.0 'g'] + true + + + Interval[5.0 'g', 10.0 'g'] overlaps after Interval[1.0 'g', 10.0 'g'] + false + + + Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 28)] overlaps Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] + true + + + Interval[DateTime(2012, 1, 26), DateTime(2012, 1, 28)] overlaps Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] + false + + + Interval[@T12:00:00.000, @T21:59:59.999] overlaps Interval[@T10:00:00.000, @T19:59:59.999] + true + + + Interval[@T20:00:00.000, @T21:59:59.999] overlaps Interval[@T10:00:00.000, @T19:59:59.999] + false + + + + + point from Interval[null, null] + null + + + point from Interval[1, 1] + 1 + + + point from Interval[1.0, 1.0] + 1.0 + + + point from Interval[1.0 'cm', 1.0 'cm'] + 1.0'cm' + + + + + Interval[@T12:00:00.000, @T21:59:59.999] properly includes @T12:00:00.001 + true + + + Interval[@T12:00:00.000, @T21:59:59.999] properly includes @T12:00:00.000 + false + + + Interval[@T12:00:00.001, @T21:59:59.999] properly includes @T12:00:00 + null + + + Interval[@T12:00:00.000, @T21:59:59.999] properly includes second of @T12:00:01 + true + + + Interval[@T12:00:00.001, @T21:59:59.999] properly includes second of @T12:00:00 + false + + + Interval[@T12:00:00.001, @T21:59:59.999] properly includes millisecond of @T12:00:00 + null + + + + + @T12:00:00.001 properly included in Interval[@T12:00:00.000, @T21:59:59.999] + true + + + @T12:00:00.000 properly included in Interval[@T12:00:00.000, @T21:59:59.999] + false + + + @T12:00:00 properly included in Interval[@T12:00:00.001, @T21:59:59.999] + null + + + @T12:00:01 properly included in second of Interval[@T12:00:00.000, @T21:59:59.999] + true + + + @T12:00:00 properly included in second of Interval[@T12:00:00.001, @T21:59:59.999] + false + + + @T12:00:00 properly included in millisecond of Interval[@T12:00:00.001, @T21:59:59.999] + null + + + + + Interval[null as Integer, null as Integer] properly includes Interval[1, 10] + true + + + Interval[1, 10] properly includes Interval[4, 10] + true + + + Interval[1, 10] properly includes Interval[4, 15] + false + + + Interval[1.0, 10.0] properly includes Interval[4.0, 10.0] + true + + + Interval[1.0, 10.0] properly includes Interval[4.0, 15.0] + false + + + Interval[1.0 'g', 10.0 'g'] properly includes Interval[5.0 'g', 10.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] properly includes Interval[5.0 'g', 15.0 'g'] + false + + + Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 28)] properly includes Interval[DateTime(2012, 1, 16), DateTime(2012, 1, 27)] + true + + + Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 28)] properly includes Interval[DateTime(2012, 1, 16), DateTime(2012, 1, 29)] + false + + + Interval[@T12:00:00.000, @T21:59:59.999] properly includes Interval[@T12:01:01.000, @T21:59:59.998] + true + + + Interval[@T12:00:00.000, @T21:59:59.999] properly includes Interval[@T12:01:01.000, @T22:00:00.000] + false + + + + + Interval[1, 10] properly included in Interval[null, null] + true + + + Interval[4, 10] properly included in Interval[1, 10] + true + + + Interval[4, 15] properly included in Interval[1, 10] + false + + + Interval[4.0, 10.0] properly included in Interval[1.0, 10.0] + true + + + Interval[4.0, 15.0] properly included in Interval[1.0, 10.0] + false + + + Interval[5.0 'g', 10.0 'g'] properly included in Interval[1.0 'g', 10.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] properly included in Interval[5.0 'g', 15.0 'g'] + false + + + Interval[DateTime(2012, 1, 16), DateTime(2012, 1, 27)] properly included in Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 28)] + true + + + Interval[DateTime(2012, 1, 16), DateTime(2012, 1, 29)] properly included in Interval[DateTime(2012, 1, 15), DateTime(2012, 1, 28)] + false + + + Interval[@T12:01:01.000, @T21:59:59.998] properly included in Interval[@T12:00:00.000, @T21:59:59.999] + true + + + Interval[@T12:01:01.000, @T22:00:00.000] properly included in Interval[@T12:00:00.000, @T21:59:59.999] + false + + + + + start of Interval[1, 10] + 1 + + + start of Interval[1.0, 10.0] + 1.0 + + + start of Interval[1.0 'g', 10.0 'g'] + 1.0'g' + + + start of Interval[@2016-05-01T00:00:00.000, @2016-05-02T00:00:00.000] + @2016-05-01T00:00:00.000 + + + start of Interval[@T00:00:00.000, @T23:59:59.599] + @T00:00:00.000 + + + + + Interval[null, null] starts Interval[1, 10] + null + + + Interval[4, 10] starts Interval[4, 15] + true + + + Interval[1, 10] starts Interval[4, 10] + false + + + Interval[4.0, 10.0] starts Interval[4.0, 15.0] + true + + + Interval[1.0, 10.0] starts Interval[4.0, 10.0] + false + + + Interval[5.0 'g', 10.0 'g'] starts Interval[5.0 'g', 15.0 'g'] + true + + + Interval[1.0 'g', 10.0 'g'] starts Interval[5.0 'g', 10.0 'g'] + false + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] starts Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 27)] + true + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] starts Interval[DateTime(2012, 1, 6), DateTime(2012, 1, 27)] + false + + + Interval[@T05:59:59.999, @T15:59:59.999] starts Interval[@T05:59:59.999, @T17:59:59.999] + true + + + Interval[@T05:59:59.999, @T15:59:59.999] starts Interval[@T04:59:59.999, @T17:59:59.999] + false + + + + + Interval[null, null] union Interval[1, 10] + null + + + Interval[1, 10] union Interval[4, 15] + Interval [ 1, 15 ] + + + Interval[1, 10] union Interval[44, 50] + null + + + Interval[1.0, 10.0] union Interval[4.0, 15.0] + Interval [ 1.0, 15.0 ] + + + Interval[1.0, 10.0] union Interval[14.0, 15.0] + null + + + Interval[1.0 'g', 10.0 'g'] union Interval[5.0 'g', 15.0 'g'] + Interval [ 1.0 'g', 15.0 'g' ] + + + Interval[1.0 'g', 10.0 'g'] union Interval[14.0 'g', 15.0 'g'] + null + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] union Interval[DateTime(2012, 1, 25), DateTime(2012, 1, 28)] + Interval [ @2012-01-05T, @2012-01-28T ] + + + Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] union Interval[DateTime(2012, 1, 27), DateTime(2012, 1, 28)] + null + + + Interval[@T05:59:59.999, @T15:59:59.999] union Interval[@T10:59:59.999, @T20:59:59.999] + Interval [ @T05:59:59.999, @T20:59:59.999 ] + + + Interval[@T05:59:59.999, @T15:59:59.999] union Interval[@T16:59:59.999, @T20:59:59.999] + null + + + + + width of Interval[1, 10] + 9 + + + width of (null as Interval<Any>) + null + + + width of Interval[4.0, 15.0] + 11.0 + + + width of Interval[5.0 'g', 10.0 'g'] + 5.0'g' + + + width of Interval[DateTime(2012, 1, 5), DateTime(2012, 1, 25)] + + + width of Interval[@T05:59:59.999, @T15:59:59.999] + + + + + Interval[1, 10] + Interval[1, 10] + + + Interval[11, 20] + Interval[11, 20] + + + Interval[44, 50] + Interval[44, 50] + + + Interval[4, 10] + Interval[4, 10] + + + Interval[4, 15] + Interval[4, 15] + + + Interval[1.0, 10.0] + Interval[1.0, 10.0] + + + Interval[11.0, 20.0] + Interval[11.0, 20.0] + + + Interval[4.0, 10.0] + Interval[4.0, 10.0] + + + Interval[4.0, 15.0] + Interval[4.0, 15.0] + + + Interval[14.0, 15.0] + Interval[14.0, 15.0] + + + Interval[1.0 'g', 10.0 'g'] + Interval[1.0 'g', 10.0 'g'] + + + Interval[11.0 'g', 20.0 'g'] + Interval[11.0 'g', 20.0 'g'] + + + Interval[5.0 'g', 10.0 'g'] + Interval[5.0 'g', 10.0 'g'] + + + Interval[5.0 'g', 15.0 'g'] + Interval[5.0 'g', 15.0 'g'] + + + Interval[14.0 'g', 15.0 'g'] + Interval[14.0 'g', 15.0 'g'] + + + Interval[@2016-05-01T00:00:00.000, @2016-05-02T00:00:00.000] + Interval[@2016-05-01T00:00:00.000, @2016-05-02T00:00:00.000] + + + Interval[@T00:00:00.000, @T23:59:59.599] + Interval[@T00:00:00.000, @T23:59:59.599] + + + {Interval[1, 10], Interval[11, 20], Interval[44, 50]} + {Interval[1, 10], Interval[11, 20], Interval[44, 50]} + + + Interval[5, 3] + + + + Interval[5, 5) + + + + diff --git a/tests/spectests/third_party/cqltests/CqlListOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlListOperatorsTest.xml new file mode 100644 index 0000000..8ba68d1 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlListOperatorsTest.xml @@ -0,0 +1,898 @@ + + + + + ({4, 5, 1, 6, 2, 1}) sL sort asc + {1, 1, 2, 4, 5, 6} + + + ({4, 5, 1, 6, 2, 1}) sL sort desc + {6, 5, 4, 2, 1, 1} + + + ({'back', 'aardvark', 'alligator', 'zebra', 'iguana', 'Wolf', 'Armadillo'}) sls sort asc + {'Armadillo', 'Wolf', 'aardvark', 'alligator', 'back', 'iguana', 'zebra'} + + + ({'back', 'aardvark', 'alligator', 'zebra', 'iguana', 'Wolf', 'Armadillo'}) sls sort desc + {'zebra', 'iguana', 'back', 'alligator', 'aardvark', 'Wolf', 'Armadillo'} + + + ({ DateTime(2012, 10, 5, 10), DateTime(2012, 1, 1), DateTime(2012, 1, 1, 12), DateTime(2012, 10, 5) }) S sort asc + {DateTime(2012, 1, 1), DateTime(2012, 1, 1, 12), DateTime(2012, 10, 5), DateTime(2012, 10, 5, 10)} + + + ({ DateTime(2012, 10, 5, 10), DateTime(2012, 1, 1), DateTime(2012, 1, 1, 12), DateTime(2012, 10, 5) }) S sort desc + {DateTime(2012, 10, 5, 10), DateTime(2012, 10, 5), DateTime(2012, 1, 1, 12), DateTime(2012, 1, 1)} + + + { 3, 2, 1 } + {3, 2, 1} + + + { 3.8, 2.4, 1.9 } + {3.8, 2.4, 1.9} + + + { 19.99 '[lb_av]', 17.33 '[lb_av]', 10.66 '[lb_av]' } + {19.99 '[lb_av]', 17.33 '[lb_av]', 10.66 '[lb_av]'} + + + { DateTime(2016), DateTime(2015), DateTime(2010) } + {@2016T, @2015T, @2010T} + + + { @T15:59:59.999, @T15:12:59.999, @T15:12:13.999 } + {@T15:59:59.999, @T15:12:59.999, @T15:12:13.999} + + + + + { 'a', 'b', null } contains null + true + + + { null, 'b', 'c' } contains 'a' + false + + + { 'a', 'b', 'c' } contains 'a' + true + + + { DateTime(2012, 10, 5), DateTime(2012, 9, 5), DateTime(2012, 1, 1) } contains DateTime(2012, 1, 1) + true + + + { DateTime(2012, 10, 5), DateTime(2012, 9, 5), DateTime(2012, 10, 1) } contains DateTime(2012, 1, 1) + false + + + { @T15:59:59.999, @T05:59:59.999, @T20:59:59.999 } contains @T05:59:59.999 + true + + + { @T15:59:59.999, @T05:59:59.999, @T20:59:59.999 } contains @T08:59:59.999 + false + + + null contains 'a' + false + + + + + (null).descendents() + null + + + + + + distinct {} + {} + + + distinct { null, null, null} + { null } + + + distinct { 'a', null, 'a', null} + {'a', null} + + + distinct { 1, 1, 2, 2, 3, 3} + {1,2,3} + + + distinct { 1, 2, 3, 1, 2, 3} + {1,2,3} + + + distinct { 'a', 'a', 'b', 'b', 'c', 'c'} + {'a','b','c'} + + + distinct { 'a', 'b', 'c', 'a', 'b', 'c'} + {'a','b','c'} + + + distinct { DateTime(2012, 10, 5), DateTime(2012, 1, 1), DateTime(2012, 1, 1)} + { DateTime(2012, 10, 5), DateTime(2012, 1, 1)} + + + distinct { @T15:59:59.999, @T20:59:59.999 } + { @T15:59:59.999, @T20:59:59.999 } + + + + + {null} = {null} + null + + + {} as List<String> = null + null + + + null = {} as List<String> + null + + + {} = {} + true + + + { 1, 2 } = { 1, 2, 3 } + false + + + { 1, 2, 3 } = { 1, 2 } + false + + + { 1, 2, 3 } = { 1, 2, 3 } + true + + + {DateTime(2012, 5, 10, 0, 0, 0, 0), DateTime(2014, 12, 10, 0, 0, 0, 0)} = {DateTime(2012, 5, 10, 0, 0, 0, 0), DateTime(2014, 12, 10, 0, 0, 0, 0)} + true + + + {DateTime(2012, 5, 10, 0, 0, 0, 0), DateTime(2014, 12, 10, 0, 0, 0, 0)} = {DateTime(2012, 1, 10, 0, 0, 0, 0), DateTime(2014, 12, 10, 0, 0, 0, 0)} + false + + + { @T15:59:59.999, @T20:59:59.999, @T20:59:59.999 } = { @T15:59:59.999, @T20:59:59.999, @T20:59:59.999 } + true + + + { @T15:59:59.999, @T20:59:59.999, @T20:59:59.999 } = { @T10:59:59.999, @T20:59:59.999, @T20:59:59.999 } + false + + + + + {} except {} + {} + + + { 1, 2, 3, 4 } except { 2, 3 } + { 1, 4 } + + + { 2, 3 } except { 1, 2, 3, 4 } + {} + + + { DateTime(2012, 5, 10), DateTime(2014, 12, 10), DateTime(2010, 1, 1)} except {DateTime(2014, 12, 10), DateTime(2010, 1, 1) } + {@2012-05-10T} + + + { @T15:59:59.999, @T20:59:59.999, @T12:59:59.999 } except { @T20:59:59.999, @T12:59:59.999 } + {@T15:59:59.999} + + + { 1, 4 } except null + {1, 4} + + + + + Exists({}) + false + + + Exists({ null }) + false + + + Exists({ 1 }) + true + + + Exists({ 1, 2 }) + true + + + Exists({ DateTime(2012, 5, 10), DateTime(2014, 12, 10) }) + true + + + Exists({ @T15:59:59.999, @T20:59:59.999 }) + true + + + Exists(null) + false + + + + + Flatten({{},{}}) + {} + + + Flatten({{null}, {null}}) + {null, null} + + + Flatten({{1,2}, {3,4}}) + {1,2,3,4} + + + Flatten({ {DateTime(2012, 5, 10)}, {DateTime(2014, 12, 10)} }) + { DateTime(2012, 5, 10), DateTime(2014, 12, 10) } + + + Flatten({ {@T15:59:59.999}, {@T20:59:59.999} }) + { @T15:59:59.999, @T20:59:59.999 } + + + + + First({}) + null + + + First({ null, 1 }) + null + + + First({ 1, null }) + 1 + + + First({ 1, 2 }) + 1 + + + First({ DateTime(2012, 5, 10), DateTime(2014, 12, 10) }) + DateTime(2012, 5, 10) + + + First({ @T15:59:59.999, @T20:59:59.999 }) + @T15:59:59.999 + + + + + null in {} + false + + + null in { 1, null } + true + + + 1 in null + false + + + 1 in { 1, 2 } + true + + + 3 in { 1, 2 } + false + + + DateTime(2012, 5, 10) in { DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10) } + true + + + DateTime(2012, 6, 10) in { DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10) } + false + + + @T15:59:59.999 in { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 } + true + + + @T16:59:59.999 in { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 } + false + + + + + {} includes {} + true + + + {null} includes {null} + true + + + {1, 2, 3} includes {} + true + + + {1, 2, 3} includes {2} + true + + + {1, 2, 3} includes {4} + false + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} includes {DateTime(2012, 5, 10)} + true + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} includes {DateTime(2012, 5, 11)} + false + + + { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 } includes @T15:59:59.999 + true + + + { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 } includes @T16:59:59.999 + false + + + null includes {2} + null + + + + {'s', 'a', 'm'} includes null + null + + + + + {} included in {} + true + + + { null } included in { null } + true + + + {} included in { 1, 2, 3 } + true + + + { 2 } included in { 1, 2, 3 } + true + + + { 4 } included in { 1, 2, 3 } + false + + + { DateTime(2012, 5, 10)} included in {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} + true + + + {DateTime(2012, 5, 11)} included in {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} + false + + + @T15:59:59.999 included in { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 } + true + + + @T16:59:59.999 included in { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 } + false + + + + null included in {2} + null + + + {'s', 'a', 'm'} included in null + null + + + + + (null as List<System.Any>)[1] + null + + + + { 1, 2 }[0] + 1 + + + { 1, 2 }[1] + 2 + + + { 1, 2 }[2] + null + + + { 1, 2 }[-1] + null + + + { DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10) }[1] + DateTime(2012, 5, 10) + + + { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 }[1] + @T15:59:59.999 + + + + + IndexOf({}, null) + null + + + IndexOf(null, {}) + null + + + IndexOf({ 1, null }, null) + null + + + IndexOf({ 1, 2 }, 1) + 0 + + + IndexOf({ 1, 2 }, 2) + 1 + + + IndexOf({ 1, 2 }, 3) + -1 + + + IndexOf({ DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10) }, DateTime(2014, 12, 10)) + 2 + + + IndexOf({ @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 }, @T15:59:59.999) + 1 + + + + + {} intersect {} + {} + + + { 1, 2, 3, 4 } intersect { 2, 3 } + { 2, 3 } + + + {2, 3} intersect { 1, 2, 3, 4 } + { 2, 3 } + + + { DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10) } intersect { DateTime(2012, 5, 10), DateTime(2014, 12, 10), DateTime(2000, 5, 5) } + {@2012-05-10T, @2014-12-10T} + + + { @T02:29:15.156, @T15:59:59.999, @T20:59:59.999 } intersect { @T01:29:15.156, @T15:59:59.999, @T20:59:59.999 } + {@T15:59:59.999, @T20:59:59.999} + + + + + Last({}) + null + + + Last({null, 1}) + 1 + + + Last({1, null}) + null + + + Last({1, 2}) + 2 + + + Last({DateTime(2012, 5, 10), DateTime(2014, 12, 10)}) + DateTime(2014, 12, 10) + + + Last({ @T15:59:59.999, @T20:59:59.999 }) + @T20:59:59.999 + + + + + Length({}) + 0 + + + Length({null, 1}) + 2 + + + Length({1, null}) + 2 + + + Length({1, 2}) + 2 + + + Length({DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)}) + 3 + + + Length({ @T15:59:59.999, @T20:59:59.999, @T15:59:59.999, @T20:59:59.999, @T15:59:59.999, @T20:59:59.999 }) + 6 + + + Length(null as List<Any>) + 0 + + + + + {} ~ {} + true + + + { 'a', 'b', 'c' } ~ { 'a', 'b', 'c' } + true + + + { 'a', 'b', 'c' } ~ { 'a', 'b' } + false + + + { 'a', 'b', 'c' } ~ { 1, 2, 3 } + false + + + + { 1, 2, 3 } ~ { 'a', 'b', 'c' } + false + + + + { 1, 2, 3 } ~ { '1', '2', '3' } + false + + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10), null} ~ {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10), null} + true + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} ~ {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10), null} + false + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} ~ {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 1)} + false + + + { @T15:59:59.999, @T20:59:59.999 } ~ { @T15:59:59.999, @T20:59:59.999 } + true + + + { @T15:59:59.999, @T20:59:59.999 } ~ { @T15:59:59.999, @T20:59:59.999, null } + false + + + { @T15:59:59.999, @T20:59:59.999 } ~ { @T15:59:59.999, @T20:59:59.995 } + false + + + + + {} != {} + false + + + { 'a', 'b', 'c' } != { 'a', 'b', 'c' } + false + + + { 'a', 'b', 'c' } != { 'a', 'b' } + true + + + { 'a', 'b', 'c' } != { 1, 2, 3 } + true + + + + { 1, 2, 3 } != { 'a', 'b', 'c' } + true + + + + { 1, 2, 3 } != { '1', '2', '3' } + true + + + + {DateTime(2001, 9, 11, 0, 0, 0, 0), DateTime(2012, 5, 10, 0, 0, 0, 0), DateTime(2014, 12, 10, 0, 0, 0, 0)} != {DateTime(2001, 9, 11, 0, 0, 0, 0), DateTime(2012, 5, 10, 0, 0, 0, 0), DateTime(2014, 12, 1, 0, 0, 0, 0)} + true + + + {DateTime(2001, 9, 11, 0, 0, 0, 0), DateTime(2012, 5, 10, 0, 0, 0, 0), DateTime(2014, 12, 10, 0, 0, 0, 0)} != {DateTime(2001, 9, 11, 0, 0, 0, 0), DateTime(2012, 5, 10, 0, 0, 0, 0), DateTime(2014, 12, 10, 0, 0, 0, 0)} + false + + + { @T15:59:59.999, @T20:59:59.999 } = { @T15:59:59.999, @T20:59:59.999 } + true + + + { @T15:59:59.999, @T20:59:59.999 } = { @T15:59:59.999, @T20:59:49.999 } + false + + + + + {'s', 'u', 'n'} properly includes null + false + + + {'s', 'u', 'n', null} properly includes null + true + + + { @T15:59:59, @T20:59:59.999, @T20:59:49.999 } properly includes @T15:59:59 + true + + + { @T15:59:59.999, @T20:59:59.999, @T20:59:49.999 } properly includes @T15:59:59 + null + + + + + null properly included in {'s', 'u', 'n'} + false + + + null properly included in {'s', 'u', 'n', null} + true + + + @T15:59:59 properly included in { @T15:59:59, @T20:59:59.999, @T20:59:49.999 } + true + + + @T15:59:59 properly included in { @T15:59:59.999, @T20:59:59.999, @T20:59:49.999 } + null + + + + + {} properly includes {} + false + + + {null} properly includes {null} + false + + + {1, 2, 3} properly includes {} + true + + + {1, 2, 3} properly includes {2} + true + + + {1, 2, 3} properly includes {4} + false + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} properly includes {DateTime(2012, 5, 10), DateTime(2014, 12, 10)} + true + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} properly includes {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} + false + + + { @T15:59:59.999, @T20:59:59.999, @T20:59:49.999 } properly includes { @T15:59:59.999, @T20:59:59.999 } + true + + + { @T15:59:59.999, @T20:59:59.999, @T20:59:49.999 } properly includes { @T15:59:59.999, @T20:59:59.999, @T14:59:22.999 } + false + + + null properly includes {2} + null + + + + + {} properly included in {} + false + + + {null} properly included in {null} + false + + + {} properly included in {1, 2, 3} + true + + + {2} properly included in {1, 2, 3} + true + + + {4} properly included in {1, 2, 3} + false + + + {DateTime(2012, 5, 10), DateTime(2014, 12, 10)} properly included in {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} + true + + + {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} properly included in {DateTime(2001, 9, 11), DateTime(2012, 5, 10), DateTime(2014, 12, 10)} + false + + + { @T15:59:59.999, @T20:59:59.999 } properly included in { @T15:59:59.999, @T20:59:59.999, @T20:59:49.999 } + true + + + { @T15:59:59.999, @T20:59:59.999, @T14:59:22.999 } properly included in { @T15:59:59.999, @T20:59:59.999, @T20:59:49.999 } + false + + + {'s', 'u', 'n'} properly included in null + null + + + + + singleton from {} + null + + + singleton from {null} + null + + + singleton from { 1 } + 1 + + + singleton from { 1, 2 } + + + + singleton from { DateTime(2012, 5, 10) } + DateTime(2012, 5, 10) + + + singleton from { @T15:59:59.999 } + @T15:59:59.999 + + + + + Skip(null, 3) + null + + + Skip({1,2,3,4,5}, 2) + {3, 4, 5} + + + Skip({1,2,3,4,5}, 3) + {4, 5} + + + Skip({1,2,3,4,5}, 0) + {1,2,3,4,5} + + + Skip({1,2,3,4,5}, 5) + {} + + + + + Tail(null) + null + + + Tail({1,2,3,4}) + {2,3,4} + + + Tail({1,2,3,4,5}) + {2,3,4,5} + + + Tail({}) + {} + + + Tail({1}) + {} + + + + + Take(null, 3) + null + + + Take({1,2,3}, null as Integer) + {} + + + Take({1,2,3}, 0) + {} + + + Take({1,2,3,4}, 2) + {1, 2} + + + Take({1,2,3,4}, 3) + {1, 2, 3} + + + Take({1,2,3,4}, 4) + {1, 2, 3, 4} + + + + + {} union {} + {} + + + { null } union { null } + {null} + + + { 1, 2, 3 } union {} + {1, 2, 3} + + + { 1, 2, 3 } union { 2 } + {1, 2, 3} + + + { 1, 2, 3 } union { 4 } + {1, 2, 3, 4} + + + { DateTime(2001, 9, 11)} union {DateTime(2012, 5, 10), DateTime(2014, 12, 10) } + {@2001-09-11T, @2012-05-10T, @2014-12-10T} + + + { @T15:59:59.999, @T20:59:59.999, @T12:59:59.999 } union { @T10:59:59.999 } + {@T15:59:59.999, @T20:59:59.999, @T12:59:59.999, @T10:59:59.999} + + + diff --git a/tests/spectests/third_party/cqltests/CqlLogicalOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlLogicalOperatorsTest.xml new file mode 100644 index 0000000..dd1446e --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlLogicalOperatorsTest.xml @@ -0,0 +1,171 @@ + + + + + true and true + true + + + true and false + false + + + true and null + null + + + false and true + false + + + false and false + false + + + false and null + false + + + null and true + null + + + null and false + false + + + null and null + null + + + + + + true implies true + true + + + true implies false + false + + + true implies null + null + + + false implies true + true + + + false implies false + true + + + false implies null + true + + + null implies true + true + + + null implies false + null + + + null implies null + null + + + + + not true + false + + + not false + true + + + not null + null + + + + + true or true + true + + + true or false + true + + + true or null + true + + + false or true + true + + + false or false + false + + + false or null + null + + + null or true + true + + + null or false + null + + + null or null + null + + + + + true xor true + false + + + true xor false + true + + + true xor null + null + + + false xor true + true + + + false xor false + false + + + false xor null + null + + + null xor true + null + + + null xor false + null + + + null xor null + null + + + diff --git a/tests/spectests/third_party/cqltests/CqlNullologicalOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlNullologicalOperatorsTest.xml new file mode 100644 index 0000000..ed66f79 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlNullologicalOperatorsTest.xml @@ -0,0 +1,100 @@ + + + + + Coalesce('a', null) + 'a' + + + Coalesce(null, 'a') + 'a' + + + Coalesce({}) + null + + + Coalesce({'a', null, null}) + 'a' + + + Coalesce({null, null, 'a'}) + 'a' + + + Coalesce({'a'},null, null) + {'a'} + + + Coalesce(null, null, {'a'}) + {'a'} + + + Coalesce(null, null, DateTime(2012, 5, 18)) + DateTime(2012, 5, 18) + + + Coalesce({ null, null, DateTime(2012, 5, 18) }) + DateTime(2012, 5, 18) + + + Coalesce(null, null, @T05:15:33.556) + @T05:15:33.556 + + + Coalesce({ null, null, @T05:15:33.556 }) + @T05:15:33.556 + + + + + IsNull(null) + true + + + IsNull('') + false + + + IsNull('abc') + false + + + IsNull(1) + false + + + IsNull(0) + false + + + + + IsFalse(false) + true + + + IsFalse(true) + false + + + IsFalse(null) + false + + + + + IsTrue(true) + true + + + IsTrue(false) + false + + + IsTrue(null) + false + + + diff --git a/tests/spectests/third_party/cqltests/CqlQueryTests.xml b/tests/spectests/third_party/cqltests/CqlQueryTests.xml new file mode 100644 index 0000000..b1354fa --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlQueryTests.xml @@ -0,0 +1,58 @@ + + + + + (4) l + 4 + + + (4) l return 'Hello World' + 'Hello World' + + + from ({2, 3}) A, ({5, 6}) B + {{ A: 2, B: 5 }, { A: 2, B: 6 }, { A: 3, B: 5 }, { A: 3, B: 6 }} + + + + + ({1, 2, 3}) l sort desc + {3, 2, 1} + + + ({1, 3, 2}) l sort ascending + {1, 2, 3} + + + ({@2013-01-02T00:00:00.000Z, @2014-01-02T00:00:00.000Z, @2015-01-02T00:00:00.000Z}) l sort desc + {@2015-01-02T00:00:00.000Z, @2014-01-02T00:00:00.000Z, @2013-01-02T00:00:00.000Z} + + + ({@2013-01-02T00:00:00.000Z, @2015-01-02T00:00:00.000Z, @2014-01-02T00:00:00.000Z}) l sort ascending + {@2013-01-02T00:00:00.000Z, @2014-01-02T00:00:00.000Z, @2015-01-02T00:00:00.000Z} + + + + + ({1, 2, 3, 3, 4}) L aggregate A starting 1: A * L + 72 + + + ({1, 2, 3, 3, 4}) L aggregate all A starting 1: A * L + 72 + + + ({1, 2, 3, 3, 4}) L aggregate distinct A starting 1: A * L + 24 + + + ({1, 2, 3}) L aggregate A : A * L + null + + + from ({1, 2, 3}) B, (4) C aggregate A : A + B + C + null + + + diff --git a/tests/spectests/third_party/cqltests/CqlStringOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlStringOperatorsTest.xml new file mode 100644 index 0000000..c260946 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlStringOperatorsTest.xml @@ -0,0 +1,366 @@ + + + + + Combine(null) + null + + + Combine({}) + '' + + + Combine({'a', 'b', 'c'}) + 'abc' + + + Combine({'a', 'b', 'c'}, '-') + 'a-b-c' + + + + + Concatenate(null, null) + null + + + Concatenate('a', null) + null + + + Concatenate(null, 'b') + null + + + Concatenate('a', 'b') + 'ab' + + + 'a' + 'b' + 'ab' + + + + + EndsWith(null, null) + null + + + EndsWith('Chris Schuler is the man!!', 'n!!') + true + + + EndsWith('Chris Schuler is the man!!', 'n!') + false + + + + + Indexer(null as String, null) + null + + + + Indexer('a', null) + null + + + Indexer(null as String, 1) + null + + + + Indexer('ab', 0) + 'a' + + + Indexer('ab', 1) + 'b' + + + Indexer('ab', 2) + null + + + Indexer('ab', -1) + null + + + + + LastPositionOf(null, null) + null + + + LastPositionOf(null, 'hi') + null + + + LastPositionOf('hi', null) + null + + + LastPositionOf('hi', 'Ohio is the place to be!') + 1 + + + LastPositionOf('hi', 'Say hi to Ohio!') + 11 + + + + + Length(null as String) + null + + + + Length('') + 0 + + + Length('a') + 1 + + + Length('ab') + 2 + + + + + Lower(null) + null + + + Lower('') + '' + + + Lower('A') + 'a' + + + Lower('b') + 'b' + + + Lower('Ab') + 'ab' + + + + + Matches('Not all who wander are lost', null) + null + + + Matches('Not all who wander are lost', '.*\\d+') + false + + + Matches('Not all who wander are lost - circa 2017', '.*\\d+') + true + + + Matches('Not all who wander are lost', '.*') + true + + + Matches('Not all who wander are lost', '[\\w|\\s]+') + true + + + Matches('Not all who wander are lost - circa 2017', '[\\w]+') + false + + + Matches(' ', '\\W+') + true + + + Matches(' \n\t', '\\s+') + true + + + + + PositionOf(null, null) + null + + + PositionOf('a', null) + null + + + PositionOf(null, 'a') + null + + + PositionOf('a', 'ab') + 0 + + + PositionOf('b', 'ab') + 1 + + + PositionOf('c', 'ab') + -1 + + + + + ReplaceMatches('Not all who wander are lost', null, 'But I am...') + null + + + ReplaceMatches('Not all who wander are lost', 'Not all who wander are lost', 'But still waters run deep') + 'But still waters run deep' + + + ReplaceMatches('Who put the bop in the bop she bop she bop?', 'bop', 'bang') + 'Who put the bang in the bang she bang she bang?' + + + ReplaceMatches('All that glitters is not gold', '\\s', '\\$') + 'All$that$glitters$is$not$gold' + + + + + Split(null, null) + null + + + Split(null, ',') + null + + + Split('a,b', null) + {'a,b'} + + + Split('a,b', '-') + {'a,b'} + + + Split('a,b', ',') + {'a','b'} + + + + + StartsWith(null, null) + null + + + StartsWith('hi', null) + null + + + StartsWith(null, 'hi') + null + + + StartsWith('Breathe deep the gathering gloom', 'Bre') + true + + + StartsWith('Breathe deep the gathering gloom', 'bre') + false + + + + + Substring(null, null) + null + + + Substring('a', null) + null + + + Substring(null, 1) + null + + + Substring('ab', 0) + 'ab' + + + Substring('ab', 1) + 'b' + + + Substring('ab', 2) + null + + + Substring('ab', -1) + null + + + Substring('ab', 0, 1) + 'a' + + + Substring('abc', 1, 1) + 'b' + + + Substring('ab', 0, 3) + 'ab' + + + + + Upper(null) + null + + + Upper('') + '' + + + Upper('a') + 'A' + + + Upper('B') + 'B' + + + Upper('aB') + 'AB' + + + + + ToString(125 'cm') + '125 \'cm\'' + + + ToString(DateTime(2000, 1, 1)) + '2000-01-01' + + + ToString(DateTime(2000, 1, 1, 15, 25, 25, 300)) + '2000-01-01T15:25:25.300' + + + ToString(DateTime(2000, 1, 1, 8, 25, 25, 300, -7)) + '2000-01-01T08:25:25.300-07:00' + + + ToString(@T09:30:01.003) + '09:30:01.003' + + + + + + + + diff --git a/tests/spectests/third_party/cqltests/CqlTypeOperatorsTest.xml b/tests/spectests/third_party/cqltests/CqlTypeOperatorsTest.xml new file mode 100644 index 0000000..03c922e --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlTypeOperatorsTest.xml @@ -0,0 +1,166 @@ + + + + + 45.5 'g' as Quantity + 45.5 'g' + + + cast 45.5 'g' as Quantity + 45.5 'g' + + + DateTime(2014, 01, 01) as DateTime + @2014-01-01T + + + + + convert 5 to Decimal + 5.0 + + + convert 5 to String + '5' + + + convert 'foo' to Integer + + + + convert '2014-01-01' to DateTime + @2014-01-01T + + + convert 'T14:30:00.0' to Time + @T14:30:00.000 + + + convert '2014/01/01' to DateTime + + + + + + 5 is Integer + true + + + '5' is Integer + false + + + + + ToBoolean('NO') + false + + + + + ToConcept(Code { code: '8480-6' }) + + Concept { + codes: Code { code: '8480-6' } + } + + + + + + ToDateTime('2014-01-01') + @2014-01-01T + + + ToDateTime('2014-01-01T12:05') + @2014-01-01T12:05 + + + ToDateTime('2014-01-01T12:05:05.955') + @2014-01-01T12:05:05.955 + + + ToDateTime('2014-01-01T12:05:05.955+01:30') + @2014-01-01T12:05:05.955+01:30 + + + ToDateTime('2014-01-01T12:05:05.955-01:15') + @2014-01-01T12:05:05.955-01:15 + + + ToDateTime('2014-01-01T12:05:05.955Z') + @2014-01-01T12:05:05.955+00:00 + + + ToDateTime('2014/01/01T12:05:05.955Z') + + + + ToDateTime(@2014-01-01) + @2014-01-01T + + + hour from ToDateTime(@2014-01-01) is null + true + + + + + ToDecimal('+25.5') + 25.5 + + + + + ToInteger('-25') + -25 + + + + + ToQuantity('5.5 \'cm\'') + 5.5'cm' + + + + + ToString(-5) + '-5' + + + ToString(18.55) + '18.55' + + + ToString(5.5 'cm') + '5.5 \'cm\'' + + + ToString(true) + 'true' + + + + + ToTime('T14:30:00.0') + @T14:30:00.000 + + + ToTime('T14:30:00.0+05:30') + @T14:30:00.000 + + + ToTime('T14:30:00.0-05:45') + @T14:30:00.000 + + + ToTime('T14:30:00.0Z') + @T14:30:00.000 + + + ToTime('T14-30-00.0') + + + + diff --git a/tests/spectests/third_party/cqltests/CqlTypesTest.xml b/tests/spectests/third_party/cqltests/CqlTypesTest.xml new file mode 100644 index 0000000..3ad6479 --- /dev/null +++ b/tests/spectests/third_party/cqltests/CqlTypesTest.xml @@ -0,0 +1,190 @@ + + + + + + + 5.0 'g' + 5.0'g' + + + DateTime(2012, 4, 4) + @2012-04-04T + + + @T09:00:00.000 + @T09:00:00.000 + + + Interval[2, 7] + Interval[2, 7] + + + {1, 2, 3} + {1, 2, 3} + + + Tuple { id: 5, name: 'Chris'} + Tuple { id: 5, name: 'Chris'} + + + Tuple { id: 5, name: 'Chris'}.name + 'Chris' + + + + + + + + DateTime(null) + null + + + DateTime(10000, 12, 31, 23, 59, 59, 999) + + + + DateTime(0000, 1, 1, 0, 0, 0, 0) + + + + DateTime(2016, 7, 7, 6, 25, 33, 910) + @2016-07-07T06:25:33.910 + + + DateTime(2015, 2, 10) + @2015-02-10T + + + days between DateTime(2015, 2, 10) and DateTime(2015, 3) + Interval [ 18, 49 ] + + + + DateTime(0001, 1, 1, 0, 0, 0, 0) + @0001-01-01T00:00:00.000 + + + DateTime(9999, 12, 31, 23, 59, 59, 999) + @9999-12-31T23:59:59.999 + + + hour from @2015-02-10T is null + true + + + + + + + + + + + + + 150.2 '[lb_av]' + 150.2 '[lb_av]' + + + 2.5589 '{eskimo kisses}' + 2.5589 '{eskimo kisses}' + + + 5.999999999 'g' + 5.999999999 'g' + + + + + '\'I start with a single quote and end with a double quote\"' + '\u0027I start with a single quote and end with a double quote\u0022' + + + '\u0048\u0069' + 'Hi' + + + + + @T24:59:59.999 + + + + @T23:60:59.999 + + + + @T23:59:60.999 + + + + @T23:59:59.10000 + + + + @T10:25:12.863 + @T10:25:12.863 + + + @T23:59:59.999 + @T23:59:59.999 + + + @T00:00:00.000 + @T00:00:00.000 + + + diff --git a/tests/spectests/third_party/cqltests/LICENSE b/tests/spectests/third_party/cqltests/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/tests/spectests/third_party/cqltests/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tests/spectests/third_party/cqltests/README.md b/tests/spectests/third_party/cqltests/README.md new file mode 100644 index 0000000..83bdffd --- /dev/null +++ b/tests/spectests/third_party/cqltests/README.md @@ -0,0 +1,5 @@ +# cqltests + +This directory contains external tests ingested from https://github.com/cqframework/cql-tests. + +In the future we may make this a submodule. \ No newline at end of file diff --git a/tests/spectests/third_party/cqltests/ValueLiteralsAndSelectors.xml b/tests/spectests/third_party/cqltests/ValueLiteralsAndSelectors.xml new file mode 100644 index 0000000..8efd879 --- /dev/null +++ b/tests/spectests/third_party/cqltests/ValueLiteralsAndSelectors.xml @@ -0,0 +1,352 @@ + + + + + + null + @2000 < @2000-01 + + + + + + + false + 1~0 + + + true + 1~1 + + + + + + + 0 + 42-42 + + + +0 + 42-42 + + + -0 + 42-42 + + + 1 + 42-41 + + + +1 + 42-41 + + + -1 + 42-43 + + + 2 + 42-40 + + + +2 + 42-40 + + + -2 + 42-44 + + + 1000000000 + Power(10,9) + + + +1000000000 + +Power(10,9) + + + -1000000000 + -Power(10,9) + + + 2147483647 + Power(2,30)-1+Power(2,30) + + + +2147483647 + +Power(2,30)-1+Power(2,30) + + + -2147483647 + -Power(2,30)+1-Power(2,30) + + + 2147483648 + + + + + +2147483648 + + + + + -2147483648 + -Power(2,30)-Power(2,30) + + + + 2147483649 + + + + + +2147483649 + + + + + -2147483649 + + + + + + + + + 0.0 + 42.0-42.0 + + + +0.0 + 42.0-42.0 + + + -0.0 + 42.0-42.0 + + + 1.0 + 42.0-41.0 + + + +1.0 + 42.0-41.0 + + + -1.0 + 42.0-43.0 + + + 2.0 + 42.0-40.0 + + + +2.0 + 42.0-40.0 + + + -2.0 + 42.0-44.0 + + + 1000000000.0 + Power(10.0,9.0) + + + +1000000000.0 + +Power(10.0,9.0) + + + -1000000000.0 + -Power(10.0,9.0) + + + 2147483647.0 + Power(2.0,30.0)-1+Power(2.0,30.0) + + + +2147483647.0 + +Power(2.0,30.0)-1+Power(2.0,30.0) + + + -2147483647.0 + -Power(2.0,30.0)+1.0-Power(2.0,30.0) + + + 2147483648.0 + Power(2.0,30.0)+Power(2.0,30.0) + + + +2147483648.0 + +Power(2.0,30.0)+Power(2.0,30.0) + + + -2147483648.0 + -Power(2.0,30.0)-Power(2.0,30.0) + + + 2147483649.0 + Power(2.0,30.0)+1.0+Power(2.0,30.0) + + + +2147483649.0 + +Power(2.0,30.0)+1.0+Power(2.0,30.0) + + + -2147483649.0 + -Power(2.0,30.0)-1.0-Power(2.0,30.0) + + + 0.00000000 + 42.0-42.0 + + + +0.00000000 + 42.0-42.0 + + + -0.00000000 + 42.0-42.0 + + + 0.00000001 + Power(10,-8) + + + +0.00000001 + +Power(10,-8) + + + -0.00000001 + -Power(10,-8) + + + 0.00000002 + 2.0*Power(10,-8) + + + +0.00000002 + +2.0*Power(10,-8) + + + -0.00000002 + -2.0*Power(10,-8) + + + 0.0000001 + Power(10,-7) + + + +0.0000001 + +Power(10,-7) + + + -0.0000001 + -Power(10,-7) + + + 0.000000001 + + + + + +0.000000001 + + + + + -0.000000001 + + + + + 9999999999999999999999999999.99999999 + 10*1000000000000000000000000000.00000000-0.00000001 + + + +9999999999999999999999999999.99999999 + +10*1000000000000000000000000000.00000000-0.00000001 + + + -9999999999999999999999999999.99999999 + -10*1000000000000000000000000000.00000000+0.00000001 + + + 10000000000000000000000000000.00000000 + + + + + +10000000000000000000000000000.00000000 + + + + + -10000000000000000000000000000.00000000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/spectests/third_party/cqltests/cqltests.go b/tests/spectests/third_party/cqltests/cqltests.go new file mode 100644 index 0000000..f1b44dd --- /dev/null +++ b/tests/spectests/third_party/cqltests/cqltests.go @@ -0,0 +1,25 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cqltests contains the XML tests from the CQL specification +package cqltests + +import ( + "embed" +) + +// XMLTests contains the CQL XML tests. +// +//go:embed * +var XMLTests embed.FS diff --git a/tests/spectests/third_party/cqltests/testSchema.xsd b/tests/spectests/third_party/cqltests/testSchema.xsd new file mode 100644 index 0000000..a53625f --- /dev/null +++ b/tests/spectests/third_party/cqltests/testSchema.xsd @@ -0,0 +1,201 @@ + + + + + + + The Tests type provides a container for test suites made up of groups of tests. + + + + + Notes about the test suite. + + + + + + + The name of the test suite. This should be a computer-friendly name. + + + + + The version of FHIRPath in which the features being tested were introduced. This should correspond to a published or planned published version of FHIRPath. + + + + + A description of the test suite. + + + + + A reference to the specification under test. + + + + + + + + Notes about the test group. + + + + + + + The name of the test group. This should be a computer-friendly name and must be unique within the test suite. + + + + + The version of FHIRPath in which the features being tested were introduced. This should correspond to a published or planned published version of FHIRPath. + + + + + A description of the test group. + + + + + A reference to area of the specification under that the tests in this group cover. + + + + + + + + The expression to be tested. + + + + + The expected output of the test. + + + + + Notes about the test. These notes should be included with the test output if the test fails. + + + + + + The name of the test suite. This should be a computer-friendly name. + + + + + The version of FHIRPath in which the feature being tested was introduced. This should correspond to a published or planned published version of FHIRPath. + + + + + A description of the test suite. + + + + + A reference to the specification under test. + + + + + If present, the name of an input file containing input data for the test. + + + + + True if this test represents a predicate. + + + + + Strict vs Loose, determines whether type checking is strict as specified in [Type Safety and Strict Evaluation](http://hl7.org/fhirpath/#type-safety-and-strict-evaluation) + + + + + Whether the results are expected to be ordered, false if not present + + + + + Whether to ensure that attempting to use ordered functions with an unordered input should throw (e.g., using .skip() on an unordered list) + + + + + + + + + Indicates whether the expression is expected to evaluate successfully, produce a runtime error, or produce a semantic error. + + + + + + + + + + + The type of the expected output. If no type is provided, the value is expected to be an expression in the language under test and will be set equal to the test expression and the expected evaluation result will be true. In the special case that the output is a null, a null predicate test will be used. + + + + + + + + + + + + + + + + + + + + + + + + Indicates the test is expected to evaluate successfully. + + + + + Indicates the test is expected to produce a semantic error. + + + + + Indicates the test is expected to produce a runtime error. + + + + + + + + + Indicates the test is expected to evaluate with strict semantics + + + + + Indicates the test is expected to evaluate with loose semantics + + + + + diff --git a/tests/spectests/xml_test.go b/tests/spectests/xml_test.go new file mode 100644 index 0000000..cec2840 --- /dev/null +++ b/tests/spectests/xml_test.go @@ -0,0 +1,194 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spectests_test + +import ( + "context" + "encoding/xml" + "fmt" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/google/cql" + "github.com/google/cql/result" + "github.com/google/cql/tests/spectests/third_party/cqltests" + "github.com/google/cql/tests/spectests/exclusions" + "github.com/google/cql/tests/spectests/models" + "github.com/google/go-cmp/cmp" + "slices" +) + +// TestCQLXML runs the CQL XML tests by building want and got CQL expression definitions. +// After evaluation, the want and got expression definition result.Values are compared using +// result.Value.Equal. For example: +// +// +// true xor true +// false +// +// +// Would be translated into: +// +// library CQL_Test +// define "got": true xor true +// define "want": false +// +// After evaluation, the got and want expression definition results are extracted and compared. +func TestCQLXML(t *testing.T) { + testDir := "." + testExclusions := exclusions.XMLTestFileExclusionDefinitions() + + files, err := cqltests.XMLTests.ReadDir(testDir) + if err != nil { + t.Fatalf("Failed to read cql directory: %v", err) + } + if len(files) == 0 { + t.Fatalf("No xml files found in %v", cqltests.XMLTests) + } + + xmlTestFiles := []string{} + for _, f := range files { + if !strings.HasSuffix(f.Name(), ".xml") { + continue + } + xmlTestFiles = append(xmlTestFiles, f.Name()) + } + if len(xmlTestFiles) == 0 { + t.Fatal("No xml files found in embedded directory to test") + } + + for _, fileName := range xmlTestFiles { + src := filepath.Join(testDir, fileName) + data, err := cqltests.XMLTests.ReadFile(src) + if err != nil { + t.Fatalf("Failed to read XML file: %v", err) + } + + var cqlTests []cqlTest + xmlTests, err := parseXML(t, data) + if err != nil { + t.Fatalf("failed to parse XML file %s: %v", fileName, err) + } + cqlTests = createCQLTests(t, fileName, xmlTests) + + currExclusions, ok := testExclusions[fileName] + if !ok { + currExclusions = exclusions.XMLTestFileExclusions{GroupExcludes: []string{}, NamesExcludes: []string{}} + } + + for _, tc := range cqlTests { + t.Run(fmt.Sprintf("%s_%s_%s", tc.FileName, tc.Group, tc.Name), func(t *testing.T) { + shouldSkip := ( + slices.Contains(currExclusions.GroupExcludes, tc.Group) || + slices.Contains(currExclusions.NamesExcludes, tc.Name) || + tc.Skip) + if shouldSkip && strings.Contains(tc.SkipReason, "no output defined") { + t.Skip("in skipped test groups or names") + } + + elm, err := cql.Parse(context.Background(), []string{tc.CQL}, cql.ParseConfig{}) + if err != nil { + if shouldSkip { + t.Skip("in skipped test groups") + } + t.Fatalf("Failed to parse test case: %s\nFilePath=%s\nGroup=%s\nName=%s", err.Error(), src, tc.Group, tc.Name) + } + results, err := elm.Eval(context.Background(), nil, cql.EvalConfig{EvaluationTimestamp: time.Date(2024, 1, 1, 0, 0, 0, 0, time.FixedZone("Fixed", 4*60*60))}) + if err != nil { + if shouldSkip { + t.Skip("in skipped test groups") + } + t.Fatalf("Failed to execute query(%s): %v\nFilePath=%s\nGroup=%s\nName=%s", tc.CQL, err, src, tc.Group, tc.Name) + } + + want := getExpDef(t, results, wantExpDef) + got := getExpDef(t, results, gotExpDef) + + if !cmp.Equal(want, got) { + if shouldSkip { + t.Skip("in skipped test groups") + } + t.Errorf("evaluating test case %s returned unexpected result. got: %v, want: %v", tc.Name, got, want) + } + if shouldSkip { + t.Errorf("test case %s in group %s was marked to skip but succeeded. You may need to update the test exclusions file", tc.Name, tc.Group) + } + }) + } + } +} + +func parseXML(t *testing.T, raw []byte) (models.Tests, error) { + t.Helper() + var testCase models.Tests + if err := xml.Unmarshal(raw, &testCase); err != nil { + return models.Tests{}, err + } + + return testCase, nil +} + +var ( + gotExpDef = "got" + wantExpDef = "want" +) + +type cqlTest struct { + FileName string + Group string + Name string + CQL string + Skip bool + SkipReason string +} + +func createCQLTests(t *testing.T, fileName string, test models.Tests) []cqlTest { + t.Helper() + cqlTests := []cqlTest{} + for _, g := range test.Group { + for _, tc := range g.Test { + newTest := cqlTest{FileName: fileName, Group: g.Name, Name: tc.Name} + if len(tc.Output) == 0 { + newTest.Skip = true + newTest.SkipReason = "no output defined for this test case" + } else { + cql := fmt.Sprintf("library CQL_Test") + cql += fmt.Sprintf("\ndefine \"%s\": %s", gotExpDef, tc.Expression.Text) + cql += fmt.Sprintf("\ndefine \"%s\": %s", wantExpDef, tc.Output[0].Text) + newTest.CQL = cql + } + cqlTests = append(cqlTests, newTest) + } + } + return cqlTests +} + +func getExpDef(t *testing.T, results result.Libraries, expDefName string) result.Value { + libKey := result.LibKey{Name: "CQL_Test"} + gotExpDefs, ok := results[libKey] + if !ok { + t.Fatalf("Failed to find CQL_Test library in CQL output") + } + + for expDef, gotResult := range gotExpDefs { + if expDef == expDefName { + return gotResult + } + } + t.Fatalf("Failed to find expDef %v in CQL output", expDefName) + return result.Value{} +} diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..2bb7d7f --- /dev/null +++ b/types/types.go @@ -0,0 +1,850 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package types holds a representation of CQL types and related logic. It is used by both the +// parser and interpreter. +package types + +import ( + "encoding/json" + "errors" + "fmt" + "sort" + "strings" + + "google.golang.org/protobuf/proto" + + ctpb "github.com/google/cql/protos/cql_types_go_proto" +) + +// IType is an interface implemented by all CQL Type structs. +type IType interface { + // TODO(b/312172420): Consider renaming IType to Type or types.Specifier. + + // Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. + Equal(IType) bool + + // String returns a print friendly representation of the type and implements fmt.Stringer. + String() string + + // ModelInfoName returns the key for this type in the model info. + // + // For Named and System types this is the fully qualified name like FHIR.EnrollmentResponseStatus + // or System.Integer. The name may be split in the XML (namespace="FHIR" + // name="EnrollmentResponseStatus"), but we use the qualified name. + // + // For other types the CQL type specifier syntax is used as the key. For example, + // Interval, Choice or Tuple { address String }. For Tuple and Choice types + // we alphabetically sort their inner types. + ModelInfoName() (string, error) + + // MarshalJSON implements the json.Marshaler interface for the IType. + MarshalJSON() ([]byte, error) +} + +// System represents the primitive types defined by CQL +// (https://cql.hl7.org/09-b-cqlreference.html#types-2). +type System string + +const ( + // Unset indicates that the parser did not set this Result Type. + Unset System = "System.UnsetType" + // Any is a CQL Any Type. It means that the type could be anything including list, interval, + // named type from ModelInfo or null. + Any System = "System.Any" + // String is a CQL String type. + String System = "System.String" + // Integer is a CQL Integer type. + Integer System = "System.Integer" + // Decimal is a CQL Decimal type. + Decimal System = "System.Decimal" + // Long is a CQL Long type. + Long System = "System.Long" + // Quantity is a CQL decimal value and unit pair. + Quantity System = "System.Quantity" + // Ratio is the type for ratio - two CQL quantities. + Ratio System = "System.Ratio" + // Boolean is a CQL Boolean type. + Boolean System = "System.Boolean" + // DateTime is the CQL DateTime type. + DateTime System = "System.DateTime" + // Date is the CQL Date type. + Date System = "System.Date" + // Time is the CQL Time type. + Time System = "System.Time" + // ValueSet is the CQL Valueset type. + ValueSet System = "System.ValueSet" + // CodeSystem is a CQL CodeSystem which contains external Code definitions. + CodeSystem System = "System.CodeSystem" + // Vocabulary is the CQL Vocabulary type which is the parent type of ValueSet and CodeSystem. + Vocabulary System = "System.Vocabulary" + // Code is the CQL System Code type (which is distinct from a FHIR code type). + Code System = "System.Code" + // Concept is the CQL System Concept type. + Concept System = "System.Concept" +) + +// ToSystem converts a string to a System type returning System.Unsupported if the string cannot be +// converted to a system type. +func ToSystem(s string) System { + switch s { + case "System.Any", "Any": + return Any + case "System.String", "String": + return String + case "System.Integer", "Integer": + return Integer + case "System.Decimal", "Decimal": + return Decimal + case "System.Long", "Long": + return Long + case "System.Quantity", "Quantity": + return Quantity + case "System.Ratio", "Ratio": + return Ratio + case "System.Boolean", "Boolean": + return Boolean + case "System.DateTime", "DateTime": + return DateTime + case "System.Date", "Date": + return Date + case "System.Time", "Time": + return Time + case "System.ValueSet", "ValueSet": + return ValueSet + case "System.CodeSystem", "CodeSystem": + return CodeSystem + case "System.Vocabulary", "Vocabulary": + return Vocabulary + case "System.Code", "Code": + return Code + case "System.Concept", "Concept": + return Concept + default: + return Unset + } +} + +// Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. +func (s System) Equal(a IType) bool { + aBase, ok := a.(System) + if !ok { + return false + } + + return s == aBase +} + +// String returns the model info based name for the type, and implements fmt.Stringer for easy +// printing. +func (s System) String() string { + return string(s) +} + +// ModelInfoName returns the fully qualified type name in the model info convention. +func (s System) ModelInfoName() (string, error) { + return string(s), nil +} + +// Proto returns the proto representation of the system type. +func (s System) Proto() *ctpb.SystemType { + switch s { + case Any: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_ANY.Enum()} + case String: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_STRING.Enum()} + case Integer: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()} + case Decimal: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_DECIMAL.Enum()} + case Long: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_LONG.Enum()} + case Quantity: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_QUANTITY.Enum()} + case Ratio: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_RATIO.Enum()} + case Boolean: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_BOOLEAN.Enum()} + case DateTime: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_DATE_TIME.Enum()} + case Date: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_DATE.Enum()} + case Time: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_TIME.Enum()} + case ValueSet: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_VALUE_SET.Enum()} + case CodeSystem: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_CODE_SYSTEM.Enum()} + case Vocabulary: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_VOCABULARY.Enum()} + case Code: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_CODE.Enum()} + case Concept: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_CONCEPT.Enum()} + default: + return &ctpb.SystemType{Type: ctpb.SystemType_TYPE_UNSPECIFIED.Enum()} + } +} + +// SystemFromProto converts a proto to a System type. +func SystemFromProto(pb *ctpb.SystemType) System { + switch pb.GetType() { + case ctpb.SystemType_TYPE_ANY: + return Any + case ctpb.SystemType_TYPE_STRING: + return String + case ctpb.SystemType_TYPE_INTEGER: + return Integer + case ctpb.SystemType_TYPE_DECIMAL: + return Decimal + case ctpb.SystemType_TYPE_LONG: + return Long + case ctpb.SystemType_TYPE_QUANTITY: + return Quantity + case ctpb.SystemType_TYPE_RATIO: + return Ratio + case ctpb.SystemType_TYPE_BOOLEAN: + return Boolean + case ctpb.SystemType_TYPE_DATE_TIME: + return DateTime + case ctpb.SystemType_TYPE_DATE: + return Date + case ctpb.SystemType_TYPE_TIME: + return Time + case ctpb.SystemType_TYPE_VALUE_SET: + return ValueSet + case ctpb.SystemType_TYPE_CODE_SYSTEM: + return CodeSystem + case ctpb.SystemType_TYPE_VOCABULARY: + return Vocabulary + case ctpb.SystemType_TYPE_CODE: + return Code + case ctpb.SystemType_TYPE_CONCEPT: + return Concept + default: + return Unset + } +} + +// MarshalJSON implements the json.Marshaler interface for the System type. +func (s System) MarshalJSON() ([]byte, error) { + return defaultTypeNameJSON(s) +} + +// Named defines a single type by name. The name refers to a type defined by ModelInfo. +type Named struct { + // TypeName is the fully qualified name of the type. + TypeName string + // TODO(b/313949948): Named type's name is a qualified identifier, we may wish to also + // consider storing those in a structured way. +} + +// Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. +func (n *Named) Equal(a IType) bool { + aName, ok := a.(*Named) + if !ok { + return false + } + + // n or aName are unknown or unsupported. + if n == nil || aName == nil { + return n == aName + } + + return aName.TypeName == n.TypeName +} + +// String returns the model info based name for the type, and implements fmt.Stringer for easy +// printing. +func (n *Named) String() string { + if n == nil { + return "nil Named" + } + var sb strings.Builder + fmt.Fprintf(&sb, "Named<%s>", string(n.TypeName)) + return sb.String() +} + +// ModelInfoName returns the fully qualified type name in the model info convention. +func (n *Named) ModelInfoName() (string, error) { + if n == nil { + return "", errTypeNil + } + return n.TypeName, nil +} + +// Proto returns the proto representation of the named type. +func (n *Named) Proto() (*ctpb.NamedType, error) { + if n == nil { + return nil, errTypeNil + } + return &ctpb.NamedType{TypeName: proto.String(n.TypeName)}, nil +} + +// NamedFromProto converts a proto to a Named type. +func NamedFromProto(pb *ctpb.NamedType) (*Named, error) { + if pb == nil { + return nil, errTypeNil + } + return &Named{TypeName: pb.GetTypeName()}, nil +} + +// MarshalJSON implements the json.Marshaler interface for the Named type. +func (n Named) MarshalJSON() ([]byte, error) { + return defaultTypeNameJSON(&n) +} + +// Interval defines the type for an interval. +type Interval struct { + PointType IType +} + +// Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. +func (i *Interval) Equal(a IType) bool { + aInterval, ok := a.(*Interval) + if !ok { + return false + } + + if i == nil || aInterval == nil { + // TODO(b/301606416): Add a test to cover this case. + return i == aInterval + } + if i.PointType == nil || aInterval.PointType == nil { + return i.PointType == aInterval.PointType + } + + return i.PointType.Equal(aInterval.PointType) +} + +// String returns the model info based name for the type, and implements fmt.Stringer for easy +// printing. +func (i *Interval) String() string { + if i == nil { + return "nil Interval" + } + if i.PointType == nil { + return "Interval" + } + var sb strings.Builder + fmt.Fprintf(&sb, "Interval<%s>", string(i.PointType.String())) + return sb.String() +} + +// ModelInfoName returns name as the CQL interval type specifier. +func (i *Interval) ModelInfoName() (string, error) { + if i == nil { + return "", errTypeNil + } + if i.PointType == nil { + return "", fmt.Errorf("internal error -- nil PointType for Interval") + } + it, err := i.PointType.ModelInfoName() + if err != nil { + return "", err + } + var sb strings.Builder + fmt.Fprintf(&sb, "Interval<%s>", it) + return sb.String(), nil +} + +// Proto returns the proto representation of the interval type. +func (i *Interval) Proto() (*ctpb.IntervalType, error) { + if i == nil { + return nil, errTypeNil + } + if i.PointType == nil { + return nil, fmt.Errorf("internal error -- nil PointType for Interval") + } + + pointTypePB, err := CQLTypeToProto(i.PointType) + if err != nil { + return nil, err + } + + return &ctpb.IntervalType{PointType: pointTypePB}, nil +} + +// IntervalFromProto converts a proto to an Interval type. +func IntervalFromProto(pb *ctpb.IntervalType) (*Interval, error) { + if pb == nil { + return nil, errTypeNil + } + pointType, err := CQLTypeFromProto(pb.GetPointType()) + if err != nil { + return nil, err + } + return &Interval{PointType: pointType}, nil +} + +// MarshalJSON implements the json.Marshaler interface for the Interval type. +func (i Interval) MarshalJSON() ([]byte, error) { + if i.PointType == nil { + return []byte(`"Interval<` + Any.String() + `>"`), nil + } + return defaultTypeNameJSON(&i) +} + +// List defines the type for a list. +type List struct { + // The type of the elements in the list. + ElementType IType +} + +// Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. +func (l *List) Equal(a IType) bool { + aList, ok := a.(*List) + if !ok { + return false + } + + if l == nil || aList == nil { + return l == aList + } + if l.ElementType == nil || aList.ElementType == nil { + return l.ElementType == aList.ElementType + } + + return l.ElementType.Equal(aList.ElementType) +} + +// String returns the model info based name for the type, and implements fmt.Stringer for easy +// printing. +func (l *List) String() string { + if l == nil { + return "nil List" + } + if l.ElementType == nil { + return "List" + } + var sb strings.Builder + fmt.Fprintf(&sb, "List<%s>", string(l.ElementType.String())) + return sb.String() +} + +// ModelInfoName returns name as the CQL list type specifier. +func (l *List) ModelInfoName() (string, error) { + if l == nil { + return "", errTypeNil + } + if l.ElementType == nil { + return "", fmt.Errorf("internal error - nil ElementType for List") + } + et, err := l.ElementType.ModelInfoName() + if err != nil { + return "", err + } + var sb strings.Builder + fmt.Fprintf(&sb, "List<%s>", et) + return sb.String(), nil +} + +// Proto returns the proto representation of the list type. +func (l *List) Proto() (*ctpb.ListType, error) { + if l == nil { + return nil, errTypeNil + } + if l.ElementType == nil { + return nil, fmt.Errorf("internal error - nil ElementType for List") + } + + listTypePB, err := CQLTypeToProto(l.ElementType) + if err != nil { + return nil, err + } + + return &ctpb.ListType{ElementType: listTypePB}, nil +} + +// ListFromProto converts a proto to a List type. +func ListFromProto(pb *ctpb.ListType) (*List, error) { + if pb == nil { + return nil, errTypeNil + } + elementType, err := CQLTypeFromProto(pb.GetElementType()) + if err != nil { + return nil, err + } + return &List{ElementType: elementType}, nil +} + +// MarshalJSON implements the json.Marshaler interface for the List type. +func (l List) MarshalJSON() ([]byte, error) { + if l.ElementType == nil { + return []byte(`"List<` + Any.String() + `>"`), nil + } + return defaultTypeNameJSON(&l) +} + +// Choice defines the type for a choice type. +type Choice struct { + ChoiceTypes []IType +} + +// Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. +func (c *Choice) Equal(a IType) bool { + if c == nil || a == nil { + return c == a + } + + aChoice, ok := a.(*Choice) + if !ok { + return false + } + + if len(aChoice.ChoiceTypes) != len(c.ChoiceTypes) { + return false + } + + // Order of the ChoiceTypes does not matter, so create a copied slice to pop from. + cChoiceSet := make([]IType, len(c.ChoiceTypes)) + copy(cChoiceSet, c.ChoiceTypes) + for _, aType := range aChoice.ChoiceTypes { + for i, cType := range cChoiceSet { + if cType.Equal(aType) { + // Pop the index from cChoiceSet. + cChoiceSet = append(cChoiceSet[:i], cChoiceSet[i+1:]...) + break + } + } + } + if len(cChoiceSet) == 0 { + return true + } + return false +} + +// String returns the model info based name for the type, and implements fmt.Stringer for easy +// printing. +func (c *Choice) String() string { + if c == nil { + return "nil Choice" + } + var sb strings.Builder + fmt.Fprintf(&sb, "Choice<%s>", ToStrings(c.ChoiceTypes)) + return sb.String() +} + +// ModelInfoName returns name as the CQL choice type specifier with ChoiceTypes sorted. +func (c *Choice) ModelInfoName() (string, error) { + if c == nil { + return "", errTypeNil + } + if c.ChoiceTypes == nil { + return "", fmt.Errorf("internal error - nil ChoiceTypes for Choice") + } + + sortedNames := make([]string, 0, len(c.ChoiceTypes)) + for _, choice := range c.ChoiceTypes { + name, err := choice.ModelInfoName() + if err != nil { + return "", err + } + sortedNames = append(sortedNames, name) + } + sort.Strings(sortedNames) + + var sb strings.Builder + fmt.Fprint(&sb, "Choice<") + for i, n := range sortedNames { + if i > 0 { + fmt.Fprint(&sb, ", ") + } + fmt.Fprint(&sb, n) + } + fmt.Fprint(&sb, ">") + return sb.String(), nil +} + +// Proto returns the proto representation of the choice type. +func (c *Choice) Proto() (*ctpb.ChoiceType, error) { + if c == nil { + return nil, errTypeNil + } + if c.ChoiceTypes == nil { + return nil, fmt.Errorf("internal error - nil ChoiceTypes for Choice") + } + + choicepb := &ctpb.ChoiceType{ChoiceTypes: make([]*ctpb.CQLType, 0, len(c.ChoiceTypes))} + for _, choiceType := range c.ChoiceTypes { + choiceTypePB, err := CQLTypeToProto(choiceType) + if err != nil { + return nil, err + } + choicepb.ChoiceTypes = append(choicepb.ChoiceTypes, choiceTypePB) + } + + return choicepb, nil +} + +// ChoiceFromProto converts a proto to a Choice type. +func ChoiceFromProto(pb *ctpb.ChoiceType) (*Choice, error) { + if pb == nil { + return nil, errTypeNil + } + choice := &Choice{ChoiceTypes: make([]IType, 0, len(pb.GetChoiceTypes()))} + for _, choiceTypePB := range pb.GetChoiceTypes() { + choiceType, err := CQLTypeFromProto(choiceTypePB) + if err != nil { + return nil, err + } + choice.ChoiceTypes = append(choice.ChoiceTypes, choiceType) + } + return choice, nil +} + +// MarshalJSON implements the json.Marshaler interface for the Choice type. +func (c Choice) MarshalJSON() ([]byte, error) { + if c.ChoiceTypes == nil { + return []byte(`"Choice"`), nil + } + if len(c.ChoiceTypes) == 0 { + return []byte(`"Choice<>"`), nil + } + return defaultTypeNameJSON(&c) +} + +// Tuple defines the type for a tuple (aka Structured Value). +type Tuple struct { + // ElementTypes is a map from element name to its type. + ElementTypes map[string]IType +} + +// Equal is a strict equal. X.Equal(Y) is true when X and Y are the exact same types. +func (t *Tuple) Equal(a IType) bool { + if t == nil || a == nil { + return t == a + } + + aTuple, ok := a.(*Tuple) + if !ok { + return false + } + + if len(aTuple.ElementTypes) != len(t.ElementTypes) { + return false + } + + for tName, tType := range t.ElementTypes { + aType, ok := aTuple.ElementTypes[tName] + if !ok { + return false + } + if !aType.Equal(tType) { + return false + } + } + + return true +} + +// String returns the model info based name for the type, and implements fmt.Stringer for easy +// printing. +func (t *Tuple) String() string { + if t == nil { + return "nil Tuple" + } + if t.ElementTypes == nil { + return "Tuple" + } + + var sb strings.Builder + fmt.Fprint(&sb, "Tuple<") + i := 0 + for name, t := range t.ElementTypes { + if i > 0 { + fmt.Fprint(&sb, ", ") + } + if t == nil { + fmt.Fprintf(&sb, "%s: nil", name) + } else { + fmt.Fprintf(&sb, "%s: %s", name, t.String()) + } + i++ + } + fmt.Fprint(&sb, ">") + return sb.String() +} + +// ModelInfoName returns name as the CQL tuple type specifier with ElementTypes sorted by name. +func (t *Tuple) ModelInfoName() (string, error) { + if t == nil { + return "", errTypeNil + } + if t.ElementTypes == nil { + return "", fmt.Errorf("internal error - nil ElementTypes for Tuple") + } + + if len(t.ElementTypes) == 0 { + return "Tuple { }", nil + } + + sortedNames := make([]string, 0, len(t.ElementTypes)) + for name := range t.ElementTypes { + sortedNames = append(sortedNames, name) + } + sort.Strings(sortedNames) + + var sb strings.Builder + fmt.Fprint(&sb, "Tuple { ") + for i, name := range sortedNames { + if i > 0 { + fmt.Fprint(&sb, ", ") + } + elemType, err := t.ElementTypes[name].ModelInfoName() + if err != nil { + return "", err + } + fmt.Fprintf(&sb, "%s %s", name, elemType) + } + fmt.Fprint(&sb, " }") + return sb.String(), nil +} + +// Proto returns the proto representation of the tuple type. +func (t *Tuple) Proto() (*ctpb.TupleType, error) { + if t == nil { + return nil, errTypeNil + } + if t.ElementTypes == nil { + return nil, fmt.Errorf("internal error - nil ElementTypes for Tuple") + } + + tuplepb := &ctpb.TupleType{ElementTypes: make(map[string]*ctpb.CQLType, len(t.ElementTypes))} + for elemName, elemType := range t.ElementTypes { + elemTypePB, err := CQLTypeToProto(elemType) + if err != nil { + return nil, err + } + tuplepb.ElementTypes[elemName] = elemTypePB + } + + return tuplepb, nil +} + +// TupleFromProto converts a proto to a Tuple type. +func TupleFromProto(pb *ctpb.TupleType) (*Tuple, error) { + if pb == nil { + return nil, errTypeNil + } + tuple := &Tuple{ElementTypes: make(map[string]IType, len(pb.GetElementTypes()))} + for elemName, elemTypePB := range pb.GetElementTypes() { + elemType, err := CQLTypeFromProto(elemTypePB) + if err != nil { + return nil, err + } + tuple.ElementTypes[elemName] = elemType + } + return tuple, nil +} + +// CQLTypeToProto returns the generic CQLType proto of the CQL type. +func CQLTypeToProto(typ IType) (*ctpb.CQLType, error) { + switch t := typ.(type) { + case System: + return &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: t.Proto()}}, nil + case *Named: + namedpb, err := t.Proto() + if err != nil { + return nil, err + } + return &ctpb.CQLType{Type: &ctpb.CQLType_NamedType{NamedType: namedpb}}, nil + case *Interval: + intervalpb, err := t.Proto() + if err != nil { + return nil, err + } + return &ctpb.CQLType{Type: &ctpb.CQLType_IntervalType{IntervalType: intervalpb}}, nil + case *List: + listpb, err := t.Proto() + if err != nil { + return nil, err + } + return &ctpb.CQLType{Type: &ctpb.CQLType_ListType{ListType: listpb}}, nil + case *Choice: + choicepb, err := t.Proto() + if err != nil { + return nil, err + } + return &ctpb.CQLType{Type: &ctpb.CQLType_ChoiceType{ChoiceType: choicepb}}, nil + case *Tuple: + tuplepb, err := t.Proto() + if err != nil { + return nil, err + } + return &ctpb.CQLType{Type: &ctpb.CQLType_TupleType{TupleType: tuplepb}}, nil + default: + return nil, fmt.Errorf("internal error - unsupported type %v in CQLTypeToProto", t) + } +} + +// CQLTypeFromProto converts the generic CQLType proto to a CQL type. +func CQLTypeFromProto(pb *ctpb.CQLType) (IType, error) { + switch t := pb.Type.(type) { + case *ctpb.CQLType_SystemType: + return SystemFromProto(t.SystemType), nil + case *ctpb.CQLType_NamedType: + return NamedFromProto(t.NamedType) + case *ctpb.CQLType_IntervalType: + return IntervalFromProto(t.IntervalType) + case *ctpb.CQLType_ListType: + return ListFromProto(t.ListType) + case *ctpb.CQLType_ChoiceType: + return ChoiceFromProto(t.ChoiceType) + case *ctpb.CQLType_TupleType: + return TupleFromProto(t.TupleType) + default: + return nil, fmt.Errorf("internal error - unsupported type %v in CQLTypeFromProto", t) + } +} + +// ToStrings returns a print friendly representation of the types. +func ToStrings(ts []IType) string { + var sb strings.Builder + for i, t := range ts { + if i > 0 { + fmt.Fprint(&sb, ", ") + } + if t == nil { + fmt.Fprint(&sb, "nil") + } else { + fmt.Fprint(&sb, t.String()) + } + } + return sb.String() +} + +// MarshalJSON implements the json.Marshaler interface for the Tuple type. +func (t Tuple) MarshalJSON() ([]byte, error) { + if t.ElementTypes == nil { + return json.Marshal("Tuple") + } + return defaultTypeNameJSON(&t) +} + +// zero is a helper function to return the Zero value of a generic type T. +func zero[T any]() T { + var zero T + return zero +} + +var errTypeNil = errors.New("internal error -- unsupported function call on a nil type") + +func defaultTypeNameJSON(t IType) ([]byte, error) { + modelInfoName, err := t.ModelInfoName() + if err != nil { + return nil, err + } + return []byte(`"` + modelInfoName + `"`), nil +} diff --git a/types/types_test.go b/types/types_test.go new file mode 100644 index 0000000..8068a71 --- /dev/null +++ b/types/types_test.go @@ -0,0 +1,781 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "strings" + "testing" + + ctpb "github.com/google/cql/protos/cql_types_go_proto" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestTypeEqual(t *testing.T) { + tests := []struct { + name string + a IType + b IType + want bool + }{ + { + name: "SystemTypes Equal", + a: Integer, + b: Integer, + want: true, + }, + { + name: "NamedTypes Equal", + a: &Named{TypeName: "TypeA"}, + b: &Named{TypeName: "TypeA"}, + want: true, + }, + { + name: "IntervalTypes Equal", + a: &Interval{PointType: Integer}, + b: &Interval{PointType: Integer}, + want: true, + }, + { + name: "IntervalTypes Not Equal", + a: &Interval{PointType: Integer}, + b: &Interval{PointType: &Named{TypeName: "TypeA"}}, + want: false, + }, + { + name: "SystemType NamedType Not Equal", + a: Integer, + b: &Named{TypeName: "TypeA"}, + want: false, + }, + { + name: "IntervalType NamedType Not Equal", + a: &Interval{PointType: &Named{TypeName: "TypeA"}}, + b: &Named{TypeName: "TypeA"}, + want: false, + }, + { + name: "NamedType Empty Equal", + a: &Named{TypeName: ""}, + b: &Named{TypeName: ""}, + want: true, + }, + { + name: "IntervalType Nil Equal", + a: &Interval{PointType: nil}, + b: &Interval{PointType: nil}, + want: true, + }, + { + name: "ListType Equal", + a: &List{ElementType: Integer}, + b: &List{ElementType: Integer}, + want: true, + }, + { + name: "ListType Not Equal", + a: &List{ElementType: Integer}, + b: &List{ElementType: String}, + want: false, + }, + { + name: "ListType Nil Equal", + a: &List{ElementType: nil}, + b: &List{ElementType: nil}, + want: true, + }, + { + name: "ChoiceType Equal", + a: &Choice{ChoiceTypes: []IType{Integer, &Named{TypeName: "TypeA"}}}, + b: &Choice{ChoiceTypes: []IType{Integer, &Named{TypeName: "TypeA"}}}, + want: true, + }, + { + name: "ChoiceType Equal Different Order", + a: &Choice{ChoiceTypes: []IType{Integer, &Named{TypeName: "TypeA"}, Integer, String}}, + b: &Choice{ChoiceTypes: []IType{String, Integer, &Named{TypeName: "TypeA"}, Integer}}, + want: true, + }, + { + name: "ChoiceType Not Equal", + a: &Choice{ChoiceTypes: []IType{Integer, &Named{TypeName: "TypeA"}}}, + b: &Choice{ChoiceTypes: []IType{String, &Named{TypeName: "TypeA"}}}, + want: false, + }, + { + name: "ChoiceType Different Lengths Not Equal", + a: &Choice{ChoiceTypes: []IType{Integer, &Named{TypeName: "TypeA"}}}, + b: &Choice{ChoiceTypes: []IType{Integer}}, + want: false, + }, + { + name: "ChoiceType Empty Equal", + a: &Choice{ChoiceTypes: []IType{}}, + b: &Choice{ChoiceTypes: []IType{}}, + want: true, + }, + { + name: "TupleType Equal", + a: &Tuple{ElementTypes: map[string]IType{"apple": Integer, "banana": String}}, + b: &Tuple{ElementTypes: map[string]IType{"banana": String, "apple": Integer}}, + want: true, + }, + { + name: "TupleType Not Equal", + a: &Tuple{ElementTypes: map[string]IType{"apple": Integer, "banana": &Named{TypeName: "TypeA"}}}, + b: &Tuple{ElementTypes: map[string]IType{"banana": String, "apple": Integer}}, + want: false, + }, + { + name: "TupleType Different Lengths Not Equal", + a: &Tuple{ElementTypes: map[string]IType{"apple": Integer}}, + b: &Tuple{ElementTypes: map[string]IType{"banana": String, "apple": Integer}}, + want: false, + }, + { + name: "TupleType Empty Equal", + a: &Tuple{ElementTypes: map[string]IType{}}, + b: &Tuple{ElementTypes: map[string]IType{}}, + want: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := tc.a.Equal(tc.b); got != tc.want { + t.Errorf("%v.Equal(%v) = %v, want %v", tc.a, tc.b, got, tc.want) + } + }) + } +} + +func TestType_String(t *testing.T) { + tests := []struct { + name string + given IType + want string + }{ + { + name: "System", + given: Integer, + want: "System.Integer", + }, + { + name: "Named", + given: &Named{TypeName: "TypeA"}, + want: "Named", + }, + { + name: "Interval", + given: &Interval{PointType: Integer}, + want: "Interval", + }, + { + name: "List", + given: &List{ElementType: Integer}, + want: "List", + }, + { + name: "List>", + given: &List{ + ElementType: &List{ElementType: Integer}, + }, + want: "List>", + }, + { + name: "List>", + given: &List{ + ElementType: &Interval{PointType: Integer}, + }, + want: "List>", + }, + { + name: "Interval", + given: &Interval{PointType: nil}, + want: "Interval", + }, + { + name: "List", + given: &List{ElementType: nil}, + want: "List", + }, + { + name: "Choice", + given: &Choice{ChoiceTypes: []IType{Integer, String}}, + want: "Choice", + }, + { + name: "Choice<>", + given: &Choice{ChoiceTypes: []IType{}}, + want: "Choice<>", + }, + { + name: "Tuple", + given: &Tuple{ElementTypes: map[string]IType{"apple": Integer, "banana": String}}, + want: "Tuple", + }, + { + name: "Tuple<>", + given: &Tuple{ElementTypes: map[string]IType{}}, + want: "Tuple<>", + }, + { + name: "Tuple", + given: &Tuple{ElementTypes: nil}, + want: "Tuple", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := tc.given.String(); got != tc.want { + t.Errorf("String() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestNilTypeString(t *testing.T) { + t.Run("Nil Named", func(t *testing.T) { + var l *Named + var i IType + i = l + want := "nil Named" + if got := i.String(); got != want { + t.Errorf("%v.GetName() = %v, want %v", i, got, want) + } + + if got := i.Equal(l); got != true { + t.Errorf("%v.Equal(%v) = %v, want true", i, l, got) + } + }) + + t.Run("Nil Interval", func(t *testing.T) { + var l *Interval + var i IType + i = l + want := "nil Interval" + if got := i.String(); got != want { + t.Errorf("%v.GetName() = %v, want %v", i, got, want) + } + + if got := i.Equal(l); got != true { + t.Errorf("%v.Equal(%v) = %v, want true", i, l, got) + } + }) + + t.Run("Nil List", func(t *testing.T) { + var l *List + var i IType + i = l + want := "nil List" + if got := i.String(); got != want { + t.Errorf("%v.GetName() = %v, want %v", i, got, want) + } + + if got := i.Equal(l); got != true { + t.Errorf("%v.Equal(%v) = %v, want true", i, l, got) + } + }) + + t.Run("Nil Choice", func(t *testing.T) { + var l *Choice + var i IType + i = l + want := "nil Choice" + if got := i.String(); got != want { + t.Errorf("%v.GetName() = %v, want %v", i, got, want) + } + + if got := i.Equal(l); got != true { + t.Errorf("%v.Equal(%v) = %v, want true", i, l, got) + } + }) + + t.Run("Nil Tuple", func(t *testing.T) { + var l *Tuple + var i IType + i = l + want := "nil Tuple" + if got := i.String(); got != want { + t.Errorf("%v.GetName() = %v, want %v", i, got, want) + } + + if got := i.Equal(l); got != true { + t.Errorf("%v.Equal(%v) = %v, want true", i, l, got) + } + }) +} + +func TestType_Name(t *testing.T) { + tests := []struct { + name string + given IType + want string + }{ + { + name: "System", + given: Integer, + want: "System.Integer", + }, + { + name: "Named", + given: &Named{TypeName: "FHIR.TypeA"}, + want: "FHIR.TypeA", + }, + { + name: "Interval", + given: &Interval{PointType: Integer}, + want: "Interval", + }, + { + name: "List", + given: &List{ElementType: Integer}, + want: "List", + }, + { + name: "List>", + given: &List{ + ElementType: &List{ElementType: &Named{TypeName: "FHIR.TypeA"}}, + }, + want: "List>", + }, + { + name: "List>", + given: &List{ + ElementType: &Interval{PointType: &Named{TypeName: "FHIR.TypeA"}}, + }, + want: "List>", + }, + { + name: "Choice Types are sorted", + given: &Choice{ChoiceTypes: []IType{Integer, &Named{TypeName: "FHIR.TypeA"}}}, + want: "Choice", + }, + { + name: "Choice Type single choice", + given: &Choice{ChoiceTypes: []IType{Integer}}, + want: "Choice", + }, + { + name: "Choice is empty", + given: &Choice{ChoiceTypes: []IType{}}, + want: "Choice<>", + }, + { + name: "Tuple Types are sorted by name", + given: &Tuple{ElementTypes: map[string]IType{ + "Banana": &Named{TypeName: "FHIR.TypeA"}, + "Apple": Integer, + }}, + want: "Tuple { Apple System.Integer, Banana FHIR.TypeA }", + }, + { + name: "Tuple Types single element", + given: &Tuple{ElementTypes: map[string]IType{ + "Apple": Integer, + }}, + want: "Tuple { Apple System.Integer }", + }, + { + name: "Tuple is empty", + given: &Tuple{ElementTypes: map[string]IType{}}, + want: "Tuple { }", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.given.ModelInfoName() + if err != nil { + t.Errorf("Name() unexpected err: %v", err) + } + if got != tc.want { + t.Errorf("Name() = %v, want %v", got, tc.want) + } + }) + } +} + +func TestType_NameErrors(t *testing.T) { + cases := []struct { + name string + nilType IType + wantErrContains string + }{ + { + name: "Nil Named", + nilType: *new(*Named), // creates a new nil Named pointer + wantErrContains: "internal error -- unsupported function call on a nil type", + }, + { + name: "Nil List", + nilType: *new(*List), // creates a new nil List pointer + wantErrContains: "internal error -- unsupported function call on a nil type", + }, + { + name: "List", + nilType: &List{}, // creates a new nil List pointer + wantErrContains: "internal error - nil ElementType for List", + }, + { + name: "Nil Interval", + nilType: *new(*Interval), // creates a new nil Interval pointer + wantErrContains: "internal error -- unsupported function call on a nil type", + }, + { + name: "Interval", + nilType: &Interval{}, // creates a new nil Interval pointer + wantErrContains: "internal error -- nil PointType for Interval", + }, + { + name: "Nil Choice", + nilType: *new(*Choice), // creates a new nil Choice pointer + wantErrContains: "internal error -- unsupported function call on a nil type", + }, + { + name: "Nil Tuple", + nilType: *new(*Tuple), // creates a new nil Tuple pointer + wantErrContains: "internal error -- unsupported function call on a nil type", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := tc.nilType.ModelInfoName() + if !strings.Contains(err.Error(), tc.wantErrContains) { + t.Errorf("Name() unexpected err. got: %v, want error containing %v", err, tc.wantErrContains) + } + }) + } +} + +func TestToSystem(t *testing.T) { + cases := []struct { + input string + want IType + }{ + {input: "Any", want: Any}, + {input: "System.Any", want: Any}, + {input: "Boolean", want: Boolean}, + {input: "System.Boolean", want: Boolean}, + {input: "Integer", want: Integer}, + {input: "System.Integer", want: Integer}, + {input: "Long", want: Long}, + {input: "System.Long", want: Long}, + {input: "Decimal", want: Decimal}, + {input: "System.Decimal", want: Decimal}, + {input: "String", want: String}, + {input: "System.String", want: String}, + {input: "DateTime", want: DateTime}, + {input: "System.DateTime", want: DateTime}, + {input: "Date", want: Date}, + {input: "System.Date", want: Date}, + {input: "Time", want: Time}, + {input: "System.Time", want: Time}, + {input: "Quantity", want: Quantity}, + {input: "System.Quantity", want: Quantity}, + {input: "Ratio", want: Ratio}, + {input: "System.Ratio", want: Ratio}, + {input: "ValueSet", want: ValueSet}, + {input: "System.ValueSet", want: ValueSet}, + {input: "CodeSystem", want: CodeSystem}, + {input: "System.CodeSystem", want: CodeSystem}, + {input: "Vocabulary", want: Vocabulary}, + {input: "System.Vocabulary", want: Vocabulary}, + {input: "Code", want: Code}, + {input: "System.Code", want: Code}, + {input: "Concept", want: Concept}, + {input: "System.Concept", want: Concept}, + {input: "Apple", want: Unset}, + {input: "System.Apple", want: Unset}, + } + + for _, tc := range cases { + t.Run(tc.input, func(t *testing.T) { + got := ToSystem(tc.input) + if !cmp.Equal(got, tc.want) { + t.Errorf("ToSystem(%v) got: %v, want: %v", tc.input, got, tc.want) + } + }) + } +} + +func TestToStrings(t *testing.T) { + tests := []struct { + name string + operands []IType + want string + }{ + { + name: "Multiple", + operands: []IType{ + String, + &Interval{PointType: DateTime}, + &List{ElementType: Integer}, + &Named{TypeName: "Patient"}, + }, + want: "System.String, Interval, List, Named", + }, + { + name: "Single", + operands: []IType{Date}, + want: "System.Date", + }, + { + name: "Empty", + operands: []IType{}, + want: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := ToStrings(tc.operands) + if got != tc.want { + t.Errorf("ToStrings() = %v want: %v", got, tc.want) + } + }) + } +} + +func TestProtoAndBack(t *testing.T) { + tests := []struct { + name string + typ IType + want *ctpb.CQLType + }{ + { + name: "Any", + typ: Any, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_ANY.Enum()}}}, + }, + { + name: "Boolean", + typ: Boolean, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_BOOLEAN.Enum()}}}, + }, + { + name: "String", + typ: String, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_STRING.Enum()}}}, + }, + { + name: "Integer", + typ: Integer, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}, + }, + { + name: "Long", + typ: Long, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_LONG.Enum()}}}, + }, + { + name: "Decimal", + typ: Decimal, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_DECIMAL.Enum()}}}, + }, + { + name: "Quantity", + typ: Quantity, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_QUANTITY.Enum()}}}, + }, + { + name: "Ratio", + typ: Ratio, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_RATIO.Enum()}}}, + }, + { + name: "Date", + typ: Date, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_DATE.Enum()}}}, + }, + { + name: "DateTime", + typ: DateTime, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_DATE_TIME.Enum()}}}, + }, + { + name: "Time", + typ: Time, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_TIME.Enum()}}}, + }, + { + name: "CodeSystem", + typ: CodeSystem, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_CODE_SYSTEM.Enum()}}}, + }, + { + name: "ValueSet", + typ: ValueSet, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_VALUE_SET.Enum()}}}, + }, + { + name: "Concept", + typ: Concept, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_CONCEPT.Enum()}}}, + }, + { + name: "Code", + typ: Code, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_CODE.Enum()}}}, + }, + { + name: "Vocabulary", + typ: Vocabulary, + want: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_VOCABULARY.Enum()}}}, + }, + { + name: "Interval", + typ: &Interval{PointType: Integer}, + want: &ctpb.CQLType{Type: &ctpb.CQLType_IntervalType{IntervalType: &ctpb.IntervalType{PointType: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}}}}, + }, + { + name: "List", + typ: &List{ElementType: Integer}, + want: &ctpb.CQLType{Type: &ctpb.CQLType_ListType{ListType: &ctpb.ListType{ElementType: &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}}}}, + }, + { + name: "Choice", + typ: &Choice{ChoiceTypes: []IType{Integer, String}}, + want: &ctpb.CQLType{Type: &ctpb.CQLType_ChoiceType{ChoiceType: &ctpb.ChoiceType{ChoiceTypes: []*ctpb.CQLType{ + &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}, + &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_STRING.Enum()}}}, + }}}}, + }, + { + name: "Tuple", + typ: &Tuple{ElementTypes: map[string]IType{"apple": Integer, "banana": String}}, + want: &ctpb.CQLType{Type: &ctpb.CQLType_TupleType{TupleType: &ctpb.TupleType{ElementTypes: map[string]*ctpb.CQLType{ + "apple": &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_INTEGER.Enum()}}}, + "banana": &ctpb.CQLType{Type: &ctpb.CQLType_SystemType{SystemType: &ctpb.SystemType{Type: ctpb.SystemType_TYPE_STRING.Enum()}}}, + }}}}, + }, + { + name: "Named", + typ: &Named{TypeName: "FHIR.TypeA"}, + want: &ctpb.CQLType{Type: &ctpb.CQLType_NamedType{NamedType: &ctpb.NamedType{TypeName: proto.String("FHIR.TypeA")}}}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := CQLTypeToProto(tc.typ) + if err != nil { + t.Errorf("CQLTypeToProto() returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.want, got, protocmp.Transform(), protocmp.SortRepeatedFields(&ctpb.ChoiceType{}, "choice_types")); diff != "" { + t.Errorf("CQLTypeToProto() returned unexpected diff (-want +got):\n%s", diff) + } + + gotValue, err := CQLTypeFromProto(tc.want) + if err != nil { + t.Errorf("CQLTypeFromProto() returned unexpected error: %v", err) + } + if diff := cmp.Diff(tc.typ, gotValue); diff != "" { + t.Errorf("CQLTypeFromProto() returned unexpected diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestMarshalAndUnmarshalJSON(t *testing.T) { + tests := []struct { + name string + declared IType + want string + }{ + { + name: "System Type", + declared: Integer, + want: `"System.Integer"`, + }, + { + name: "Named", + declared: &Named{TypeName: "TypeA"}, + want: `"TypeA"`, + }, + { + name: "Interval", + declared: &Interval{PointType: Integer}, + want: `"Interval"`, + }, + { + name: "List", + declared: &List{ElementType: Integer}, + want: `"List"`, + }, + { + name: "List>", + declared: &List{ + ElementType: &List{ElementType: Integer}, + }, + want: `"List>"`, + }, + { + name: "List>", + declared: &List{ + ElementType: &Interval{PointType: Integer}, + }, + want: `"List>"`, + }, + { + name: "Interval", + declared: &Interval{PointType: nil}, + want: `"Interval"`, + }, + { + name: "List", + declared: &List{ElementType: nil}, + want: `"List"`, + }, + { + name: "Choice", + declared: &Choice{ChoiceTypes: []IType{Integer, String}}, + want: `"Choice"`, + }, + { + name: "Choice<>", + declared: &Choice{ChoiceTypes: []IType{}}, + want: `"Choice<>"`, + }, + { + name: "Tuple", + declared: &Tuple{ElementTypes: map[string]IType{"apple": Integer, "banana": String}}, + want: `"Tuple { apple System.Integer, banana System.String }"`, + }, + { + name: "Tuple<>", + declared: &Tuple{ElementTypes: map[string]IType{}}, + want: `"Tuple { }"`, + }, + { + name: "Tuple", + declared: &Tuple{ElementTypes: nil}, + want: `"Tuple"`, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + gotBytes, err := tc.declared.MarshalJSON() + if err != nil { + t.Errorf("json.Marshal(%v) returned unexpected error, %v", string(gotBytes), err) + } + if diff := cmp.Diff(tc.want, string(gotBytes)); diff != "" { + t.Errorf("json.Marshal(%v) returned unexpected diff (-want +got):\n%s", tc.declared, diff) + } + }) + } +}