Skip to content

Commit 2682fe5

Browse files
committed
gh-153569: Add tokenizer source and cursor primitives
1 parent a2d3787 commit 2682fe5

14 files changed

Lines changed: 647 additions & 2 deletions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import unittest
2+
from test.support import import_helper
3+
4+
5+
_testinternalcapi = import_helper.import_module("_testinternalcapi")
6+
7+
8+
class TokenizerTests(unittest.TestCase):
9+
def test_source(self):
10+
_testinternalcapi.test_tokenizer_source()
11+
12+
def test_cursor(self):
13+
_testinternalcapi.test_tokenizer_cursor()
14+
15+
16+
if __name__ == "__main__":
17+
unittest.main()

Makefile.pre.in

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,10 @@ TOKENIZER_OBJS= \
398398
Parser/lexer/number.o \
399399
Parser/lexer/state.o \
400400
Parser/lexer/string.o \
401+
Parser/tokenizer/cursor.o \
401402
Parser/tokenizer/file_tokenizer.o \
402403
Parser/tokenizer/readline_tokenizer.o \
404+
Parser/tokenizer/source.o \
403405
Parser/tokenizer/string_tokenizer.o \
404406
Parser/tokenizer/utf8_tokenizer.o \
405407
Parser/tokenizer/helpers.o
@@ -414,6 +416,8 @@ TOKENIZER_HEADERS= \
414416
Parser/lexer/lexer.h \
415417
Parser/lexer/lexer_internal.h \
416418
Parser/lexer/state.h \
419+
Parser/tokenizer/cursor.h \
420+
Parser/tokenizer/source.h \
417421
Parser/tokenizer/tokenizer.h \
418422
Parser/tokenizer/helpers.h
419423

@@ -3479,7 +3483,7 @@ MODULE__SOCKET_DEPS=$(srcdir)/Modules/socketmodule.h $(srcdir)/Modules/addrinfo.
34793483
MODULE__SSL_DEPS=$(srcdir)/Modules/_ssl.h $(srcdir)/Modules/_ssl/cert.c $(srcdir)/Modules/_ssl/debughelpers.c $(srcdir)/Modules/_ssl/misc.c $(srcdir)/Modules/_ssl_data_111.h $(srcdir)/Modules/_ssl_data_300.h $(srcdir)/Modules/socketmodule.h
34803484
MODULE__TESTCAPI_DEPS=$(srcdir)/Modules/_testcapi/parts.h $(srcdir)/Modules/_testcapi/util.h
34813485
MODULE__TESTLIMITEDCAPI_DEPS=$(srcdir)/Modules/_testlimitedcapi/testcapi_long.h $(srcdir)/Modules/_testlimitedcapi/parts.h $(srcdir)/Modules/_testlimitedcapi/util.h
3482-
MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h $(srcdir)/Python/ceval.h $(srcdir)/Modules/_testinternalcapi/test_targets.h $(srcdir)/Modules/_testinternalcapi/test_cases.c.h
3486+
MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h $(srcdir)/Parser/tokenizer/cursor.h $(srcdir)/Parser/tokenizer/source.h $(srcdir)/Python/ceval.h $(srcdir)/Modules/_testinternalcapi/test_targets.h $(srcdir)/Modules/_testinternalcapi/test_cases.c.h
34833487
MODULE__SQLITE3_DEPS=$(srcdir)/Modules/_sqlite/connection.h $(srcdir)/Modules/_sqlite/cursor.h $(srcdir)/Modules/_sqlite/microprotocols.h $(srcdir)/Modules/_sqlite/module.h $(srcdir)/Modules/_sqlite/prepare_protocol.h $(srcdir)/Modules/_sqlite/row.h $(srcdir)/Modules/_sqlite/util.h
34843488
MODULE__ZSTD_DEPS=$(srcdir)/Modules/_zstd/_zstdmodule.h $(srcdir)/Modules/_zstd/buffer.h $(srcdir)/Modules/_zstd/zstddict.h
34853489

