Skip to content

Commit 7ff280d

Browse files
committed
GR-9831: format the message for Syntax error to add more context akin to how cpython does it
1 parent f4c8b6d commit 7ff280d

File tree

2 files changed

+105
-0
lines changed

2 files changed

+105
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright (c) 2018, Oracle and/or its affiliates.
3+
*
4+
* The Universal Permissive License (UPL), Version 1.0
5+
*
6+
* Subject to the condition set forth below, permission is hereby granted to any
7+
* person obtaining a copy of this software, associated documentation and/or data
8+
* (collectively the "Software"), free of charge and under any and all copyright
9+
* rights in the Software, and any and all patent rights owned or freely
10+
* licensable by each licensor hereunder covering either (i) the unmodified
11+
* Software as contributed to or provided by such licensor, or (ii) the Larger
12+
* Works (as defined below), to deal in both
13+
*
14+
* (a) the Software, and
15+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
* one is included with the Software (each a "Larger Work" to which the
17+
* Software is contributed by such licensors),
18+
*
19+
* without restriction, including without limitation the rights to copy, create
20+
* derivative works of, display, perform, and distribute the Software and make,
21+
* use, sell, offer for sale, import, export, have made, and have sold the
22+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
* either these or other terms.
24+
*
25+
* This license is subject to the following condition:
26+
*
27+
* The above copyright notice and either this complete permission notice or at a
28+
* minimum a reference to the UPL must be included in all copies or substantial
29+
* portions of the Software.
30+
*
31+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37+
* SOFTWARE.
38+
*/
39+
package com.oracle.graal.python.parser;
40+
41+
import org.antlr.v4.runtime.DefaultErrorStrategy;
42+
import org.antlr.v4.runtime.InputMismatchException;
43+
import org.antlr.v4.runtime.Parser;
44+
import org.antlr.v4.runtime.ParserRuleContext;
45+
import org.antlr.v4.runtime.RecognitionException;
46+
import org.antlr.v4.runtime.Token;
47+
import org.antlr.v4.runtime.TokenStream;
48+
import org.antlr.v4.runtime.misc.Interval;
49+
50+
public class PythonErrorStrategy extends DefaultErrorStrategy {
51+
private static final String LINE_PADDING = " ";
52+
53+
@Override
54+
public void recover(Parser recognizer, RecognitionException e) {
55+
super.recover(recognizer, e);
56+
}
57+
58+
private String getTokeLineText(Parser recognizer, Token token) {
59+
TokenStream tokenStream = recognizer.getTokenStream();
60+
int index = token.getTokenIndex();
61+
// search for line start
62+
int tokenIndex = index - 1;
63+
int start = -1;
64+
while (tokenIndex >= 0) {
65+
Token t = tokenStream.get(tokenIndex);
66+
if (t.getText().equals("\n")) {
67+
break;
68+
}
69+
start = t.getStartIndex();
70+
tokenIndex--;
71+
}
72+
73+
// search for line stop
74+
tokenIndex = index + 1;
75+
int stop = -1;
76+
while (tokenIndex < tokenStream.size()) {
77+
Token t = tokenStream.get(tokenIndex);
78+
stop = t.getStopIndex();
79+
if (t.getText().equals("\n")) {
80+
break;
81+
}
82+
tokenIndex++;
83+
}
84+
85+
return token.getInputStream().getText(Interval.of(start, stop));
86+
}
87+
88+
@Override
89+
protected void reportInputMismatch(Parser recognizer, InputMismatchException e) {
90+
Token offendingToken = e.getOffendingToken();
91+
String lineText = getTokeLineText(recognizer, offendingToken);
92+
String errorMarker = new String(new char[offendingToken.getCharPositionInLine() + 1]).replace('\0', ' ') + "^";
93+
String pythonSyntaxErrorMessage = "\n" + LINE_PADDING + lineText + "\n" + LINE_PADDING + errorMarker;
94+
95+
RecognitionException re = e;
96+
// the error message in some cases is not set properly producing an annoying "null" in the
97+
// final message, we replace the error with a copy of itself but, no message
98+
if (re.getMessage() == null) {
99+
re = new RecognitionException("", e.getRecognizer(), e.getInputStream(), (ParserRuleContext) e.getCtx());
100+
}
101+
recognizer.notifyErrorListeners(offendingToken, pythonSyntaxErrorMessage, re);
102+
}
103+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/PythonParserImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static com.oracle.graal.python.runtime.exception.PythonErrorType.SyntaxError;
2929

3030
import java.util.HashMap;
31+
import java.util.InputMismatchException;
3132
import java.util.Map;
3233
import java.util.regex.Pattern;
3334

@@ -63,6 +64,7 @@ private static ParserRuleContext preParseWithAntlr(PythonCore core, Source sourc
6364
String fileDirAndName = pathParts[pathParts.length - 2] + PythonCore.FILE_SEPARATOR + pathParts[pathParts.length - 1];
6465
CodePointCharStream fromString = CharStreams.fromString(source.getCharacters().toString(), fileDirAndName);
6566
Python3Parser parser = new com.oracle.graal.python.parser.antlr.Builder.Parser(fromString).build();
67+
parser.setErrorHandler(new PythonErrorStrategy());
6668
ParserRuleContext input;
6769
if (!core.isInitialized()) {
6870
input = cachedParseTrees.get(fileDirAndName);

0 commit comments

Comments
 (0)