Skip to content

Commit 7c098aa

Browse files
committed
[GR-14200] Implement f-string support.
PullRequest: graalpython/644
2 parents cb2403e + 0e7b78a commit 7c098aa

33 files changed

+1612
-92
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
2323
* Improve performance of Java interop when Python objects are accessed from Java
2424
* Add a new `--python.EmulateJython` flag to support importing Java classes using normal Python import syntax when the package is known and to catch Java exceptions from Python code
2525
* Update standard library to Python 3.7.4
26+
* Initial implementatin of PEP 498 -- Literal String Interpolation
2627

2728
## Version 19.2.0
2829

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
/*
2+
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
42+
package com.oracle.graal.python.nodes.literal;
43+
44+
import com.oracle.graal.python.runtime.PythonParser;
45+
import com.oracle.graal.python.test.parser.ParserTestBase;
46+
import com.oracle.truffle.api.Truffle;
47+
import com.oracle.truffle.api.frame.FrameDescriptor;
48+
import com.oracle.truffle.api.frame.VirtualFrame;
49+
import com.oracle.truffle.api.nodes.Node;
50+
import org.junit.Assert;
51+
import org.junit.Test;
52+
53+
public class FormatStringTests extends ParserTestBase {
54+
55+
@Test
56+
public void empty() throws Exception {
57+
testFormatString("f''", "");
58+
}
59+
60+
@Test
61+
public void justBraces() throws Exception {
62+
testFormatString("f'{{}}'", "{}");
63+
}
64+
65+
@Test
66+
public void doubleBrace01() throws Exception {
67+
testFormatString("f'{{name}}'", "{name}");
68+
}
69+
70+
@Test
71+
public void doubleBrace02() throws Exception {
72+
testFormatString("f'Hi {{name}}'", "Hi {name}");
73+
}
74+
75+
@Test
76+
public void doubleBrace03() throws Exception {
77+
testFormatString("f'{{name}} first'", "{name} first");
78+
}
79+
80+
@Test
81+
public void doubleBrace04() throws Exception {
82+
testFormatString("f'Hi {{name}} first'", "Hi {name} first");
83+
}
84+
85+
@Test
86+
public void doubleBrace05() throws Exception {
87+
testFormatString("f'{{'", "{");
88+
}
89+
90+
@Test
91+
public void doubleBrace06() throws Exception {
92+
testFormatString("f'a{{'", "a{");
93+
}
94+
95+
@Test
96+
public void doubleBrace07() throws Exception {
97+
testFormatString("f'{{b'", "{b");
98+
}
99+
100+
@Test
101+
public void doubleBrace08() throws Exception {
102+
testFormatString("f'a{{b'", "a{b");
103+
}
104+
105+
@Test
106+
public void doubleBrace09() throws Exception {
107+
testFormatString("f'}}'", "}");
108+
}
109+
110+
@Test
111+
public void doubleBrace10() throws Exception {
112+
testFormatString("f'a}}'", "a}");
113+
}
114+
115+
@Test
116+
public void doubleBrace11() throws Exception {
117+
testFormatString("f'}}b'", "}b");
118+
}
119+
120+
@Test
121+
public void doubleBrace12() throws Exception {
122+
testFormatString("f'a}}b'", "a}b");
123+
}
124+
125+
@Test
126+
public void doubleBrace13() throws Exception {
127+
testFormatString("f'{{}}'", "{}");
128+
}
129+
130+
@Test
131+
public void doubleBrace14() throws Exception {
132+
testFormatString("f'a{{}}'", "a{}");
133+
}
134+
135+
@Test
136+
public void doubleBrace15() throws Exception {
137+
testFormatString("f'{{b}}'", "{b}");
138+
}
139+
140+
@Test
141+
public void doubleBrace16() throws Exception {
142+
testFormatString("f'{{}}c'", "{}c");
143+
}
144+
145+
@Test
146+
public void doubleBrace17() throws Exception {
147+
testFormatString("f'a{{b}}'", "a{b}");
148+
}
149+
150+
@Test
151+
public void doubleBrace18() throws Exception {
152+
testFormatString("f'a{{}}c'", "a{}c");
153+
}
154+
155+
@Test
156+
public void doubleBrace19() throws Exception {
157+
testFormatString("f'{{b}}'", "{b}");
158+
}
159+
160+
@Test
161+
public void doubleBrace20() throws Exception {
162+
testFormatString("f'a{{b}}c'", "a{b}c");
163+
}
164+
165+
@Test
166+
public void doubleBrace21() throws Exception {
167+
testFormatString("f'{{{10}'", "{+format((10))");
168+
}
169+
170+
@Test
171+
public void doubleBrace22() throws Exception {
172+
testFormatString("f'}}{10}'", "}+format((10))");
173+
}
174+
175+
@Test
176+
public void doubleBrace23() throws Exception {
177+
testFormatString("f'}}{{{10}'", "}{+format((10))");
178+
}
179+
180+
@Test
181+
public void doubleBrace24() throws Exception {
182+
testFormatString("f'}}a{{{10}'", "}a{+format((10))");
183+
}
184+
185+
@Test
186+
public void doubleBrace25() throws Exception {
187+
testFormatString("f'{10}{{'", "format((10))+{");
188+
}
189+
190+
@Test
191+
public void doubleBrace26() throws Exception {
192+
testFormatString("f'{10}}}'", "format((10))+}");
193+
}
194+
195+
@Test
196+
public void doubleBrace27() throws Exception {
197+
testFormatString("f'{10}}}{{'", "format((10))+}{");
198+
}
199+
200+
@Test
201+
public void doubleBrace28() throws Exception {
202+
testFormatString("f'{10}}}a{{'", "format((10))+}a{");
203+
}
204+
205+
@Test
206+
public void quotes01() throws Exception {
207+
testFormatString("f'{\"{{}}\"}'", "format((\"{{}}\"))");
208+
}
209+
210+
@Test
211+
public void parser01() throws Exception {
212+
testFormatString("f'{name}'", "format((name))");
213+
}
214+
215+
@Test
216+
public void parser02() throws Exception {
217+
testFormatString("f'name'", "name");
218+
}
219+
220+
@Test
221+
public void parser03() throws Exception {
222+
testFormatString("f'First: {name}'", "First: +format((name))");
223+
}
224+
225+
@Test
226+
public void parser04() throws Exception {
227+
testFormatString("f'{name} was here'", "format((name))+ was here");
228+
}
229+
230+
@Test
231+
public void parser05() throws Exception {
232+
testFormatString("f'It {name} was'", "It +format((name))+ was");
233+
}
234+
235+
@Test
236+
public void str01() throws Exception {
237+
testFormatString("f'{name!s}'", "format(str((name)))");
238+
}
239+
240+
@Test
241+
public void repr01() throws Exception {
242+
testFormatString("f'{name!r}'", "format(repr((name)))");
243+
}
244+
245+
@Test
246+
public void ascii01() throws Exception {
247+
testFormatString("f'{name!a}'", "format(ascii((name)))");
248+
}
249+
250+
@Test
251+
public void emptyExpression01() throws Exception {
252+
checkSyntaxError("f'{}'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
253+
}
254+
255+
@Test
256+
public void emptyExpression02() throws Exception {
257+
checkSyntaxError("f'start{}end'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
258+
}
259+
260+
@Test
261+
public void emptyExpression03() throws Exception {
262+
checkSyntaxError("f'start{}}end'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
263+
}
264+
265+
@Test
266+
public void emptyExpression04() throws Exception {
267+
checkSyntaxError("f'start{{{}}}end'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
268+
}
269+
270+
@Test
271+
public void singleBracket01() throws Exception {
272+
checkSyntaxError("f'}'", FormatStringLiteralNode.ERROR_MESSAGE_SINGLE_BRACE);
273+
}
274+
275+
@Test
276+
public void singleBracket02() throws Exception {
277+
checkSyntaxError("f'start}end'", FormatStringLiteralNode.ERROR_MESSAGE_SINGLE_BRACE);
278+
}
279+
280+
@Test
281+
public void singleBracket03() throws Exception {
282+
checkSyntaxError("f'start{{}end'", FormatStringLiteralNode.ERROR_MESSAGE_SINGLE_BRACE);
283+
}
284+
285+
@Test
286+
public void spaces01() throws Exception {
287+
testFormatString("f'{ {}}'", "format(({}))");
288+
}
289+
290+
@Test
291+
public void spaces02() throws Exception {
292+
testFormatString("f'{ {} }'", "format(({}))");
293+
}
294+
295+
@Test
296+
public void innerExp01() throws Exception {
297+
testFormatString("f'result: {value:{width}.{precision}}'", "result: +format((value),(format((width))+\".\"+format((precision))))");
298+
}
299+
300+
@Test
301+
public void missingSpecifier01() throws Exception {
302+
testFormatString("f'{x:}'", "format((x))");
303+
}
304+
305+
@Test
306+
public void missingSpecifier02() throws Exception {
307+
testFormatString("f'{x!s:}'", "format(str((x)))");
308+
}
309+
310+
@Test
311+
public void missingExpression01() throws Exception {
312+
checkSyntaxError("f'{!x}'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
313+
}
314+
315+
@Test
316+
public void missingExpression02() throws Exception {
317+
checkSyntaxError("f'{ !x}'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
318+
}
319+
320+
@Test
321+
public void missingExpression03() throws Exception {
322+
checkSyntaxError("f'{ !xr:a}'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
323+
}
324+
325+
@Test
326+
public void missingExpression04() throws Exception {
327+
checkSyntaxError("f'{:x'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
328+
}
329+
330+
@Test
331+
public void missingExpression05() throws Exception {
332+
checkSyntaxError("f'{!'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
333+
}
334+
335+
@Test
336+
public void missingExpression06() throws Exception {
337+
checkSyntaxError("f'{10:{ }}'", FormatStringLiteralNode.ERROR_MESSAGE_EMPTY_EXPRESSION);
338+
}
339+
340+
private void checkSyntaxError(String text, String expectedMessage) throws Exception {
341+
try {
342+
testFormatString(text, "Expected Error: " + expectedMessage);
343+
} catch (RuntimeException e) {
344+
Assert.assertEquals("SyntaxError: " + expectedMessage, e.getMessage());
345+
}
346+
}
347+
348+
private void testFormatString(String text, String expected) throws Exception {
349+
VirtualFrame frame = Truffle.getRuntime().createVirtualFrame(new Object[8], new FrameDescriptor());
350+
351+
Node parserResult = parseNew(text, "<fstringtest>", PythonParser.ParserMode.InlineEvaluation, frame);
352+
353+
Assert.assertTrue("The source has to be just fstring", parserResult instanceof FormatStringLiteralNode);
354+
FormatStringLiteralNode fsl = (FormatStringLiteralNode) parserResult;
355+
int[][] tokens = FormatStringLiteralNode.createTokens(fsl, fsl.getValues(), true);
356+
FormatStringLiteralNode.StringPart[] fslParts = fsl.getValues();
357+
String[] expressions = FormatStringLiteralNode.createExpressionSources(fslParts, tokens, 0, tokens.length);
358+
int expressionsIndex = 0;
359+
StringBuilder actual = new StringBuilder();
360+
boolean first = true;
361+
boolean wasLastString = true;
362+
for (int index = 0; index < tokens.length; index++) {
363+
int[] token = tokens[index];
364+
if (first) {
365+
first = false;
366+
} else if (!(wasLastString && token[0] == FormatStringLiteralNode.TOKEN_TYPE_STRING)) {
367+
actual.append("+");
368+
}
369+
if (token[0] == FormatStringLiteralNode.TOKEN_TYPE_STRING) {
370+
actual.append(fslParts[token[1]].getText().substring(token[2], token[3]));
371+
wasLastString = true;
372+
} else {
373+
actual.append(expressions[expressionsIndex]);
374+
index += token[4];
375+
wasLastString = false;
376+
}
377+
}
378+
Assert.assertEquals(expected, actual.toString());
379+
}
380+
381+
}

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/parser/BasicTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public void inline01() throws Exception {
114114
FrameDescriptor fd = new FrameDescriptor(44);
115115
fd.addFrameSlot("a");
116116
fd.addFrameSlot("b");
117-
Frame frame = Truffle.getRuntime().createVirtualFrame(new Object[]{2, 3}, fd);
117+
Frame frame = Truffle.getRuntime().createVirtualFrame(new Object[]{2, 3, null}, fd);
118118
checkTreeResult("a + b", PythonParser.ParserMode.InlineEvaluation, frame);
119119
}
120120

0 commit comments

Comments
 (0)