Modules/Setup.stdlib.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@
172172
@MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c
173173
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
174174
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
175-
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c
175+
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c
176176
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
177177
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c
178178
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c

Modules/_testinternalcapi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3420,6 +3420,9 @@ module_exec(PyObject *module)
34203420
if (_PyTestInternalCapi_Init_CriticalSection(module) < 0) {
34213421
return 1;
34223422
}
3423+
if (_PyTestInternalCapi_Init_Tokenizer(module) < 0) {
3424+
return 1;
3425+
}
34233426
if (_PyTestInternalCapi_Init_Tuple(module) < 0) {
34243427
return 1;
34253428
}

Modules/_testinternalcapi/parts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ int _PyTestInternalCapi_Init_PyTime(PyObject *module);
1515
int _PyTestInternalCapi_Init_Set(PyObject *module);
1616
int _PyTestInternalCapi_Init_Complex(PyObject *module);
1717
int _PyTestInternalCapi_Init_CriticalSection(PyObject *module);
18+
int _PyTestInternalCapi_Init_Tokenizer(PyObject *module);
1819
int _PyTestInternalCapi_Init_Tuple(PyObject *module);
1920

2021
#endif // Py_TESTINTERNALCAPI_PARTS_H
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#include "parts.h"
2+
3+
#include "../../Parser/tokenizer/cursor.h"
4+
5+
static int
6+
check(int condition, const char *message)
7+
{
8+
if (condition) {
9+
return 0;
10+
}
11+
PyErr_SetString(PyExc_AssertionError, message);
12+
return -1;
13+
}
14+
15+
static int
16+
append_line(_PyTok_SourceText *source, const char *text, Py_ssize_t len,
17+
int implicit)
18+
{
19+
_PyTok_Off start = _PyTok_SourceAppend(source, text, len);
20+
if (start < 0) {
21+
return -1;
22+
}
23+
return _PyTok_SourceAddLine(source, start, start + len, implicit);
24+
}
25+
26+
static PyObject *
27+
test_tokenizer_source(PyObject *Py_UNUSED(module),
28+
PyObject *Py_UNUSED(args))
29+
{
30+
_PyTok_SourceText source;
31+
_PyTok_SourceInit(&source);
32+
33+
if (append_line(&source, "alpha\n", 6, 0) < 0 ||
34+
append_line(&source, "\xce\xb2\n", 3, 1) < 0 ||
35+
append_line(&source, "nul\0x\n", 6, 0) < 0) {
36+
goto error;
37+
}
38+
for (int i = 0; i < 300; i++) {
39+
if (append_line(&source, "x\n", 2, 0) < 0) {
40+
goto error;
41+
}
42+
}
43+
44+
_PyTok_Line line;
45+
if (check(source.nlines == 303, "wrong source line count") < 0 ||
46+
check(source.ncheckpoints == 2,
47+
"wrong source checkpoint count") < 0 ||
48+
check(_PyTok_SourceLine(&source, 2, &line) == 0,
49+
"cannot find second source line") < 0 ||
50+
check(line.start == 6 && line.end == 9,
51+
"wrong second source line span") < 0 ||
52+
check(line.implicit_newline,
53+
"missing implicit newline flag") < 0 ||
54+
check(!line.contains_nul,
55+
"unexpected null byte flag") < 0 ||
56+
check(_PyTok_SourceLine(&source, 3, &line) == 0,
57+
"cannot find third source line") < 0 ||
58+
check(line.contains_nul, "missing null byte flag") < 0 ||
59+
check(_PyTok_SourceLine(&source, 257, &line) == 0,
60+
"cannot find checkpointed source line") < 0 ||
61+
check(line.end - line.start == 2,
62+
"wrong checkpointed source line span") < 0) {
63+
goto error;
64+
}
65+
66+
Py_ssize_t len;
67+
const char *view = _PyTok_SourceSpanView(
68+
&source, _PyTok_SpanFromBounds(6, 8), &len);
69+
if (check(view != NULL, "cannot view source span") < 0 ||
70+
check(len == 2 && memcmp(view, "\xce\xb2", 2) == 0,
71+
"wrong source span contents") < 0) {
72+
goto error;
73+
}
74+
75+
_PyTok_Loc loc;
76+
if (check(_PyTok_SourceLocation(&source, 8, &loc) == 0,
77+
"cannot locate source offset") < 0 ||
78+
check(loc.lineno == 2 && loc.col == 2,
79+
"wrong source location") < 0 ||
80+
check(_PyTok_SourceLocation(&source, 9, &loc) == 0,
81+
"cannot locate line boundary") < 0 ||
82+
check(loc.lineno == 3 && loc.col == 0,
83+
"wrong line boundary location") < 0) {
84+
goto error;
85+
}
86+
87+
view = _PyTok_SourceSpanView(
88+
&source, _PyTok_SpanFromBounds(0, source.len + 1), &len);
89+
if (check(view == NULL, "accepted invalid source span") < 0 ||
90+
check(PyErr_ExceptionMatches(PyExc_SystemError),
91+
"invalid source span raised wrong error") < 0) {
92+
goto error;
93+
}
94+
PyErr_Clear();
95+
_PyTok_SourceClear(&source);
96+
Py_RETURN_NONE;
97+
98+
error:
99+
_PyTok_SourceClear(&source);
100+
return NULL;
101+
}
102+
103+
static PyObject *
104+
test_tokenizer_cursor(PyObject *Py_UNUSED(module),
105+
PyObject *Py_UNUSED(args))
106+
{
107+
_PyTok_SourceText source;
108+
_PyTok_SourceInit(&source);
109+
if (append_line(&source, "ab\n", 3, 0) < 0 ||
110+
append_line(&source, "cd\n", 3, 0) < 0) {
111+
goto error;
112+
}
113+
114+
_PyTok_Cursor cursor;
115+
_PyTok_CursorInit(&cursor);
116+
if (check(_PyTok_CursorSetLine(&cursor, &source, 1) == 0,
117+
"cannot set cursor line") < 0 ||
118+
check(_PyTok_CursorPeek(&cursor, &source, 0) == 'a',
119+
"wrong cursor peek") < 0 ||
120+
check(_PyTok_CursorPeek(&cursor, &source, 1) == 'b',
121+
"wrong distant cursor peek") < 0 ||
122+
check(_PyTok_CursorMark(&cursor) == 0,
123+
"wrong cursor mark") < 0 ||
124+
check(_PyTok_CursorAdvance(&cursor, &source) == 'a',
125+
"wrong first cursor byte") < 0 ||
126+
check(_PyTok_CursorAdvance(&cursor, &source) == 'b',
127+
"wrong second cursor byte") < 0 ||
128+
check(_PyTok_CursorAdvance(&cursor, &source) == '\n',
129+
"wrong final cursor byte") < 0 ||
130+
check(_PyTok_CursorAdvance(&cursor, &source) == EOF,
131+
"cursor advanced past line") < 0 ||
132+
check(_PyTok_CursorSetOffset(&cursor, &source, 3) == 0,
133+
"cannot set cursor offset") < 0 ||
134+
check(cursor.lineno == 2 && cursor.line_start == 3,
135+
"wrong cursor line") < 0 ||
136+
check(_PyTok_CursorAdvance(&cursor, &source) == 'c',
137+
"wrong cursor byte after reset") < 0 ||
138+
check(_PyTok_CursorSetOffset(
139+
&cursor, &source, source.len + 1) < 0,
140+
"accepted invalid cursor offset") < 0) {
141+
goto error;
142+
}
143+
144+
_PyTok_SourceClear(&source);
145+
Py_RETURN_NONE;
146+
147+
error:
148+
_PyTok_SourceClear(&source);
149+
return NULL;
150+
}
151+
152+
static PyMethodDef test_methods[] = {
153+
{"test_tokenizer_source", test_tokenizer_source, METH_NOARGS},
154+
{"test_tokenizer_cursor", test_tokenizer_cursor, METH_NOARGS},
155+
{NULL},
156+
};
157+
158+
int
159+
_PyTestInternalCapi_Init_Tokenizer(PyObject *module)
160+
{
161+
return PyModule_AddFunctions(module, test_methods);
162+
}

PCbuild/_testinternalcapi.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
<ClCompile Include="..\Modules\_testinternalcapi\set.c" />
101101
<ClCompile Include="..\Modules\_testinternalcapi\complex.c" />
102102
<ClCompile Include="..\Modules\_testinternalcapi\interpreter.c" />
103+
<ClCompile Include="..\Modules\_testinternalcapi\tokenizer.c" />
103104
<ClCompile Include="..\Modules\_testinternalcapi\tuple.c" />
104105
</ItemGroup>
105106
<ItemGroup>

PCbuild/_testinternalcapi.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
<ClCompile Include="..\Modules\_testinternalcapi\complex.c">
2828
<Filter>Source Files</Filter>
2929
</ClCompile>
30+
<ClCompile Include="..\Modules\_testinternalcapi\tokenizer.c">
31+
<Filter>Source Files</Filter>
32+
</ClCompile>
3033
<ClCompile Include="..\Modules\_testinternalcapi\tuple.c">
3134
<Filter>Source Files</Filter>
3235
</ClCompile>

PCbuild/pythoncore.vcxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@
422422
<ClInclude Include="..\Parser\lexer\lexer.h" />
423423
<ClInclude Include="..\Parser\lexer\lexer_internal.h" />
424424
<ClInclude Include="..\Parser\lexer\buffer.h" />
425+
<ClInclude Include="..\Parser\tokenizer\cursor.h" />
426+
<ClInclude Include="..\Parser\tokenizer\source.h" />
425427
<ClInclude Include="..\Parser\tokenizer\helpers.h" />
426428
<ClInclude Include="..\Parser\tokenizer\tokenizer.h" />
427429
<ClInclude Include="..\Parser\string_parser.h" />
@@ -588,6 +590,8 @@
588590
<ClCompile Include="..\Parser\lexer\number.c" />
589591
<ClCompile Include="..\Parser\lexer\string.c" />
590592
<ClCompile Include="..\Parser\lexer\buffer.c" />
593+
<ClCompile Include="..\Parser\tokenizer\cursor.c" />
594+
<ClCompile Include="..\Parser\tokenizer\source.c" />
591595
<ClCompile Include="..\Parser\tokenizer\string_tokenizer.c" />
592596
<ClCompile Include="..\Parser\tokenizer\file_tokenizer.c" />
593597
<ClCompile Include="..\Parser\tokenizer\utf8_tokenizer.c" />

