Belgacem
Belgacem

Reputation: 193

Antlr 4 does not give the correct line and position of the error

I have a problem with error handling with Antlr 4 , when I call the method notifyErrorListeners("msg") in my custom listener Antlr does not give me the exact line and position of the error. Here is part of my dsl.

study( region = "region" , timestamp = 12:10 , processType = "processType")
...

My problem is that the grammar accepts the duplication of variables in the study , for example.

study( region = "region" , region = "region" , timestamp = 12:10)
...

So as a solution I decide to manage this in my custom listener .Here is my listener

@Getter
public class StudyPatternListener extends StudyBaseListener {

     private Map<String, Object> studyParams = new HashMap<>();

     @Setter
     private StudyParser parser;

     @Override
     public void enterStudyAssignProcessType(StudyAssignProcessTypeContext ctx) {
        String processType = ctx.STRING().getText().replace("\"", "");
        if (!this.studyParams.containsKey("processType")) {
            this.studyParams.put("processType", processType);
        } else {
            // here Antlr doesn't give me the right line and the right position of the error.
            parser.notifyErrorListeners("processType parameter is already assigned");
        }
    }

Here is my custom error listener.

public class StudyErrorListener extends BaseErrorListener {

 @Override
 public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,
                 String msg, RecognitionException e) {
        System.err.println("line = " + line + ", position = " + charPositionInLine + " , msg =  " + msg);
    }
}

Here is my grammar.

grammar Study;

program: main EOF;  // the program rule.
// LINE_COMMENT* 
main:  NEWLINE* study  NEWLINE* ; // the main rule;
study : 'study' '(' studyParameters  ')';
studyParameters: (| ( studyAssign (',' studyAssign)*) ); // assign in the study

studyAssign: 'timestamp'            '='  TIMESTAMP  # studyAssignTimestamp
           | 'region'               '='  STRING     # studyAssignRegion
           | 'processType'          '='  STRING     # studyAssignProcessType
           | 'businessDate'         '='  DATE       # studyAssignBusinessDate
           ;                                        // valid study parameters .

fragment LETTER : [A-Za-z];
fragment DIGIT : [0-9];
fragment TWODIGIT : DIGIT DIGIT;
TIMESTAMP: TWODIGIT ':' TWODIGIT; 
DATE : TWODIGIT TWODIGIT ;
ID : LETTER+; 
STRING : '"' ( ~ '"' )* '"' ;
NEWLINE:'\r'? '\n' ;
LINE_COMMENT: '#' ~[\r\n]* -> skip;
WS  :   [ \t]+ -> skip ; 

Upvotes: 1

Views: 382

Answers (1)

GRosenberg
GRosenberg

Reputation: 6001

Reporting back through the parser error listener during a validation tree walk is going to pick up the last position of the parser. Not what you want (the parser will have completed).

Need to dig the information from the context node and convert to a reasonably understandable form:

@Override
public void enterStudyAssignProcessType(StudyAssignProcessTypeContext ctx) {
    ... 
    // validate key/value pairs specified by an 'attribute' rule
    for (AttributeContext attribute : ctx.attribute()) {
        String id = attribute.id().getText();
        Props props = AttrMap.get(id);            // external map of allowed keys & values
        if (props.name.equals(KeyAttr.INVALID)) { // whatever error check(s)
            String cause = "Invalid name '" + id + "' at %s:%s[%s]";
            reportProblem(IMarker.SEVERITY_ERROR, Kind.ATTRIBUTE, attribute.id().name, cause);
        }
    }
}

private void reportProblem(int severity, Kind kind, String cause, Token... tokens) {
    Token beg = tokens[0];
    Token end = tokens[tokens.length - 1];
    errLine = beg.getLine();
    errPos = beg.getCharPositionInLine();
    errOffset = beg.getStartIndex();
    errLen = end.getStopIndex() - errOffset + 1;
    displayProblem(severity, kind, calcVisual(cause)); // your presentation
}

private String calcVisual(String cause) {
    int beg = errOffset - errPos;       // beg of line
    if (beg > 0) {
        String txt = Strings.EMPTY; // ""
        Interval range = Interval.of(beg, errOffset);
        try {
            txt = record.cs.getText(range);
            errVisPos = Strings.measureVisualWidth(txt, tabWidth); // adj for embedded tabs
        } catch (Exception e) {
            String name = ...; // source name
            Log.error(this, "Bad visual position %s: %s (%s: %s)", e.getMessage(), name, range, txt);
        }

        txt = Strings.EMPTY;
        range = Interval.of(beg, errOffset + errLen);
        try {
            txt = record.cs.getText(range);
            errVisLen = Strings.measureVisualWidth(txt, tabWidth) - errVisPos;
        } catch (Exception e) {
            String name = ...;
            Log.error(this, "Bad visual position %s: %s (%s: %s)", e.getMessage(), name, range, txt);
        }
    }

    return String.format(cause, errLine, errVisPos, errVisLen);
}

Upvotes: 1

Related Questions