File: error-handling.md

package info (click to toggle)
javacc 7.0.13-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,712 kB
  • sloc: java: 27,671; xml: 2,305; cpp: 404; sh: 128; makefile: 24
file content (173 lines) | stat: -rw-r--r-- 6,540 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
[Home](../index.md) > [Tutorials](index.md) > 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:

```java
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:

```java
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:

```java
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):

```java
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:

```java
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:

```java
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:

```java
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.

<br>

---

[NEXT >>](lexer-tips.md)

<br>