PCbuild/pythoncore.vcxproj.filters

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,12 @@
333333
<ClInclude Include="..\Parser\lexer\buffer.h">
334334
<Filter>Parser</Filter>
335335
</ClInclude>
336+
<ClInclude Include="..\Parser\tokenizer\cursor.h">
337+
<Filter>Parser</Filter>
338+
</ClInclude>
339+
<ClInclude Include="..\Parser\tokenizer\source.h">
340+
<Filter>Parser</Filter>
341+
</ClInclude>
336342
<ClInclude Include="..\Parser\tokenizer\tokenizer.h">
337343
<Filter>Parser</Filter>
338344
</ClInclude>
@@ -1346,6 +1352,12 @@
13461352
<ClCompile Include="..\Parser\lexer\buffer.c">
13471353
<Filter>Parser</Filter>
13481354
</ClCompile>
1355+
<ClCompile Include="..\Parser\tokenizer\cursor.c">
1356+
<Filter>Parser</Filter>
1357+
</ClCompile>
1358+
<ClCompile Include="..\Parser\tokenizer\source.c">
1359+
<Filter>Parser</Filter>
1360+
</ClCompile>
13491361
<ClCompile Include="..\Parser\tokenizer\string_tokenizer.c">
13501362
<Filter>Parser</Filter>
13511363
</ClCompile>

0 commit comments

Comments
 (0)