Skip to content

Commit 0e04861

Browse files
committed
Active parser error handling and recovery
1 parent 0610122 commit 0e04861

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-5
lines changed

server/src/project/parser/vbaAntlr.ts

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Antlr
2-
import { CharStream, CommonTokenStream, Token, TokenStream } from 'antlr4ng';
2+
import { CharStream, CommonTokenStream, InputMismatchException, IntervalSet, Token, TokenStream } from 'antlr4ng';
33
import { DefaultErrorStrategy, Parser, RecognitionException } from 'antlr4ng';
44
import { vbaLexer } from '../../antlr/out/vbaLexer';
55
import { vbaParser } from '../../antlr/out/vbaParser';
@@ -89,12 +89,80 @@ export class VbaFmtParser extends vbafmtParser {
8989

9090

9191
export class VbaErrorHandler extends DefaultErrorStrategy {
92-
recover(recognizer: Parser, e: RecognitionException): void {
92+
override recover(recognizer: Parser, e: RecognitionException): void {
9393
// Consume the error token if look-ahead is not EOF.
9494
const inputStream = recognizer.inputStream;
95-
if (inputStream.LA(1) === Token.EOF) {
95+
if (inputStream.LA(1) !== Token.EOF) {
9696
inputStream.consume();
9797
}
9898
this.endErrorCondition(recognizer);
9999
}
100+
101+
override recoverInline(recognizer: Parser): Token {
102+
const stream = recognizer.inputStream;
103+
const thisToken = recognizer.getCurrentToken();
104+
105+
// Recover using deletion strategy.
106+
const nextToken = stream.LT(2);
107+
const expectedTokens = recognizer.getExpectedTokens();
108+
if (nextToken && expectedTokens.contains(nextToken.type)) {
109+
recognizer.consume();
110+
this.reportMatch(recognizer);
111+
return thisToken;
112+
}
113+
114+
// Failsafe to prevent circular insertions.
115+
const MAXRECURSION = -20
116+
for (let i = -1; i >= MAXRECURSION; i--) {
117+
if (i <= -20) {
118+
throw new InputMismatchException(recognizer);
119+
}
120+
const wasInsertedToken = this.isTokenPositionMatch(thisToken, recognizer.inputStream.LT(i));
121+
if (!wasInsertedToken) {
122+
break;
123+
}
124+
}
125+
126+
// Recover using insertion strategy.
127+
const missingToken = this.createErrorToken(recognizer, expectedTokens);
128+
this.reportMatch(recognizer);
129+
return missingToken;
130+
}
131+
132+
private createErrorToken(recognizer: Parser, expectedTokens: IntervalSet): Token {
133+
// Set up the token attributes.
134+
const type = expectedTokens.length === 0
135+
? Token.INVALID_TYPE
136+
: expectedTokens.minElement;
137+
138+
const expectedIdentifiers = expectedTokens.toArray().map(
139+
t => recognizer.vocabulary.getLiteralName(t)
140+
?? recognizer.vocabulary.getDisplayName(t)
141+
);
142+
const plural = expectedIdentifiers.length > 1 ? 's' : '';
143+
const expectedText = expectedIdentifiers.join(', ');
144+
const text = `<missing token${plural} ${expectedText}>`;
145+
const currentToken = recognizer.getCurrentToken();
146+
147+
// Create the token.
148+
return recognizer.getTokenFactory().create(
149+
[
150+
recognizer.tokenStream.tokenSource,
151+
recognizer.tokenStream.tokenSource.inputStream
152+
],
153+
type,
154+
text,
155+
Token.DEFAULT_CHANNEL,
156+
currentToken.start,
157+
currentToken.stop,
158+
currentToken.line,
159+
currentToken.column
160+
)
161+
}
162+
163+
private isTokenPositionMatch(a: Token | null, b: Token | null): boolean {
164+
return !!a && !!b
165+
&& a.line === b.line
166+
&& a.column === b.column;
167+
}
100168
}

server/src/project/parser/vbaListener.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import {
4343
DocumentElementContext,
4444
IndentAfterElementContext,
4545
LabelStatementContext,
46-
LineEndingContext,
4746
MethodParametersContext,
4847
OutdentBeforeElementContext,
4948
OutdentOnIndentAfterElementContext,

server/src/project/parser/vbaParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Antlr
22
import { ParseCancellationException, ParseTreeWalker } from 'antlr4ng';
3-
import { VbaFmtParser, VbaParser, VbaPreParser } from './vbaAntlr';
3+
import { VbaErrorHandler, VbaFmtParser, VbaParser, VbaPreParser } from './vbaAntlr';
44
import { VbaFmtListener, VbaListener, VbaPreListener } from './vbaListener';
55

66
// Project

0 commit comments

Comments
 (0)