All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cqframework.cql.cql2elm.CqlCompiler Maven / Gradle / Ivy

Go to download

The cql-to-elm library for the Clinical Quality Language Java reference implementation

There is a newer version: 3.18.0
Show newest version
package org.cqframework.cql.cql2elm;

import static org.cqframework.cql.cql2elm.CqlCompilerOptions.Options.EnableAnnotations;
import static org.cqframework.cql.cql2elm.CqlCompilerOptions.Options.EnableLocators;
import static org.cqframework.cql.cql2elm.CqlCompilerOptions.Options.EnableResultTypes;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;
import org.cqframework.cql.cql2elm.elm.ElmEdit;
import org.cqframework.cql.cql2elm.elm.ElmEditor;
import org.cqframework.cql.cql2elm.elm.IElmEdit;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.cqframework.cql.cql2elm.preprocessor.CqlPreprocessor;
import org.cqframework.cql.elm.IdObjectFactory;
import org.cqframework.cql.elm.tracking.TrackBack;
import org.cqframework.cql.gen.cqlLexer;
import org.cqframework.cql.gen.cqlParser;
import org.hl7.cql.model.NamespaceAware;
import org.hl7.cql.model.NamespaceInfo;
import org.hl7.elm.r1.Library;
import org.hl7.elm.r1.Retrieve;
import org.hl7.elm.r1.VersionedIdentifier;

public class CqlCompiler {
    private Library library = null;
    private CompiledLibrary compiledLibrary = null;
    private Object visitResult = null;
    private List retrieves = null;
    private List exceptions = null;
    private List errors = null;
    private List warnings = null;
    private List messages = null;
    private final VersionedIdentifier sourceInfo;
    private final NamespaceInfo namespaceInfo;
    private final LibraryManager libraryManager;

    public CqlCompiler(LibraryManager libraryManager) {
        this(null, null, libraryManager);
    }

    public CqlCompiler(NamespaceInfo namespaceInfo, LibraryManager libraryManager) {
        this(namespaceInfo, null, libraryManager);
    }

    public CqlCompiler(NamespaceInfo namespaceInfo, VersionedIdentifier sourceInfo, LibraryManager libraryManager) {
        this.namespaceInfo = namespaceInfo;
        this.libraryManager = libraryManager;

        if (sourceInfo == null) {
            this.sourceInfo = new VersionedIdentifier().withId("Anonymous").withSystem("text/cql");
        } else {
            this.sourceInfo = sourceInfo;
        }

        if (this.namespaceInfo != null) {
            libraryManager.getNamespaceManager().ensureNamespaceRegistered(this.namespaceInfo);
        }

        if (libraryManager.getNamespaceManager().hasNamespaces()
                && libraryManager.getLibrarySourceLoader() instanceof NamespaceAware) {
            ((NamespaceAware) libraryManager.getLibrarySourceLoader())
                    .setNamespaceManager(libraryManager.getNamespaceManager());
        }
    }

    public Library getLibrary() {
        return library;
    }

    public CompiledLibrary getCompiledLibrary() {
        return compiledLibrary;
    }

    public Object toObject() {
        return visitResult;
    }

    public List toRetrieves() {
        return retrieves;
    }

    public Map getCompiledLibraries() {
        return libraryManager.getCompiledLibraries();
    }

    public Map getLibraries() {
        var result = new HashMap();
        for (var id : libraryManager.getCompiledLibraries().keySet()) {
            result.put(id, libraryManager.getCompiledLibraries().get(id).getLibrary());
        }
        return result;
    }

    public List getExceptions() {
        return exceptions;
    }

    public List getErrors() {
        return errors;
    }

    public List getWarnings() {
        return warnings;
    }

    public List getMessages() {
        return messages;
    }

    private class CqlErrorListener extends BaseErrorListener {

        private LibraryBuilder builder;
        private boolean detailedErrors;

        public CqlErrorListener(LibraryBuilder builder, boolean detailedErrors) {
            this.builder = builder;
            this.detailedErrors = detailedErrors;
        }

        private VersionedIdentifier extractLibraryIdentifier(cqlParser parser) {
            RuleContext context = parser.getContext();
            while (context != null && !(context instanceof cqlParser.LibraryContext)) {
                context = context.parent;
            }

            if (context instanceof cqlParser.LibraryContext) {
                cqlParser.LibraryDefinitionContext ldc = ((cqlParser.LibraryContext) context).libraryDefinition();
                if (ldc != null
                        && ldc.qualifiedIdentifier() != null
                        && ldc.qualifiedIdentifier().identifier() != null) {
                    return new VersionedIdentifier()
                            .withId(StringEscapeUtils.unescapeCql(
                                    ldc.qualifiedIdentifier().identifier().getText()));
                }
            }

            return null;
        }

