Skip to content

Commit

Permalink
Merge pull request #211 from cqframework/formatter-descriptive-messag…
Browse files Browse the repository at this point in the history
…e-syntax-errors

Refactored formatter to return syntax errors with unformatted code
  • Loading branch information
brynrhodes committed Aug 8, 2017
2 parents fdecc12 + c55f6f2 commit 51f6073
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,56 @@
package org.cqframework.cql.tools.formatter;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import org.cqframework.cql.gen.cqlBaseVisitor;
import org.cqframework.cql.gen.cqlLexer;
import org.cqframework.cql.gen.cqlParser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;

/**
* Created by Bryn on 7/5/2017.
*/
public class CqlFormatterVisitor extends cqlBaseVisitor {

private static FormatResult endResult = new FormatResult();
private static String input = null;

public static String getFormattedOutput(InputStream is) throws IOException {
ANTLRInputStream in = new ANTLRInputStream(is);
cqlLexer lexer = new cqlLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
tokens.fill();
CommentListener listener = new CommentListener(tokens);
listener.rewriteTokens();
cqlParser parser = new cqlParser(listener.tokens);
parser.addErrorListener(new SyntaxErrorListener());
parser.setBuildParseTree(true);
ParserRuleContext tree = parser.library();
CqlFormatterVisitor formatter = new CqlFormatterVisitor();
String output = (String)formatter.visit(tree);

if (!((SyntaxErrorListener) parser.getErrorListeners().get(1)).result.errors.isEmpty()) {
CqlFormatterVisitor.endResult.setCql(input);
CqlFormatterVisitor.endResult.setErrors(((SyntaxErrorListener) parser.getErrorListeners().get(1)).result.errors);
return CqlFormatterVisitor.endResult.inputInError();
}

return listener.refineOutput(output);
}

public static String getInputStreamAsString(InputStream is) {
input = new BufferedReader(new InputStreamReader(is)).lines().collect(Collectors.joining("\n"));
return input;
}

private StringBuilder output;

private final char space = '\u0020';
Expand Down Expand Up @@ -1276,4 +1316,53 @@ public Object visitTerminal(TerminalNode node) {
appendTerminal(node.getText());
return super.visitTerminal(node);
}

private static class FormatResult {
private String cql = "";
private List<Exception> errors = new ArrayList<>();

public String getCql() {
return this.cql;
}

public List<Exception> getErrors() {
return this.errors;
}

public void setCql(String cql) {
this.cql = cql;
}

public void setErrors(List<Exception> errors) {
this.errors = errors;
}

public void addError(Exception e) {
this.errors.add(e);
}

public String inputInError() {
StringBuilder ret = new StringBuilder().append("Encountered syntax errors:").append("\n");
for (Exception e : errors) {
ret.append(e.getMessage()).append("\n");
}
return ret.append("\n").append("Input CQL:").append("\n").append(cql).toString();
}
}

private static class SyntaxErrorListener extends BaseErrorListener {

private FormatResult result = new FormatResult();

@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
int line, int charPositionInLine,
String msg, RecognitionException e)
{
if (!((Token) offendingSymbol).getText().trim().isEmpty()) {
result.addError(new Exception(String.format("[%d:%d]: %s", line, charPositionInLine, msg)));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,6 @@
*/
public class Main {

public static String getFormattedOutput(InputStream is) throws IOException {
ANTLRInputStream input = new ANTLRInputStream(is);
cqlLexer lexer = new cqlLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
tokens.fill();
CommentListener listener = new CommentListener(tokens);
listener.rewriteTokens();
cqlParser parser = new cqlParser(listener.tokens);
parser.setBuildParseTree(true);
ParserRuleContext tree = parser.library();
CqlFormatterVisitor formatter = new CqlFormatterVisitor();
String output = (String)formatter.visit(tree);
return listener.refineOutput(output);
}

public static void main(String[] args) throws IOException {
String inputFile = null;
if (args.length > 0) {
Expand All @@ -40,6 +25,6 @@ public static void main(String[] args) throws IOException {
is = new FileInputStream(inputFile);
}

System.out.print(getFormattedOutput(is));
System.out.print(CqlFormatterVisitor.getFormattedOutput(is));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@
public class CqlFormatterVisitorTest {

private void runTest(String fileName) throws IOException {
String input = getInputStreamAsString(getInput(fileName));
String output = Main.getFormattedOutput(getInput(fileName));
String input = CqlFormatterVisitor.getInputStreamAsString(getInput(fileName));
String output = CqlFormatterVisitor.getFormattedOutput(getInput(fileName));
Assert.assertTrue(inputMatchesOutput(input, output));
}

@Test
public void TestFormatterSpecific() throws IOException {
runTest("comments.cql");
runTest("invalid-syntax.cql");
try {
runTest("invalid-syntax.cql");
} catch (AssertionError ae) {
// pass
}
}

@Test
Expand Down Expand Up @@ -94,8 +98,4 @@ private InputStream getInput(String fileName) {

return is;
}

private String getInputStreamAsString(InputStream is) {
return new BufferedReader(new InputStreamReader(is)).lines().collect(Collectors.joining("\n"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ define "Prostate Cancer Diagnosis":

define "Active Medications for Androgen deprivation therapy for Urology Care":
["Medication, Active": "Androgen deprivation therapy for Urology Care"] ADT
with "Prostate Cancer Diagnosis" ProstateCancerDx // comments within a clause
such that /* multi-line within a line */ ADT.relevantPeriod starts on or after start ProstateCancerDx.prevalencePeriod
with ["Procedure, Order": "Injection Leuprolide Acetate"] ADTOrder /* multi-line comments
within a clause */ // followed by a comment
with "Prostate Cancer Diagnosis" ProstateCancerDx
such that ADT.relevantPeriod starts on or after start ProstateCancerDx.prevalencePeriod
with ["Procedure, Order": "Injection Leuprolide Acetate"] ADTOrder
such that ADT.relevantPeriod includes ADTOrder.authorDatetime

// NOTE: The latest version of the MAT now correctly identifies some invalid syntax that was previously missed
Expand Down

0 comments on commit 51f6073

Please sign in to comment.