Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAT-7784: fix functions #219

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions src/AntlrUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,12 @@ export default class AntlrUtils {
}

static findChildName(children: ParseTree[] | undefined): string | undefined {

if (children?.length != 4) {
console.error(
"########### Entering.. children length is ",
children?.length
);
throw new Error("Definition might be malformed.");

}
return children ? children[1].text : undefined;
}

static findChildExpression(
children: ParseTree[] | undefined
): string | undefined {

if (children?.length != 4) {
console.error(
"########### Entering.. children length is ",
children?.length
)
throw new Error("Definition might be malformed.");
}
return children ? children[3].text : undefined;
}

Expand Down Expand Up @@ -85,11 +68,12 @@ export default class AntlrUtils {
* @param comment -> a comment with comment characters
*/
static formatComment(comment: string): string {
return comment.replace(AntlrUtils.SINGLE_LINE_COMMENT_REGEX, "")
return comment
.replace(AntlrUtils.SINGLE_LINE_COMMENT_REGEX, "")
.replace(AntlrUtils.MULTI_LINE_COMMENT_REGEX, "")
.split("\n")
.map((line) => line.trim())
.filter(line=> line !== "")
.filter((line) => line !== "")
.join("\n");
}

Expand Down
9 changes: 6 additions & 3 deletions src/CqlAntlr.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {BufferedTokenStream, CharStreams, CommonTokenStream} from "antlr4ts";
import { BufferedTokenStream, CharStreams, CommonTokenStream } from "antlr4ts";
import { CodePointCharStream } from "antlr4ts/CodePointCharStream";
import { ParseTreeWalker } from "antlr4ts/tree";
import { cqlLexer, cqlParser, LibraryContext, cqlListener } from "../generated";
Expand All @@ -18,9 +18,12 @@ class CqlAntlr {
const charStream: CodePointCharStream = CharStreams.fromString(this.cql);
const lexer: cqlLexer = new cqlLexer(charStream);
const bufferedTokenStream = new BufferedTokenStream(lexer);
bufferedTokenStream.fill()
bufferedTokenStream.fill();

const listener: cqlListener = new CqlAntlrListener(result, bufferedTokenStream);
const listener: cqlListener = new CqlAntlrListener(
result,
bufferedTokenStream
);
ParseTreeWalker.DEFAULT.walk(listener, tree);
/**
* Disabled. Only partially implemented and would be
Expand Down
58 changes: 39 additions & 19 deletions src/CqlAntlrListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
cqlLexer,
cqlListener,
ExpressionDefinitionContext,
FunctionDefinitionContext,
IncludeDefinitionContext,
LibraryDefinitionContext,
ParameterDefinitionContext,
Expand All @@ -27,13 +28,14 @@ import CqlParameter from "./dto/CqlParameter";
import CqlContextCreator from "./CqlContextCreator";
import CqlExpressionDefinition from "./dto/CqlExpressionDefinition";
import CqlExpressionDefinitionCreator from "./CqlExpressionDefinitionCreator";
import {CqlCode} from "./dto";
import { CqlCode } from "./dto";
import CqlIdentifier from "./dto/CqlIdentifier";
import CqlIdentifierCreator from "./CqlIdentifierCreator";
import CqlRetrieve from "./dto/CqlRetrieve";
import CqlRetrieveCreator from "./CqlRetrieveCreator";
import {BufferedTokenStream} from "antlr4ts";
import { BufferedTokenStream } from "antlr4ts";
import AntlrUtils from "./AntlrUtils";
import { ParserRuleContext } from "antlr4ts/ParserRuleContext";

export default class CqlAntlrListener implements cqlListener {
// save bufferedTokenStream from lexer
Expand Down Expand Up @@ -107,30 +109,48 @@ export default class CqlAntlrListener implements cqlListener {
this.cqlResult.context = new CqlContextCreator(ctx).buildDao();
}

enterExpressionDefinition(ctx: ExpressionDefinitionContext): void {
const cqlExpressionCreator = new CqlExpressionDefinitionCreator(ctx);
const expressionDefinition: CqlExpressionDefinition | undefined = cqlExpressionCreator.buildDao();

if (expressionDefinition) {
if (ctx.start.inputStream) {
const hiddenTokens = this.bufferedTokenStream.getHiddenTokensToLeft(ctx.start.tokenIndex, cqlLexer.HIDDEN)
let comment = "";
hiddenTokens.forEach((token) => {
if (token.text){
comment += token.text;
}
})
comment = comment.trim();
if (comment){
// if expression has comment, start needs to be adjusted to consider comments as comment is part of definition
expressionDefinition.start = cqlExpressionCreator.buildLineInfo(hiddenTokens[1]);
private processDefinitionWithComments(
ctx: ParserRuleContext,
buildDao: () => CqlExpressionDefinition | undefined
): void {
const expressionDefinition: CqlExpressionDefinition | undefined =
buildDao();

if (expressionDefinition && ctx.start.inputStream) {
const hiddenTokens = this.bufferedTokenStream.getHiddenTokensToLeft(
ctx.start.tokenIndex,
cqlLexer.HIDDEN
);

if (hiddenTokens && hiddenTokens.length > 0) {
const comment = hiddenTokens
.map((token) => token.text?.trim())
.filter(Boolean)
.join(" ");

if (comment) {
expressionDefinition.start = new CqlExpressionDefinitionCreator(
ctx
).buildLineInfo(hiddenTokens[1]);
expressionDefinition.comment = AntlrUtils.formatComment(comment);
}
}
this.cqlResult.expressionDefinitions.push(expressionDefinition);
}
}

enterExpressionDefinition(ctx: ExpressionDefinitionContext): void {
this.processDefinitionWithComments(ctx, () => {
return new CqlExpressionDefinitionCreator(ctx).buildDao();
});
}

enterFunctionDefinition(ctx: FunctionDefinitionContext): void {
this.processDefinitionWithComments(ctx, () => {
return new CqlExpressionDefinitionCreator(ctx).buildDao();
});
}

enterAggregateClause(ctx: AggregateClauseContext): void {
const identifier: CqlIdentifier | undefined = new CqlIdentifierCreator(
ctx
Expand Down
26 changes: 18 additions & 8 deletions test/CqlAntlr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("test antlr", () => {
expect(cqlResult.valueSets.length).toBe(0);
expect(cqlResult.codeSystems.length).toBe(0);
expect(cqlResult.parameters.length).toBe(0);
expect(cqlResult.expressionDefinitions.length).toEqual(0)
expect(cqlResult.expressionDefinitions.length).toEqual(0);
});

it("parse blank CQL", () => {
Expand All @@ -30,7 +30,7 @@ describe("test antlr", () => {
expect(cqlResult.valueSets.length).toBe(0);
expect(cqlResult.codeSystems.length).toBe(0);
expect(cqlResult.parameters.length).toBe(0);
expect(cqlResult.expressionDefinitions.length).toEqual(0)
expect(cqlResult.expressionDefinitions.length).toEqual(0);
});

it("parse simple Fhir CQL Definition", () => {
Expand All @@ -45,7 +45,7 @@ describe("test antlr", () => {

expect(cqlResult.parameters.length).toBe(0);

expect(cqlResult.expressionDefinitions.length).toEqual(4);
expect(cqlResult.expressionDefinitions.length).toEqual(6);
cqlResult.expressionDefinitions.forEach((def) => {
expect(def.name).toBeDefined();
});
Expand All @@ -54,11 +54,21 @@ describe("test antlr", () => {
const cqlAntlr = new CqlAntlr(simpleDefinitionCql);
const cqlResult: CqlResult = cqlAntlr.parse();
const expressions = cqlResult.expressionDefinitions;
expect(expressions.length).toEqual(4);
expect(cqlResult.expressionDefinitions[0].comment).toEqual("ehnicity comment");
expect(expressions.length).toEqual(6);
expect(cqlResult.expressionDefinitions[0].comment).toEqual(
"ehnicity comment"
);
expect(cqlResult.expressionDefinitions[1].comment).toEqual("multi line");
expect(cqlResult.expressionDefinitions[2].comment).toEqual("@author: john doe\n@description: this is Numerator");
expect(cqlResult.expressionDefinitions[2].comment).toEqual(
"@author: john doe\n@description: this is Numerator"
);
expect(cqlResult.expressionDefinitions[3].comment).toEqual(undefined);
expect(cqlResult.expressionDefinitions[4].comment).toEqual(
"multiline comment outside of a function with multiple\nrows"
);
expect(cqlResult.expressionDefinitions[5].comment).toEqual(
"comment outside of function"
);
});
it("parse fhir cql", () => {
const cqlAntlr = new CqlAntlr(fhirTestCql);
Expand Down Expand Up @@ -88,11 +98,11 @@ describe("test antlr", () => {
expect(cqlResult.usings[0]?.name).toBe("QDM");
expect(cqlResult.valueSets.length).toBe(2);
expect(cqlResult.valueSets[0].name).toBe(
"\"Adolescent depression screening assessment\""
'"Adolescent depression screening assessment"'
);
expect(cqlResult.valueSets[0].version).toBeUndefined();
expect(cqlResult.valueSets[1].name).toBe(
"\"Adolescent depression screening assessment with version\""
'"Adolescent depression screening assessment with version"'
);
expect(cqlResult.valueSets[1].version).toBe("'urn:hl7:version:20240307'");
});
Expand Down
12 changes: 6 additions & 6 deletions test/CqlExpressionVisitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ describe("test visitor", () => {
cqlResult.includes.push(createInclude("FHIRHelpers"));
cqlResult.includes.push(createInclude("Global"));

cqlResult.valueSets.push(createValueSet("\"Patient Refusal\""));
cqlResult.valueSets.push(createValueSet("\"Medical Reason\""));
cqlResult.valueSets.push(createValueSet("\"Antithrombotic Therapy\""));
cqlResult.valueSets.push(createValueSet('"Patient Refusal"'));
cqlResult.valueSets.push(createValueSet('"Medical Reason"'));
cqlResult.valueSets.push(createValueSet('"Antithrombotic Therapy"'));

const cqlExpressionVisitor = new CqlExpressionVisitor(cqlResult);
cqlExpressionVisitor.visit(createAntlrContext(testDefineWithAlias));
Expand Down Expand Up @@ -101,9 +101,9 @@ describe("test visitor", () => {
cqlResult.includes.push(createInclude("FHIRHelpers"));
cqlResult.includes.push(createInclude("Global"));

cqlResult.valueSets.push(createValueSet("\"Patient Refusal\""));
cqlResult.valueSets.push(createValueSet("\"Medical Reason\""));
cqlResult.valueSets.push(createValueSet("\"Antithrombotic Therapy\""));
cqlResult.valueSets.push(createValueSet('"Patient Refusal"'));
cqlResult.valueSets.push(createValueSet('"Medical Reason"'));
cqlResult.valueSets.push(createValueSet('"Antithrombotic Therapy"'));

const v = new CqlExpressionVisitor(cqlResult);
v.visit(createAntlrContext(sdeValueset));
Expand Down
13 changes: 13 additions & 0 deletions test/testCql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ const simpleDefinitionCql = `
( ["MedicationAdministration": medication in "Low Dose Unfractionated Heparin for VTE Prophylaxis"] VTEMedication
where VTEMedication.status = 'completed'
)

/*
multiline comment outside of a function with multiple
rows
*/

define function "Denominator Observation"(Encounter "Encounter, Performed" ):
// inside of definition to ignore
duration in hours of Encounter.relevantPeriod

// comment outside of function
define function "Numerator Observation"(Encounter "Encounter, Performed" ):
duration in hours of Encounter.relevantPeriod
`;

const fhirTestCql = `library TJCOverall_FHIR4 version '4.0.000'
Expand Down
Loading