Home > Tutorials > Error Handling


This tutorial describes the error recovery features in JavaCC.

Exception Classes

JavaCC supports two exception types:

Exception Type Description
TokenMgrError Whenever the token manager detects a problem, it throws the exception TokenMgrError.
ParseException Whenever the parser detects a problem, it throws the exception ParseException.

TokenMgrError is a subclass of Error while ParseException is a subclass of Exception.

The reasoning here is that the token manager is never expected to throw an exception - you must be careful in defining your token specifications such that you cover all cases. Hence the suffix Error in TokenMgrError. You do not have to worry about this exception - if you have designed your tokens well, it should never get thrown.

Conversely, it is typical to attempt recovery from Parser errors. If you still want to recover from token manager errors you can do it - it’s just that you are not forced to catch them.

The JavaCC grammar specification includes a syntax to specify additional exceptions that may be thrown by methods corresponding to non-terminals. This syntax is identical to the Java throws ... syntax.

Here is an example of how to use this:

void VariableDeclaration() throws SymbolTableException, IOException :
{...}
{
  ...
}

VariableDeclaration is defined to throw exceptions SymbolTableException and IOException in addition to ParseException.

Error Reporting

The scheme for error reporting is straightforward - simply modify the file ParseException.java for your purposes. Typically, you should modify the getMessage() method to do your own customized error reporting. All information regarding these methods can be obtained from the comments in the generated files ParseException.java and TokenMgrError.java. It will also help to understand the standard Java functionality of the class Throwable.

There is a method in the generated parser called generateParseException(). You can call this method anytime you wish to generate an object of type ParseException. This object will contain all the choices that the parser has attempted since the last successfully consumed token.

Error Recovery

JavaCC offers two kinds of error recovery - shallow recovery and deep recovery.

Shallow recovery recovers if none of the current choices have succeeded in being selected, while deep recovery is when a choice is selected but then an error happens sometime during the parsing of this choice.

Shallow error recovery

Consider the following example:

void Stm() :
{}
{
  IfStm()
|
  WhileStm()
}

Let’s assume that IfStm starts with the reserved word if and WhileStm starts with the reserved word while. Suppose you want to recover by skipping all the way to the next semicolon when neither IfStm nor WhileStm can be matched by the next input token (assuming a LOOKAHEAD of 1). That is, the next token is neither if nor while.

You can specify the following:

void Stm() :
{}
{
  IfStm()
|
  WhileStm()
|
  error_skipto(SEMICOLON)
}

But you have to define error_skipto first. So far as JavaCC is concerned, error_skipto is just like any other non-terminal.

The following is one way to define error_skipto (here we use the standard JAVACODE production):

JAVACODE
void error_skipto(int kind) {
  ParseException e = generateParseException();  // generate the exception object
  System.out.println(e.toString());  // print the error message
  Token t;
  // consume tokens all the way up to a token of "kind" - use a do-while loop
  // rather than a while because the current token is the one immediately before
  // the erroneous token (in our case the token immediately before what should
  // have been "if"/"while".
  do {
    t = getNextToken();
  }
  while (t.kind != kind);
}

That’s it for shallow error recovery. In a future version of JavaCC we will have support for modular composition of grammars. When this happens, one can place all these error recovery routines into a separate module that can be imported into the main grammar module. We intend to supply a library of useful routines (for error recovery and otherwise) when we implement this capability.

Deep Error Recovery

Using the same example as for shallow recovery:

void Stm() :
{}
{
  IfStm()
|
  WhileStm()
}

In this case we wish to recover in the same way, however we wish to recover even when there is an error deeper into the parse.

For example, suppose the next token was while - therefore the choice WhileStm was taken. But suppose that during the parse of WhileStm some error is encountered - say we have while (foo { stm; } i.e. the closing parentheses has been missed. Shallow recovery will not work for this situation, we need deep recovery to achieve this. For this, we offer a new syntactic entity in JavaCC - the try-catch-finally block.

First, let us refactor the above example for deep error recovery and then explain the try-catch-finally block in more detail:

void Stm() :
{}
{
  try {
    (
      IfStm()
    |
      WhileStm()
    )
  }
  catch (ParseException e) {
    error_skipto(SEMICOLON);
  }
}

That’s it. If there is any unrecovered error during the parse of IfStm or WhileStm, then the catch block takes over. You can have any number of catch blocks and optionally a finally block (just as with Java errors). What goes into the catch blocks is Java code, not JavaCC expansions.

For example, the above example could have been rewritten as:

void Stm() :
{}
{
  try {
    (
      IfStm()
    |
      WhileStm()
    )
  }
  catch (ParseException e) {
    System.out.println(e.toString());
    Token t;
    do {
      t = getNextToken();
    } while (t.kind != SEMICOLON);
  }
}

It is best to avoid placing too much Java code in the catch and finally blocks since it overwhelms the grammar reader - it is best to define methods that you can then from the catch blocks.

Note that in the second version of the example, we essentially copied the code out of the implementation of error_skipto. But we left out the first statement - the call to generateParseException(). In this case, the catch block already provides us with the exception. Even if you did call this method, you will get back an identical object.



NEXT »