From b80b6132d151f683f102ad3872171d8a10ff97ce Mon Sep 17 00:00:00 2001 From: Anton Vasetenkov Date: Wed, 27 Nov 2024 07:04:07 +1300 Subject: [PATCH] Convert cql-to-elm preprocessor Java code to Kotlin (#1450) * Rename .java to .kt * Convert cql-to-elm preprocessor Java code to Kotlin * Use builtin methods for size and length checks * Use builtin trimStart method * Use more builtin methods and vals * Cleanup * Cleanup * Couple of tweaks * More clean-up --------- Co-authored-by: JP --- .../cql/cql2elm/Cql2ElmVisitor.java | 51 +- .../cql/cql2elm/preprocessor/BaseInfo.java | 35 - .../cql/cql2elm/preprocessor/BaseInfo.kt | 9 + .../preprocessor/CodeDefinitionInfo.java | 37 - .../preprocessor/CodeDefinitionInfo.kt | 7 + .../CodesystemDefinitionInfo.java | 34 - .../preprocessor/CodesystemDefinitionInfo.kt | 8 + .../preprocessor/ConceptDefinitionInfo.java | 37 - .../preprocessor/ConceptDefinitionInfo.kt | 7 + .../preprocessor/ContextDefinitionInfo.java | 24 - .../preprocessor/ContextDefinitionInfo.kt | 8 + .../cql2elm/preprocessor/CqlPreprocessor.java | 327 ------- .../cql2elm/preprocessor/CqlPreprocessor.kt | 299 ++++++ .../CqlPreprocessorElmCommonVisitor.java | 911 ------------------ .../CqlPreprocessorElmCommonVisitor.kt | 864 +++++++++++++++++ .../ExpressionDefinitionInfo.java | 43 - .../preprocessor/ExpressionDefinitionInfo.kt | 9 + .../preprocessor/FunctionDefinitionInfo.java | 70 -- .../preprocessor/FunctionDefinitionInfo.kt | 9 + .../preprocessor/IncludeDefinitionInfo.java | 71 -- .../preprocessor/IncludeDefinitionInfo.kt | 11 + .../cql/cql2elm/preprocessor/LibraryInfo.java | 315 ------ .../cql/cql2elm/preprocessor/LibraryInfo.kt | 187 ++++ .../preprocessor/ParameterDefinitionInfo.java | 34 - .../preprocessor/ParameterDefinitionInfo.kt | 8 + .../preprocessor/UsingDefinitionInfo.java | 76 -- .../preprocessor/UsingDefinitionInfo.kt | 11 + .../preprocessor/ValuesetDefinitionInfo.java | 37 - .../preprocessor/ValuesetDefinitionInfo.kt | 6 + 29 files changed, 1467 insertions(+), 2078 deletions(-) delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.kt delete mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.java create mode 100644 Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.kt diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java index f52f43e10..7aea89dc5 100755 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java @@ -1,5 +1,7 @@ package org.cqframework.cql.cql2elm; +import static java.util.Objects.requireNonNull; + import java.math.BigDecimal; import java.util.*; import java.util.List; @@ -20,8 +22,12 @@ import org.hl7.cql.model.*; import org.hl7.elm.r1.*; import org.hl7.elm_modelinfo.r1.ModelInfo; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Cql2ElmVisitor extends CqlPreprocessorElmCommonVisitor { + private static final Logger log = LoggerFactory.getLogger(Cql2ElmVisitor.class); private final SystemMethodResolver systemMethodResolver; private final Set definedExpressionDefinitions = new HashSet<>(); @@ -37,7 +43,7 @@ public class Cql2ElmVisitor extends CqlPreprocessorElmCommonVisitor { public Cql2ElmVisitor(LibraryBuilder libraryBuilder, TokenStream tokenStream, LibraryInfo libraryInfo) { super(libraryBuilder, tokenStream); - this.libraryInfo = Objects.requireNonNull(libraryInfo, "libraryInfo required"); + this.setLibraryInfo(requireNonNull(libraryInfo, "libraryInfo required")); this.systemMethodResolver = new SystemMethodResolver(this, libraryBuilder); } @@ -127,21 +133,13 @@ public UsingDef visitUsingDefinition(cqlParser.UsingDefinitionContext ctx) { return usingDef; } - public Model getModel() { - return getModel((String) null); - } - public Model getModel(String modelName) { return getModel(null, modelName, null, null); } + @NotNull public Model getModel(NamespaceInfo modelNamespace, String modelName, String version, String localIdentifier) { - if (modelName == null) { - var defaultUsing = libraryInfo.getDefaultUsingDefinition(); - modelName = defaultUsing.getName(); - version = defaultUsing.getVersion(); - } - + requireNonNull(modelName, "modelName"); var modelIdentifier = new ModelIdentifier().withId(modelName).withVersion(version); if (modelNamespace != null) { modelIdentifier.setSystem(modelNamespace.getUri()); @@ -408,8 +406,8 @@ public ConceptDef visitConceptDefinition(cqlParser.ConceptDefinitionContext ctx) @Override public NamedTypeSpecifier visitNamedTypeSpecifier(cqlParser.NamedTypeSpecifierContext ctx) { List qualifiers = parseQualifiers(ctx); - String modelIdentifier = getModelIdentifier(qualifiers); - String identifier = getTypeIdentifier(qualifiers, parseString(ctx.referentialOrTypeNameIdentifier())); + String modelIdentifier = Companion.getModelIdentifier(qualifiers); + String identifier = Companion.getTypeIdentifier(qualifiers, parseString(ctx.referentialOrTypeNameIdentifier())); final ResultWithPossibleError retrievedResult = libraryBuilder.getNamedTypeSpecifierResult(String.format("%s:%s", modelIdentifier, identifier)); @@ -459,7 +457,7 @@ public Object visitContextDefinition(cqlParser.ContextDefinitionContext ctx) { if (libraryBuilder.hasUsings()) { ModelInfo modelInfo = modelIdentifier == null ? libraryBuilder - .getModel(libraryInfo.getDefaultModelName()) + .getModel(getLibraryInfo().getDefaultModelName()) .getModelInfo() : libraryBuilder.getModel(modelIdentifier).getModelInfo(); // String contextTypeName = modelContext.getName(); @@ -2812,8 +2810,8 @@ public Object visitSetAggregateExpressionTerm(cqlParser.SetAggregateExpressionTe public Expression visitRetrieve(cqlParser.RetrieveContext ctx) { libraryBuilder.checkLiteralContext(); List qualifiers = parseQualifiers(ctx.namedTypeSpecifier()); - String model = getModelIdentifier(qualifiers); - String label = getTypeIdentifier( + String model = Companion.getModelIdentifier(qualifiers); + String label = Companion.getTypeIdentifier( qualifiers, parseString(ctx.namedTypeSpecifier().referentialOrTypeNameIdentifier())); DataType dataType = libraryBuilder.resolveTypeName(model, label); if (dataType == null) { @@ -2910,7 +2908,7 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) { // Only expand a choice-valued code path if no comparator is specified // Otherwise, a code comparator will always choose a specific representation - boolean hasFHIRHelpers = libraryInfo.resolveLibraryName("FHIRHelpers") != null; + boolean hasFHIRHelpers = getLibraryInfo().resolveLibraryName("FHIRHelpers") != null; if (property != null && property.getResultType() instanceof ChoiceType && codeComparator == null) { for (DataType propertyType : ((ChoiceType) property.getResultType()).getTypes()) { if (hasFHIRHelpers @@ -4043,7 +4041,7 @@ private Expression resolveIdentifier(String identifier) { // and parameters Expression result = libraryBuilder.resolveIdentifier(identifier, false); if (result == null) { - ExpressionDefinitionInfo expressionInfo = libraryInfo.resolveExpressionReference(identifier); + ExpressionDefinitionInfo expressionInfo = getLibraryInfo().resolveExpressionReference(identifier); if (expressionInfo != null) { String saveContext = saveCurrentContext(expressionInfo.getContext()); try { @@ -4069,7 +4067,7 @@ private Expression resolveIdentifier(String identifier) { } } - ParameterDefinitionInfo parameterInfo = libraryInfo.resolveParameterReference(identifier); + ParameterDefinitionInfo parameterInfo = getLibraryInfo().resolveParameterReference(identifier); if (parameterInfo != null) { visitParameterDefinition(parameterInfo.getDefinition()); } @@ -4140,8 +4138,10 @@ public Expression resolveFunction( // Find all functionDefinitionInfo instances with the given name // register each functionDefinitionInfo - if (libraryName == null || libraryName.equals("") || libraryName.equals(this.libraryInfo.getLibraryName())) { - Iterable fdis = libraryInfo.resolveFunctionReference(functionName); + if (libraryName == null + || libraryName.equals("") + || libraryName.equals(this.getLibraryInfo().getLibraryName())) { + Iterable fdis = getLibraryInfo().resolveFunctionReference(functionName); if (fdis != null) { for (FunctionDefinitionInfo fdi : fdis) { String saveContext = saveCurrentContext(fdi.getContext()); @@ -4353,7 +4353,6 @@ private FunctionHeader getFunctionHeader(Operator op) { String.format("Could not resolve function header for operator %s", op.getName())); } FunctionHeader result = getFunctionHeaderByDef(fd); - // FunctionHeader result = functionHeadersByDef.get(fd); if (result == null) { throw new IllegalArgumentException( String.format("Could not resolve function header for operator %s", op.getName())); @@ -4449,7 +4448,7 @@ public FunctionDef compileFunctionDefinition(cqlParser.FunctionDefinitionContext try { libraryBuilder.popIdentifier(); } catch (Exception e) { - e.printStackTrace(); + log.info("Error popping identifier: {}", e.getMessage()); } } // Intentionally do _not_ pop the function name, it needs to remain in global scope! @@ -4504,25 +4503,23 @@ private TrackBack getTrackBack(ParseTree tree) { } private TrackBack getTrackBack(TerminalNode node) { - TrackBack tb = new TrackBack( + return new TrackBack( libraryBuilder.getLibraryIdentifier(), node.getSymbol().getLine(), node.getSymbol().getCharPositionInLine() + 1, // 1-based instead of 0-based node.getSymbol().getLine(), node.getSymbol().getCharPositionInLine() + node.getSymbol().getText().length()); - return tb; } private TrackBack getTrackBack(ParserRuleContext ctx) { - TrackBack tb = new TrackBack( + return new TrackBack( libraryBuilder.getLibraryIdentifier(), ctx.getStart().getLine(), ctx.getStart().getCharPositionInLine() + 1, // 1-based instead of 0-based ctx.getStop().getLine(), ctx.getStop().getCharPositionInLine() + ctx.getStop().getText().length() // 1-based instead of 0-based ); - return tb; } private void decorate(Element element, TrackBack tb) { diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.java deleted file mode 100644 index 4ddcc5d8c..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; - -@SuppressWarnings("checkstyle:abstractclassname") -public class BaseInfo { - private String header; - private Interval headerInterval; - private ParseTree definition; - - public String getHeader() { - return header; - } - - public void setHeader(String header) { - this.header = header; - } - - public Interval getHeaderInterval() { - return headerInterval; - } - - public void setHeaderInterval(Interval headerInterval) { - this.headerInterval = headerInterval; - } - - public ParseTree getDefinition() { - return definition; - } - - public void setDefinition(ParseTree definition) { - this.definition = definition; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.kt new file mode 100644 index 000000000..1c43e61ee --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.kt @@ -0,0 +1,9 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.antlr.v4.runtime.misc.Interval +import org.antlr.v4.runtime.tree.ParseTree + +open class BaseInfo(open val definition: ParseTree?) { + var header: String? = null + var headerInterval: Interval? = null +} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.java deleted file mode 100644 index 7e852e182..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -/** - * Created by Bryn on 5/22/2016. - */ -public class CodeDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public CodeDefinitionInfo withName(String value) { - setName(value); - return this; - } - - @Override - public cqlParser.CodeDefinitionContext getDefinition() { - return (cqlParser.CodeDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.CodeDefinitionContext value) { - super.setDefinition(value); - } - - public CodeDefinitionInfo withDefinition(cqlParser.CodeDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.kt new file mode 100644 index 000000000..c4b855540 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.kt @@ -0,0 +1,7 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.CodeDefinitionContext + +/** Created by Bryn on 5/22/2016. */ +class CodeDefinitionInfo(val name: String, override val definition: CodeDefinitionContext) : + BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.java deleted file mode 100644 index 51358f77b..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class CodesystemDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.CodesystemDefinitionContext getDefinition() { - return (cqlParser.CodesystemDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.CodesystemDefinitionContext value) { - super.setDefinition(value); - } - - public CodesystemDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public CodesystemDefinitionInfo withDefinition(cqlParser.CodesystemDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.kt new file mode 100644 index 000000000..0cb086868 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.kt @@ -0,0 +1,8 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.CodesystemDefinitionContext + +class CodesystemDefinitionInfo( + val name: String, + override val definition: CodesystemDefinitionContext +) : BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.java deleted file mode 100644 index d12590a77..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -/** - * Created by Bryn on 5/22/2016. - */ -public class ConceptDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.ConceptDefinitionContext getDefinition() { - return (cqlParser.ConceptDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ConceptDefinitionContext value) { - super.setDefinition(value); - } - - public ConceptDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ConceptDefinitionInfo withDefinition(cqlParser.ConceptDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.kt new file mode 100644 index 000000000..cba194e95 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.kt @@ -0,0 +1,7 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.ConceptDefinitionContext + +/** Created by Bryn on 5/22/2016. */ +class ConceptDefinitionInfo(val name: String, override val definition: ConceptDefinitionContext) : + BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.java deleted file mode 100644 index b77e77cdc..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class ContextDefinitionInfo extends BaseInfo { - private String context; - - public String getContext() { - return context; - } - - public void setContext(String context) { - this.context = context; - } - - @Override - public cqlParser.ContextDefinitionContext getDefinition() { - return (cqlParser.ContextDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ContextDefinitionContext definition) { - super.setDefinition(definition); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.kt new file mode 100644 index 000000000..d8380becd --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.kt @@ -0,0 +1,8 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.ContextDefinitionContext + +class ContextDefinitionInfo(override val definition: ContextDefinitionContext) : + BaseInfo(definition) { + var context: String? = null +} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.java deleted file mode 100644 index e731b74ee..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.java +++ /dev/null @@ -1,327 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import java.util.ArrayList; -import java.util.List; -import org.antlr.v4.runtime.Recognizer; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.cqframework.cql.cql2elm.*; -import org.cqframework.cql.cql2elm.model.Model; -import org.cqframework.cql.gen.cqlLexer; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.cql.model.*; -import org.hl7.elm.r1.*; - -public class CqlPreprocessor extends CqlPreprocessorElmCommonVisitor { - private int lastSourceIndex = -1; - - public CqlPreprocessor(LibraryBuilder libraryBuilder, TokenStream tokenStream) { - super(libraryBuilder, tokenStream); - } - - public LibraryInfo getLibraryInfo() { - return libraryInfo; - } - - private void processHeader(ParseTree ctx, BaseInfo info) { - Interval header = null; - org.antlr.v4.runtime.misc.Interval sourceInterval = ctx.getSourceInterval(); - int beforeDefinition = sourceInterval.a - 1; - if (beforeDefinition >= lastSourceIndex) { - header = new org.antlr.v4.runtime.misc.Interval(lastSourceIndex + 1, sourceInterval.a - 1); - lastSourceIndex = sourceInterval.b; - - info.setHeaderInterval(header); - info.setHeader(tokenStream.getText(header)); - } - } - - @Override - public Object visitLibrary(cqlParser.LibraryContext ctx) { - Object lastResult = null; - // NOTE: Need to set the library identifier here so the builder can begin the translation appropriately - VersionedIdentifier identifier = - new VersionedIdentifier().withId(libraryInfo.getLibraryName()).withVersion(libraryInfo.getVersion()); - if (libraryInfo.getNamespaceName() != null) { - identifier.setSystem(libraryBuilder.resolveNamespaceUri(libraryInfo.getNamespaceName(), true)); - } else if (libraryBuilder.getNamespaceInfo() != null) { - identifier.setSystem(libraryBuilder.getNamespaceInfo().getUri()); - } - libraryBuilder.setLibraryIdentifier(identifier); - libraryBuilder.beginTranslation(); - try { - // Loop through and call visit on each child (to ensure they are tracked) - for (int i = 0; i < ctx.getChildCount(); i++) { - ParseTree tree = ctx.getChild(i); - TerminalNode terminalNode = tree instanceof TerminalNode ? (TerminalNode) tree : null; - if (terminalNode != null && terminalNode.getSymbol().getType() == Recognizer.EOF) { - continue; - } - - Object childResult = visit(tree); - // Only set the last result if we received something useful - if (childResult != null) { - lastResult = childResult; - } - } - - // Return last result (consistent with super implementation and helps w/ testing) - return lastResult; - } finally { - libraryBuilder.endTranslation(); - } - } - - @Override - @SuppressWarnings("unchecked") - public Object visitLibraryDefinition(cqlParser.LibraryDefinitionContext ctx) { - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - libraryInfo.setLibraryName(identifiers.remove(identifiers.size() - 1)); - if (!identifiers.isEmpty()) { - libraryInfo.setNamespaceName(String.join(".", identifiers)); - } - if (ctx.versionSpecifier() != null) { - libraryInfo.setVersion((String) visit(ctx.versionSpecifier())); - } - libraryInfo.setDefinition(ctx); - processHeader(ctx, libraryInfo); - return super.visitLibraryDefinition(ctx); - } - - @Override - @SuppressWarnings("unchecked") - public Object visitIncludeDefinition(cqlParser.IncludeDefinitionContext ctx) { - IncludeDefinitionInfo includeDefinition = new IncludeDefinitionInfo(); - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - includeDefinition.setName(identifiers.remove(identifiers.size() - 1)); - if (!identifiers.isEmpty()) { - includeDefinition.setNamespaceName(String.join(".", identifiers)); - } - if (ctx.versionSpecifier() != null) { - includeDefinition.setVersion((String) visit(ctx.versionSpecifier())); - } - if (ctx.localIdentifier() != null) { - includeDefinition.setLocalName(parseString(ctx.localIdentifier())); - } else { - includeDefinition.setLocalName(includeDefinition.getName()); - } - includeDefinition.setDefinition(ctx); - processHeader(ctx, includeDefinition); - libraryInfo.addIncludeDefinition(includeDefinition); - return includeDefinition; - } - - @Override - @SuppressWarnings("unchecked") - public Object visitUsingDefinition(cqlParser.UsingDefinitionContext ctx) { - UsingDefinitionInfo usingDefinition = new UsingDefinitionInfo(); - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - final String unqualifiedIdentifier = identifiers.remove(identifiers.size() - 1); - usingDefinition.setName(unqualifiedIdentifier); - if (!identifiers.isEmpty()) { - usingDefinition.setNamespaceName(String.join(".", identifiers)); - } - if (ctx.versionSpecifier() != null) { - usingDefinition.setVersion((String) visit(ctx.versionSpecifier())); - } - if (ctx.localIdentifier() != null) { - usingDefinition.setLocalName(parseString(ctx.localIdentifier())); - } else { - usingDefinition.setLocalName(usingDefinition.getName()); - } - usingDefinition.setDefinition(ctx); - processHeader(ctx, usingDefinition); - libraryInfo.addUsingDefinition(usingDefinition); - - final String namespaceName = !identifiers.isEmpty() - ? String.join(".", identifiers) - : libraryBuilder.isWellKnownModelName(unqualifiedIdentifier) - ? null - : (libraryBuilder.getNamespaceInfo() != null - ? libraryBuilder.getNamespaceInfo().getName() - : null); - - NamespaceInfo modelNamespace = null; - if (namespaceName != null) { - String namespaceUri = libraryBuilder.resolveNamespaceUri(namespaceName, true); - modelNamespace = new NamespaceInfo(namespaceName, namespaceUri); - } - - String localIdentifier = - ctx.localIdentifier() == null ? unqualifiedIdentifier : parseString(ctx.localIdentifier()); - if (!localIdentifier.equals(unqualifiedIdentifier)) { - throw new IllegalArgumentException(String.format( - "Local identifiers for models must be the same as the name of the model in this release of the translator (Model %s, Called %s)", - unqualifiedIdentifier, localIdentifier)); - } - - // This should only be called once, from this class, and not from Cql2ElmVisitor otherwise there will be - // duplicate errors sometimes - Model model = - getModel(modelNamespace, unqualifiedIdentifier, parseString(ctx.versionSpecifier()), localIdentifier); - - return usingDefinition; - } - - @Override - public Object visitCodesystemDefinition(cqlParser.CodesystemDefinitionContext ctx) { - CodesystemDefinitionInfo codesystemDefinition = new CodesystemDefinitionInfo(); - codesystemDefinition.setName(parseString(ctx.identifier())); - codesystemDefinition.setDefinition(ctx); - processHeader(ctx, codesystemDefinition); - libraryInfo.addCodesystemDefinition(codesystemDefinition); - return codesystemDefinition; - } - - @Override - public Object visitValuesetDefinition(cqlParser.ValuesetDefinitionContext ctx) { - ValuesetDefinitionInfo valuesetDefinition = new ValuesetDefinitionInfo(); - valuesetDefinition.setName(parseString(ctx.identifier())); - valuesetDefinition.setDefinition(ctx); - processHeader(ctx, valuesetDefinition); - libraryInfo.addValuesetDefinition(valuesetDefinition); - return valuesetDefinition; - } - - @Override - public Object visitCodeDefinition(cqlParser.CodeDefinitionContext ctx) { - CodeDefinitionInfo codeDefinition = new CodeDefinitionInfo(); - codeDefinition.setName(parseString(ctx.identifier())); - codeDefinition.setDefinition(ctx); - processHeader(ctx, codeDefinition); - libraryInfo.addCodeDefinition(codeDefinition); - return codeDefinition; - } - - @Override - public Object visitConceptDefinition(cqlParser.ConceptDefinitionContext ctx) { - ConceptDefinitionInfo conceptDefinition = new ConceptDefinitionInfo(); - conceptDefinition.setName(parseString(ctx.identifier())); - conceptDefinition.setDefinition(ctx); - processHeader(ctx, conceptDefinition); - libraryInfo.addConceptDefinition(conceptDefinition); - return conceptDefinition; - } - - @Override - public Object visitParameterDefinition(cqlParser.ParameterDefinitionContext ctx) { - ParameterDefinitionInfo parameterDefinition = new ParameterDefinitionInfo(); - parameterDefinition.setName(parseString(ctx.identifier())); - parameterDefinition.setDefinition(ctx); - processHeader(ctx, parameterDefinition); - libraryInfo.addParameterDefinition(parameterDefinition); - return parameterDefinition; - } - - @Override - public Object visitContextDefinition(cqlParser.ContextDefinitionContext ctx) { - String modelIdentifier = ctx.modelIdentifier() != null ? parseString(ctx.modelIdentifier()) : null; - String unqualifiedContext = parseString(ctx.identifier()); - if (modelIdentifier != null && !modelIdentifier.isEmpty()) { - setCurrentContext(modelIdentifier + "." + unqualifiedContext); - } else { - setCurrentContext(unqualifiedContext); - } - - ContextDefinitionInfo contextDefinition = new ContextDefinitionInfo(); - contextDefinition.setDefinition(ctx); - processHeader(ctx, contextDefinition); - libraryInfo.addContextDefinition(contextDefinition); - - if (!getImplicitContextCreated() && !unqualifiedContext.equals("Unfiltered")) { - ExpressionDefinitionInfo expressionDefinition = new ExpressionDefinitionInfo(); - expressionDefinition.setName(unqualifiedContext); - expressionDefinition.setContext(getCurrentContext()); - libraryInfo.addExpressionDefinition(expressionDefinition); - setImplicitContextCreated(true); - } - return getCurrentContext(); - } - - @Override - public Object visitExpressionDefinition(cqlParser.ExpressionDefinitionContext ctx) { - ExpressionDefinitionInfo expressionDefinition = new ExpressionDefinitionInfo(); - expressionDefinition.setName(parseString(ctx.identifier())); - expressionDefinition.setContext(getCurrentContext()); - expressionDefinition.setDefinition(ctx); - processHeader(ctx, expressionDefinition); - libraryInfo.addExpressionDefinition(expressionDefinition); - return expressionDefinition; - } - - @Override - public Object visitFunctionDefinition(cqlParser.FunctionDefinitionContext ctx) { - FunctionDefinitionInfo functionDefinition = new FunctionDefinitionInfo(); - functionDefinition.setName(parseString(ctx.identifierOrFunctionIdentifier())); - functionDefinition.setContext(getCurrentContext()); - functionDefinition.setDefinition(ctx); - processHeader(ctx, functionDefinition); - libraryInfo.addFunctionDefinition(functionDefinition); - return functionDefinition; - } - - @Override - public NamedTypeSpecifier visitNamedTypeSpecifier(cqlParser.NamedTypeSpecifierContext ctx) { - List qualifiers = parseQualifiers(ctx); - String modelIdentifier = getModelIdentifier(qualifiers); - String identifier = getTypeIdentifier(qualifiers, parseString(ctx.referentialOrTypeNameIdentifier())); - final String typeSpecifierKey = String.format("%s:%s", modelIdentifier, identifier); - - DataType resultType = libraryBuilder.resolveTypeName(modelIdentifier, identifier); - if (null == resultType) { - libraryBuilder.addNamedTypeSpecifierResult(typeSpecifierKey, ResultWithPossibleError.withError()); - throw new CqlCompilerException( - String.format("Could not find type for model: %s and name: %s", modelIdentifier, identifier)); - } - NamedTypeSpecifier result = of.createNamedTypeSpecifier().withName(libraryBuilder.dataTypeToQName(resultType)); - - // Fluent API would be nice here, but resultType isn't part of the model so... - result.setResultType(resultType); - - libraryBuilder.addNamedTypeSpecifierResult(typeSpecifierKey, ResultWithPossibleError.withTypeSpecifier(result)); - - return result; - } - - @Override - public Object visitTerminal(TerminalNode node) { - String text = node.getText(); - int tokenType = node.getSymbol().getType(); - if (cqlLexer.STRING == tokenType || cqlLexer.QUOTEDIDENTIFIER == tokenType) { - // chop off leading and trailing ' or " - text = text.substring(1, text.length() - 1); - } - - return text; - } - - @Override - public Object visitQualifiedIdentifier(cqlParser.QualifiedIdentifierContext ctx) { - // Return the list of qualified identifiers for resolution by the containing element - List identifiers = new ArrayList<>(); - for (cqlParser.QualifierContext qualifierContext : ctx.qualifier()) { - String qualifier = (String) visit(qualifierContext); - identifiers.add(qualifier); - } - - String identifier = parseString(ctx.identifier()); - identifiers.add(identifier); - return identifiers; - } - - @Override - public Object visitQualifiedIdentifierExpression(cqlParser.QualifiedIdentifierExpressionContext ctx) { - // Return the list of qualified identifiers for resolution by the containing element - List identifiers = new ArrayList<>(); - for (cqlParser.QualifierExpressionContext qualifierContext : ctx.qualifierExpression()) { - String qualifier = (String) visit(qualifierContext); - identifiers.add(qualifier); - } - - String identifier = parseString(ctx.referentialIdentifier()); - identifiers.add(identifier); - return identifiers; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.kt new file mode 100644 index 000000000..ca1364175 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.kt @@ -0,0 +1,299 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm.preprocessor + +import java.util.* +import kotlin.collections.ArrayList +import org.antlr.v4.runtime.Recognizer +import org.antlr.v4.runtime.TokenStream +import org.antlr.v4.runtime.misc.Interval +import org.antlr.v4.runtime.tree.ParseTree +import org.antlr.v4.runtime.tree.TerminalNode +import org.cqframework.cql.cql2elm.CqlCompilerException +import org.cqframework.cql.cql2elm.LibraryBuilder +import org.cqframework.cql.cql2elm.ResultWithPossibleError +import org.cqframework.cql.gen.cqlLexer +import org.cqframework.cql.gen.cqlParser.* +import org.hl7.cql.model.NamespaceInfo +import org.hl7.elm.r1.* + +@Suppress("TooManyFunctions") +class CqlPreprocessor(libraryBuilder: LibraryBuilder, tokenStream: TokenStream) : + CqlPreprocessorElmCommonVisitor(libraryBuilder, tokenStream) { + private var lastSourceIndex = -1 + + private fun processHeader(ctx: ParseTree, info: BaseInfo) { + val sourceInterval = ctx.sourceInterval + val beforeDefinition = sourceInterval.a - 1 + if (beforeDefinition >= lastSourceIndex) { + val header = Interval(lastSourceIndex + 1, sourceInterval.a - 1) + lastSourceIndex = sourceInterval.b + info.headerInterval = header + info.header = tokenStream.getText(header) + } + } + + override fun visitLibrary(ctx: LibraryContext): Any? { + var lastResult: Any? = null + // NOTE: Need to set the library identifier here so the builder can begin the translation + // appropriately + val identifier = + VersionedIdentifier().withId(libraryInfo.libraryName).withVersion(libraryInfo.version) + if (libraryInfo.namespaceName != null) { + identifier.system = libraryBuilder.resolveNamespaceUri(libraryInfo.namespaceName, true) + } else if (libraryBuilder.namespaceInfo != null) { + identifier.system = libraryBuilder.namespaceInfo.uri + } + libraryBuilder.libraryIdentifier = identifier + libraryBuilder.beginTranslation() + return try { + // Loop through and call visit on each child (to ensure they are tracked) + for (i in 0 until ctx.childCount) { + val tree = ctx.getChild(i) + val terminalNode = tree as? TerminalNode + if (terminalNode != null && terminalNode.symbol.type == Recognizer.EOF) { + continue + } + val childResult = visit(tree) + // Only set the last result if we received something useful + if (childResult != null) { + lastResult = childResult + } + } + + // Return last result (consistent with super implementation and helps w/ testing) + lastResult + } finally { + libraryBuilder.endTranslation() + } + } + + override fun visitLibraryDefinition(ctx: LibraryDefinitionContext): Any? { + val identifiers = visit(ctx.qualifiedIdentifier()) as MutableList + val libraryName = identifiers.removeAt(identifiers.size - 1) + val namespaceName = if (identifiers.isNotEmpty()) identifiers.joinToString(".") else null + val version = + if (ctx.versionSpecifier() != null) visit(ctx.versionSpecifier()) as String else null + val newLibraryInfo = LibraryInfo(namespaceName, libraryName, version, ctx) + libraryInfo = newLibraryInfo + processHeader(ctx, newLibraryInfo) + return super.visitLibraryDefinition(ctx) + } + + override fun visitIncludeDefinition(ctx: IncludeDefinitionContext): Any { + val identifiers = visit(ctx.qualifiedIdentifier()) as MutableList + val name = identifiers.removeAt(identifiers.size - 1) + val namespaceName = if (identifiers.isNotEmpty()) identifiers.joinToString(".") else null + val version = + if (ctx.versionSpecifier() != null) visit(ctx.versionSpecifier()) as String else null + val localName = + if (ctx.localIdentifier() != null) parseString(ctx.localIdentifier())!! else name + val includeDefinition = IncludeDefinitionInfo(namespaceName, name, version, localName, ctx) + processHeader(ctx, includeDefinition) + libraryInfo.addIncludeDefinition(includeDefinition) + return includeDefinition + } + + override fun visitUsingDefinition(ctx: UsingDefinitionContext): Any { + val identifiers = visit(ctx.qualifiedIdentifier()) as MutableList + val unqualifiedIdentifier = identifiers.removeAt(identifiers.size - 1) + val namespaceNameForUsingDefinition = + if (identifiers.isNotEmpty()) identifiers.joinToString(".") else null + val version = + if (ctx.versionSpecifier() != null) visit(ctx.versionSpecifier()) as String else null + val localName = + if (ctx.localIdentifier() != null) parseString(ctx.localIdentifier())!! + else unqualifiedIdentifier + val usingDefinition = + UsingDefinitionInfo( + namespaceNameForUsingDefinition, + unqualifiedIdentifier, + version, + localName, + ctx + ) + processHeader(ctx, usingDefinition) + libraryInfo.addUsingDefinition(usingDefinition) + val namespaceName = + when { + identifiers.isNotEmpty() -> identifiers.joinToString(".") + libraryBuilder.isWellKnownModelName(unqualifiedIdentifier) -> null + libraryBuilder.namespaceInfo != null -> libraryBuilder.namespaceInfo.name + else -> null + } + var modelNamespace: NamespaceInfo? = null + if (namespaceName != null) { + val namespaceUri = libraryBuilder.resolveNamespaceUri(namespaceName, true) + modelNamespace = NamespaceInfo(namespaceName, namespaceUri) + } + val localIdentifier = + if (ctx.localIdentifier() == null) unqualifiedIdentifier + else parseString(ctx.localIdentifier())!! + require(localIdentifier == unqualifiedIdentifier) { + String.format( + Locale.US, + @Suppress("MaxLineLength") + "Local identifiers for models must be the same as the name of the model in this release of the translator (Model %s, Called %s)", + unqualifiedIdentifier, + localIdentifier + ) + } + + // This has the side effect of initializing + // the model in the ModelManager + getModel( + modelNamespace, + unqualifiedIdentifier, + parseString(ctx.versionSpecifier()), + localIdentifier + ) + + return usingDefinition + } + + override fun visitCodesystemDefinition(ctx: CodesystemDefinitionContext): Any { + val codesystemDefinition = CodesystemDefinitionInfo(parseString(ctx.identifier())!!, ctx) + processHeader(ctx, codesystemDefinition) + libraryInfo.addCodesystemDefinition(codesystemDefinition) + return codesystemDefinition + } + + override fun visitValuesetDefinition(ctx: ValuesetDefinitionContext): Any { + val valuesetDefinition = ValuesetDefinitionInfo(parseString(ctx.identifier())!!, ctx) + processHeader(ctx, valuesetDefinition) + libraryInfo.addValuesetDefinition(valuesetDefinition) + return valuesetDefinition + } + + override fun visitCodeDefinition(ctx: CodeDefinitionContext): Any { + val codeDefinition = CodeDefinitionInfo(parseString(ctx.identifier())!!, ctx) + processHeader(ctx, codeDefinition) + libraryInfo.addCodeDefinition(codeDefinition) + return codeDefinition + } + + override fun visitConceptDefinition(ctx: ConceptDefinitionContext): Any { + val conceptDefinition = ConceptDefinitionInfo(parseString(ctx.identifier())!!, ctx) + processHeader(ctx, conceptDefinition) + libraryInfo.addConceptDefinition(conceptDefinition) + return conceptDefinition + } + + override fun visitParameterDefinition(ctx: ParameterDefinitionContext): Any { + val parameterDefinition = ParameterDefinitionInfo(parseString(ctx.identifier())!!, ctx) + processHeader(ctx, parameterDefinition) + libraryInfo.addParameterDefinition(parameterDefinition) + return parameterDefinition + } + + override fun visitContextDefinition(ctx: ContextDefinitionContext): Any { + val modelIdentifier = + if (ctx.modelIdentifier() != null) parseString(ctx.modelIdentifier()) else null + val unqualifiedContext = parseString(ctx.identifier())!! + currentContext = + if (!modelIdentifier.isNullOrEmpty()) { + "$modelIdentifier.$unqualifiedContext" + } else { + unqualifiedContext + } + val contextDefinition = ContextDefinitionInfo(ctx) + processHeader(ctx, contextDefinition) + libraryInfo.addContextDefinition(contextDefinition) + if (!implicitContextCreated && unqualifiedContext != "Unfiltered") { + val expressionDefinition = + ExpressionDefinitionInfo(unqualifiedContext, currentContext, null) + libraryInfo.addExpressionDefinition(expressionDefinition) + implicitContextCreated = true + } + return currentContext + } + + override fun visitExpressionDefinition(ctx: ExpressionDefinitionContext): Any { + val expressionDefinition = + ExpressionDefinitionInfo(parseString(ctx.identifier())!!, currentContext, ctx) + processHeader(ctx, expressionDefinition) + libraryInfo.addExpressionDefinition(expressionDefinition) + return expressionDefinition + } + + override fun visitFunctionDefinition(ctx: FunctionDefinitionContext): Any { + val functionDefinition = + FunctionDefinitionInfo( + parseString(ctx.identifierOrFunctionIdentifier())!!, + currentContext, + ctx + ) + processHeader(ctx, functionDefinition) + libraryInfo.addFunctionDefinition(functionDefinition) + return functionDefinition + } + + override fun visitNamedTypeSpecifier(ctx: NamedTypeSpecifierContext): NamedTypeSpecifier { + val qualifiers = parseQualifiers(ctx) + val modelIdentifier = getModelIdentifier(qualifiers) + val identifier = + getTypeIdentifier(qualifiers, parseString(ctx.referentialOrTypeNameIdentifier())!!) + val typeSpecifierKey = String.format(Locale.US, "%s:%s", modelIdentifier, identifier) + val resultType = libraryBuilder.resolveTypeName(modelIdentifier, identifier) + if (null == resultType) { + libraryBuilder.addNamedTypeSpecifierResult( + typeSpecifierKey, + ResultWithPossibleError.withError() + ) + throw CqlCompilerException( + String.format( + Locale.US, + "Could not find type for model: %s and name: %s", + modelIdentifier, + identifier + ) + ) + } + val result = + of.createNamedTypeSpecifier().withName(libraryBuilder.dataTypeToQName(resultType)) + + // Fluent API would be nice here, but resultType isn't part of the model so... + result.resultType = resultType + libraryBuilder.addNamedTypeSpecifierResult( + typeSpecifierKey, + ResultWithPossibleError.withTypeSpecifier(result) + ) + return result + } + + override fun visitTerminal(node: TerminalNode): Any { + var text = node.text + val tokenType = node.symbol.type + if (cqlLexer.STRING == tokenType || cqlLexer.QUOTEDIDENTIFIER == tokenType) { + // chop off leading and trailing ' or " + text = text.substring(1, text.length - 1) + } + return text + } + + override fun visitQualifiedIdentifier(ctx: QualifiedIdentifierContext): Any { + // Return the list of qualified identifiers for resolution by the containing element + val identifiers = ArrayList() + for (qualifierContext in ctx.qualifier()) { + val qualifier = visit(qualifierContext) as String + identifiers.add(qualifier) + } + val identifier = parseString(ctx.identifier())!! + identifiers.add(identifier) + return identifiers + } + + override fun visitQualifiedIdentifierExpression( + ctx: QualifiedIdentifierExpressionContext + ): Any { + // Return the list of qualified identifiers for resolution by the containing element + val identifiers = ArrayList() + for (qualifierContext in ctx.qualifierExpression()) { + val qualifier = visit(qualifierContext) as String + identifiers.add(qualifier) + } + val identifier = parseString(ctx.referentialIdentifier())!! + identifiers.add(identifier) + return identifiers + } +} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java deleted file mode 100644 index a65b62035..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java +++ /dev/null @@ -1,911 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import jakarta.xml.bind.JAXBElement; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Stack; -import javax.xml.namespace.QName; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.apache.commons.lang3.tuple.Pair; -import org.cqframework.cql.cql2elm.*; -import org.cqframework.cql.cql2elm.model.Chunk; -import org.cqframework.cql.cql2elm.model.FunctionHeader; -import org.cqframework.cql.cql2elm.model.Model; -import org.cqframework.cql.elm.IdObjectFactory; -import org.cqframework.cql.elm.tracking.TrackBack; -import org.cqframework.cql.elm.tracking.Trackable; -import org.cqframework.cql.gen.cqlBaseVisitor; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.cql.model.*; -import org.hl7.cql_annotations.r1.Annotation; -import org.hl7.cql_annotations.r1.Narrative; -import org.hl7.cql_annotations.r1.Tag; -import org.hl7.elm.r1.*; - -/** - * Common functionality used by {@link CqlPreprocessor} and {@link Cql2ElmVisitor} - */ -public class CqlPreprocessorElmCommonVisitor extends cqlBaseVisitor { - protected final IdObjectFactory of; - protected final org.hl7.cql_annotations.r1.ObjectFactory af = new org.hl7.cql_annotations.r1.ObjectFactory(); - private boolean implicitContextCreated = false; - private String currentContext = "Unfiltered"; - protected Stack chunks = new Stack<>(); - protected final LibraryBuilder libraryBuilder; - protected final TokenStream tokenStream; - protected LibraryInfo libraryInfo = new LibraryInfo(); - private boolean annotate = false; - private boolean detailedErrors = false; - private boolean locate = false; - private boolean resultTypes = false; - private boolean dateRangeOptimization = false; - private boolean methodInvocation = true; - private boolean fromKeywordRequired = false; - - private boolean includeDeprecatedElements = false; - - public CqlPreprocessorElmCommonVisitor(LibraryBuilder libraryBuilder, TokenStream tokenStream) { - this.libraryBuilder = Objects.requireNonNull(libraryBuilder, "libraryBuilder required"); - this.tokenStream = Objects.requireNonNull(tokenStream, "tokenStream required"); - this.of = Objects.requireNonNull(libraryBuilder.getObjectFactory(), "libraryBuilder.objectFactory required"); - - // Don't talk to strangers. Except when you have to. - this.setCompilerOptions(libraryBuilder.getLibraryManager().getCqlCompilerOptions()); - } - - protected boolean getImplicitContextCreated() { - return this.implicitContextCreated; - } - - protected void setImplicitContextCreated(boolean implicitContextCreated) { - this.implicitContextCreated = implicitContextCreated; - } - - protected String getCurrentContext() { - return this.currentContext; - } - - protected void setCurrentContext(String currentContext) { - this.currentContext = currentContext; - } - - protected String saveCurrentContext(String currentContext) { - String saveContext = this.currentContext; - this.currentContext = currentContext; - return saveContext; - } - - @Override - public Object visit(ParseTree tree) { - Objects.requireNonNull(tree, "ParseTree required"); - boolean pushedChunk = pushChunk(tree); - Object o = null; - try { - // ERROR: - try { - o = super.visit(tree); - if (o instanceof Element) { - Element element = (Element) o; - if (element.getLocalId() == null) { - throw new CqlInternalException( - String.format( - "Internal translator error. 'localId' was not assigned for Element \"%s\"", - element.getClass().getName()), - getTrackBack(tree)); - } - } - } catch (CqlIncludeException e) { - CqlCompilerException translatorException = - new CqlCompilerException(e.getMessage(), getTrackBack(tree), e); - if (translatorException.getLocator() == null) { - throw translatorException; - } - libraryBuilder.recordParsingException(translatorException); - } catch (CqlCompilerException e) { - libraryBuilder.recordParsingException(e); - } catch (Exception e) { - CqlCompilerException ex = null; - if (e.getMessage() == null) { - ex = new CqlInternalException("Internal translator error.", getTrackBack(tree), e); - } else { - ex = new CqlSemanticException(e.getMessage(), getTrackBack(tree), e); - } - - Exception rootCause = libraryBuilder.determineRootCause(); - if (rootCause == null) { - rootCause = ex; - libraryBuilder.recordParsingException(ex); - libraryBuilder.setRootCause(rootCause); - } else { - if (detailedErrors) { - libraryBuilder.recordParsingException(ex); - } - } - o = of.createNull(); - } - - if (o instanceof Trackable && !(tree instanceof cqlParser.LibraryContext)) { - this.track((Trackable) o, tree); - } - - return o; - } finally { - popChunk(tree, o, pushedChunk); - processTags(tree, o); - } - } - - @Override - public TupleElementDefinition visitTupleElementDefinition(cqlParser.TupleElementDefinitionContext ctx) { - TupleElementDefinition result = of.createTupleElementDefinition() - .withName(parseString(ctx.referentialIdentifier())) - .withElementType(parseTypeSpecifier(ctx.typeSpecifier())); - - if (includeDeprecatedElements) { - result.setType(result.getElementType()); - } - - return result; - } - - @Override - public Object visitTupleTypeSpecifier(cqlParser.TupleTypeSpecifierContext ctx) { - TupleType resultType = new TupleType(); - TupleTypeSpecifier typeSpecifier = of.createTupleTypeSpecifier(); - for (cqlParser.TupleElementDefinitionContext definitionContext : ctx.tupleElementDefinition()) { - TupleElementDefinition element = (TupleElementDefinition) visit(definitionContext); - resultType.addElement(new TupleTypeElement( - element.getName(), element.getElementType().getResultType())); - typeSpecifier.getElement().add(element); - } - - typeSpecifier.setResultType(resultType); - - return typeSpecifier; - } - - @Override - public ChoiceTypeSpecifier visitChoiceTypeSpecifier(cqlParser.ChoiceTypeSpecifierContext ctx) { - var typeSpecifiers = new ArrayList(); - var types = new ArrayList(); - for (cqlParser.TypeSpecifierContext typeSpecifierContext : ctx.typeSpecifier()) { - TypeSpecifier typeSpecifier = parseTypeSpecifier(typeSpecifierContext); - typeSpecifiers.add(typeSpecifier); - types.add(typeSpecifier.getResultType()); - } - ChoiceTypeSpecifier result = of.createChoiceTypeSpecifier().withChoice(typeSpecifiers); - if (includeDeprecatedElements) { - result.getType().addAll(typeSpecifiers); - } - ChoiceType choiceType = new ChoiceType(types); - result.setResultType(choiceType); - return result; - } - - @Override - public IntervalTypeSpecifier visitIntervalTypeSpecifier(cqlParser.IntervalTypeSpecifierContext ctx) { - IntervalTypeSpecifier result = - of.createIntervalTypeSpecifier().withPointType(parseTypeSpecifier(ctx.typeSpecifier())); - IntervalType intervalType = new IntervalType(result.getPointType().getResultType()); - result.setResultType(intervalType); - return result; - } - - @Override - public ListTypeSpecifier visitListTypeSpecifier(cqlParser.ListTypeSpecifierContext ctx) { - ListTypeSpecifier result = - of.createListTypeSpecifier().withElementType(parseTypeSpecifier(ctx.typeSpecifier())); - ListType listType = new ListType(result.getElementType().getResultType()); - result.setResultType(listType); - return result; - } - - public FunctionHeader parseFunctionHeader(cqlParser.FunctionDefinitionContext ctx) { - final FunctionDef fun = of.createFunctionDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(parseString(ctx.identifierOrFunctionIdentifier())) - .withContext(getCurrentContext()); - - if (ctx.fluentModifier() != null) { - libraryBuilder.checkCompatibilityLevel("Fluent functions", "1.5"); - fun.setFluent(true); - } - - if (ctx.operandDefinition() != null) { - for (cqlParser.OperandDefinitionContext opdef : ctx.operandDefinition()) { - TypeSpecifier typeSpecifier = parseTypeSpecifier(opdef.typeSpecifier()); - fun.getOperand().add((OperandDef) of.createOperandDef() - .withName(parseString(opdef.referentialIdentifier())) - .withOperandTypeSpecifier(typeSpecifier) - .withResultType(typeSpecifier.getResultType())); - } - } - - final cqlParser.TypeSpecifierContext typeSpecifierContext = ctx.typeSpecifier(); - - if (typeSpecifierContext != null) { - return FunctionHeader.withReturnType(fun, parseTypeSpecifier(typeSpecifierContext)); - } - - return FunctionHeader.noReturnType(fun); - } - - protected TypeSpecifier parseTypeSpecifier(ParseTree pt) { - return pt == null ? null : (TypeSpecifier) visit(pt); - } - - protected AccessModifier parseAccessModifier(ParseTree pt) { - return pt == null ? AccessModifier.PUBLIC : (AccessModifier) visit(pt); - } - - protected List parseQualifiers(cqlParser.NamedTypeSpecifierContext ctx) { - List qualifiers = new ArrayList<>(); - if (ctx.qualifier() != null) { - for (cqlParser.QualifierContext qualifierContext : ctx.qualifier()) { - String qualifier = parseString(qualifierContext); - qualifiers.add(qualifier); - } - } - return qualifiers; - } - - protected Model getModel(NamespaceInfo modelNamespace, String modelName, String version, String localIdentifier) { - if (modelName == null) { - var defaultUsing = libraryInfo.getDefaultUsingDefinition(); - modelName = defaultUsing.getName(); - version = defaultUsing.getVersion(); - } - - var modelIdentifier = new ModelIdentifier().withId(modelName).withVersion(version); - if (modelNamespace != null) { - modelIdentifier.setSystem(modelNamespace.getUri()); - } - return libraryBuilder.getModel(modelIdentifier, localIdentifier); - } - - private boolean pushChunk(ParseTree tree) { - if (!isAnnotationEnabled()) { - return false; - } - - org.antlr.v4.runtime.misc.Interval sourceInterval = tree.getSourceInterval(); - - // An interval of i..i-1 indicates an empty interval at position i in the input stream, - if (sourceInterval.b < sourceInterval.a) { - return false; - } - - Chunk chunk = new Chunk().withInterval(sourceInterval); - chunks.push(chunk); - return true; - } - - private void popChunk(ParseTree tree, Object o, boolean pushedChunk) { - if (!pushedChunk) { - return; - } - - Chunk chunk = chunks.pop(); - if (o instanceof Element) { - Element element = (Element) o; - chunk.setElement(element); - - if (!(tree instanceof cqlParser.LibraryContext)) { - if (element instanceof UsingDef - || element instanceof IncludeDef - || element instanceof CodeSystemDef - || element instanceof ValueSetDef - || element instanceof CodeDef - || element instanceof ConceptDef - || element instanceof ParameterDef - || element instanceof ContextDef - || element instanceof ExpressionDef) { - Annotation a = getAnnotation(element); - if (a == null || a.getS() == null) { - // Add header information (comments prior to the definition) - BaseInfo definitionInfo = libraryInfo.resolveDefinition(tree); - if (definitionInfo != null && definitionInfo.getHeaderInterval() != null) { - Chunk headerChunk = new Chunk() - .withInterval(definitionInfo.getHeaderInterval()) - .withIsHeaderChunk(true); - Chunk newChunk = new Chunk() - .withInterval(new org.antlr.v4.runtime.misc.Interval( - headerChunk.getInterval().a, chunk.getInterval().b)); - newChunk.addChunk(headerChunk); - newChunk.setElement(chunk.getElement()); - for (Chunk c : chunk.getChunks()) { - newChunk.addChunk(c); - } - chunk = newChunk; - } - if (a == null) { - element.getAnnotation().add(buildAnnotation(chunk)); - } else { - addNarrativeToAnnotation(a, chunk); - } - } - } - } else { - if (libraryInfo.getDefinition() != null && libraryInfo.getHeaderInterval() != null) { - Chunk headerChunk = new Chunk() - .withInterval(libraryInfo.getHeaderInterval()) - .withIsHeaderChunk(true); - Chunk definitionChunk = - new Chunk().withInterval(libraryInfo.getDefinition().getSourceInterval()); - Chunk newChunk = new Chunk() - .withInterval(new org.antlr.v4.runtime.misc.Interval( - headerChunk.getInterval().a, definitionChunk.getInterval().b)); - newChunk.addChunk(headerChunk); - newChunk.addChunk(definitionChunk); - newChunk.setElement(chunk.getElement()); - chunk = newChunk; - Annotation a = getAnnotation(libraryBuilder.getLibrary()); - if (a == null) { - libraryBuilder.getLibrary().getAnnotation().add(buildAnnotation(chunk)); - } else { - addNarrativeToAnnotation(a, chunk); - } - } - } - } - - if (!chunks.isEmpty()) { - chunks.peek().addChunk(chunk); - } - } - - private void processTags(ParseTree tree, Object o) { - if (!libraryBuilder.isCompatibleWith("1.5")) { - return; - } - - if (!(o instanceof Element)) { - return; - } - - Element element = (Element) o; - if (!(tree instanceof cqlParser.LibraryContext)) { - if (element instanceof UsingDef - || element instanceof IncludeDef - || element instanceof CodeSystemDef - || element instanceof ValueSetDef - || element instanceof CodeDef - || element instanceof ConceptDef - || element instanceof ParameterDef - || element instanceof ContextDef - || element instanceof ExpressionDef) { - var tags = getTags(tree); - if (!tags.isEmpty()) { - Annotation a = getAnnotation(element); - if (a == null) { - a = buildAnnotation(); - element.getAnnotation().add(a); - } - // If the definition was processed as a forward declaration, the tag processing will already - // have occurred - // and just adding tags would duplicate them here. This doesn't account for the possibility - // that - // tags would be added for some other reason, but I didn't want the overhead of checking for - // existing - // tags, and there is currently nothing that would add tags other than being processed from - // comments - if (a.getT().isEmpty()) { - a.getT().addAll(tags); - } - } - } - } else { - if (libraryInfo.getDefinition() != null && libraryInfo.getHeaderInterval() != null) { - var tags = getTags(libraryInfo.getHeader()); - if (!tags.isEmpty()) { - Annotation a = getAnnotation(libraryBuilder.getLibrary()); - if (a == null) { - a = buildAnnotation(); - libraryBuilder.getLibrary().getAnnotation().add(a); - } - a.getT().addAll(tags); - } - } - } - } - - private List getTags(String header) { - if (header != null) { - header = parseComments(header); - return parseTags(header); - } - - return Collections.emptyList(); - } - - private List getTags(ParseTree tree) { - BaseInfo bi = libraryInfo.resolveDefinition(tree); - if (bi != null) { - return getTags(bi.getHeader()); - } - - return Collections.emptyList(); - } - - private List parseTags(String header) { - header = String.join("\n", Arrays.asList(header.trim().split("\n[ \t]*\\*[ \t\\*]*"))); - var tags = new ArrayList(); - - int startFrom = 0; - while (startFrom < header.length()) { - Pair tagNamePair = lookForTagName(header, startFrom); - if (tagNamePair != null) { - if (tagNamePair.getLeft().length() > 0 && isValidIdentifier(tagNamePair.getLeft())) { - Tag t = af.createTag().withName(tagNamePair.getLeft()); - startFrom = tagNamePair.getRight(); - Pair tagValuePair = lookForTagValue(header, startFrom); - if (tagValuePair != null) { - if (tagValuePair.getLeft().length() > 0) { - t = t.withValue(tagValuePair.getLeft()); - startFrom = tagValuePair.getRight(); - } - } - tags.add(t); - } else { - startFrom = tagNamePair.getRight(); - } - } else { // no name tag found, no need to traverse more - break; - } - } - return tags; - } - - private String parseComments(String header) { - List result = new ArrayList<>(); - if (header != null) { - header = header.replace("\r\n", "\n"); - String[] lines = header.split("\n"); - boolean inMultiline = false; - for (String line : lines) { - if (!inMultiline) { - int start = line.indexOf("/*"); - if (start >= 0) { - if (line.endsWith("*/")) { - result.add(line.substring(start + 2, line.length() - 2)); - } else { - result.add(line.substring(start + 2)); - } - inMultiline = true; - } else start = line.indexOf("//"); - if (start >= 0 && !inMultiline) { - result.add(line.substring(start + 2)); - } - } else { - int end = line.indexOf("*/"); - if (end >= 0) { - inMultiline = false; - if (end > 0) { - result.add(line.substring(0, end)); - } - } else { - result.add(line); - } - } - } - } - return String.join("\n", result); - } - - public boolean isAnnotationEnabled() { - return annotate; - } - - public void enableAnnotations() { - annotate = true; - } - - public void disableAnnotations() { - annotate = false; - } - - private Annotation buildAnnotation(Chunk chunk) { - Annotation annotation = af.createAnnotation(); - annotation.setS(buildNarrative(chunk)); - return annotation; - } - - private Annotation buildAnnotation() { - return af.createAnnotation(); - } - - private void addNarrativeToAnnotation(Annotation annotation, Chunk chunk) { - annotation.setS(buildNarrative(chunk)); - } - - private Narrative buildNarrative(Chunk chunk) { - Narrative narrative = af.createNarrative(); - if (chunk.getElement() != null) { - narrative.setR(chunk.getElement().getLocalId()); - } - - if (chunk.hasChunks()) { - Narrative currentNarrative = null; - for (Chunk childChunk : chunk.getChunks()) { - Narrative chunkNarrative = buildNarrative(childChunk); - if (hasChunks(chunkNarrative)) { - if (currentNarrative != null) { - narrative.getContent().add(wrapNarrative(currentNarrative)); - currentNarrative = null; - } - narrative.getContent().add(wrapNarrative(chunkNarrative)); - } else { - if (currentNarrative == null) { - currentNarrative = chunkNarrative; - } else { - currentNarrative.getContent().addAll(chunkNarrative.getContent()); - if (currentNarrative.getR() == null) { - currentNarrative.setR(chunkNarrative.getR()); - } - } - } - } - if (currentNarrative != null) { - narrative.getContent().add(wrapNarrative(currentNarrative)); - } - } else { - String chunkContent = tokenStream.getText(chunk.getInterval()); - if (chunk.isHeaderChunk()) { - chunkContent = stripLeading(chunkContent); - } - chunkContent = normalizeWhitespace(chunkContent); - narrative.getContent().add(chunkContent); - } - - return narrative; - } - - private boolean hasChunks(Narrative narrative) { - for (Serializable c : narrative.getContent()) { - if (!(c instanceof String)) { - return true; - } - } - return false; - } - - private TrackBack getTrackBack(ParseTree tree) { - if (tree instanceof ParserRuleContext) { - return getTrackBack((ParserRuleContext) tree); - } - if (tree instanceof TerminalNode) { - return getTrackBack((TerminalNode) tree); - } - return null; - } - - private TrackBack getTrackBack(ParserRuleContext ctx) { - return new TrackBack( - libraryBuilder.getLibraryIdentifier(), - ctx.getStart().getLine(), - ctx.getStart().getCharPositionInLine() + 1, // 1-based instead of 0-based - ctx.getStop().getLine(), - ctx.getStop().getCharPositionInLine() + ctx.getStop().getText().length() // 1-based instead of 0-based - ); - } - - private TrackBack track(Trackable trackable, ParseTree pt) { - TrackBack tb = getTrackBack(pt); - - if (tb != null) { - trackable.getTrackbacks().add(tb); - } - - if (trackable instanceof Element) { - decorate((Element) trackable, tb); - } - - return tb; - } - - private void decorate(Element element, TrackBack tb) { - if (locate && tb != null) { - element.setLocator(tb.toLocator()); - } - - if (resultTypes && element.getResultType() != null) { - if (element.getResultType() instanceof NamedType) { - element.setResultTypeName(libraryBuilder.dataTypeToQName(element.getResultType())); - } else { - element.setResultTypeSpecifier(libraryBuilder.dataTypeToTypeSpecifier(element.getResultType())); - } - } - } - - private Pair lookForTagName(String header, int startFrom) { - - if (startFrom >= header.length()) { - return null; - } - int start = header.indexOf("@", startFrom); - if (start < 0) { - return null; - } - int nextTagStart = header.indexOf("@", start + 1); - int nextColon = header.indexOf(":", start + 1); - - if (nextTagStart < 0) { // no next tag , no next colon - if (nextColon < 0) { - return Pair.of(header.substring(start + 1, header.length()).trim(), header.length()); - } - } else { - if (nextColon < 0 - || nextColon - > nextTagStart) { // (has next tag and no colon) or (has next tag and next colon belongs to - // next tag) - return Pair.of(header.substring(start + 1, nextTagStart).trim(), nextTagStart); - } - } - return Pair.of(header.substring(start + 1, nextColon).trim(), nextColon + 1); - } - - // this method returns Pair starting from startFrom - // can return null in cases. - // for @1980-12-01, it will potentially check to be treated as value date - // it looks for parameter in double quotes, e.g. @parameter: "Measurement Interval" [@2019,@2020] - public static Pair lookForTagValue(String header, int startFrom) { - - if (startFrom >= header.length()) { - return null; - } - int nextTag = header.indexOf('@', startFrom); - int nextStartDoubleQuote = header.indexOf("\"", startFrom); - if ((nextTag < 0 || nextTag > nextStartDoubleQuote) - && nextStartDoubleQuote > 0 - && (header.length() > (nextStartDoubleQuote + 1))) { - int nextEndDoubleQuote = header.indexOf("\"", nextStartDoubleQuote + 1); - if (nextEndDoubleQuote > 0) { - int parameterEnd = header.indexOf("\n", (nextStartDoubleQuote + 1)); - if (parameterEnd < 0) { - return Pair.of(header.substring(nextStartDoubleQuote), header.length()); - } else { - return Pair.of(header.substring(nextStartDoubleQuote, parameterEnd), parameterEnd); - } - } else { // branch where the 2nd double quote is missing - return Pair.of(header.substring(nextStartDoubleQuote), header.length()); - } - } - if (nextTag == startFrom - && !isStartingWithDigit(header, nextTag + 1)) { // starts with `@` and not potential date value - return Pair.of("", startFrom); - } else if (nextTag > 0) { // has some text before tag - String interimText = header.substring(startFrom, nextTag).trim(); - if (isStartingWithDigit(header, nextTag + 1)) { // next `@` is a date value - if (!interimText.isEmpty() - && !interimText.equals(":")) { // interim text has value, regards interim text - return Pair.of(interimText, nextTag); - } else { - int nextSpace = header.indexOf(' ', nextTag); - int nextLine = header.indexOf("\n", nextTag); - int mul = nextSpace * nextLine; - int nextDelimeterIndex = header.length(); - - if (mul < 0) { - nextDelimeterIndex = Math.max(nextLine, nextSpace); - } else if (mul > 1) { - nextDelimeterIndex = Math.min(nextLine, nextSpace); - } - - return Pair.of(header.substring(nextTag, nextDelimeterIndex), nextDelimeterIndex); - } - } else { // next `@` is not date - return Pair.of(interimText, nextTag); - } - } - - return Pair.of(header.substring(startFrom).trim(), header.length()); - } - - public static Serializable wrapNarrative(Narrative narrative) { - /* - TODO: Should be able to collapse narrative if the span doesn't have an attribute - That's what this code is doing, but it doesn't work and I don't have time to debug it - if (narrative.getR() == null) { - StringBuilder content = new StringBuilder(); - boolean onlyStrings = true; - for (Serializable s : narrative.getContent()) { - if (s instanceof String) { - content.append((String)s); - } - else { - onlyStrings = false; - } - } - if (onlyStrings) { - return content.toString(); - } - } - */ - return new JAXBElement<>(new QName("urn:hl7-org:cql-annotations:r1", "s"), Narrative.class, narrative); - } - - public static boolean isValidIdentifier(String tagName) { - for (int i = 0; i < tagName.length(); i++) { - if (tagName.charAt(i) == '_') { - continue; - } - - if (i == 0) { - if (!Character.isLetter(tagName.charAt(i))) { - return false; - } - } else { - if (!Character.isLetterOrDigit(tagName.charAt(i))) { - return false; - } - } - } - - return true; - } - - public static String getTypeIdentifier(List qualifiers, String identifier) { - if (qualifiers.size() > 1) { - String result = null; - for (int i = 1; i < qualifiers.size(); i++) { - result = result == null ? qualifiers.get(i) : (result + "." + qualifiers.get(i)); - } - return result + "." + identifier; - } - - return identifier; - } - - public static String getModelIdentifier(List qualifiers) { - return !qualifiers.isEmpty() ? qualifiers.get(0) : null; - } - - // TODO: Should just use String.stripLeading() but that is only available in 11+ - public static String stripLeading(String s) { - int index = 0; - while (index < s.length()) { - if (!Character.isWhitespace(s.charAt(index))) { - break; - } - index++; - } - if (index == s.length()) { - return ""; - } - return s.substring(index); - } - - private Annotation getAnnotation(Element element) { - for (var o : element.getAnnotation()) { - if (o instanceof Annotation) { - return (Annotation) o; - } - } - - return null; - } - - protected String parseString(ParseTree pt) { - return StringEscapeUtils.unescapeCql(pt == null ? null : (String) visit(pt)); - } - - public static String normalizeWhitespace(String input) { - return input.replace("\r\n", "\n"); - } - - public static boolean isStartingWithDigit(String header, int index) { - return (index < header.length()) && Character.isDigit(header.charAt(index)); - } - - public void enableLocators() { - locate = true; - } - - public boolean locatorsEnabled() { - return locate; - } - - public void disableLocators() { - locate = false; - } - - public void enableResultTypes() { - resultTypes = true; - } - - public void disableResultTypes() { - resultTypes = false; - } - - public boolean resultTypesEnabled() { - return resultTypes; - } - - public void enableDateRangeOptimization() { - dateRangeOptimization = true; - } - - public void disableDateRangeOptimization() { - dateRangeOptimization = false; - } - - public boolean getDateRangeOptimization() { - return dateRangeOptimization; - } - - public void enableDetailedErrors() { - detailedErrors = true; - } - - public void disableDetailedErrors() { - detailedErrors = false; - } - - public boolean isDetailedErrorsEnabled() { - return detailedErrors; - } - - public void enableMethodInvocation() { - methodInvocation = true; - } - - public void disableMethodInvocation() { - methodInvocation = false; - } - - public boolean isMethodInvocationEnabled() { - return methodInvocation; - } - - public boolean isFromKeywordRequired() { - return fromKeywordRequired; - } - - public void enableFromKeywordRequired() { - fromKeywordRequired = true; - } - - public void disableFromKeywordRequired() { - fromKeywordRequired = false; - } - - public boolean getIncludeDeprecatedElements() { - return includeDeprecatedElements; - } - - public void setIncludeDeprecatedElements(boolean includeDeprecatedElements) { - this.includeDeprecatedElements = includeDeprecatedElements; - } - - private void setCompilerOptions(CqlCompilerOptions options) { - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDateRangeOptimization)) { - this.enableDateRangeOptimization(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableAnnotations)) { - this.enableAnnotations(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableLocators)) { - this.enableLocators(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableResultTypes)) { - this.enableResultTypes(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDetailedErrors)) { - this.enableDetailedErrors(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.DisableMethodInvocation)) { - this.disableMethodInvocation(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.RequireFromKeyword)) { - this.enableFromKeywordRequired(); - } - libraryBuilder.setCompatibilityLevel(options.getCompatibilityLevel()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.kt new file mode 100644 index 000000000..15adee48c --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.kt @@ -0,0 +1,864 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm.preprocessor + +import jakarta.xml.bind.JAXBElement +import java.io.Serializable +import java.util.* +import javax.xml.namespace.QName +import org.antlr.v4.runtime.ParserRuleContext +import org.antlr.v4.runtime.TokenStream +import org.antlr.v4.runtime.misc.Interval +import org.antlr.v4.runtime.tree.ParseTree +import org.antlr.v4.runtime.tree.TerminalNode +import org.apache.commons.lang3.tuple.Pair +import org.cqframework.cql.cql2elm.* +import org.cqframework.cql.cql2elm.model.Chunk +import org.cqframework.cql.cql2elm.model.FunctionHeader +import org.cqframework.cql.cql2elm.model.Model +import org.cqframework.cql.elm.IdObjectFactory +import org.cqframework.cql.elm.tracking.TrackBack +import org.cqframework.cql.elm.tracking.Trackable +import org.cqframework.cql.gen.cqlBaseVisitor +import org.cqframework.cql.gen.cqlParser.* +import org.hl7.cql.model.* +import org.hl7.cql_annotations.r1.Annotation +import org.hl7.cql_annotations.r1.Narrative +import org.hl7.cql_annotations.r1.ObjectFactory +import org.hl7.cql_annotations.r1.Tag +import org.hl7.elm.r1.* + +/** Common functionality used by [CqlPreprocessor] and [Cql2ElmVisitor] */ +@Suppress( + "LargeClass", + "CyclomaticComplexMethod", + "NestedBlockDepth", + "TooManyFunctions", + "ComplexCondition", + "ReturnCount" +) +open class CqlPreprocessorElmCommonVisitor( + @JvmField protected val libraryBuilder: LibraryBuilder, + protected val tokenStream: TokenStream +) : cqlBaseVisitor() { + @JvmField + protected val of: IdObjectFactory = + requireNotNull(libraryBuilder.objectFactory) { "libraryBuilder.objectFactory required" } + protected val af = ObjectFactory() + protected var implicitContextCreated = false + protected var currentContext = "Unfiltered" + @JvmField protected var chunks = Stack() + var libraryInfo = LibraryInfo() + protected set + + var isAnnotationEnabled = false + private set + + var isDetailedErrorsEnabled = false + private set + + private var locate = false + private var resultTypes = false + var dateRangeOptimization = false + private set + + var isMethodInvocationEnabled = true + private set + + var isFromKeywordRequired = false + private set + + var includeDeprecatedElements = false + + init { + // Don't talk to strangers. Except when you have to. + setCompilerOptions(libraryBuilder.libraryManager.cqlCompilerOptions) + } + + protected fun saveCurrentContext(currentContext: String): String { + val saveContext = this.currentContext + this.currentContext = currentContext + return saveContext + } + + override fun visit(tree: ParseTree): Any? { + val pushedChunk = pushChunk(tree) + var o: Any? = null + return try { + // ERROR: + try { + o = super.visit(tree) + if (o is Element) { + val element = o + if (element.localId == null) { + throw CqlInternalException( + String.format( + Locale.US, + "Internal translator error. 'localId' was not assigned for Element \"%s\"", + element.javaClass.name + ), + getTrackBack(tree) + ) + } + } + } catch (e: CqlIncludeException) { + val translatorException = CqlCompilerException(e.message, getTrackBack(tree), e) + if (translatorException.locator == null) { + throw translatorException + } + libraryBuilder.recordParsingException(translatorException) + } catch (e: CqlCompilerException) { + libraryBuilder.recordParsingException(e) + } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { + val ex = + if (e.message == null) { + CqlInternalException("Internal translator error.", getTrackBack(tree), e) + } else { + CqlSemanticException(e.message, getTrackBack(tree), e) + } + var rootCause = libraryBuilder.determineRootCause() + if (rootCause == null) { + rootCause = ex + libraryBuilder.recordParsingException(ex) + libraryBuilder.setRootCause(rootCause) + } else { + if (isDetailedErrorsEnabled) { + libraryBuilder.recordParsingException(ex) + } + } + o = of.createNull() + } + if (o is Trackable && tree !is LibraryContext) { + track(o, tree) + } + o + } finally { + popChunk(tree, o, pushedChunk) + processTags(tree, o) + } + } + + override fun visitTupleElementDefinition( + ctx: TupleElementDefinitionContext + ): TupleElementDefinition { + val result = + of.createTupleElementDefinition() + .withName(parseString(ctx.referentialIdentifier())) + .withElementType(parseTypeSpecifier(ctx.typeSpecifier())) + if (includeDeprecatedElements) { + result.type = result.elementType + } + return result + } + + override fun visitTupleTypeSpecifier(ctx: TupleTypeSpecifierContext): Any { + val resultType = TupleType() + val typeSpecifier = of.createTupleTypeSpecifier() + for (definitionContext in ctx.tupleElementDefinition()) { + val element = visit(definitionContext) as TupleElementDefinition + resultType.addElement(TupleTypeElement(element.name, element.elementType.resultType)) + typeSpecifier.element.add(element) + } + typeSpecifier.resultType = resultType + return typeSpecifier + } + + override fun visitChoiceTypeSpecifier(ctx: ChoiceTypeSpecifierContext): ChoiceTypeSpecifier { + val typeSpecifiers = ArrayList() + val types = ArrayList() + for (typeSpecifierContext in ctx.typeSpecifier()) { + val typeSpecifier = parseTypeSpecifier(typeSpecifierContext)!! + typeSpecifiers.add(typeSpecifier) + types.add(typeSpecifier.resultType) + } + val result = of.createChoiceTypeSpecifier().withChoice(typeSpecifiers) + if (includeDeprecatedElements) { + result.type.addAll(typeSpecifiers) + } + val choiceType = ChoiceType(types) + result.resultType = choiceType + return result + } + + override fun visitIntervalTypeSpecifier( + ctx: IntervalTypeSpecifierContext + ): IntervalTypeSpecifier { + val result = + of.createIntervalTypeSpecifier().withPointType(parseTypeSpecifier(ctx.typeSpecifier())) + val intervalType = IntervalType(result.pointType.resultType) + result.resultType = intervalType + return result + } + + override fun visitListTypeSpecifier(ctx: ListTypeSpecifierContext): ListTypeSpecifier { + val result = + of.createListTypeSpecifier().withElementType(parseTypeSpecifier(ctx.typeSpecifier())) + val listType = ListType(result.elementType.resultType) + result.resultType = listType + return result + } + + fun parseFunctionHeader(ctx: FunctionDefinitionContext): FunctionHeader { + val functionDef = + of.createFunctionDef() + .withAccessLevel(parseAccessModifier(ctx.accessModifier())) + .withName(parseString(ctx.identifierOrFunctionIdentifier())) + .withContext(currentContext) + if (ctx.fluentModifier() != null) { + libraryBuilder.checkCompatibilityLevel("Fluent functions", "1.5") + functionDef.isFluent = true + } + if (ctx.operandDefinition() != null) { + for (opdef in ctx.operandDefinition()) { + val typeSpecifier = parseTypeSpecifier(opdef.typeSpecifier())!! + functionDef.operand.add( + of.createOperandDef() + .withName(parseString(opdef.referentialIdentifier())) + .withOperandTypeSpecifier(typeSpecifier) + .withResultType(typeSpecifier.resultType) as OperandDef + ) + } + } + val typeSpecifierContext = ctx.typeSpecifier() + return if (typeSpecifierContext != null) { + FunctionHeader.withReturnType(functionDef, parseTypeSpecifier(typeSpecifierContext)) + } else FunctionHeader.noReturnType(functionDef) + } + + protected fun parseTypeSpecifier(pt: ParseTree?): TypeSpecifier? { + return if (pt == null) null else visit(pt) as TypeSpecifier + } + + protected fun parseAccessModifier(pt: ParseTree?): AccessModifier { + return if (pt == null) AccessModifier.PUBLIC else (visit(pt) as AccessModifier) + } + + protected fun parseQualifiers(ctx: NamedTypeSpecifierContext): List { + val qualifiers = ArrayList() + if (ctx.qualifier() != null) { + for (qualifierContext in ctx.qualifier()) { + val qualifier = parseString(qualifierContext)!! + qualifiers.add(qualifier) + } + } + return qualifiers + } + + protected open fun getModel( + modelNamespace: NamespaceInfo?, + modelName: String?, + version: String?, + localIdentifier: String + ): Model { + var modelName = modelName + var version = version + if (modelName == null) { + val defaultUsing = libraryInfo.defaultUsingDefinition + modelName = defaultUsing?.name + version = defaultUsing?.version + } + val modelIdentifier = ModelIdentifier().withId(modelName).withVersion(version) + if (modelNamespace != null) { + modelIdentifier.system = modelNamespace.uri + } + return libraryBuilder.getModel(modelIdentifier, localIdentifier) + } + + private fun pushChunk(tree: ParseTree): Boolean { + if (!isAnnotationEnabled) { + return false + } + val sourceInterval = tree.sourceInterval + + // An interval of i..i-1 indicates an empty interval at position i in the input stream, + if (sourceInterval.b < sourceInterval.a) { + return false + } + val chunk = Chunk().withInterval(sourceInterval) + chunks.push(chunk) + return true + } + + @Suppress("LongMethod") + private fun popChunk(tree: ParseTree, o: Any?, pushedChunk: Boolean) { + if (!pushedChunk) { + return + } + var chunk = chunks.pop() + if (o is Element) { + chunk.element = o + if (tree !is LibraryContext) { + if ( + o is UsingDef || + o is IncludeDef || + o is CodeSystemDef || + o is ValueSetDef || + o is CodeDef || + o is ConceptDef || + o is ParameterDef || + o is ContextDef || + o is ExpressionDef + ) { + val a = getAnnotation(o) + if (a == null || a.s == null) { + // Add header information (comments prior to the definition) + val definitionInfo = libraryInfo.resolveDefinition(tree) + if (definitionInfo?.headerInterval != null) { + val headerChunk = + Chunk() + .withInterval(definitionInfo.headerInterval) + .withIsHeaderChunk(true) + val newChunk = + Chunk() + .withInterval( + Interval(headerChunk.interval.a, chunk.interval.b) + ) + newChunk.addChunk(headerChunk) + newChunk.element = chunk.element + for (c in chunk.chunks) { + newChunk.addChunk(c) + } + chunk = newChunk + } + a?.let { addNarrativeToAnnotation(it, chunk) } + ?: o.annotation.add(buildAnnotation(chunk)) + } + } + } else { + if (libraryInfo.definition != null && libraryInfo.headerInterval != null) { + val headerChunk = + Chunk().withInterval(libraryInfo.headerInterval).withIsHeaderChunk(true) + val definitionChunk = + Chunk().withInterval(libraryInfo.definition?.sourceInterval) + val newChunk = + Chunk() + .withInterval( + Interval(headerChunk.interval.a, definitionChunk.interval.b) + ) + newChunk.addChunk(headerChunk) + newChunk.addChunk(definitionChunk) + newChunk.element = chunk.element + chunk = newChunk + val a = getAnnotation(libraryBuilder.library) + a?.let { addNarrativeToAnnotation(it, chunk) } + ?: libraryBuilder.library.annotation.add(buildAnnotation(chunk)) + } + } + } + if (chunks.isNotEmpty()) { + chunks.peek().addChunk(chunk) + } + } + + private fun processTags(tree: ParseTree, o: Any?) { + if (!libraryBuilder.isCompatibleWith("1.5")) { + return + } + if (o !is Element) { + return + } + if (tree !is LibraryContext) { + if ( + o is UsingDef || + o is IncludeDef || + o is CodeSystemDef || + o is ValueSetDef || + o is CodeDef || + o is ConceptDef || + o is ParameterDef || + o is ContextDef || + o is ExpressionDef + ) { + val tags = getTags(tree) + if (tags.isNotEmpty()) { + var a = getAnnotation(o) + if (a == null) { + a = buildAnnotation() + o.annotation.add(a) + } + // If the definition was processed as a forward declaration, the tag processing + // will already + // have occurred + // and just adding tags would duplicate them here. This doesn't account for the + // possibility + // that + // tags would be added for some other reason, but I didn't want the overhead of + // checking for + // existing + // tags, and there is currently nothing that would add tags other than being + // processed from + // comments + if (a.t.isEmpty()) { + a.t.addAll(tags) + } + } + } + } else { + if (libraryInfo.definition != null && libraryInfo.headerInterval != null) { + val tags = getTags(libraryInfo.header) + if (tags.isNotEmpty()) { + var a = getAnnotation(libraryBuilder.library) + if (a == null) { + a = buildAnnotation() + libraryBuilder.library.annotation.add(a) + } + a.t.addAll(tags) + } + } + } + } + + private fun getTags(header: String?): List { + return when { + header != null -> parseTags(parseComments(header)) + else -> emptyList() + } + } + + private fun getTags(tree: ParseTree): List { + val bi = libraryInfo.resolveDefinition(tree) + return when { + bi != null -> getTags(bi.header) + else -> emptyList() + } + } + + private fun parseTags(header: String): List { + val header = + header + .trim { it <= ' ' } + .split("\n[ \t]*\\*[ \t\\*]*".toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + .joinToString("\n") + val tags = ArrayList() + var startFrom = 0 + while (startFrom < header.length) { + val tagNamePair = lookForTagName(header, startFrom) + if (tagNamePair != null) { + if (tagNamePair.left.isNotEmpty() && isValidIdentifier(tagNamePair.left)) { + var t = af.createTag().withName(tagNamePair.left) + startFrom = tagNamePair.right + val tagValuePair = lookForTagValue(header, startFrom) + if (tagValuePair != null && tagValuePair.left.isNotEmpty()) { + t = t.withValue(tagValuePair.left) + startFrom = tagValuePair.right + } + tags.add(t) + } else { + startFrom = tagNamePair.right + } + } else { // no name tag found, no need to traverse more + break + } + } + return tags + } + + private fun parseComments(header: String?): String { + val result = ArrayList() + if (header != null) { + val normalized = header.replace("\r\n", "\n") + val lines = + normalized.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + var inMultiline = false + for (line in lines) { + if (!inMultiline) { + var start = line.indexOf("/*") + if (start >= 0) { + if (line.endsWith("*/")) { + result.add(line.substring(start + 2, line.length - 2)) + } else { + result.add(line.substring(start + 2)) + } + inMultiline = true + } else start = line.indexOf("//") + if (start >= 0 && !inMultiline) { + result.add(line.substring(start + 2)) + } + } else { + val end = line.indexOf("*/") + if (end >= 0) { + inMultiline = false + if (end > 0) { + result.add(line.substring(0, end)) + } + } else { + result.add(line) + } + } + } + } + return result.joinToString("\n") + } + + fun enableAnnotations() { + isAnnotationEnabled = true + } + + fun disableAnnotations() { + isAnnotationEnabled = false + } + + private fun buildAnnotation(chunk: Chunk): Annotation { + val annotation = af.createAnnotation() + annotation.s = buildNarrative(chunk) + return annotation + } + + private fun buildAnnotation(): Annotation { + return af.createAnnotation() + } + + private fun addNarrativeToAnnotation(annotation: Annotation, chunk: Chunk) { + annotation.s = buildNarrative(chunk) + } + + private fun buildNarrative(chunk: Chunk): Narrative { + val narrative = af.createNarrative() + if (chunk.element != null) { + narrative.r = chunk.element.localId + } + if (chunk.hasChunks()) { + var currentNarrative: Narrative? = null + for (childChunk in chunk.chunks) { + val chunkNarrative = buildNarrative(childChunk) + if (hasChunks(chunkNarrative)) { + if (currentNarrative != null) { + narrative.content.add(wrapNarrative(currentNarrative)) + currentNarrative = null + } + narrative.content.add(wrapNarrative(chunkNarrative)) + } else { + if (currentNarrative == null) { + currentNarrative = chunkNarrative + } else { + currentNarrative.content.addAll(chunkNarrative.content) + if (currentNarrative.r == null) { + currentNarrative.r = chunkNarrative.r + } + } + } + } + if (currentNarrative != null) { + narrative.content.add(wrapNarrative(currentNarrative)) + } + } else { + var chunkContent = tokenStream.getText(chunk.interval) + if (chunk.isHeaderChunk) { + chunkContent = chunkContent.trimStart() + } + chunkContent = normalizeWhitespace(chunkContent) + narrative.content.add(chunkContent) + } + return narrative + } + + private fun hasChunks(narrative: Narrative): Boolean { + for (c in narrative.content) { + if (c !is String) { + return true + } + } + return false + } + + private fun getTrackBack(tree: ParseTree): TrackBack? { + if (tree is ParserRuleContext) { + return getTrackBack(tree) + } + return if (tree is TerminalNode) { + getTrackBack(tree) + } else null + } + + private fun getTrackBack(ctx: ParserRuleContext): TrackBack { + return TrackBack( + libraryBuilder.libraryIdentifier, + ctx.getStart().line, + ctx.getStart().charPositionInLine + 1, // 1-based instead of 0-based + ctx.getStop().line, + ctx.getStop().charPositionInLine + + ctx.getStop().text.length // 1-based instead of 0-based + ) + } + + private fun track(trackable: Trackable, pt: ParseTree): TrackBack? { + val tb = getTrackBack(pt) + if (tb != null) { + trackable.trackbacks.add(tb) + } + if (trackable is Element) { + decorate(trackable, tb) + } + return tb + } + + private fun decorate(element: Element, tb: TrackBack?) { + if (locate && tb != null) { + element.locator = tb.toLocator() + } + if (resultTypes && element.resultType != null) { + if (element.resultType is NamedType) { + element.resultTypeName = libraryBuilder.dataTypeToQName(element.resultType) + } else { + element.resultTypeSpecifier = + libraryBuilder.dataTypeToTypeSpecifier(element.resultType) + } + } + } + + private fun lookForTagName(header: String, startFrom: Int): Pair? { + if (startFrom >= header.length) { + return null + } + val start = header.indexOf("@", startFrom) + if (start < 0) { + return null + } + val nextTagStart = header.indexOf("@", start + 1) + val nextColon = header.indexOf(":", start + 1) + if (nextTagStart < 0) { // no next tag , no next colon + if (nextColon < 0) { + return Pair.of( + header.substring(start + 1, header.length).trim { it <= ' ' }, + header.length + ) + } + } else { + if ( + nextColon < 0 || nextColon > nextTagStart + ) { // (has next tag and no colon) or (has next tag and next colon belongs to + // next tag) + return Pair.of( + header.substring(start + 1, nextTagStart).trim { it <= ' ' }, + nextTagStart + ) + } + } + return Pair.of(header.substring(start + 1, nextColon).trim { it <= ' ' }, nextColon + 1) + } + + private fun getAnnotation(element: Element): Annotation? { + for (o in element.annotation) { + if (o is Annotation) { + return o + } + } + return null + } + + protected fun parseString(pt: ParseTree?): String? { + return StringEscapeUtils.unescapeCql(if (pt == null) null else visit(pt) as String) + } + + fun enableLocators() { + locate = true + } + + fun locatorsEnabled(): Boolean { + return locate + } + + fun disableLocators() { + locate = false + } + + fun enableResultTypes() { + resultTypes = true + } + + fun disableResultTypes() { + resultTypes = false + } + + fun resultTypesEnabled(): Boolean { + return resultTypes + } + + fun enableDateRangeOptimization() { + dateRangeOptimization = true + } + + fun disableDateRangeOptimization() { + dateRangeOptimization = false + } + + fun enableDetailedErrors() { + isDetailedErrorsEnabled = true + } + + fun disableDetailedErrors() { + isDetailedErrorsEnabled = false + } + + fun enableMethodInvocation() { + isMethodInvocationEnabled = true + } + + fun disableMethodInvocation() { + isMethodInvocationEnabled = false + } + + fun enableFromKeywordRequired() { + isFromKeywordRequired = true + } + + fun disableFromKeywordRequired() { + isFromKeywordRequired = false + } + + private fun setCompilerOptions(options: CqlCompilerOptions) { + if (options.options.contains(CqlCompilerOptions.Options.EnableDateRangeOptimization)) { + enableDateRangeOptimization() + } + if (options.options.contains(CqlCompilerOptions.Options.EnableAnnotations)) { + enableAnnotations() + } + if (options.options.contains(CqlCompilerOptions.Options.EnableLocators)) { + enableLocators() + } + if (options.options.contains(CqlCompilerOptions.Options.EnableResultTypes)) { + enableResultTypes() + } + if (options.options.contains(CqlCompilerOptions.Options.EnableDetailedErrors)) { + enableDetailedErrors() + } + if (options.options.contains(CqlCompilerOptions.Options.DisableMethodInvocation)) { + disableMethodInvocation() + } + if (options.options.contains(CqlCompilerOptions.Options.RequireFromKeyword)) { + enableFromKeywordRequired() + } + libraryBuilder.compatibilityLevel = options.compatibilityLevel + } + + companion object { + // this method returns Pair starting from startFrom + // can return null in cases. + // for @1980-12-01, it will potentially check to be treated as value date + // it looks for parameter in double quotes, e.g. @parameter: "Measurement Interval" + // [@2019,@2020] + fun lookForTagValue(header: String, startFrom: Int): Pair? { + if (startFrom >= header.length) { + return null + } + val nextTag = header.indexOf('@', startFrom) + val nextStartDoubleQuote = header.indexOf("\"", startFrom) + if ( + (nextTag < 0 || nextTag > nextStartDoubleQuote) && + nextStartDoubleQuote > 0 && + header.length > nextStartDoubleQuote + 1 + ) { + val nextEndDoubleQuote = header.indexOf("\"", nextStartDoubleQuote + 1) + return if (nextEndDoubleQuote > 0) { + val parameterEnd = header.indexOf("\n", nextStartDoubleQuote + 1) + if (parameterEnd < 0) { + Pair.of(header.substring(nextStartDoubleQuote), header.length) + } else { + Pair.of(header.substring(nextStartDoubleQuote, parameterEnd), parameterEnd) + } + } else { // branch where the 2nd double quote is missing + Pair.of(header.substring(nextStartDoubleQuote), header.length) + } + } + if ( + nextTag == startFrom && !isStartingWithDigit(header, nextTag + 1) + ) { // starts with `@` and not potential date value + return Pair.of("", startFrom) + } else if (nextTag > 0) { // has some text before tag + val interimText = header.substring(startFrom, nextTag).trim { it <= ' ' } + return if (isStartingWithDigit(header, nextTag + 1)) { // next `@` is a date value + if ( + interimText.isNotEmpty() && interimText != ":" + ) { // interim text has value, regards interim text + Pair.of(interimText, nextTag) + } else { + val nextSpace = header.indexOf(' ', nextTag) + val nextLine = header.indexOf("\n", nextTag) + val mul = nextSpace * nextLine + var nextDelimiterIndex = header.length + if (mul < 0) { + nextDelimiterIndex = Math.max(nextLine, nextSpace) + } else if (mul > 1) { + nextDelimiterIndex = Math.min(nextLine, nextSpace) + } + Pair.of(header.substring(nextTag, nextDelimiterIndex), nextDelimiterIndex) + } + } else { // next `@` is not date + Pair.of(interimText, nextTag) + } + } + return Pair.of(header.substring(startFrom).trim { it <= ' ' }, header.length) + } + + fun wrapNarrative(narrative: Narrative): Serializable { + @Suppress("ForbiddenComment") + /* + TODO: Should be able to collapse narrative if the span doesn't have an attribute + That's what this code is doing, but it doesn't work and I don't have time to debug it + if (narrative.getR() == null) { + StringBuilder content = new StringBuilder(); + boolean onlyStrings = true; + for (Serializable s : narrative.getContent()) { + if (s instanceof String) { + content.append((String)s); + } + else { + onlyStrings = false; + } + } + if (onlyStrings) { + return content.toString(); + } + } + */ + return JAXBElement( + QName("urn:hl7-org:cql-annotations:r1", "s"), + Narrative::class.java, + narrative + ) + } + + fun isValidIdentifier(tagName: String): Boolean { + for (i in tagName.indices) { + if (tagName[i] == '_') { + continue + } + if (i == 0) { + if (!Character.isLetter(tagName[i])) { + return false + } + } else { + if (!Character.isLetterOrDigit(tagName[i])) { + return false + } + } + } + return true + } + + fun getTypeIdentifier(qualifiers: List, identifier: String): String { + if (qualifiers.size > 1) { + var result: String? = null + for (i in 1 until qualifiers.size) { + result = if (result == null) qualifiers[i] else result + "." + qualifiers[i] + } + return "$result.$identifier" + } + return identifier + } + + fun getModelIdentifier(qualifiers: List): String? { + return if (qualifiers.isNotEmpty()) qualifiers[0] else null + } + + fun normalizeWhitespace(input: String): String { + return input.replace("\r\n", "\n") + } + + fun isStartingWithDigit(header: String, index: Int): Boolean { + return index < header.length && Character.isDigit(header[index]) + } + } +} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.java deleted file mode 100644 index 8155d865a..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class ExpressionDefinitionInfo extends BaseInfo { - private String name; - private String context; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public String getContext() { - return context; - } - - public void setContext(String value) { - context = value; - } - - @Override - public cqlParser.ExpressionDefinitionContext getDefinition() { - return (cqlParser.ExpressionDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ExpressionDefinitionContext value) { - super.setDefinition(value); - } - - public ExpressionDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ExpressionDefinitionInfo withDefinition(cqlParser.ExpressionDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.kt new file mode 100644 index 000000000..b26f29a95 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.kt @@ -0,0 +1,9 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.ExpressionDefinitionContext + +class ExpressionDefinitionInfo( + val name: String, + val context: String, + override val definition: ExpressionDefinitionContext? +) : BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.java deleted file mode 100644 index 663e28f3f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import java.util.Objects; -import java.util.StringJoiner; -import org.cqframework.cql.cql2elm.model.FunctionHeader; -import org.cqframework.cql.gen.cqlParser; - -public class FunctionDefinitionInfo extends BaseInfo { - private String name; - private String context; - private FunctionHeader functionHeader; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public void setPreCompileOutput(FunctionHeader functionHeader) { - this.functionHeader = functionHeader; - } - - public FunctionHeader getPreCompileOutput() { - return this.functionHeader; - } - - public String getContext() { - return context; - } - - public void setContext(String value) { - context = value; - } - - @Override - public cqlParser.FunctionDefinitionContext getDefinition() { - return (cqlParser.FunctionDefinitionContext) super.getDefinition(); - } - - public FunctionDefinitionInfo withName(String value) { - setName(value); - return this; - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (other == null || getClass() != other.getClass()) return false; - FunctionDefinitionInfo that = (FunctionDefinitionInfo) other; - return Objects.equals(name, that.name) - && Objects.equals(context, that.context) - && Objects.equals(functionHeader, that.functionHeader); - } - - @Override - public int hashCode() { - return Objects.hash(name, context, functionHeader); - } - - @Override - public String toString() { - return new StringJoiner(", ", FunctionDefinitionInfo.class.getSimpleName() + "[", "]") - .add("name='" + name + "'") - .add("context='" + context + "'") - .add("preCompileOutput=" + functionHeader) - .toString(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.kt new file mode 100644 index 000000000..3a55073c1 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.kt @@ -0,0 +1,9 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.FunctionDefinitionContext + +data class FunctionDefinitionInfo( + val name: String, + val context: String, + override val definition: FunctionDefinitionContext +) : BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.java deleted file mode 100644 index b6f3d0a50..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class IncludeDefinitionInfo extends BaseInfo { - private String namespaceName; - private String name; - private String version; - private String localName; - - public String getNamespaceName() { - return namespaceName; - } - - public void setNamespaceName(String namespaceName) { - this.namespaceName = namespaceName; - } - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public String getVersion() { - return version; - } - - public void setVersion(String value) { - version = value; - } - - public String getLocalName() { - return localName; - } - - public void setLocalName(String value) { - localName = value; - } - - public IncludeDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public IncludeDefinitionInfo withVersion(String value) { - setVersion(value); - return this; - } - - public IncludeDefinitionInfo withLocalName(String value) { - setLocalName(value); - return this; - } - - @Override - public cqlParser.IncludeDefinitionContext getDefinition() { - return (cqlParser.IncludeDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.IncludeDefinitionContext value) { - super.setDefinition(value); - } - - public IncludeDefinitionInfo withDefinition(cqlParser.IncludeDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.kt new file mode 100644 index 000000000..c79e96a16 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.kt @@ -0,0 +1,11 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.IncludeDefinitionContext + +class IncludeDefinitionInfo( + val namespaceName: String?, + val name: String, + val version: String?, + val localName: String, + override val definition: IncludeDefinitionContext +) : BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.java deleted file mode 100644 index 6f1caad4d..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.java +++ /dev/null @@ -1,315 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import java.util.*; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; -import org.cqframework.cql.cql2elm.ResultWithPossibleError; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.elm.r1.OperandDef; - -public class LibraryInfo extends BaseInfo { - private String namespaceName; - private String libraryName; - private String version; - - private UsingDefinitionInfo preferredUsingDefinition; - private final Map usingDefinitions; - private final Map includeDefinitions; - private final Map codesystemDefinitions; - private final Map valuesetDefinitions; - private final Map codeDefinitions; - private final Map conceptDefinitions; - private final Map parameterDefinitions; - private final Map expressionDefinitions; - private final Map> functionDefinitions; - private final List contextDefinitions; - private final Map definitions; - - public LibraryInfo() { - usingDefinitions = new LinkedHashMap<>(); - includeDefinitions = new LinkedHashMap<>(); - codesystemDefinitions = new LinkedHashMap<>(); - valuesetDefinitions = new LinkedHashMap<>(); - codeDefinitions = new LinkedHashMap<>(); - conceptDefinitions = new LinkedHashMap<>(); - parameterDefinitions = new LinkedHashMap<>(); - expressionDefinitions = new LinkedHashMap<>(); - functionDefinitions = new LinkedHashMap<>(); - contextDefinitions = new ArrayList<>(); - definitions = new HashMap<>(); - } - - public String getNamespaceName() { - return namespaceName; - } - - public void setNamespaceName(String namespaceName) { - this.namespaceName = namespaceName; - } - - public String getLibraryName() { - return libraryName; - } - - public void setLibraryName(String libraryName) { - this.libraryName = libraryName; - } - - public LibraryInfo withLibraryName(String value) { - setLibraryName(value); - return this; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public LibraryInfo withVersion(String value) { - setVersion(value); - return this; - } - - private void addDefinition(BaseInfo definition) { - if (definition != null && definition.getDefinition() != null) { - Interval sourceInterval = definition.getDefinition().getSourceInterval(); - if (sourceInterval != null) { - definitions.put(sourceInterval, definition); - } - } - } - - @Override - public cqlParser.LibraryDefinitionContext getDefinition() { - return (cqlParser.LibraryDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.LibraryDefinitionContext value) { - super.setDefinition(value); - addDefinition(this); - } - - public LibraryInfo withDefinition(cqlParser.LibraryDefinitionContext value) { - setDefinition(value); - return this; - } - - public void addUsingDefinition(UsingDefinitionInfo usingDefinition) { - // First using definition encountered is "preferred", meaning it will resolve as the default model info - if (preferredUsingDefinition == null) { - preferredUsingDefinition = usingDefinition; - } - usingDefinitions.put(usingDefinition.getName(), usingDefinition); - addDefinition(usingDefinition); - } - - public UsingDefinitionInfo resolveModelReference(String identifier) { - return usingDefinitions.get(identifier); - } - - public UsingDefinitionInfo getDefaultUsingDefinition() { - return preferredUsingDefinition; - } - - public String getDefaultModelName() { - UsingDefinitionInfo usingDefinitionInfo = getDefaultUsingDefinition(); - if (usingDefinitionInfo == null) { - throw new IllegalArgumentException( - "Could not determine a default model because no usings have been defined."); - } - - return usingDefinitionInfo.getName(); - } - - public void addIncludeDefinition(IncludeDefinitionInfo includeDefinition) { - includeDefinitions.put(includeDefinition.getLocalName(), includeDefinition); - addDefinition(includeDefinition); - } - - public IncludeDefinitionInfo resolveLibraryReference(String identifier) { - return includeDefinitions.get(identifier); - } - - public String resolveLibraryName(String identifier) { - IncludeDefinitionInfo includeDefinition = resolveLibraryReference(identifier); - if (includeDefinition != null) { - return includeDefinition.getLocalName(); - } - - return null; - } - - public void addParameterDefinition(ParameterDefinitionInfo parameterDefinition) { - parameterDefinitions.put(parameterDefinition.getName(), parameterDefinition); - addDefinition(parameterDefinition); - } - - public ParameterDefinitionInfo resolveParameterReference(String identifier) { - return parameterDefinitions.get(identifier); - } - - public String resolveParameterName(String identifier) { - ParameterDefinitionInfo parameterDefinition = resolveParameterReference(identifier); - if (parameterDefinition != null) { - return parameterDefinition.getName(); - } - - return null; - } - - public void addCodesystemDefinition(CodesystemDefinitionInfo codesystemDefinition) { - codesystemDefinitions.put(codesystemDefinition.getName(), codesystemDefinition); - addDefinition(codesystemDefinition); - } - - public CodesystemDefinitionInfo resolveCodesystemReference(String identifier) { - return codesystemDefinitions.get(identifier); - } - - public void addValuesetDefinition(ValuesetDefinitionInfo valuesetDefinition) { - valuesetDefinitions.put(valuesetDefinition.getName(), valuesetDefinition); - addDefinition(valuesetDefinition); - } - - public ValuesetDefinitionInfo resolveValuesetReference(String identifier) { - return valuesetDefinitions.get(identifier); - } - - public String resolveValuesetName(String identifier) { - ValuesetDefinitionInfo valuesetDefinition = resolveValuesetReference(identifier); - if (valuesetDefinition != null) { - return valuesetDefinition.getName(); - } - - return null; - } - - public void addCodeDefinition(CodeDefinitionInfo codeDefinition) { - codeDefinitions.put(codeDefinition.getName(), codeDefinition); - addDefinition(codeDefinition); - } - - public CodeDefinitionInfo resolveCodeReference(String identifier) { - return codeDefinitions.get(identifier); - } - - public void addConceptDefinition(ConceptDefinitionInfo conceptDefinition) { - conceptDefinitions.put(conceptDefinition.getName(), conceptDefinition); - addDefinition(conceptDefinition); - } - - public ConceptDefinitionInfo resolveConceptReference(String identifier) { - return conceptDefinitions.get(identifier); - } - - public void addExpressionDefinition(ExpressionDefinitionInfo letStatement) { - expressionDefinitions.put(letStatement.getName(), letStatement); - addDefinition(letStatement); - } - - public ExpressionDefinitionInfo resolveExpressionReference(String identifier) { - return expressionDefinitions.get(identifier); - } - - public String resolveExpressionName(String identifier) { - ExpressionDefinitionInfo letStatement = resolveExpressionReference(identifier); - if (letStatement != null) { - return letStatement.getName(); - } - - return null; - } - - public void addFunctionDefinition(FunctionDefinitionInfo functionDefinition) { - List infos = functionDefinitions.get(functionDefinition.getName()); - if (infos == null) { - infos = new ArrayList(); - functionDefinitions.put(functionDefinition.getName(), infos); - } - infos.add(functionDefinition); - addDefinition(functionDefinition); - } - - public Iterable resolveFunctionReference(String identifier) { - return functionDefinitions.get(identifier); - } - - public String resolveFunctionName(String identifier) { - Iterable functionDefinitions = resolveFunctionReference(identifier); - for (FunctionDefinitionInfo functionInfo : functionDefinitions) { - return functionInfo.getName(); - } - - return null; - } - - public void addContextDefinition(ContextDefinitionInfo contextDefinition) { - contextDefinitions.add(contextDefinition); - addDefinition(contextDefinition); - } - - public ContextDefinitionInfo resolveContext(cqlParser.ContextDefinitionContext ctx) { - for (ContextDefinitionInfo cd : contextDefinitions) { - if (ctx.getSourceInterval().equals(cd.getDefinition().getSourceInterval())) { - return cd; - } - } - - return null; - } - - public BaseInfo resolveDefinition(ParseTree pt) { - return definitions.get(pt.getSourceInterval()); - } - - private static boolean isFunctionDefInfoAlreadyPresent( - ResultWithPossibleError existingFunctionDefInfo, - ResultWithPossibleError functionDefinition) { - // equals/hashCode only goes so far because we don't control the entire class hierarchy - return matchesFunctionDefInfos(existingFunctionDefInfo, functionDefinition); - } - - private static boolean matchesFunctionDefInfos( - ResultWithPossibleError existingInfo, - ResultWithPossibleError newInfo) { - if (existingInfo == null) { - return false; - } - - if (existingInfo.hasError() || newInfo.hasError()) { - return existingInfo.hasError() && newInfo.hasError(); - } - - final List existingOperands = existingInfo - .getUnderlyingResultIfExists() - .getPreCompileOutput() - .getFunctionDef() - .getOperand(); - final List newOperands = newInfo.getUnderlyingResultIfExists() - .getPreCompileOutput() - .getFunctionDef() - .getOperand(); - - if (existingOperands.size() != newOperands.size()) { - return false; - } - - for (int index = 0; index < existingOperands.size(); index++) { - final OperandDef existingOperand = existingOperands.get(index); - final OperandDef newOperand = newOperands.get(index); - - if (!matchesOperands(existingOperand, newOperand)) { - return false; - } - } - - return true; - } - - private static boolean matchesOperands(OperandDef existingOperand, OperandDef newOperand) { - return existingOperand.getResultType().equals(newOperand.getResultType()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.kt new file mode 100644 index 000000000..ab37a049f --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.kt @@ -0,0 +1,187 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.antlr.v4.runtime.misc.Interval +import org.antlr.v4.runtime.tree.ParseTree +import org.cqframework.cql.gen.cqlParser.ContextDefinitionContext +import org.cqframework.cql.gen.cqlParser.LibraryDefinitionContext + +@Suppress("TooManyFunctions") +class LibraryInfo( + val namespaceName: String? = null, + val libraryName: String? = null, + val version: String? = null, + override val definition: LibraryDefinitionContext? = null +) : BaseInfo(definition) { + + var defaultUsingDefinition: UsingDefinitionInfo? = null + + private val usingDefinitions = LinkedHashMap() + private val includeDefinitions = LinkedHashMap() + private val codesystemDefinitions = LinkedHashMap() + private val valuesetDefinitions = LinkedHashMap() + private val codeDefinitions = LinkedHashMap() + private val conceptDefinitions = LinkedHashMap() + private val parameterDefinitions = LinkedHashMap() + private val expressionDefinitions = LinkedHashMap() + private val functionDefinitions = LinkedHashMap>() + private val contextDefinitions = ArrayList() + private val definitions = HashMap() + + private fun addDefinition(definition: BaseInfo) { + if (definition.definition != null) { + val sourceInterval = definition.definition?.sourceInterval + if (sourceInterval != null) { + definitions[sourceInterval] = definition + } + } + } + + fun addUsingDefinition(usingDefinition: UsingDefinitionInfo) { + // First using definition encountered is "preferred", meaning it will resolve as the default + // model info + if (defaultUsingDefinition == null) { + defaultUsingDefinition = usingDefinition + } + usingDefinitions[usingDefinition.name] = usingDefinition + addDefinition(usingDefinition) + } + + fun resolveModelReference(identifier: String): UsingDefinitionInfo? { + return usingDefinitions[identifier] + } + + val defaultModelName: String + get() { + val usingDefinitionInfo = + defaultUsingDefinition + ?: throw IllegalArgumentException( + "Could not determine a default model because no usings have been defined." + ) + return usingDefinitionInfo.name + } + + fun addIncludeDefinition(includeDefinition: IncludeDefinitionInfo) { + includeDefinitions[includeDefinition.localName] = includeDefinition + addDefinition(includeDefinition) + } + + fun resolveLibraryReference(identifier: String): IncludeDefinitionInfo? { + return includeDefinitions[identifier] + } + + fun resolveLibraryName(identifier: String): String? { + val includeDefinition = resolveLibraryReference(identifier) + return includeDefinition?.localName + } + + fun addParameterDefinition(parameterDefinition: ParameterDefinitionInfo) { + parameterDefinitions[parameterDefinition.name] = parameterDefinition + addDefinition(parameterDefinition) + } + + fun resolveParameterReference(identifier: String): ParameterDefinitionInfo? { + return parameterDefinitions[identifier] + } + + fun resolveParameterName(identifier: String): String? { + val parameterDefinition = resolveParameterReference(identifier) + return parameterDefinition?.name + } + + fun addCodesystemDefinition(codesystemDefinition: CodesystemDefinitionInfo) { + codesystemDefinitions[codesystemDefinition.name] = codesystemDefinition + addDefinition(codesystemDefinition) + } + + fun resolveCodesystemReference(identifier: String): CodesystemDefinitionInfo? { + return codesystemDefinitions[identifier] + } + + fun addValuesetDefinition(valuesetDefinition: ValuesetDefinitionInfo) { + valuesetDefinitions[valuesetDefinition.name] = valuesetDefinition + addDefinition(valuesetDefinition) + } + + fun resolveValuesetReference(identifier: String): ValuesetDefinitionInfo? { + return valuesetDefinitions[identifier] + } + + fun resolveValuesetName(identifier: String): String? { + val valuesetDefinition = resolveValuesetReference(identifier) + return valuesetDefinition?.name + } + + fun addCodeDefinition(codeDefinition: CodeDefinitionInfo) { + codeDefinitions[codeDefinition.name] = codeDefinition + addDefinition(codeDefinition) + } + + fun resolveCodeReference(identifier: String): CodeDefinitionInfo? { + return codeDefinitions[identifier] + } + + fun addConceptDefinition(conceptDefinition: ConceptDefinitionInfo) { + conceptDefinitions[conceptDefinition.name] = conceptDefinition + addDefinition(conceptDefinition) + } + + fun resolveConceptReference(identifier: String): ConceptDefinitionInfo? { + return conceptDefinitions[identifier] + } + + fun addExpressionDefinition(letStatement: ExpressionDefinitionInfo) { + expressionDefinitions[letStatement.name] = letStatement + addDefinition(letStatement) + } + + fun resolveExpressionReference(identifier: String): ExpressionDefinitionInfo? { + return expressionDefinitions[identifier] + } + + fun resolveExpressionName(identifier: String): String? { + val letStatement = resolveExpressionReference(identifier) + return letStatement?.name + } + + fun addFunctionDefinition(functionDefinition: FunctionDefinitionInfo) { + var infos = functionDefinitions[functionDefinition.name] + if (infos == null) { + infos = ArrayList() + functionDefinitions[functionDefinition.name] = infos + } + infos.add(functionDefinition) + addDefinition(functionDefinition) + } + + fun resolveFunctionReference(identifier: String): Iterable? { + return functionDefinitions[identifier] + } + + fun resolveFunctionName(identifier: String): String? { + val functionDefinitions = resolveFunctionReference(identifier) + if (functionDefinitions != null) { + for (functionInfo in functionDefinitions) { + return functionInfo.name + } + } + return null + } + + fun addContextDefinition(contextDefinition: ContextDefinitionInfo) { + contextDefinitions.add(contextDefinition) + addDefinition(contextDefinition) + } + + fun resolveContext(ctx: ContextDefinitionContext): ContextDefinitionInfo? { + for (cd in contextDefinitions) { + if (ctx.sourceInterval == cd.definition.sourceInterval) { + return cd + } + } + return null + } + + fun resolveDefinition(pt: ParseTree): BaseInfo? { + return definitions[pt.sourceInterval] + } +} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.java deleted file mode 100644 index ea6a37396..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class ParameterDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.ParameterDefinitionContext getDefinition() { - return (cqlParser.ParameterDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ParameterDefinitionContext value) { - super.setDefinition(value); - } - - public ParameterDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ParameterDefinitionInfo withDefinition(cqlParser.ParameterDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.kt new file mode 100644 index 000000000..b7d266efb --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.kt @@ -0,0 +1,8 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.ParameterDefinitionContext + +class ParameterDefinitionInfo( + val name: String, + override val definition: ParameterDefinitionContext +) : BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.java deleted file mode 100644 index ddcdfb43f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class UsingDefinitionInfo extends BaseInfo { - private String namespaceName; - private String name; - private String version; - private String localName; - - public String getNamespaceName() { - return namespaceName; - } - - public void setNamespaceName(String value) { - namespaceName = value; - } - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public String getVersion() { - return version; - } - - public void setVersion(String value) { - version = value; - } - - public String getLocalName() { - return localName; - } - - public void setLocalName(String value) { - localName = value; - } - - public UsingDefinitionInfo withNamespaceName(String value) { - setNamespaceName(value); - return this; - } - - public UsingDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public UsingDefinitionInfo withVersion(String value) { - setVersion(value); - return this; - } - - public UsingDefinitionInfo withLocalName(String value) { - setLocalName(value); - return this; - } - - @Override - public cqlParser.UsingDefinitionContext getDefinition() { - return (cqlParser.UsingDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.UsingDefinitionContext value) { - super.setDefinition(value); - } - - public UsingDefinitionInfo withDefinition(cqlParser.UsingDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.kt new file mode 100644 index 000000000..a643b5e5a --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.kt @@ -0,0 +1,11 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.UsingDefinitionContext + +class UsingDefinitionInfo( + val namespaceName: String?, + val name: String, + val version: String?, + val localName: String, + override val definition: UsingDefinitionContext +) : BaseInfo(definition) diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.java deleted file mode 100644 index c52fc4243..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.antlr.v4.runtime.misc.Interval; -import org.cqframework.cql.gen.cqlParser; - -public class ValuesetDefinitionInfo extends BaseInfo { - private String name; - private String header; - private Interval headerInterval; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.ValuesetDefinitionContext getDefinition() { - return (cqlParser.ValuesetDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ValuesetDefinitionContext value) { - super.setDefinition(value); - } - - public ValuesetDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ValuesetDefinitionInfo withDefinition(cqlParser.ValuesetDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.kt b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.kt new file mode 100644 index 000000000..d97aa194a --- /dev/null +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.kt @@ -0,0 +1,6 @@ +package org.cqframework.cql.cql2elm.preprocessor + +import org.cqframework.cql.gen.cqlParser.ValuesetDefinitionContext + +class ValuesetDefinitionInfo(val name: String, override val definition: ValuesetDefinitionContext) : + BaseInfo(definition)