Skip to content

Commit b5a1636

Browse files
committed
[GR-35417] Intrinsify _csv module.
PullRequest: graalpython/2034
2 parents 39a5c91 + bc359f2 commit b5a1636

29 files changed

+2686
-628
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
66
## Version 22.0.0
77
* Added support for `pyexpat` module.
88
* Added partial support for `PYTHONHASHSEED` environment variable (also available via `HashSeed` context option), currently only affecting hashing in `pyexpat` module.
9+
* Implement `_csv` module.
910
* Improved compatibility with PyPI packages `wheel` and `click`
1011

1112
## Version 21.3.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2019, 2021, 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.util;
43+
44+
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
45+
import com.oracle.graal.python.builtins.objects.PNone;
46+
import com.oracle.graal.python.lib.PyObjectLookupAttr;
47+
import com.oracle.truffle.api.frame.VirtualFrame;
48+
import com.oracle.truffle.api.nodes.Node;
49+
import com.oracle.truffle.api.nodes.RootNode;
50+
import org.junit.After;
51+
import org.junit.Assert;
52+
import org.junit.Before;
53+
import org.junit.Test;
54+
55+
import com.oracle.graal.python.test.PythonTests;
56+
57+
public class PyObjectLookupAttrTests {
58+
59+
@Before
60+
public void setUp() {
61+
PythonTests.enterContext();
62+
}
63+
64+
@After
65+
public void tearDown() {
66+
PythonTests.closeContext();
67+
}
68+
69+
// Regression test to ensure that super class attributes are evaluated for BuiltinClassType
70+
// attribute lookup.
71+
// Booleans super class PInt defines "real" which we expect to find in an attribute lookup.
72+
@Test
73+
public void lookupThroughMRO() {
74+
Object v = new RootNode(null) {
75+
@Node.Child private PyObjectLookupAttr lookupNode = PyObjectLookupAttr.create();
76+
77+
@Override
78+
public Object execute(VirtualFrame frame) {
79+
return lookupNode.execute(frame, PythonBuiltinClassType.Boolean, "real");
80+
}
81+
}.getCallTarget().call();
82+
Assert.assertNotSame(PNone.NO_VALUE, v);
83+
}
84+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
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
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
import unittest
41+
import csv
42+
from tempfile import TemporaryFile
43+
44+
class CsvTest(unittest.TestCase):
45+
def test_read_utf_32_delimiter(self):
46+
test_data = ['a\U0001F642b']
47+
reader = csv.reader(test_data, delimiter="\U0001F642")
48+
self.assertEqual(list(reader), [['a', 'b']])
49+
50+
def test_read_utf_32_field(self):
51+
test_data = ['a,\U0001F642,b']
52+
reader = csv.reader(test_data)
53+
self.assertEqual(list(reader), [['a', '\U0001F642', 'b']])
54+
55+
def test_write_utf32_field(self):
56+
with TemporaryFile("w+", newline='') as fileobj:
57+
writer = csv.writer(fileobj)
58+
writer.writerow(['a', '\U0001F642','b'])
59+
60+
expected = 'a,\U0001F642,b'
61+
fileobj.seek(0)
62+
63+
self.assertEqual(fileobj.read(),
64+
expected + writer.dialect.lineterminator)
65+
66+
def test_write_utf32_field_with_utf32_delimiter(self):
67+
with TemporaryFile("w+", newline='') as fileobj:
68+
writer = csv.writer(fileobj, delimiter = '\U0001F642')
69+
writer.writerow(['a', '\U0001F642','b'])
70+
71+
expected = 'a\U0001F642"\U0001F642"\U0001F642b'
72+
fileobj.seek(0)
73+
74+
self.assertEqual(fileobj.read(),
75+
expected + writer.dialect.lineterminator)
76+
77+
78+
79+

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_csv.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
*graalpython.lib-python.3.test.test_csv.TestDictFields.test_write_multiple_dict_rows
6464
*graalpython.lib-python.3.test.test_csv.TestDictFields.test_write_no_fields
6565
*graalpython.lib-python.3.test.test_csv.TestDictFields.test_write_simple_dict
66+
*graalpython.lib-python.3.test.test_csv.TestDictFields.test_writeheader_return_value
6667
*graalpython.lib-python.3.test.test_csv.TestEscapedExcel.test_escape_fieldsep
6768
*graalpython.lib-python.3.test.test_csv.TestEscapedExcel.test_read_escape_fieldsep
6869
*graalpython.lib-python.3.test.test_csv.TestLeaks.test_create_read
@@ -80,6 +81,7 @@
8081
*graalpython.lib-python.3.test.test_csv.TestUnicode.test_unicode_read
8182
*graalpython.lib-python.3.test.test_csv.TestUnicode.test_unicode_write
8283
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_read_bigfield
84+
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_read_eof
8385
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_read_eol
8486
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_read_escape
8587
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_read_linenum
@@ -89,6 +91,7 @@
8991
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_reader_attrs
9092
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_reader_dialect_attrs
9193
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_reader_kw_attrs
94+
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_roundtrip_escaped_unquoted_newlines
9295
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_roundtrip_quoteed_newlines
9396
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_write_arg_valid
9497
*graalpython.lib-python.3.test.test_csv.Test_Csv.test_write_bigfield

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
import com.oracle.graal.python.builtins.modules.bz2.BZ2CompressorBuiltins;
110110
import com.oracle.graal.python.builtins.modules.bz2.BZ2DecompressorBuiltins;
111111
import com.oracle.graal.python.builtins.modules.bz2.BZ2ModuleBuiltins;
112+
import com.oracle.graal.python.builtins.modules.csv.CSVDialectBuiltins;
113+
import com.oracle.graal.python.builtins.modules.csv.CSVModuleBuiltins;
114+
import com.oracle.graal.python.builtins.modules.csv.CSVReaderBuiltins;
115+
import com.oracle.graal.python.builtins.modules.csv.CSVWriterBuiltins;
112116
import com.oracle.graal.python.builtins.modules.ctypes.CArgObjectBuiltins;
113117
import com.oracle.graal.python.builtins.modules.ctypes.CDataBuiltins;
114118
import com.oracle.graal.python.builtins.modules.ctypes.CDataTypeBuiltins;
@@ -482,6 +486,7 @@ private static PythonBuiltins[] initializeBuiltins(boolean nativeAccessAllowed)
482486
new TupleGetterBuiltins(),
483487
new JavaModuleBuiltins(),
484488
new JArrayModuleBuiltins(),
489+
new CSVModuleBuiltins(),
485490
new JSONModuleBuiltins(),
486491
new SREModuleBuiltins(),
487492
new AstModuleBuiltins(),
@@ -567,6 +572,11 @@ private static PythonBuiltins[] initializeBuiltins(boolean nativeAccessAllowed)
567572
new JSONScannerBuiltins(),
568573
new JSONEncoderBuiltins(),
569574

575+
// csv
576+
new CSVDialectBuiltins(),
577+
new CSVReaderBuiltins(),
578+
new CSVWriterBuiltins(),
579+
570580
// _ast
571581
new AstBuiltins(),
572582

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import com.oracle.graal.python.builtins.objects.function.BuiltinMethodDescriptor;
5656
import com.oracle.graal.python.builtins.objects.type.SpecialMethodSlot;
5757
import com.oracle.graal.python.runtime.PythonContext;
58+
import com.oracle.graal.python.util.PythonUtils;
5859
import com.oracle.truffle.api.CompilerAsserts;
5960
import com.oracle.truffle.api.CompilerDirectives;
6061
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
@@ -234,6 +235,11 @@ public enum PythonBuiltinClassType implements TruffleObject {
234235
JSONScanner("Scanner", "_json", Flags.PUBLIC_BASE_WODICT),
235236
JSONEncoder("Encoder", "_json", Flags.PUBLIC_BASE_WODICT),
236237

238+
// csv
239+
CSVDialect("Dialect", "_csv", Flags.PUBLIC_BASE_WODICT),
240+
CSVReader("Reader", "_csv", Flags.PUBLIC_BASE_WODICT),
241+
CSVWriter("Writer", "_csv", Flags.PUBLIC_BASE_WODICT),
242+
237243
// _ast (rest of the classes are not builtin, they are generated in AstModuleBuiltins)
238244
AST("AST", "_ast", Flags.PUBLIC_BASE_WDICT),
239245

@@ -306,6 +312,7 @@ public enum PythonBuiltinClassType implements TruffleObject {
306312
TimeoutError("TimeoutError", BUILTINS, Flags.EXCEPTION),
307313
ZipImportError("ZipImportError", "zipimport", Flags.EXCEPTION),
308314
ZLibError("error", "zlib", Flags.EXCEPTION),
315+
CSVError("Error", "_csv", Flags.EXCEPTION),
309316
LZMAError("LZMAError", "_lzma", Flags.EXCEPTION),
310317
StructError("StructError", "_struct", Flags.EXCEPTION),
311318
PickleError("PickleError", "_pickle", Flags.EXCEPTION),
@@ -601,6 +608,7 @@ public final Shape getInstanceShape(PythonLanguage lang) {
601608
TimeoutError.base = OSError;
602609
ZipImportError.base = ImportError;
603610
ZLibError.base = Exception;
611+
CSVError.base = Exception;
604612
LZMAError.base = Exception;
605613
SocketGAIError.base = OSError;
606614
SocketHError.base = OSError;
@@ -709,9 +717,10 @@ public final Shape getInstanceShape(PythonLanguage lang) {
709717

710718
Empty.base = Exception;
711719

712-
HashSet<String> set = new HashSet<>();
720+
HashSet<String> set = PythonUtils.ASSERTIONS_ENABLED ? new HashSet<>() : null;
713721
for (PythonBuiltinClassType type : VALUES) {
714-
assert set.add(type.name) : type.name(); // check uniqueness
722+
// check uniqueness
723+
assert set.add("" + type.moduleName + "." + type.name) : type.name();
715724

716725
/* Initialize type.base (defaults to PythonObject unless that's us) */
717726
if (type.base == null && type != PythonObject) {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) 2021, 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+
package com.oracle.graal.python.builtins.modules.csv;
42+
43+
import static com.oracle.graal.python.builtins.modules.csv.CSVModuleBuiltins.NOT_SET;
44+
import static com.oracle.graal.python.builtins.modules.csv.CSVModuleBuiltins.NOT_SET_CODEPOINT;
45+
46+
import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject;
47+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
48+
import com.oracle.truffle.api.object.Shape;
49+
50+
public final class CSVDialect extends PythonBuiltinObject {
51+
String delimiter; /* field separator */
52+
boolean doubleQuote; /* is " represented by ""? */
53+
String escapeChar; /* escape character */
54+
String lineTerminator; /* string to write between records */
55+
QuoteStyle quoting; /* style of quoting to write */
56+
String quoteChar; /* quote character */
57+
boolean skipInitialSpace; /* ignore spaces following delimiter? */
58+
boolean strict; /* raise exception on bad CSV */
59+
60+
int delimiterCodePoint; /* code point representation for handling utf-32 delimiters */
61+
int escapeCharCodePoint; /* code point representation for handling utf-32 escape chars */
62+
int quoteCharCodePoint; /* code point representation for handling utf-32 quote chars */
63+
int[] lineTerminatorCodePoints; /*
64+
* code point representation for handling utf-32 chars in line
65+
* terminator
66+
*/
67+
68+
public CSVDialect(Object cls, Shape instanceShape) {
69+
super(cls, instanceShape);
70+
}
71+
72+
public CSVDialect(Object cls, Shape instanceShape, String delimiter, boolean doubleQuote, String escapeChar,
73+
String lineTerminator, String quoteChar, QuoteStyle quoting, boolean skipInitialSpace,
74+
boolean strict) {
75+
super(cls, instanceShape);
76+
this.delimiter = delimiter;
77+
this.doubleQuote = doubleQuote;
78+
this.escapeChar = escapeChar;
79+
this.lineTerminator = lineTerminator;
80+
this.quoteChar = quoteChar;
81+
this.quoting = quoting;
82+
this.skipInitialSpace = skipInitialSpace;
83+
this.strict = strict;
84+
85+
this.delimiterCodePoint = this.delimiter.codePointAt(0); // delimiter cannot be NOT_SET
86+
this.escapeCharCodePoint = this.escapeChar.equals(NOT_SET) ? NOT_SET_CODEPOINT : this.escapeChar.codePointAt(0);
87+
this.quoteCharCodePoint = this.quoteChar.equals(NOT_SET) ? NOT_SET_CODEPOINT : this.quoteChar.codePointAt(0);
88+
this.lineTerminatorCodePoints = strToCodePointArray(this.lineTerminator);
89+
}
90+
91+
@TruffleBoundary
92+
private static int[] strToCodePointArray(String str) {
93+
final int strLen = str.length();
94+
final int codePointCount = str.codePointCount(0, strLen);
95+
int[] codePoints = new int[codePointCount];
96+
97+
for (int offset = 0, index = 0; offset < strLen; index++) {
98+
final int c = str.codePointAt(offset);
99+
codePoints[index] = c;
100+
offset += Character.charCount(c);
101+
}
102+
return codePoints;
103+
}
104+
}

0 commit comments

Comments
 (0)