        @Override
        public void syntaxError(
                Recognizer recognizer,
                Object offendingSymbol,
                int line,
                int charPositionInLine,
                String msg,
                RecognitionException e) {
            var libraryIdentifier = builder.getLibraryIdentifier();
            if (libraryIdentifier == null) {
                // Attempt to extract a libraryIdentifier from the currently parsed content
                if (recognizer instanceof cqlParser) {
                    libraryIdentifier = extractLibraryIdentifier((cqlParser) recognizer);
                }
                if (libraryIdentifier == null) {
                    libraryIdentifier = sourceInfo;
                }
            }
            TrackBack trackback = new TrackBack(libraryIdentifier, line, charPositionInLine, line, charPositionInLine);

            if (detailedErrors) {
                builder.recordParsingException(new CqlSyntaxException(msg, trackback, e));
                builder.recordParsingException(new CqlCompilerException(msg, trackback, e));
            } else {
                if (offendingSymbol instanceof CommonToken) {
                    CommonToken token = (CommonToken) offendingSymbol;
                    builder.recordParsingException(
                            new CqlSyntaxException(String.format("Syntax error at %s", token.getText()), trackback, e));
                } else {
                    builder.recordParsingException(new CqlSyntaxException("Syntax error", trackback, e));
                }
            }
        }
    }

    public Library run(File cqlFile) throws IOException {
        return run(CharStreams.fromStream(new FileInputStream(cqlFile)));
    }

    public Library run(String cqlText) {
        return run(CharStreams.fromString(cqlText));
    }

    public Library run(InputStream is) throws IOException {
        return run(CharStreams.fromStream(is));
    }

    public Library run(CharStream is) {
        exceptions = new ArrayList<>();
        errors = new ArrayList<>();
        warnings = new ArrayList<>();
        messages = new ArrayList<>();

        var options = libraryManager.getCqlCompilerOptions().getOptions();

        LibraryBuilder builder = new LibraryBuilder(namespaceInfo, libraryManager, new IdObjectFactory());
        CqlCompiler.CqlErrorListener errorListener = new CqlCompiler.CqlErrorListener(
                builder, options.contains(CqlCompilerOptions.Options.EnableDetailedErrors));

        // Phase 1: Lexing
        cqlLexer lexer = new cqlLexer(is);
        lexer.removeErrorListeners();
        lexer.addErrorListener(errorListener);
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        // Phase 2: Parsing (the lexer is actually streaming, so Phase 1 and 2 happen together)
        cqlParser parser = new cqlParser(tokens);
        parser.setBuildParseTree(true);
        parser.removeErrorListeners(); // Clear the default console listener
        parser.addErrorListener(errorListener);
        ParseTree tree = parser.library();

        // Phase 3: preprocess the parse tree (generates the LibraryInfo with
        // header information for definitions)
        CqlPreprocessor preprocessor = new CqlPreprocessor(builder, tokens);
        preprocessor.visit(tree);

        // Phase 4: generate the ELM (the ELM is generated with full type information that can be used
        // for validation, optimization, rewriting, debugging, etc.)
        Cql2ElmVisitor visitor = new Cql2ElmVisitor(builder, tokens, preprocessor.getLibraryInfo());
        visitResult = visitor.visit(tree);
        library = builder.getLibrary();

        // Phase 5: ELM optimization/reduction (this is where result types, annotations, etc. are removed
        // and there will probably be a lot of other optimizations that happen here in the future)
        var edits = allNonNull(
                !options.contains(EnableAnnotations) ? ElmEdit.REMOVE_ANNOTATION : null,
                !options.contains(EnableResultTypes) ? ElmEdit.REMOVE_RESULT_TYPE : null,
                !options.contains(EnableLocators) ? ElmEdit.REMOVE_LOCATOR : null,
                ElmEdit.REMOVE_CHOICE_TYPE_SPECIFIER_TYPE_IF_EMPTY);

        new ElmEditor(edits).edit(library);

        compiledLibrary = builder.getCompiledLibrary();
        retrieves = visitor.getRetrieves();
        exceptions.addAll(builder.getExceptions());
        errors.addAll(builder.getErrors());
        warnings.addAll(builder.getWarnings());
        messages.addAll(builder.getMessages());

        return library;
    }

    private List allNonNull(IElmEdit... ts) {
        return Arrays.stream(ts).filter(x -> x != null).collect(Collectors.toList());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy