Skip to content

Commit 7ae65a9

Browse files
committed
[GR-20784] Fix interactive multi-line continuation
PullRequest: graalpython/884
2 parents 208a0d5 + 82d08f9 commit 7ae65a9

File tree

8 files changed

+460
-317
lines changed

8 files changed

+460
-317
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/DescriptiveBailErrorListener.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -41,11 +41,15 @@
4141
package com.oracle.graal.python.parser.antlr;
4242

4343
import org.antlr.v4.runtime.BaseErrorListener;
44+
import org.antlr.v4.runtime.CharStream;
45+
import org.antlr.v4.runtime.ParserRuleContext;
4446
import org.antlr.v4.runtime.RecognitionException;
4547
import org.antlr.v4.runtime.Recognizer;
4648
import org.antlr.v4.runtime.Token;
49+
import org.antlr.v4.runtime.TokenStream;
4750
import org.antlr.v4.runtime.misc.IntervalSet;
4851

52+
import com.oracle.graal.python.parser.antlr.Python3Parser.Single_inputContext;
4953
import com.oracle.graal.python.runtime.PythonParser.PIncompleteSourceException;
5054
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
5155

@@ -74,6 +78,12 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
7478
throw handleRecognitionException;
7579
}
7680
}
81+
if (isInteractive(recognizer)) {
82+
PIncompleteSourceException handleRecognitionException = handleInteractiveException(recognizer, offendingSymbol);
83+
if (handleRecognitionException != null) {
84+
throw handleRecognitionException;
85+
}
86+
}
7787
if (offendingSymbol instanceof Token) {
7888
throw new RuntimeException(entireMessage, new EmptyRecognitionException(entireMessage, recognizer, (Token) offendingSymbol));
7989
}
@@ -87,6 +97,52 @@ private static PIncompleteSourceException handleRecognitionException(IntervalSet
8797
return null;
8898
}
8999

100+
private static PIncompleteSourceException handleInteractiveException(Recognizer<?, ?> recognizer, Object offendingSymbol) {
101+
if (isOpened(((Python3Parser) recognizer).getTokenStream()) || isBackslash(offendingSymbol)) {
102+
return new PIncompleteSourceException("", null, -1);
103+
}
104+
return null;
105+
}
106+
107+
private static ParserRuleContext getRootCtx(ParserRuleContext ctx) {
108+
ParserRuleContext c = ctx;
109+
while (c.getParent() != null) {
110+
c = c.getParent();
111+
}
112+
return c;
113+
}
114+
115+
private static boolean isInteractive(Recognizer<?, ?> recognizer) {
116+
if (!(recognizer instanceof Python3Parser)) {
117+
return false;
118+
}
119+
ParserRuleContext ctx = getRootCtx(((Python3Parser) recognizer).getContext());
120+
if (ctx instanceof Single_inputContext) {
121+
return ((Single_inputContext) ctx).interactive;
122+
}
123+
return false;
124+
}
125+
126+
/**
127+
* @return true if there are an open '(', '[' or '{'.
128+
*/
129+
private static boolean isOpened(TokenStream input) {
130+
final Python3Lexer lexer = (Python3Lexer) input.getTokenSource();
131+
return lexer.isOpened();
132+
}
133+
134+
private static final int BACKSLASH = '\\';
135+
136+
private static boolean isBackslash(Object offendingSymbol) {
137+
if (offendingSymbol instanceof Token) {
138+
CharStream cs = ((Token) offendingSymbol).getInputStream();
139+
if (cs.size() > 2 && cs.LA(-2) == BACKSLASH) {
140+
return true;
141+
}
142+
}
143+
return false;
144+
}
145+
90146
private static class EmptyRecognitionException extends RecognitionException {
91147
private static final long serialVersionUID = 1L;
92148
private Token offendingToken;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/Python3.g4

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ tokens { INDENT, DEDENT }
5151
// wether we have expanded EOF to include necessary DEDENTS and a NEWLINE
5252
private boolean expandedEOF = false;
5353
54+
private boolean longQuote1 = false; // """
55+
private boolean longQuote2 = false; // '''
56+
5457
@Override
5558
public void emit(Token t) {
5659
super.setToken(t);
@@ -92,6 +95,22 @@ tokens { INDENT, DEDENT }
9295
return tokens.isEmpty() ? next : tokens.poll();
9396
}
9497

98+
public boolean isOpened() {
99+
return this.opened > 0 || this.longQuote1 || this.longQuote2;
100+
}
101+
102+
private void usedQuote1() {
103+
if (!this.longQuote2){
104+
this.longQuote1 = !this.longQuote1;
105+
}
106+
}
107+
108+
private void usedQuote2() {
109+
if (!this.longQuote1){
110+
this.longQuote2 = !this.longQuote2;
111+
}
112+
}
113+
95114
private Token createDedent() {
96115
CommonToken dedent = commonToken(Python3Parser.DEDENT, "");
97116
dedent.setLine(this.lastToken.getLine());
@@ -1797,6 +1816,8 @@ LEFT_SHIFT_ASSIGN : '<<=';
17971816
RIGHT_SHIFT_ASSIGN : '>>=';
17981817
POWER_ASSIGN : '**=';
17991818
IDIV_ASSIGN : '//=';
1819+
LONG_QUOTES1 : '"""' {usedQuote1();};
1820+
LONG_QUOTES2 : '\'\'\'' {usedQuote2();};
18001821

18011822
SKIP_
18021823
: ( SPACES | COMMENT | LINE_JOINING ) -> skip

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/Python3.interp

Lines changed: 5 additions & 1 deletion
Large diffs are not rendered by default.

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/Python3.tokens

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,12 @@ LEFT_SHIFT_ASSIGN=90
9191
RIGHT_SHIFT_ASSIGN=91
9292
POWER_ASSIGN=92
9393
IDIV_ASSIGN=93
94-
SKIP_=94
95-
UNKNOWN_CHAR=95
96-
INDENT=96
97-
DEDENT=97
94+
LONG_QUOTES1=94
95+
LONG_QUOTES2=95
96+
SKIP_=96
97+
UNKNOWN_CHAR=97
98+
INDENT=98
99+
DEDENT=99
98100
'def'=2
99101
'return'=3
100102
'raise'=4
@@ -177,3 +179,5 @@ DEDENT=97
177179
'>>='=91
178180
'**='=92
179181
'//='=93
182+
'"""'=94
183+
'\'\'\''=95

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/parser/antlr/Python3Lexer.interp

Lines changed: 7 additions & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)