gh-153569: Unify tokenizer input readers (#156472)
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
index 73b1f67..3802ed6 100644
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -9,6 +9,7 @@
import os
import os.path
import py_compile
+import select
import subprocess
import io
@@ -168,6 +169,24 @@ def test_stdin_loader(self):
expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8")
self.assertIn(expected, out)
+ @unittest.skipIf(sys.platform == "win32", "select() cannot wait for pipes")
+ def test_stdin_syntax_error_does_not_read_ahead(self):
+ process = spawn_python()
+ try:
+ process.stdin.write(b")\n")
+ process.stdin.flush()
+ output = b""
+ while b"SyntaxError" not in output:
+ ready, _, _ = select.select(
+ [process.stdout], [], [], support.SHORT_TIMEOUT
+ )
+ self.assertTrue(ready, output)
+ data = os.read(process.stdout.fileno(), 4096)
+ self.assertTrue(data, output)
+ output += data
+ finally:
+ kill_python(process)
+
@contextlib.contextmanager
def interactive_python(self, separate_stderr=False):
if separate_stderr:
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index 45890f8..c1ef1a7 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -1603,6 +1603,8 @@ def test_debug_conversion(self):
self.assertEqual(f'''{
3
=}''', '\n3\n=3')
+ x = 1
+ self.assertEqual(eval('f"""{(\nx\n)=}"""'), '(\nx\n)=1')
# Since = is handled specially, make sure all existing uses of
# it still work.
diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py
index 0f5009c..ea0f72e 100644
--- a/Lib/test/test_repl.py
+++ b/Lib/test/test_repl.py
@@ -152,6 +152,36 @@ def test_multiline_string_parsing(self):
output = kill_python(p)
self.assertEqual(p.returncode, 0)
+ @unittest.skipIf(sys.platform == "win32", "select() cannot wait for pipes")
+ def test_secondary_prompt_is_not_read_ahead(self):
+ process = spawn_repl()
+ output = ""
+
+ def read_until(marker, start=0):
+ nonlocal output
+ while marker not in output[start:]:
+ ready, _, _ = select.select(
+ [process.stdout], [], [], SHORT_TIMEOUT
+ )
+ self.assertTrue(ready, output)
+ data = os.read(process.stdout.fileno(), 4096)
+ self.assertTrue(data, output)
+ output += data.decode()
+
+ try:
+ read_until(">>> ")
+ process.stdin.write("(\n")
+ process.stdin.flush()
+ read_until("... ")
+ after_secondary_prompt = len(output)
+
+ process.stdin.write("1)\n")
+ process.stdin.flush()
+ read_until(">>> ", after_secondary_prompt)
+ self.assertEqual(output[after_secondary_prompt:], "1\n>>> ")
+ finally:
+ kill_python(process)
+
@cpython_only
def test_lexer_buffer_realloc_with_null_start(self):
# gh-144759: NULL pointer arithmetic in the lexer when start and
diff --git a/Lib/test/test_source_encoding.py b/Lib/test/test_source_encoding.py
index 53fffe7..862a20a 100644
--- a/Lib/test/test_source_encoding.py
+++ b/Lib/test/test_source_encoding.py
@@ -82,6 +82,46 @@ def test_truncated_utf8_at_eof(self):
with self.subTest(seq=seq):
self.assertRaises(SyntaxError, compile, seq, '<test>', 'exec')
+ def test_invalid_utf8_offset_after_non_ascii(self):
+ with self.assertRaises(SyntaxError) as caught:
+ compile(b"x = \xc3\xa9\xff\n", "<test>", "exec")
+ error = caught.exception
+ self.assertEqual(
+ (error.lineno, error.offset, error.end_lineno, error.end_offset),
+ (1, 6, 1, 6),
+ )
+
+ def test_long_bom_conflict_message_is_not_truncated(self):
+ encoding = "x" * 400
+ source = b"\xef\xbb\xbf# coding:" + encoding.encode() + b"\n"
+ with self.assertRaises(SyntaxError) as caught:
+ compile(source, "<test>", "exec")
+ self.assertEqual(
+ caught.exception.msg,
+ f"encoding problem: {encoding} with BOM",
+ )
+
+ def _assert_python_file_ok(self, source):
+ with tempfile.TemporaryDirectory() as directory:
+ filename = script_helper.make_script(directory, "source", source)
+ script_helper.assert_python_ok(filename)
+
+ @support.requires_subprocess()
+ def test_stateful_file_decoder_spans_lines(self):
+ encoded_name = "変数".encode("iso2022_jp")
+ payload = encoded_name[3:-3]
+ source = (
+ b"# coding: iso2022_jp\n"
+ b"# \x1b$B" + payload + b"\n"
+ + payload + b"\x1b(B = 1\n"
+ )
+ self._assert_python_file_ok(source)
+
+ @support.requires_subprocess()
+ def test_stateful_file_decoder_finalizes_before_implicit_newline(self):
+ source = b"# coding: hz\n# ~{1dA?"
+ self._assert_python_file_ok(source)
+
@support.requires_subprocess()
def test_20731(self):
sub = subprocess.Popen([sys.executable,
diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py
index e2db09d..7e02191 100644
--- a/Lib/test/test_tokenize.py
+++ b/Lib/test/test_tokenize.py
@@ -1,4 +1,5 @@
import contextlib
+import _tokenize
import itertools
import os
import re
@@ -2273,6 +2274,159 @@ def readline(encoding):
))
self.assertEqual(tokens, expected)
+ def test_stateful_decoder_spans_readline_calls(self):
+ encoded_name = "変数".encode("iso2022_jp")
+ payload = encoded_name[3:-3]
+ lines = iter([
+ b"# \x1b$B" + payload + b"\n",
+ payload + b"\x1b(B\n",
+ b"",
+ ])
+ tokens = list(tokenize._generate_tokens_from_c_tokenizer(
+ lines.__next__,
+ extra_tokens=True,
+ encoding="iso2022_jp",
+ ))
+ self.assertEqual(tokens, [
+ tokenize.TokenInfo(
+ token.COMMENT, "# 変数", (1, 0), (1, 4), "# 変数\n"
+ ),
+ tokenize.TokenInfo(token.NL, "\n", (1, 4), (1, 5), "# 変数\n"),
+ tokenize.TokenInfo(token.NAME, "変数", (2, 0), (2, 2), "変数\n"),
+ tokenize.TokenInfo(token.NEWLINE, "\n", (2, 2), (2, 3), "変数\n"),
+ tokenize.TokenInfo(token.ENDMARKER, "", (3, 0), (3, 0), ""),
+ ])
+
+ def test_utf16_bom_in_each_readline_chunk(self):
+ lines = iter([
+ "x\n".encode("utf-16"),
+ "y\n".encode("utf-16"),
+ b"",
+ ])
+ tokens = _tokenize.TokenizerIter(
+ lines.__next__, encoding="utf-16", extra_tokens=True
+ )
+ self.assertEqual(list(tokens), [
+ (token.NAME, "x", (1, 0), (1, 1), "x\n"),
+ (token.NEWLINE, "\n", (1, 1), (1, 2), "x\n"),
+ (token.NAME, "y", (2, 0), (2, 1), "y\n"),
+ (token.NEWLINE, "\n", (2, 1), (2, 2), "y\n"),
+ (token.ENDMARKER, "", (3, 0), (3, 0), ""),
+ ])
+
+ def test_utf8_decoder_spans_readline_calls(self):
+ lines = iter([b"x\xc3", b"\xa9\n", b""])
+ tokens = list(tokenize._generate_tokens_from_c_tokenizer(
+ lines.__next__,
+ extra_tokens=True,
+ encoding="utf-8",
+ ))
+ self.assertEqual(tokens, [
+ tokenize.TokenInfo(token.NAME, "xé", (1, 0), (1, 2), "xé\n"),
+ tokenize.TokenInfo(token.NEWLINE, "\n", (1, 2), (1, 3), "xé\n"),
+ tokenize.TokenInfo(token.ENDMARKER, "", (2, 0), (2, 0), ""),
+ ])
+
+ def test_utf8_decoder_replaces_incomplete_input_at_eof(self):
+ expected = [
+ tokenize.TokenInfo(token.NAME, "x�", (1, 0), (1, 2), "x�"),
+ tokenize.TokenInfo(token.NEWLINE, "", (1, 2), (1, 3), "x�"),
+ tokenize.TokenInfo(token.ENDMARKER, "", (2, 0), (2, 0), ""),
+ ]
+ for chunks in ([b"x\xe9", b""], [b"x\xe9"]):
+ with self.subTest(chunks=chunks):
+ lines = iter(chunks)
+ tokens = list(tokenize._generate_tokens_from_c_tokenizer(
+ lines.__next__,
+ extra_tokens=True,
+ encoding="utf-8",
+ ))
+ self.assertEqual(tokens, expected)
+
+ def test_multiline_readline_chunk(self):
+ expected = [
+ tokenize.TokenInfo(token.NAME, "x", (1, 0), (1, 1), "x=1\n"),
+ tokenize.TokenInfo(token.OP, "=", (1, 1), (1, 2), "x=1\n"),
+ tokenize.TokenInfo(token.NUMBER, "1", (1, 2), (1, 3), "x=1\n"),
+ tokenize.TokenInfo(token.NEWLINE, "\n", (1, 3), (1, 4), "x=1\n"),
+ tokenize.TokenInfo(token.NAME, "y", (2, 0), (2, 1), "y=2\n"),
+ tokenize.TokenInfo(token.OP, "=", (2, 1), (2, 2), "y=2\n"),
+ tokenize.TokenInfo(token.NUMBER, "2", (2, 2), (2, 3), "y=2\n"),
+ tokenize.TokenInfo(token.NEWLINE, "\n", (2, 3), (2, 4), "y=2\n"),
+ tokenize.TokenInfo(token.ENDMARKER, "", (3, 0), (3, 0), ""),
+ ]
+ lines = iter([b"x=1\ny=2\n", b""])
+ tokens = list(tokenize._generate_tokens_from_c_tokenizer(
+ lines.__next__,
+ extra_tokens=True,
+ encoding="utf-8",
+ ))
+ self.assertEqual(tokens, expected)
+
+ def test_multiline_readline_chunk_with_unterminated_tail(self):
+ readline = mock.Mock(side_effect=["x\nz", ""])
+ iterator = _tokenize.TokenizerIter(readline, extra_tokens=True)
+ expected = [
+ (token.NAME, "x", (1, 0), (1, 1), "x\n"),
+ (token.NEWLINE, "\n", (1, 1), (1, 2), "x\n"),
+ (token.NAME, "z", (2, 0), (2, 1), "z"),
+ (token.NEWLINE, "", (2, 1), (2, 2), "z"),
+ ]
+ self.assertEqual(readline.call_count, 0)
+ for token_info in expected:
+ self.assertEqual(next(iterator), token_info)
+ self.assertEqual(readline.call_count, 1)
+ self.assertEqual(
+ next(iterator),
+ (token.ENDMARKER, "", (3, 0), (3, 0), ""),
+ )
+ self.assertEqual(readline.call_count, 2)
+
+ def test_readline_callback_is_not_read_ahead(self):
+ readline = mock.Mock(side_effect=["x\n", "y\n", ""])
+ iterator = _tokenize.TokenizerIter(readline, extra_tokens=True)
+ expected = [
+ ((token.NAME, "x", (1, 0), (1, 1), "x\n"), 1),
+ ((token.NEWLINE, "\n", (1, 1), (1, 2), "x\n"), 1),
+ ((token.NAME, "y", (2, 0), (2, 1), "y\n"), 2),
+ ]
+ self.assertEqual(readline.call_count, 0)
+ for token_info, calls in expected:
+ self.assertEqual(next(iterator), token_info)
+ self.assertEqual(readline.call_count, calls)
+
+ def test_encoded_readline_replaces_invalid_bytes(self):
+ lines = iter([b"\xff\n", b""])
+ tokens = list(tokenize._generate_tokens_from_c_tokenizer(
+ lines.__next__,
+ extra_tokens=True,
+ encoding="utf-8",
+ ))
+ self.assertEqual(tokens, [
+ tokenize.TokenInfo(token.NAME, "�", (1, 0), (1, 1), "�\n"),
+ tokenize.TokenInfo(token.NEWLINE, "\n", (1, 1), (1, 2), "�\n"),
+ tokenize.TokenInfo(token.ENDMARKER, "", (2, 0), (2, 0), ""),
+ ])
+
+ def test_stop_iteration_skips_encoded_readline_codec_lookup(self):
+ iterator = _tokenize.TokenizerIter(
+ lambda: b"",
+ extra_tokens=True,
+ encoding="missing-tokenizer-codec",
+ )
+ with self.assertRaises(LookupError):
+ next(iterator)
+
+ iterator = _tokenize.TokenizerIter(
+ iter(()).__next__,
+ extra_tokens=True,
+ encoding="missing-tokenizer-codec",
+ )
+ self.assertEqual(
+ next(iterator),
+ (token.ENDMARKER, "", (1, 0), (1, 0), ""),
+ )
+
def test_extra_tokens_relaxes_lexer_errors(self):
cases = [
(
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 97e3dcd..adcfe4c 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -400,11 +400,9 @@
Parser/lexer/state.o \
Parser/lexer/string.o \
Parser/tokenizer/cursor.o \
- Parser/tokenizer/file_tokenizer.o \
- Parser/tokenizer/readline_tokenizer.o \
+ Parser/tokenizer/decoder.o \
+ Parser/tokenizer/reader.o \
Parser/tokenizer/source.o \
- Parser/tokenizer/string_tokenizer.o \
- Parser/tokenizer/utf8_tokenizer.o \
Parser/tokenizer/helpers.o
PEGEN_HEADERS= \
@@ -418,6 +416,8 @@
Parser/lexer/lexer_internal.h \
Parser/lexer/state.h \
Parser/tokenizer/cursor.h \
+ Parser/tokenizer/reader.h \
+ Parser/tokenizer/reader_internal.h \
Parser/tokenizer/source.h \
Parser/tokenizer/tokenizer.h \
Parser/tokenizer/helpers.h
diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj
index 3cd930e..469fd77 100644
--- a/PCbuild/_freeze_module.vcxproj
+++ b/PCbuild/_freeze_module.vcxproj
@@ -186,10 +186,9 @@
<ClCompile Include="..\Parser\lexer\lexer.c" />
<ClCompile Include="..\Parser\lexer\number.c" />
<ClCompile Include="..\Parser\lexer\string.c" />
- <ClCompile Include="..\Parser\tokenizer\string_tokenizer.c" />
- <ClCompile Include="..\Parser\tokenizer\file_tokenizer.c" />
- <ClCompile Include="..\Parser\tokenizer\utf8_tokenizer.c" />
- <ClCompile Include="..\Parser\tokenizer\readline_tokenizer.c" />
+ <ClCompile Include="..\Parser\tokenizer\decoder.c" />
+ <ClCompile Include="..\Parser\tokenizer\reader.c" />
+ <ClCompile Include="..\Parser\tokenizer\source.c" />
<ClCompile Include="..\Parser\tokenizer\helpers.c" />
<ClCompile Include="..\PC\invalid_parameter_handler.c" />
<ClCompile Include="..\PC\msvcrtmodule.c" />
diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters
index 72f3669..976c99b 100644
--- a/PCbuild/_freeze_module.vcxproj.filters
+++ b/PCbuild/_freeze_module.vcxproj.filters
@@ -475,16 +475,13 @@
<ClCompile Include="..\Parser\lexer\state.c">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\Parser\tokenizer\string_tokenizer.c">
+ <ClCompile Include="..\Parser\tokenizer\decoder.c">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\Parser\tokenizer\utf8_tokenizer.c">
+ <ClCompile Include="..\Parser\tokenizer\reader.c">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\Parser\tokenizer\file_tokenizer.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\Parser\tokenizer\readline_tokenizer.c">
+ <ClCompile Include="..\Parser\tokenizer\source.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Parser\tokenizer\helpers.c">
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index 8451ffb..33647ec 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -424,6 +424,8 @@
<ClInclude Include="..\Parser\lexer\lexer_internal.h" />
<ClInclude Include="..\Parser\lexer\buffer.h" />
<ClInclude Include="..\Parser\tokenizer\cursor.h" />
+ <ClInclude Include="..\Parser\tokenizer\reader.h" />
+ <ClInclude Include="..\Parser\tokenizer\reader_internal.h" />
<ClInclude Include="..\Parser\tokenizer\source.h" />
<ClInclude Include="..\Parser\tokenizer\helpers.h" />
<ClInclude Include="..\Parser\tokenizer\tokenizer.h" />
@@ -593,10 +595,8 @@
<ClCompile Include="..\Parser\lexer\buffer.c" />
<ClCompile Include="..\Parser\tokenizer\cursor.c" />
<ClCompile Include="..\Parser\tokenizer\source.c" />
- <ClCompile Include="..\Parser\tokenizer\string_tokenizer.c" />
- <ClCompile Include="..\Parser\tokenizer\file_tokenizer.c" />
- <ClCompile Include="..\Parser\tokenizer\utf8_tokenizer.c" />
- <ClCompile Include="..\Parser\tokenizer\readline_tokenizer.c" />
+ <ClCompile Include="..\Parser\tokenizer\decoder.c" />
+ <ClCompile Include="..\Parser\tokenizer\reader.c" />
<ClCompile Include="..\Parser\tokenizer\helpers.c" />
<ClCompile Include="..\Parser\token.c" />
<ClCompile Include="..\Parser\pegen.c" />
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index 81e736d..434dd13 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -336,6 +336,12 @@
<ClInclude Include="..\Parser\tokenizer\cursor.h">
<Filter>Parser</Filter>
</ClInclude>
+ <ClInclude Include="..\Parser\tokenizer\reader.h">
+ <Filter>Parser</Filter>
+ </ClInclude>
+ <ClInclude Include="..\Parser\tokenizer\reader_internal.h">
+ <Filter>Parser</Filter>
+ </ClInclude>
<ClInclude Include="..\Parser\tokenizer\source.h">
<Filter>Parser</Filter>
</ClInclude>
@@ -1361,16 +1367,10 @@
<ClCompile Include="..\Parser\tokenizer\source.c">
<Filter>Parser</Filter>
</ClCompile>
- <ClCompile Include="..\Parser\tokenizer\string_tokenizer.c">
+ <ClCompile Include="..\Parser\tokenizer\decoder.c">
<Filter>Parser</Filter>
</ClCompile>
- <ClCompile Include="..\Parser\tokenizer\file_tokenizer.c">
- <Filter>Parser</Filter>
- </ClCompile>
- <ClCompile Include="..\Parser\tokenizer\utf8_tokenizer.c">
- <Filter>Parser</Filter>
- </ClCompile>
- <ClCompile Include="..\Parser\tokenizer\readline_tokenizer.c">
+ <ClCompile Include="..\Parser\tokenizer\reader.c">
<Filter>Parser</Filter>
</ClCompile>
<ClCompile Include="..\Parser\tokenizer\helpers.c">
diff --git a/Parser/lexer/buffer.c b/Parser/lexer/buffer.c
index e122fd0..cd6885a 100644
--- a/Parser/lexer/buffer.c
+++ b/Parser/lexer/buffer.c
@@ -32,20 +32,6 @@ _PyLexer_restore_fstring_buffers(struct tok_state *tok)
}
}
-/* Read a line of text from TOK into S, using the stream in TOK.
- Return NULL on failure, else S.
-
- On entry, tok->decoding_buffer will be one of:
- 1) NULL: need to call tok->decoding_readline to get a new line
- 2) PyUnicodeObject *: decoding_feof has called tok->decoding_readline and
- stored the result in tok->decoding_buffer
- 3) PyByteArrayObject *: previous call to tok_readline_recode did not have enough room
- (in the s buffer) to copy entire contents of the line read
- by tok->decoding_readline. tok->decoding_buffer has the overflow.
- In this case, tok_readline_recode is called in a loop (with an expanded buffer)
- until the buffer ends with a '\n' (or until the end of the file is
- reached): see tok_nextc and its calls to tok_reserve_buf.
-*/
int
_PyLexer_tok_reserve_buf(struct tok_state *tok, Py_ssize_t size)
{
diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c
index 41f03a4..a96362c 100644
--- a/Parser/lexer/lexer.c
+++ b/Parser/lexer/lexer.c
@@ -5,6 +5,7 @@
#include "lexer_internal.h"
#include "../tokenizer/helpers.h"
+#include "../tokenizer/reader.h"
/* Alternate tab spacing */
#define ALTTABSIZE 1
@@ -41,7 +42,7 @@ _PyLexer_nextc(struct tok_state *tok)
if (tok->done != E_OK) {
return EOF;
}
- rc = tok->underflow(tok);
+ rc = _PyTok_ReaderUnderflow(tok);
#if defined(Py_DEBUG)
if (tok->debug) {
fprintf(stderr, "line[%d] = ", tok->lineno);
@@ -89,7 +90,7 @@ verify_identifier(struct tok_state *tok)
return 1;
}
PyObject *s;
- if (tok->decoding_erred)
+ if (tok->input_error)
return 0;
s = PyUnicode_DecodeUTF8(tok->start, tok->cur - tok->start, NULL);
if (s == NULL) {
@@ -483,7 +484,6 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
}
p_start = tok->start;
p_end = tok->cur - 1; /* Leave '\n' out of the string */
- tok->cont_line = 0;
return MAKE_TOKEN(NEWLINE);
}
@@ -528,7 +528,6 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
if ((c = tok_continuation_line(tok)) == -1) {
return MAKE_TOKEN(ERRORTOKEN);
}
- tok->cont_line = 1;
goto again; /* Read next line */
}
@@ -687,9 +686,8 @@ int
_PyTokenizer_Get(struct tok_state *tok, struct token *token)
{
int result = tok_get(tok, token);
- if (tok->decoding_erred) {
+ if (tok->input_error) {
result = ERRORTOKEN;
- tok->done = E_DECODE;
}
return result;
}
diff --git a/Parser/lexer/state.c b/Parser/lexer/state.c
index 5cf9b4d..2a6408b 100644
--- a/Parser/lexer/state.c
+++ b/Parser/lexer/state.c
@@ -4,6 +4,7 @@
#include "errcode.h"
#include "state.h"
+#include "../tokenizer/reader.h"
/* Never change this */
#define TABSIZE 8
@@ -28,36 +29,30 @@ _PyTokenizer_tok_new(void)
tok->end = NULL;
tok->done = E_OK;
tok->fp = NULL;
- tok->input = NULL;
tok->tabsize = TABSIZE;
tok->indent = 0;
tok->indstack[0] = 0;
tok->atbol = 1;
tok->pendin = 0;
- tok->prompt = tok->nextprompt = NULL;
+ tok->prompt = NULL;
tok->lineno = 0;
tok->starting_col_offset = -1;
tok->col_offset = -1;
tok->level = 0;
tok->altindstack[0] = 0;
- tok->decoding_state = STATE_INIT;
- tok->decoding_erred = 0;
- tok->enc = NULL;
+ tok->input_error = 0;
tok->encoding = NULL;
- tok->cont_line = 0;
tok->filename = NULL;
tok->module = NULL;
- tok->decoding_readline = NULL;
- tok->decoding_buffer = NULL;
- tok->readline = NULL;
tok->type_comments = 0;
tok->interactive_underflow = IUNDERFLOW_NORMAL;
- tok->underflow = NULL;
tok->str = NULL;
tok->report_warnings = 1;
tok->tok_extra_tokens = 0;
tok->comment_newline = 0;
tok->implicit_newline = 0;
+ _PyTok_SourceInit(&tok->source);
+ tok->reader = NULL;
tok->tok_mode_stack[0] = (tokenizer_mode){.kind =TOK_REGULAR_MODE, .quote='\0', .quote_size = 0, .in_debug=0};
tok->tok_mode_stack_index = 0;
#ifdef Py_DEBUG
@@ -91,20 +86,10 @@ _PyTokenizer_Free(struct tok_state *tok)
if (tok->encoding != NULL) {
PyMem_Free(tok->encoding);
}
- Py_XDECREF(tok->decoding_readline);
- Py_XDECREF(tok->decoding_buffer);
- Py_XDECREF(tok->readline);
Py_XDECREF(tok->filename);
Py_XDECREF(tok->module);
- if ((tok->readline != NULL || tok->fp != NULL ) && tok->buf != NULL) {
- PyMem_Free(tok->buf);
- }
- if (tok->input) {
- PyMem_Free(tok->input);
- }
- if (tok->interactive_src_start != NULL) {
- PyMem_Free(tok->interactive_src_start);
- }
+ _PyTok_ReaderFree(tok);
+ _PyTok_SourceClear(&tok->source);
free_fstring_expressions(tok);
PyMem_Free(tok);
}
diff --git a/Parser/lexer/state.h b/Parser/lexer/state.h
index 9cd196a..0824785 100644
--- a/Parser/lexer/state.h
+++ b/Parser/lexer/state.h
@@ -2,6 +2,7 @@
#define _PY_LEXER_H_
#include "object.h"
+#include "../tokenizer/source.h"
#define MAXINDENT 100 /* Max indentation level */
#define MAXLEVEL 200 /* Max parentheses level */
@@ -12,12 +13,6 @@
#define INSIDE_FSTRING_EXPR_AT_TOP(tok) \
(tok->curly_bracket_depth - tok->curly_bracket_expr_start_depth == 1)
-enum decoding_state {
- STATE_INIT,
- STATE_SEEK_CODING,
- STATE_NORMAL
-};
-
enum interactive_underflow_t {
/* Normal mode of operation: return a new token when asked in interactive mode */
IUNDERFLOW_NORMAL,
@@ -90,7 +85,7 @@ struct tok_state {
int indstack[MAXINDENT]; /* Stack of indents */
int atbol; /* Nonzero if at begin of new line */
int pendin; /* Pending indents (if > 0) or dedents (if < 0) */
- const char *prompt, *nextprompt; /* For interactive prompting */
+ const char *prompt; /* For interactive prompting */
int lineno; /* Current line number */
int first_lineno; /* First line of a single line or multi line string
expression (cf. issue 16806) */
@@ -106,27 +101,21 @@ struct tok_state {
/* Stuff for checking on different tab sizes */
int altindstack[MAXINDENT]; /* Stack of alternate indents */
/* Stuff for PEP 0263 */
- enum decoding_state decoding_state;
- int decoding_erred; /* whether erred in decoding */
+ int input_error;
char *encoding; /* Source encoding. */
- int cont_line; /* whether we are in a continuation line. */
const char* line_start; /* pointer to start of current line */
const char* multi_line_start; /* pointer to start of first line of
a single line or multi line string
expression (cf. issue 16806) */
- PyObject *decoding_readline; /* open(...).readline */
- PyObject *decoding_buffer;
- PyObject *readline; /* readline() function */
- const char* enc; /* Encoding for the current str. */
char* str; /* Source string being tokenized (if tokenizing from a string)*/
- char* input; /* Tokenizer's newline translated copy of the string. */
+
+ _PyTok_SourceText source;
+ struct _PyTok_Reader *reader;
int type_comments; /* Whether to look for type comments */
/* How to proceed when asked for a new token in interactive mode */
enum interactive_underflow_t interactive_underflow;
- int (*underflow)(struct tok_state *); /* Function to call when buffer is empty and we need to refill it*/
-
int report_warnings;
// TODO: Factor this into its own thing
tokenizer_mode tok_mode_stack[MAXFSTRINGLEVEL];
diff --git a/Parser/lexer/string.c b/Parser/lexer/string.c
index e546954..d67c48f 100644
--- a/Parser/lexer/string.c
+++ b/Parser/lexer/string.c
@@ -489,7 +489,7 @@ _PyLexer_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, st
);
if (c == EOF || (current_tok->quote_size == 1 && c == '\n')) {
- if (tok->decoding_erred) {
+ if (tok->input_error) {
return MAKE_TOKEN(ERRORTOKEN);
}
diff --git a/Parser/myreadline.c b/Parser/myreadline.c
index ee77479..457b959 100644
--- a/Parser/myreadline.c
+++ b/Parser/myreadline.c
@@ -366,8 +366,6 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, const char *) = NULL;
-/* Interface used by file_tokenizer.c and bltinmodule.c */
-
char *
PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
{
diff --git a/Parser/tokenizer/decoder.c b/Parser/tokenizer/decoder.c
new file mode 100644
index 0000000..af17b8b
--- /dev/null
+++ b/Parser/tokenizer/decoder.c
@@ -0,0 +1,519 @@
+#include "Python.h"
+#include "pycore_codecs.h"
+#include "pycore_global_strings.h"
+#include "pycore_runtime.h"
+#include "errcode.h"
+
+#include "reader_internal.h"
+#include "helpers.h"
+#include "../lexer/state.h"
+
+char *
+_PyTok_CopyBytes(const char *data, Py_ssize_t len)
+{
+ if (len < 0 || len == PY_SSIZE_T_MAX) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ char *copy = PyMem_Malloc((size_t)len + 1);
+ if (copy == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ memcpy(copy, data, (size_t)len);
+ copy[len] = '\0';
+ return copy;
+}
+
+static void
+chunk_release_data(_PyTok_Chunk *chunk)
+{
+ switch (chunk->ownership) {
+ case _PYTOK_CHUNK_BORROWED:
+ break;
+ case _PYTOK_CHUNK_PYMEM:
+ PyMem_Free(chunk->data);
+ break;
+ case _PYTOK_CHUNK_PYOBJECT:
+ Py_DECREF(chunk->owner);
+ break;
+ }
+}
+
+void
+_PyTok_ChunkClear(_PyTok_Chunk *chunk)
+{
+ chunk_release_data(chunk);
+ *chunk = (_PyTok_Chunk){0};
+}
+
+static int
+chunk_set_unicode(struct tok_state *tok, _PyTok_Chunk *chunk,
+ PyObject *unicode, int strip_bom)
+{
+ Py_ssize_t utf8_len;
+ const char *utf8 = PyUnicode_AsUTF8AndSize(unicode, &utf8_len);
+ if (utf8 == NULL) {
+ Py_DECREF(unicode);
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_DECODE;
+ return -1;
+ }
+ if (strip_bom && PyUnicode_GET_LENGTH(unicode) > 0 &&
+ PyUnicode_ReadChar(unicode, 0) == 0xFEFF) {
+ utf8 += 3;
+ utf8_len -= 3;
+ }
+ chunk_release_data(chunk);
+ chunk->owner = unicode;
+ chunk->data = (char *)utf8;
+ chunk->len = utf8_len;
+ chunk->ownership = _PYTOK_CHUNK_PYOBJECT;
+ return 0;
+}
+
+char *
+_PyTok_NormalizeNewlines(const char *data, Py_ssize_t len, int preserve_crlf,
+ int add_final_newline, Py_ssize_t *out_len,
+ int *implicit_newline)
+{
+ if (len > PY_SSIZE_T_MAX - 2) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ char *result = PyMem_Malloc((size_t)len + 2);
+ if (result == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ Py_ssize_t write = 0;
+ for (Py_ssize_t read = 0; read < len; read++) {
+ char c = data[read];
+ if (!preserve_crlf && c == '\r') {
+ if (read + 1 < len && data[read + 1] == '\n') {
+ read++;
+ }
+ c = '\n';
+ }
+ result[write++] = c;
+ }
+ int implicit = add_final_newline && write > 0 && result[write - 1] != '\n';
+ if (implicit) {
+ result[write++] = '\n';
+ }
+ result[write] = '\0';
+ *out_len = write;
+ *implicit_newline = implicit;
+ return result;
+}
+
+int
+_PyTok_SetEncoding(struct tok_state *tok, const char *encoding)
+{
+ char *copy = _PyTok_CopyBytes(encoding, strlen(encoding));
+ if (copy == NULL) {
+ tok->done = E_NOMEM;
+ return -1;
+ }
+ PyMem_Free(tok->encoding);
+ tok->encoding = copy;
+ return 0;
+}
+
+static int
+find_cookie(const char *line, Py_ssize_t len, char **encoding, int *scan_next)
+{
+ Py_ssize_t i = 0;
+ *encoding = NULL;
+ *scan_next = 1;
+ for (; i < len; i++) {
+ if (line[i] == '#') {
+ break;
+ }
+ if (line[i] == '\n' || line[i] == '\r') {
+ return 0;
+ }
+ if (line[i] != ' ' && line[i] != '\t' && line[i] != '\f') {
+ *scan_next = 0;
+ return 0;
+ }
+ }
+ for (; i + 6 < len; i++) {
+ if (memcmp(line + i, "coding", 6) != 0) {
+ continue;
+ }
+ const char *cursor = line + i + 6;
+ if (*cursor != ':' && *cursor != '=') {
+ continue;
+ }
+ do {
+ cursor++;
+ } while (cursor < line + len &&
+ (*cursor == ' ' || *cursor == '\t'));
+ const char *start = cursor;
+ while (cursor < line + len &&
+ (Py_ISALNUM(*cursor) || *cursor == '-' ||
+ *cursor == '_' || *cursor == '.')) {
+ cursor++;
+ }
+ if (cursor == start) {
+ continue;
+ }
+ char *found = _PyTok_CopyBytes(start, cursor - start);
+ if (found == NULL) {
+ return -1;
+ }
+ char normalized[13];
+ int n;
+ for (n = 0; n < 12 && found[n] != '\0'; n++) {
+ normalized[n] = found[n] == '_' ? '-' : Py_TOLOWER(found[n]);
+ }
+ normalized[n] = '\0';
+ const char *canonical = found;
+ if (strcmp(normalized, "utf-8") == 0 ||
+ strncmp(normalized, "utf-8-", 6) == 0) {
+ canonical = "utf-8";
+ }
+ else if (strcmp(normalized, "latin-1") == 0 ||
+ strcmp(normalized, "iso-8859-1") == 0 ||
+ strcmp(normalized, "iso-latin-1") == 0 ||
+ strncmp(normalized, "latin-1-", 8) == 0 ||
+ strncmp(normalized, "iso-8859-1-", 11) == 0 ||
+ strncmp(normalized, "iso-latin-1-", 12) == 0) {
+ canonical = "iso-8859-1";
+ }
+ if (canonical != found) {
+ PyMem_Free(found);
+ found = _PyTok_CopyBytes(canonical, strlen(canonical));
+ if (found == NULL) {
+ return -1;
+ }
+ }
+ *encoding = found;
+ *scan_next = 0;
+ return 0;
+ }
+ return 0;
+}
+
+_PyTok_EncodingResult
+_PyTok_DetectEncoding(struct tok_state *tok, const _PyTok_Chunk *first,
+ const _PyTok_Chunk *second, int final,
+ Py_ssize_t *bom_len)
+{
+ int bom = first->len >= 3 &&
+ (unsigned char)first->data[0] == 0xEF &&
+ (unsigned char)first->data[1] == 0xBB &&
+ (unsigned char)first->data[2] == 0xBF;
+ *bom_len = bom ? 3 : 0;
+
+ char *cookie = NULL;
+ int scan_next = 0;
+ int cookie_line = 1;
+ const char *first_data = first->data + (bom ? 3 : 0);
+ Py_ssize_t first_len = first->len - (bom ? 3 : 0);
+ if (find_cookie(first_data, first_len, &cookie, &scan_next) < 0) {
+ return _PYTOK_ENCODING_ERROR;
+ }
+ if (cookie == NULL && scan_next && second != NULL) {
+ if (find_cookie(second->data, second->len, &cookie, &scan_next) < 0) {
+ return _PYTOK_ENCODING_ERROR;
+ }
+ cookie_line = 2;
+ }
+ else if (cookie == NULL && scan_next && !final) {
+ return _PYTOK_ENCODING_NEED_SECOND_LINE;
+ }
+
+ if (bom) {
+ if (_PyTok_SetEncoding(tok, "utf-8") < 0) {
+ PyMem_Free(cookie);
+ return _PYTOK_ENCODING_ERROR;
+ }
+ }
+ if (cookie == NULL) {
+ return _PYTOK_ENCODING_DONE;
+ }
+ if (bom && strcmp(cookie, "utf-8") != 0) {
+ const _PyTok_Chunk *line = cookie_line == 2 ? second : first;
+ const char *line_data = line->data + (cookie_line == 1 ? 3 : 0);
+ Py_ssize_t line_len = line->len - (cookie_line == 1 ? 3 : 0);
+ const char *saved_line_start = tok->line_start;
+ char *saved_cur = tok->cur;
+ int saved_lineno = tok->lineno;
+ tok->line_start = line_data;
+ tok->cur = (char *)line_data;
+ tok->lineno = cookie_line;
+ int end_col = (int)Py_MIN(line_len, INT_MAX);
+ if (end_col > 0 && (line_data[end_col - 1] == '\n' ||
+ line_data[end_col - 1] == '\r')) {
+ end_col--;
+ }
+ _PyTokenizer_syntaxerror_known_range(
+ tok, 0, end_col, "encoding problem: %s with BOM", cookie);
+ tok->line_start = saved_line_start;
+ tok->cur = saved_cur;
+ tok->lineno = saved_lineno;
+ PyMem_Free(cookie);
+ return _PYTOK_ENCODING_ERROR;
+ }
+ if (!bom && _PyTok_SetEncoding(tok, cookie) < 0) {
+ PyMem_Free(cookie);
+ return _PYTOK_ENCODING_ERROR;
+ }
+ PyMem_Free(cookie);
+ return _PYTOK_ENCODING_DONE;
+}
+
+int
+_PyTok_DecodeOnce(struct tok_state *tok, _PyTok_Chunk *chunk,
+ const char *encoding, const char *errors)
+{
+ PyObject *unicode = PyUnicode_Decode(
+ chunk->data, chunk->len, encoding, errors);
+ if (unicode == NULL) {
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_DECODE;
+ return -1;
+ }
+ return chunk_set_unicode(tok, chunk, unicode, 0);
+}
+
+static Py_ssize_t
+raw_line_length(const char *data, Py_ssize_t len)
+{
+ for (Py_ssize_t i = 0; i < len; i++) {
+ if (data[i] == '\n') {
+ return i + 1;
+ }
+ if (data[i] == '\r') {
+ return i + 1 < len && data[i + 1] == '\n' ? i + 2 : i + 1;
+ }
+ }
+ return len;
+}
+
+static int
+store_prepared_source(struct tok_state *tok, const char *data, Py_ssize_t len,
+ int preserve_crlf, int add_final_newline)
+{
+ Py_ssize_t pos = 0;
+ while (pos < len) {
+ Py_ssize_t raw_line_len;
+ if (preserve_crlf) {
+ const char *newline = memchr(data + pos, '\n', len - pos);
+ raw_line_len = newline == NULL
+ ? len - pos : newline - data - pos + 1;
+ }
+ else {
+ raw_line_len = raw_line_length(data + pos, len - pos);
+ }
+ int terminated = preserve_crlf
+ ? data[pos + raw_line_len - 1] == '\n'
+ : data[pos + raw_line_len - 1] == '\n' ||
+ data[pos + raw_line_len - 1] == '\r';
+ int add_newline = add_final_newline &&
+ pos + raw_line_len == len && !terminated;
+ int normalize = add_newline ||
+ (!preserve_crlf &&
+ memchr(data + pos, '\r', raw_line_len) != NULL);
+
+ const char *line = data + pos;
+ Py_ssize_t line_len = raw_line_len;
+ char *normalized = NULL;
+ int implicit = 0;
+ if (normalize) {
+ normalized = _PyTok_NormalizeNewlines(
+ line, line_len, preserve_crlf, add_newline,
+ &line_len, &implicit);
+ if (normalized == NULL) {
+ tok->done = E_NOMEM;
+ return -1;
+ }
+ line = normalized;
+ }
+ _PyTok_Off appended = _PyTok_SourceAppendLine(
+ &tok->source, line, line_len, implicit);
+ PyMem_Free(normalized);
+ if (appended < 0) {
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_ERROR;
+ return -1;
+ }
+ pos += raw_line_len;
+ }
+ return 0;
+}
+
+int
+_PyTok_PrepareString(struct tok_state *tok, const char *input, int utf8_only,
+ int exec_input, int preserve_crlf)
+{
+ Py_ssize_t raw_len = strlen(input);
+ char *raw = (char *)input;
+
+ if (utf8_only) {
+ if (_PyTok_SetEncoding(tok, "utf-8") < 0) {
+ return -1;
+ }
+ }
+ else {
+ Py_ssize_t first_original_len = raw_line_length(raw, raw_len);
+ _PyTok_Chunk first = {
+ .data = raw,
+ .len = first_original_len,
+ .ownership = _PYTOK_CHUNK_BORROWED,
+ };
+ _PyTok_Chunk second = {0};
+ int have_second = first_original_len < raw_len;
+ if (have_second) {
+ second.data = raw + first_original_len;
+ second.len = raw_line_length(second.data,
+ raw_len - first_original_len);
+ }
+ Py_ssize_t bom_len;
+ _PyTok_EncodingResult detection = _PyTok_DetectEncoding(
+ tok, &first, have_second ? &second : NULL, 1, &bom_len);
+ if (detection == _PYTOK_ENCODING_ERROR) {
+ return -1;
+ }
+ raw += bom_len;
+ raw_len -= bom_len;
+ }
+
+ _PyTok_Chunk decoded = {
+ .data = raw,
+ .len = raw_len,
+ .ownership = _PYTOK_CHUNK_BORROWED,
+ };
+ if (tok->encoding != NULL && strcmp(tok->encoding, "utf-8") != 0) {
+ if (_PyTok_DecodeOnce(
+ tok, &decoded, tok->encoding, NULL) < 0) {
+ return -1;
+ }
+ }
+
+ int stored = store_prepared_source(
+ tok, decoded.data, decoded.len, preserve_crlf, exec_input);
+ _PyTok_ChunkClear(&decoded);
+ if (stored < 0) {
+ return -1;
+ }
+ tok->str = tok->source.bytes != NULL ? tok->source.bytes : (char *)"";
+ if (!utf8_only &&
+ (tok->encoding == NULL || strcmp(tok->encoding, "utf-8") == 0) &&
+ !_PyTokenizer_ensure_utf8(tok->str, tok, 1)) {
+ return -1;
+ }
+ return 0;
+}
+
+int
+_PyTok_StartDecoder(struct tok_state *tok, const char *errors)
+{
+ _PyTok_Reader *reader = tok->reader;
+ if (tok->encoding == NULL || reader->decoder != NULL) {
+ return 0;
+ }
+ if (reader->kind == _PYTOK_READER_FILE &&
+ strcmp(tok->encoding, "utf-8") == 0) {
+ return 0;
+ }
+
+ PyObject *codec = _PyCodec_LookupTextEncoding(tok->encoding, NULL);
+ if (codec != NULL) {
+ PyObject *factory = PyObject_GetAttrString(codec, "incrementaldecoder");
+ Py_DECREF(codec);
+ if (factory != NULL) {
+ reader->decoder = PyObject_CallFunction(factory, "s", errors);
+ Py_DECREF(factory);
+ }
+ }
+ if (reader->decoder == NULL) {
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_DECODE;
+ if (reader->kind == _PYTOK_READER_FILE) {
+ _PyTokenizer_raise_init_error(
+ tok->filename != NULL ? tok->filename : Py_None);
+ }
+ return -1;
+ }
+ return 0;
+}
+
+int
+_PyTok_DecodeChunk(struct tok_state *tok, _PyTok_Chunk *chunk, int final)
+{
+ _PyTok_Reader *reader = tok->reader;
+ if (reader->decoder == NULL) {
+ return 0;
+ }
+ int strip_bom = reader->kind == _PYTOK_READER_READLINE &&
+ chunk->len >= 2 &&
+ (((unsigned char)chunk->data[0] == 0xFF &&
+ (unsigned char)chunk->data[1] == 0xFE) ||
+ ((unsigned char)chunk->data[0] == 0xFE &&
+ (unsigned char)chunk->data[1] == 0xFF));
+ PyObject *input;
+ if (chunk->ownership == _PYTOK_CHUNK_PYOBJECT &&
+ PyBytes_Check(chunk->owner) &&
+ chunk->data == PyBytes_AS_STRING(chunk->owner)) {
+ input = Py_NewRef(chunk->owner);
+ }
+ else {
+ input = PyBytes_FromStringAndSize(chunk->data, chunk->len);
+ }
+ if (input == NULL) {
+ tok->done = E_NOMEM;
+ return -1;
+ }
+ PyObject *unicode = PyObject_CallMethodObjArgs(
+ reader->decoder, &_Py_ID(decode), input,
+ final ? Py_True : Py_False, NULL);
+ Py_DECREF(input);
+ if (unicode == NULL) {
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_DECODE;
+ if (reader->kind == _PYTOK_READER_FILE) {
+ _PyTokenizer_raise_init_error(
+ tok->filename != NULL ? tok->filename : Py_None);
+ }
+ return -1;
+ }
+ if (!PyUnicode_Check(unicode)) {
+ PyErr_Format(PyExc_TypeError,
+ "decoder should return a string result, not '%.200s'",
+ Py_TYPE(unicode)->tp_name);
+ Py_DECREF(unicode);
+ tok->done = E_DECODE;
+ return -1;
+ }
+ return chunk_set_unicode(tok, chunk, unicode, strip_bom);
+}
+
+int
+_PyTok_DecoderHasBufferedInput(struct tok_state *tok)
+{
+ if (tok->reader->decoder == NULL) {
+ return 0;
+ }
+ PyObject *state = PyObject_CallMethodNoArgs(
+ tok->reader->decoder, &_Py_ID(getstate));
+ if (state == NULL) {
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_DECODE;
+ return -1;
+ }
+ if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 2 ||
+ !PyBytes_Check(PyTuple_GET_ITEM(state, 0)) ||
+ !PyLong_Check(PyTuple_GET_ITEM(state, 1))) {
+ Py_DECREF(state);
+ PyErr_SetString(PyExc_TypeError,
+ "incremental decoder getstate() must return (bytes, int)");
+ tok->done = E_DECODE;
+ return -1;
+ }
+ int pending = PyBytes_GET_SIZE(PyTuple_GET_ITEM(state, 0)) != 0;
+ Py_DECREF(state);
+ return pending;
+}
diff --git a/Parser/tokenizer/file_tokenizer.c b/Parser/tokenizer/file_tokenizer.c
deleted file mode 100644
index a117025..0000000
--- a/Parser/tokenizer/file_tokenizer.c
+++ /dev/null
@@ -1,494 +0,0 @@
-#include "Python.h"
-#include "pycore_call.h" // _PyObject_CallNoArgs()
-#include "pycore_fileutils.h" // _Py_UniversalNewlineFgetsWithSize()
-#include "pycore_runtime.h" // _Py_ID()
-
-#include "errcode.h" // E_NOMEM
-
-#ifdef HAVE_UNISTD_H
-# include <unistd.h> // lseek(), read()
-#endif
-
-#include "helpers.h"
-#include "../lexer/state.h"
-#include "../lexer/lexer.h"
-#include "../lexer/buffer.h"
-
-
-static int
-tok_concatenate_interactive_new_line(struct tok_state *tok, const char *line) {
- assert(tok->fp_interactive);
-
- if (!line) {
- return 0;
- }
-
- Py_ssize_t current_size = tok->interactive_src_end - tok->interactive_src_start;
- Py_ssize_t line_size = strlen(line);
- char last_char = line[line_size > 0 ? line_size - 1 : line_size];
- if (last_char != '\n') {
- line_size += 1;
- }
- char* new_str = tok->interactive_src_start;
-
- new_str = PyMem_Realloc(new_str, current_size + line_size + 1);
- if (!new_str) {
- if (tok->interactive_src_start) {
- PyMem_Free(tok->interactive_src_start);
- }
- tok->interactive_src_start = NULL;
- tok->interactive_src_end = NULL;
- tok->done = E_NOMEM;
- return -1;
- }
- strcpy(new_str + current_size, line);
- tok->implicit_newline = 0;
- if (last_char != '\n') {
- /* Last line does not end in \n, fake one */
- new_str[current_size + line_size - 1] = '\n';
- new_str[current_size + line_size] = '\0';
- tok->implicit_newline = 1;
- }
- tok->interactive_src_start = new_str;
- tok->interactive_src_end = new_str + current_size + line_size;
- return 0;
-}
-
-static int
-tok_readline_raw(struct tok_state *tok)
-{
- do {
- if (!_PyLexer_tok_reserve_buf(tok, BUFSIZ)) {
- return 0;
- }
- int n_chars = (int)(tok->end - tok->inp);
- size_t line_size = 0;
- char *line = _Py_UniversalNewlineFgetsWithSize(tok->inp, n_chars, tok->fp, NULL, &line_size);
- if (line == NULL) {
- return 1;
- }
- if (tok->fp_interactive &&
- tok_concatenate_interactive_new_line(tok, line) == -1) {
- return 0;
- }
- tok->inp += line_size;
- if (tok->inp == tok->buf) {
- return 0;
- }
- } while (tok->inp[-1] != '\n');
- return 1;
-}
-
-static int
-tok_readline_recode(struct tok_state *tok) {
- PyObject *line;
- const char *buf;
- Py_ssize_t buflen;
- line = tok->decoding_buffer;
- if (line == NULL) {
- line = PyObject_CallNoArgs(tok->decoding_readline);
- if (line == NULL) {
- _PyTokenizer_error_ret(tok);
- goto error;
- }
- }
- else {
- tok->decoding_buffer = NULL;
- }
- buf = PyUnicode_AsUTF8AndSize(line, &buflen);
- if (buf == NULL) {
- _PyTokenizer_error_ret(tok);
- goto error;
- }
- // Make room for the null terminator *and* potentially
- // an extra newline character that we may need to artificially
- // add.
- size_t buffer_size = buflen + 2;
- if (!_PyLexer_tok_reserve_buf(tok, buffer_size)) {
- goto error;
- }
- memcpy(tok->inp, buf, buflen);
- tok->inp += buflen;
- *tok->inp = '\0';
- if (tok->fp_interactive &&
- tok_concatenate_interactive_new_line(tok, buf) == -1) {
- goto error;
- }
- Py_DECREF(line);
- return 1;
-error:
- Py_XDECREF(line);
- return 0;
-}
-
-/* Fetch the next byte from TOK. */
-static int fp_getc(struct tok_state *tok) {
- return getc(tok->fp);
-}
-
-/* Unfetch the last byte back into TOK. */
-static void fp_ungetc(int c, struct tok_state *tok) {
- ungetc(c, tok->fp);
-}
-
-/* Set the readline function for TOK to a StreamReader's
- readline function. The StreamReader is named ENC.
-
- This function is called from _PyTokenizer_check_bom and _PyTokenizer_check_coding_spec.
-
- ENC is usually identical to the future value of tok->encoding,
- except for the (currently unsupported) case of UTF-16.
-
- Return 1 on success, 0 on failure. */
-static int
-fp_setreadl(struct tok_state *tok, const char* enc)
-{
- PyObject *readline, *open, *stream;
- int fd;
- long pos;
-
- fd = fileno(tok->fp);
- /* Due to buffering the file offset for fd can be different from the file
- * position of tok->fp. If tok->fp was opened in text mode on Windows,
- * its file position counts CRLF as one char and can't be directly mapped
- * to the file offset for fd. Instead we step back one byte and read to
- * the end of line.*/
- pos = ftell(tok->fp);
- if (pos == -1 ||
- lseek(fd, (off_t)(pos > 0 ? pos - 1 : pos), SEEK_SET) == (off_t)-1) {
- PyErr_SetFromErrnoWithFilename(PyExc_OSError, NULL);
- return 0;
- }
-
- open = PyImport_ImportModuleAttrString("io", "open");
- if (open == NULL) {
- return 0;
- }
- stream = PyObject_CallFunction(open, "isisOOO",
- fd, "r", -1, enc, Py_None, Py_None, Py_False);
- Py_DECREF(open);
- if (stream == NULL) {
- return 0;
- }
-
- readline = PyObject_GetAttr(stream, &_Py_ID(readline));
- Py_DECREF(stream);
- if (readline == NULL) {
- return 0;
- }
- Py_XSETREF(tok->decoding_readline, readline);
-
- if (pos > 0) {
- PyObject *bufobj = _PyObject_CallNoArgs(readline);
- if (bufobj == NULL) {
- return 0;
- }
- Py_DECREF(bufobj);
- }
-
- return 1;
-}
-
-static int
-tok_underflow_interactive(struct tok_state *tok) {
- if (tok->interactive_underflow == IUNDERFLOW_STOP) {
- tok->done = E_INTERACT_STOP;
- return 1;
- }
- char *newtok = PyOS_Readline(tok->fp ? tok->fp : stdin, stdout, tok->prompt);
- if (newtok != NULL) {
- char *translated = _PyTokenizer_translate_newlines(newtok, 0, 0, tok);
- PyMem_Free(newtok);
- if (translated == NULL) {
- return 0;
- }
- newtok = translated;
- }
- if (tok->encoding && newtok && *newtok) {
- /* Recode to UTF-8 */
- Py_ssize_t buflen;
- const char* buf;
- PyObject *u = _PyTokenizer_translate_into_utf8(newtok, tok->encoding);
- PyMem_Free(newtok);
- if (u == NULL) {
- tok->done = E_DECODE;
- return 0;
- }
- buflen = PyBytes_GET_SIZE(u);
- buf = PyBytes_AS_STRING(u);
- newtok = PyMem_Malloc(buflen+1);
- if (newtok == NULL) {
- Py_DECREF(u);
- tok->done = E_NOMEM;
- return 0;
- }
- strcpy(newtok, buf);
- Py_DECREF(u);
- }
- if (tok->fp_interactive &&
- tok_concatenate_interactive_new_line(tok, newtok) == -1) {
- PyMem_Free(newtok);
- return 0;
- }
- if (tok->nextprompt != NULL) {
- tok->prompt = tok->nextprompt;
- }
- if (newtok == NULL) {
- tok->done = E_INTR;
- }
- else if (*newtok == '\0') {
- PyMem_Free(newtok);
- tok->done = E_EOF;
- }
- else if (tok->start != NULL) {
- Py_ssize_t cur_multi_line_start = tok->multi_line_start - tok->buf;
- _PyLexer_remember_fstring_buffers(tok);
- size_t size = strlen(newtok);
- ADVANCE_LINENO();
- if (!_PyLexer_tok_reserve_buf(tok, size + 1)) {
- PyMem_Free(tok->buf);
- tok->buf = NULL;
- PyMem_Free(newtok);
- return 0;
- }
- memcpy(tok->cur, newtok, size + 1);
- PyMem_Free(newtok);
- tok->inp += size;
- tok->multi_line_start = tok->buf + cur_multi_line_start;
- _PyLexer_restore_fstring_buffers(tok);
- }
- else {
- _PyLexer_remember_fstring_buffers(tok);
- ADVANCE_LINENO();
- PyMem_Free(tok->buf);
- tok->buf = newtok;
- tok->cur = tok->buf;
- tok->line_start = tok->buf;
- tok->inp = strchr(tok->buf, '\0');
- tok->end = tok->inp + 1;
- _PyLexer_restore_fstring_buffers(tok);
- }
- if (tok->done != E_OK) {
- if (tok->prompt != NULL) {
- PySys_WriteStderr("\n");
- }
- return 0;
- }
-
- if (tok->tok_mode_stack_index && !_PyLexer_update_ftstring_expr(tok, 0)) {
- return 0;
- }
- return 1;
-}
-
-static int
-tok_underflow_file(struct tok_state *tok)
-{
- if (tok->decoding_state == STATE_INIT) {
- /* We have not yet determined the encoding.
- If an encoding is found, use the file-pointer
- reader functions from now on. */
- if (!_PyTokenizer_check_bom(fp_getc, fp_ungetc, fp_setreadl, tok)) {
- _PyTokenizer_error_ret(tok);
- return 0;
- }
- assert(tok->decoding_state != STATE_INIT);
- }
- int raw = tok->decoding_readline == NULL;
- if (raw && tok->decoding_state != STATE_NORMAL) {
- /* Keep the first line in the buffer to validate it later if
- * the encoding has not yet been determined. */
- }
- else if (tok->start == NULL && !INSIDE_FSTRING(tok)) {
- tok->cur = tok->inp = tok->buf;
- }
- /* Read until '\n' or EOF */
- if (!raw) {
- /* We already have a codec associated with this input. */
- if (!tok_readline_recode(tok)) {
- return 0;
- }
- }
- else {
- /* We want a 'raw' read. */
- if (!tok_readline_raw(tok)) {
- return 0;
- }
- }
- if (tok->inp == tok->cur) {
- tok->done = E_EOF;
- return 0;
- }
- tok->implicit_newline = 0;
- if (tok->inp[-1] != '\n') {
- assert(tok->inp + 1 < tok->end);
- /* Last line does not end in \n, fake one */
- *tok->inp++ = '\n';
- *tok->inp = '\0';
- tok->implicit_newline = 1;
- }
-
- if (tok->tok_mode_stack_index && !_PyLexer_update_ftstring_expr(tok, 0)) {
- return 0;
- }
-
- ADVANCE_LINENO();
- if (tok->decoding_state != STATE_NORMAL) {
- if (!_PyTokenizer_check_coding_spec(tok->cur, strlen(tok->cur),
- tok, fp_setreadl))
- {
- return 0;
- }
- if (tok->lineno >= 2) {
- tok->decoding_state = STATE_NORMAL;
- }
- }
- if (raw && tok->decoding_state == STATE_NORMAL) {
- const char *line = tok->lineno <= 2 ? tok->buf : tok->cur;
- int lineno = tok->lineno <= 2 ? 1 : tok->lineno;
- if (!tok->encoding) {
- /* The default encoding is UTF-8, so make sure we don't have any
- non-UTF-8 sequences in it. */
- if (!_PyTokenizer_ensure_utf8(line, tok, lineno)) {
- _PyTokenizer_error_ret(tok);
- return 0;
- }
- }
- else {
- PyObject *tmp = PyUnicode_Decode(line, strlen(line),
- tok->encoding, NULL);
- if (tmp == NULL) {
- _PyTokenizer_error_ret(tok);
- return 0;
- }
- Py_DECREF(tmp);
- }
- }
- assert(tok->done == E_OK);
- return tok->done == E_OK;
-}
-
-/* Set up tokenizer for file */
-struct tok_state *
-_PyTokenizer_FromFile(FILE *fp, const char* enc,
- const char *ps1, const char *ps2)
-{
- struct tok_state *tok = _PyTokenizer_tok_new();
- if (tok == NULL)
- return NULL;
- if ((tok->buf = (char *)PyMem_Malloc(BUFSIZ)) == NULL) {
- _PyTokenizer_Free(tok);
- PyErr_NoMemory();
- return NULL;
- }
- tok->cur = tok->inp = tok->buf;
- tok->end = tok->buf + BUFSIZ;
- tok->fp = fp;
- tok->prompt = ps1;
- tok->nextprompt = ps2;
- if (ps1 || ps2) {
- tok->underflow = &tok_underflow_interactive;
- } else {
- tok->underflow = &tok_underflow_file;
- }
- if (enc != NULL) {
- /* Must copy encoding declaration since it
- gets copied into the parse tree. */
- tok->encoding = _PyTokenizer_new_string(enc, strlen(enc), tok);
- if (!tok->encoding) {
- _PyTokenizer_Free(tok);
- return NULL;
- }
- tok->decoding_state = STATE_NORMAL;
- }
- return tok;
-}
-
-#if defined(__wasi__) || (defined(__EMSCRIPTEN__) && (__EMSCRIPTEN_major__ >= 3))
-// fdopen() with borrowed fd. WASI does not provide dup() and Emscripten's
-// dup() emulation with open() is slow.
-typedef union {
- void *cookie;
- int fd;
-} borrowed;
-
-static ssize_t
-borrow_read(void *cookie, char *buf, size_t size)
-{
- borrowed b = {.cookie = cookie};
- return read(b.fd, (void *)buf, size);
-}
-
-static FILE *
-fdopen_borrow(int fd) {
- // supports only reading. seek fails. close and write are no-ops.
- cookie_io_functions_t io_cb = {borrow_read, NULL, NULL, NULL};
- borrowed b = {.fd = fd};
- return fopencookie(b.cookie, "r", io_cb);
-}
-#else
-static FILE *
-fdopen_borrow(int fd) {
- fd = _Py_dup(fd);
- if (fd < 0) {
- return NULL;
- }
- return fdopen(fd, "r");
-}
-#endif
-
-/* Get the encoding of a Python file. Check for the coding cookie and check if
- the file starts with a BOM.
-
- _PyTokenizer_FindEncodingFilename() returns NULL when it can't find the
- encoding in the first or second line of the file (in which case the encoding
- should be assumed to be UTF-8).
-
- The char* returned is malloc'ed via PyMem_Malloc() and thus must be freed
- by the caller. */
-char *
-_PyTokenizer_FindEncodingFilename(int fd, PyObject *filename)
-{
- struct tok_state *tok;
- FILE *fp;
- char *encoding = NULL;
-
- fp = fdopen_borrow(fd);
- if (fp == NULL) {
- return NULL;
- }
- tok = _PyTokenizer_FromFile(fp, NULL, NULL, NULL);
- if (tok == NULL) {
- fclose(fp);
- return NULL;
- }
- if (filename != NULL) {
- tok->filename = Py_NewRef(filename);
- }
- else {
- tok->filename = PyUnicode_FromString("<string>");
- if (tok->filename == NULL) {
- fclose(fp);
- _PyTokenizer_Free(tok);
- return encoding;
- }
- }
- struct token token;
- // We don't want to report warnings here because it could cause infinite recursion
- // if fetching the encoding shows a warning.
- tok->report_warnings = 0;
- while (tok->lineno < 2 && tok->done == E_OK) {
- _PyToken_Init(&token);
- _PyTokenizer_Get(tok, &token);
- _PyToken_Free(&token);
- }
- fclose(fp);
- if (tok->encoding) {
- encoding = (char *)PyMem_Malloc(strlen(tok->encoding) + 1);
- if (encoding) {
- strcpy(encoding, tok->encoding);
- }
- }
- _PyTokenizer_Free(tok);
- return encoding;
-}
diff --git a/Parser/tokenizer/helpers.c b/Parser/tokenizer/helpers.c
index 62b0971..bbd6476 100644
--- a/Parser/tokenizer/helpers.c
+++ b/Parser/tokenizer/helpers.c
@@ -95,20 +95,6 @@ _PyTokenizer_indenterror(struct tok_state *tok)
return ERRORTOKEN;
}
-char *
-_PyTokenizer_error_ret(struct tok_state *tok) /* XXX */
-{
- tok->decoding_erred = 1;
- if ((tok->fp != NULL || tok->readline != NULL) && tok->buf != NULL) {/* see _PyTokenizer_Free */
- PyMem_Free(tok->buf);
- }
- tok->buf = tok->cur = tok->inp = NULL;
- tok->start = NULL;
- tok->end = NULL;
- tok->done = E_DECODE;
- return NULL; /* as if it were EOF */
-}
-
int
_PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_invalid_escape_char)
{
@@ -234,261 +220,6 @@ _PyTokenizer_parser_warn(struct tok_state *tok, PyObject *category, const char *
}
-/* ############## STRING MANIPULATION ############## */
-
-char *
-_PyTokenizer_new_string(const char *s, Py_ssize_t len, struct tok_state *tok)
-{
- char* result = (char *)PyMem_Malloc(len + 1);
- if (!result) {
- tok->done = E_NOMEM;
- PyErr_NoMemory();
- return NULL;
- }
- memcpy(result, s, len);
- result[len] = '\0';
- return result;
-}
-
-PyObject *
-_PyTokenizer_translate_into_utf8(const char* str, const char* enc) {
- PyObject *utf8;
- PyObject* buf = PyUnicode_Decode(str, strlen(str), enc, NULL);
- if (buf == NULL)
- return NULL;
- utf8 = PyUnicode_AsUTF8String(buf);
- Py_DECREF(buf);
- return utf8;
-}
-
-char *
-_PyTokenizer_translate_newlines(const char *s, int exec_input, int preserve_crlf,
- struct tok_state *tok) {
- int skip_next_lf = 0;
- size_t needed_length = strlen(s) + 2, final_length;
- char *buf, *current;
- char c = '\0';
- buf = PyMem_Malloc(needed_length);
- if (buf == NULL) {
- tok->done = E_NOMEM;
- PyErr_NoMemory();
- return NULL;
- }
- for (current = buf; *s; s++, current++) {
- c = *s;
- if (skip_next_lf) {
- skip_next_lf = 0;
- if (c == '\n') {
- c = *++s;
- if (!c)
- break;
- }
- }
- if (!preserve_crlf && c == '\r') {
- skip_next_lf = 1;
- c = '\n';
- }
- *current = c;
- }
- /* If this is exec input, add a newline to the end of the string if
- there isn't one already. */
- if (exec_input && c != '\n' && c != '\0') {
- *current = '\n';
- current++;
- }
- *current = '\0';
- final_length = current - buf + 1;
- if (final_length < needed_length && final_length) {
- /* should never fail */
- char* result = PyMem_Realloc(buf, final_length);
- if (result == NULL) {
- PyMem_Free(buf);
- }
- buf = result;
- }
- return buf;
-}
-
-/* ############## ENCODING STUFF ############## */
-
-
-/* See whether the file starts with a BOM. If it does,
- invoke the set_readline function with the new encoding.
- Return 1 on success, 0 on failure. */
-int
-_PyTokenizer_check_bom(int get_char(struct tok_state *),
- void unget_char(int, struct tok_state *),
- int set_readline(struct tok_state *, const char *),
- struct tok_state *tok)
-{
- int ch1, ch2, ch3;
- ch1 = get_char(tok);
- tok->decoding_state = STATE_SEEK_CODING;
- if (ch1 == EOF) {
- return 1;
- } else if (ch1 == 0xEF) {
- ch2 = get_char(tok);
- if (ch2 != 0xBB) {
- unget_char(ch2, tok);
- unget_char(ch1, tok);
- return 1;
- }
- ch3 = get_char(tok);
- if (ch3 != 0xBF) {
- unget_char(ch3, tok);
- unget_char(ch2, tok);
- unget_char(ch1, tok);
- return 1;
- }
- } else {
- unget_char(ch1, tok);
- return 1;
- }
- if (tok->encoding != NULL)
- PyMem_Free(tok->encoding);
- tok->encoding = _PyTokenizer_new_string("utf-8", 5, tok);
- if (!tok->encoding)
- return 0;
- /* No need to set_readline: input is already utf-8 */
- return 1;
-}
-
-static const char *
-get_normal_name(const char *s) /* for utf-8 and latin-1 */
-{
- char buf[13];
- int i;
- for (i = 0; i < 12; i++) {
- int c = s[i];
- if (c == '\0')
- break;
- else if (c == '_')
- buf[i] = '-';
- else
- buf[i] = Py_TOLOWER(c);
- }
- buf[i] = '\0';
- if (strcmp(buf, "utf-8") == 0 ||
- strncmp(buf, "utf-8-", 6) == 0)
- return "utf-8";
- else if (strcmp(buf, "latin-1") == 0 ||
- strcmp(buf, "iso-8859-1") == 0 ||
- strcmp(buf, "iso-latin-1") == 0 ||
- strncmp(buf, "latin-1-", 8) == 0 ||
- strncmp(buf, "iso-8859-1-", 11) == 0 ||
- strncmp(buf, "iso-latin-1-", 12) == 0)
- return "iso-8859-1";
- else
- return s;
-}
-
-/* Return the coding spec in S, or NULL if none is found. */
-static int
-get_coding_spec(const char *s, char **spec, Py_ssize_t size, struct tok_state *tok)
-{
- Py_ssize_t i;
- *spec = NULL;
- /* Coding spec must be in a comment, and that comment must be
- * the only statement on the source code line. */
- for (i = 0; i < size - 6; i++) {
- if (s[i] == '#')
- break;
- if (s[i] != ' ' && s[i] != '\t' && s[i] != '\014')
- return 1;
- }
- for (; i < size - 6; i++) { /* XXX inefficient search */
- const char* t = s + i;
- if (memcmp(t, "coding", 6) == 0) {
- const char* begin = NULL;
- t += 6;
- if (t[0] != ':' && t[0] != '=')
- continue;
- do {
- t++;
- } while (t[0] == ' ' || t[0] == '\t');
-
- begin = t;
- while (Py_ISALNUM(t[0]) ||
- t[0] == '-' || t[0] == '_' || t[0] == '.')
- t++;
-
- if (begin < t) {
- char* r = _PyTokenizer_new_string(begin, t - begin, tok);
- const char* q;
- if (!r)
- return 0;
- q = get_normal_name(r);
- if (r != q) {
- PyMem_Free(r);
- r = _PyTokenizer_new_string(q, strlen(q), tok);
- if (!r)
- return 0;
- }
- *spec = r;
- break;
- }
- }
- }
- return 1;
-}
-
-/* Check whether the line contains a coding spec. If it does,
- invoke the set_readline function for the new encoding.
- This function receives the tok_state and the new encoding.
- Return 1 on success, 0 on failure. */
-int
-_PyTokenizer_check_coding_spec(const char* line, Py_ssize_t size, struct tok_state *tok,
- int set_readline(struct tok_state *, const char *))
-{
- char *cs;
- if (tok->cont_line) {
- /* It's a continuation line, so it can't be a coding spec. */
- tok->decoding_state = STATE_NORMAL;
- return 1;
- }
- if (!get_coding_spec(line, &cs, size, tok)) {
- return 0;
- }
- if (!cs) {
- Py_ssize_t i;
- for (i = 0; i < size; i++) {
- if (line[i] == '#' || line[i] == '\n' || line[i] == '\r')
- break;
- if (line[i] != ' ' && line[i] != '\t' && line[i] != '\014') {
- /* Stop checking coding spec after a line containing
- * anything except a comment. */
- tok->decoding_state = STATE_NORMAL;
- break;
- }
- }
- return 1;
- }
- tok->decoding_state = STATE_NORMAL;
- if (tok->encoding == NULL) {
- assert(tok->decoding_readline == NULL);
- if (strcmp(cs, "utf-8") != 0 && !set_readline(tok, cs)) {
- _PyTokenizer_raise_init_error(tok->filename);
- _PyTokenizer_error_ret(tok);
- PyMem_Free(cs);
- return 0;
- }
- tok->encoding = cs;
- } else { /* then, compare cs with BOM */
- if (strcmp(tok->encoding, cs) != 0) {
- tok->line_start = line;
- tok->cur = (char *)line;
- assert(size <= INT_MAX);
- _PyTokenizer_syntaxerror_known_range(tok, 0, (int)size,
- "encoding problem: %s with BOM", cs);
- PyMem_Free(cs);
- _PyTokenizer_error_ret(tok);
- return 0;
- }
- PyMem_Free(cs);
- }
- return 1;
-}
-
/* Check whether the characters at s start a valid
UTF-8 sequence. Return the number of characters forming
the sequence if yes, 0 if not. The special cases match
diff --git a/Parser/tokenizer/helpers.h b/Parser/tokenizer/helpers.h
index 3430399..5edf5a3 100644
--- a/Parser/tokenizer/helpers.h
+++ b/Parser/tokenizer/helpers.h
@@ -14,19 +14,8 @@ int _PyTokenizer_syntaxerror_known_range(struct tok_state *tok, int col_offset,
int _PyTokenizer_indenterror(struct tok_state *tok);
int _PyTokenizer_warn_invalid_escape_sequence(struct tok_state *tok, int first_invalid_escape_char);
int _PyTokenizer_parser_warn(struct tok_state *tok, PyObject *category, const char *format, ...);
-char *_PyTokenizer_error_ret(struct tok_state *tok);
void _PyTokenizer_raise_init_error(PyObject *filename);
-char *_PyTokenizer_new_string(const char *s, Py_ssize_t len, struct tok_state *tok);
-char *_PyTokenizer_translate_newlines(const char *s, int exec_input, int preserve_crlf, struct tok_state *tok);
-PyObject *_PyTokenizer_translate_into_utf8(const char* str, const char* enc);
-
-int _PyTokenizer_check_bom(int get_char(struct tok_state *),
- void unget_char(int, struct tok_state *),
- int set_readline(struct tok_state *, const char *),
- struct tok_state *tok);
-int _PyTokenizer_check_coding_spec(const char* line, Py_ssize_t size, struct tok_state *tok,
- int set_readline(struct tok_state *, const char *));
int _PyTokenizer_ensure_utf8(const char *line, struct tok_state *tok, int lineno);
#ifdef Py_DEBUG
diff --git a/Parser/tokenizer/reader.c b/Parser/tokenizer/reader.c
new file mode 100644
index 0000000..82b824f
--- /dev/null
+++ b/Parser/tokenizer/reader.c
@@ -0,0 +1,780 @@
+#include "Python.h"
+#include "pycore_fileutils.h"
+
+#include "errcode.h"
+#include "helpers.h"
+#include "reader.h"
+#include "reader_internal.h"
+#include "../lexer/buffer.h"
+#include "../lexer/lexer.h"
+#include "../lexer/state.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+void
+_PyTok_ReaderFree(struct tok_state *tok)
+{
+ _PyTok_Reader *reader = tok->reader;
+ if (reader == NULL) {
+ return;
+ }
+ Py_XDECREF(reader->readline);
+ Py_XDECREF(reader->decoder);
+ for (int i = 0;
+ i < (int)Py_ARRAY_LENGTH(reader->prefetched_lines); i++) {
+ _PyTok_ChunkClear(&reader->prefetched_lines[i]);
+ }
+ PyMem_Free(reader->file_buffer);
+ PyMem_Free(reader->decoded);
+ if (reader->kind != _PYTOK_READER_PREPARED) {
+ PyMem_Free(tok->buf);
+ tok->buf = NULL;
+ }
+ PyMem_Free(reader);
+ tok->reader = NULL;
+}
+
+static int
+reserve_buffer(char **buffer, Py_ssize_t *capacity, Py_ssize_t needed)
+{
+ if (needed <= *capacity) {
+ return 0;
+ }
+ Py_ssize_t cap = *capacity > 0 ? *capacity : BUFSIZ;
+ while (cap < needed) {
+ if (cap > PY_SSIZE_T_MAX / 2) {
+ cap = needed;
+ break;
+ }
+ cap *= 2;
+ }
+ char *resized = PyMem_Realloc(*buffer, cap);
+ if (resized == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ *buffer = resized;
+ *capacity = cap;
+ return 0;
+}
+
+static int
+append_decoded(_PyTok_Reader *reader, const char *data, Py_ssize_t len)
+{
+ if (reader->decoded_pos > 0) {
+ Py_ssize_t remaining = reader->decoded_len - reader->decoded_pos;
+ memmove(reader->decoded, reader->decoded + reader->decoded_pos,
+ (size_t)remaining);
+ reader->decoded_pos = 0;
+ reader->decoded_len = remaining;
+ }
+ if (len < 0 || reader->decoded_len > PY_SSIZE_T_MAX - len - 1 ||
+ reserve_buffer(&reader->decoded, &reader->decoded_cap,
+ reader->decoded_len + len + 1) < 0) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ memcpy(reader->decoded + reader->decoded_len, data, (size_t)len);
+ reader->decoded_len += len;
+ reader->decoded[reader->decoded_len] = '\0';
+ return 0;
+}
+
+static int
+append_implicit_newline(_PyTok_Reader *reader)
+{
+ if (reader->decoded_pos == reader->decoded_len ||
+ reader->decoded[reader->decoded_len - 1] == '\n') {
+ return 0;
+ }
+ if (append_decoded(reader, "\n", 1) < 0) {
+ return -1;
+ }
+ reader->decoded_tail_is_implicit = 1;
+ return 0;
+}
+
+static int
+pop_decoded_line(_PyTok_Reader *reader, _PyTok_Chunk *chunk)
+{
+ if (reader->decoded_pos == reader->decoded_len) {
+ return 0;
+ }
+ char *start = reader->decoded + reader->decoded_pos;
+ char *newline = memchr(start, '\n',
+ reader->decoded_len - reader->decoded_pos);
+ if (newline == NULL) {
+ return 0;
+ }
+ Py_ssize_t len = newline - start + 1;
+ chunk->data = start;
+ chunk->len = len;
+ chunk->ownership = _PYTOK_CHUNK_BORROWED;
+ reader->decoded_pos += len;
+ chunk->implicit_newline = reader->decoded_pos == reader->decoded_len &&
+ reader->decoded_tail_is_implicit;
+ if (reader->decoded_pos == reader->decoded_len) {
+ reader->decoded_pos = reader->decoded_len = 0;
+ reader->decoded_tail_is_implicit = 0;
+ }
+ return 1;
+}
+
+static int
+chunk_is_line(const _PyTok_Chunk *chunk)
+{
+ if (chunk->len == 0 || chunk->data[chunk->len - 1] != '\n') {
+ return 0;
+ }
+ return memchr(chunk->data, '\n', chunk->len - 1) == NULL;
+}
+
+static _PyTok_ReadResult
+next_prepared(struct tok_state *tok, _PyTok_Chunk *chunk)
+{
+ int lineno = tok->lineno + 1;
+ if (lineno > tok->source.nlines) {
+ return _PYTOK_READ_EOF;
+ }
+ const char *start = tok->inp;
+ const char *newline = memchr(
+ start, '\n', tok->source.bytes + tok->source.len - start);
+ _PyTok_Off end = newline != NULL
+ ? newline - tok->source.bytes + 1 : tok->source.len;
+ chunk->data = (char *)start;
+ chunk->len = tok->source.bytes + end - start;
+ chunk->ownership = _PYTOK_CHUNK_BORROWED;
+ chunk->implicit_newline = _PyTok_SourceLineIsImplicit(
+ &tok->source, lineno);
+ return _PYTOK_READ_LINE;
+}
+
+static _PyTok_ReadResult
+read_file_line(struct tok_state *tok, _PyTok_Chunk *chunk)
+{
+ _PyTok_Reader *reader = tok->reader;
+ Py_ssize_t len = 0;
+ for (;;) {
+ if (len > PY_SSIZE_T_MAX - BUFSIZ ||
+ reserve_buffer(&reader->file_buffer, &reader->file_buffer_cap,
+ len + BUFSIZ) < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ int available = (int)Py_MIN(reader->file_buffer_cap - len, INT_MAX);
+ size_t read = 0;
+ char *result = _Py_UniversalNewlineFgetsWithSize(
+ reader->file_buffer + len, available, tok->fp, NULL, &read);
+ if (result == NULL) {
+ if (len == 0) {
+ return _PYTOK_READ_EOF;
+ }
+ break;
+ }
+ len += (Py_ssize_t)read;
+ if (len > 0 && reader->file_buffer[len - 1] == '\n') {
+ break;
+ }
+ }
+ int implicit = len == 0 || reader->file_buffer[len - 1] != '\n';
+ chunk->data = reader->file_buffer;
+ chunk->len = len;
+ chunk->implicit_newline = implicit;
+ chunk->ownership = _PYTOK_CHUNK_BORROWED;
+ return _PYTOK_READ_LINE;
+}
+
+static int
+initialize_file(struct tok_state *tok)
+{
+ _PyTok_Reader *reader = tok->reader;
+ reader->file_initialized = 1;
+ if (tok->encoding != NULL) {
+ return _PyTok_StartDecoder(tok, "strict");
+ }
+
+ _PyTok_ReadResult result = read_file_line(
+ tok, &reader->prefetched_lines[0]);
+ if (result == _PYTOK_READ_EOF) {
+ reader->file_eof = 1;
+ return 0;
+ }
+ if (result != _PYTOK_READ_LINE) {
+ return -1;
+ }
+ reader->prefetched_count = 1;
+ Py_ssize_t bom_len;
+ _PyTok_EncodingResult detection = _PyTok_DetectEncoding(
+ tok, &reader->prefetched_lines[0], NULL, 0, &bom_len);
+ if (detection == _PYTOK_ENCODING_ERROR) {
+ return -1;
+ }
+ if (detection == _PYTOK_ENCODING_NEED_SECOND_LINE) {
+ char *first = _PyTok_CopyBytes(
+ reader->prefetched_lines[0].data,
+ reader->prefetched_lines[0].len);
+ if (first == NULL) {
+ tok->done = E_NOMEM;
+ return -1;
+ }
+ reader->prefetched_lines[0].data = first;
+ reader->prefetched_lines[0].ownership = _PYTOK_CHUNK_PYMEM;
+ result = read_file_line(tok, &reader->prefetched_lines[1]);
+ if (result == _PYTOK_READ_LINE) {
+ reader->prefetched_count = 2;
+ }
+ else if (result == _PYTOK_READ_EOF) {
+ reader->file_eof = 1;
+ }
+ else {
+ return -1;
+ }
+ _PyTok_Chunk *second = reader->prefetched_count == 2
+ ? &reader->prefetched_lines[1] : NULL;
+ detection = _PyTok_DetectEncoding(
+ tok, &reader->prefetched_lines[0], second, 1, &bom_len);
+ if (detection == _PYTOK_ENCODING_ERROR) {
+ return -1;
+ }
+ }
+ if (bom_len != 0) {
+ _PyTok_Chunk *first = &reader->prefetched_lines[0];
+ if (first->ownership == _PYTOK_CHUNK_PYMEM) {
+ memmove(first->data, first->data + bom_len,
+ (size_t)(first->len - bom_len));
+ first->data[first->len - bom_len] = '\0';
+ }
+ else {
+ first->data += bom_len;
+ }
+ first->len -= bom_len;
+ }
+ if (_PyTok_StartDecoder(tok, "strict") < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+finalize_decoding(struct tok_state *tok)
+{
+ _PyTok_Reader *reader = tok->reader;
+ if (reader->decoder_finalized) {
+ return 0;
+ }
+ reader->decoder_finalized = 1;
+ if (reader->decoder != NULL) {
+ _PyTok_Chunk input = {
+ .data = "",
+ .ownership = _PYTOK_CHUNK_BORROWED,
+ };
+ int decoded = _PyTok_DecodeChunk(tok, &input, 1);
+ if (decoded == 0 &&
+ append_decoded(reader, input.data, input.len) < 0) {
+ tok->done = E_NOMEM;
+ decoded = -1;
+ }
+ _PyTok_ChunkClear(&input);
+ if (decoded < 0) {
+ return -1;
+ }
+ }
+ if (reader->decoded_pos < reader->decoded_len &&
+ append_implicit_newline(reader) < 0) {
+ tok->done = E_NOMEM;
+ return -1;
+ }
+ return 0;
+}
+
+static _PyTok_ReadResult
+next_file(struct tok_state *tok, _PyTok_Chunk *chunk)
+{
+ _PyTok_Reader *reader = tok->reader;
+ if (!reader->file_initialized && initialize_file(tok) < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ for (;;) {
+ if (pop_decoded_line(reader, chunk)) {
+ return _PYTOK_READ_LINE;
+ }
+ _PyTok_Chunk input = {0};
+ if (reader->prefetched_index < reader->prefetched_count) {
+ input = reader->prefetched_lines[reader->prefetched_index];
+ reader->prefetched_lines[reader->prefetched_index++] =
+ (_PyTok_Chunk){0};
+ }
+ else if (!reader->file_eof) {
+ _PyTok_ReadResult result = read_file_line(tok, &input);
+ if (result == _PYTOK_READ_ERROR) {
+ return result;
+ }
+ if (result == _PYTOK_READ_EOF) {
+ reader->file_eof = 1;
+ }
+ }
+ if (input.data != NULL) {
+ int implicit = input.implicit_newline;
+ if (reader->decoder == NULL && !implicit) {
+ *chunk = input;
+ return _PYTOK_READ_LINE;
+ }
+ int decoded = _PyTok_DecodeChunk(tok, &input, 0);
+ if (decoded == 0 && chunk_is_line(&input)) {
+ *chunk = input;
+ return _PYTOK_READ_LINE;
+ }
+ if (decoded == 0 &&
+ append_decoded(reader, input.data, input.len) < 0) {
+ tok->done = E_NOMEM;
+ decoded = -1;
+ }
+ if (decoded == 0 && implicit) {
+ reader->decoded_tail_is_implicit = 1;
+ }
+ _PyTok_ChunkClear(&input);
+ if (decoded < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ continue;
+ }
+ if (!reader->decoder_finalized) {
+ if (finalize_decoding(tok) < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ continue;
+ }
+ return _PYTOK_READ_EOF;
+ }
+}
+
+static _PyTok_ReadResult
+next_readline(struct tok_state *tok, _PyTok_Chunk *chunk)
+{
+ _PyTok_Reader *reader = tok->reader;
+ for (;;) {
+ if (pop_decoded_line(reader, chunk)) {
+ return _PYTOK_READ_LINE;
+ }
+ if (reader->decoder_finalized) {
+ return _PYTOK_READ_EOF;
+ }
+
+ PyObject *raw = PyObject_CallNoArgs(reader->readline);
+ if (raw == NULL) {
+ if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
+ PyErr_Clear();
+ if (finalize_decoding(tok) < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ continue;
+ }
+ return _PYTOK_READ_ERROR;
+ }
+
+ _PyTok_Chunk input = {0};
+ if (tok->encoding != NULL) {
+ if (!PyBytes_Check(raw)) {
+ PyErr_SetString(PyExc_TypeError,
+ "readline() returned a non-bytes object");
+ Py_DECREF(raw);
+ return _PYTOK_READ_ERROR;
+ }
+ if (PyBytes_GET_SIZE(raw) == 0) {
+ Py_DECREF(raw);
+ if (_PyTok_StartDecoder(tok, "replace") < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ if (finalize_decoding(tok) < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ continue;
+ }
+ input.owner = raw;
+ input.data = PyBytes_AS_STRING(raw);
+ input.len = PyBytes_GET_SIZE(raw);
+ input.ownership = _PYTOK_CHUNK_PYOBJECT;
+ int decoded;
+ if (reader->decoder == NULL &&
+ strcmp(tok->encoding, "utf-8") == 0 &&
+ chunk_is_line(&input)) {
+ decoded = _PyTok_DecodeOnce(
+ tok, &input, "utf-8", "replace");
+ }
+ else {
+ decoded = _PyTok_StartDecoder(tok, "replace");
+ if (decoded == 0) {
+ decoded = _PyTok_DecodeChunk(tok, &input, 0);
+ }
+ }
+ if (decoded < 0) {
+ _PyTok_ChunkClear(&input);
+ return _PYTOK_READ_ERROR;
+ }
+ }
+ else {
+ if (!PyUnicode_Check(raw)) {
+ PyErr_SetString(PyExc_TypeError,
+ "readline() returned a non-string object");
+ Py_DECREF(raw);
+ return _PYTOK_READ_ERROR;
+ }
+ Py_ssize_t utf8_len;
+ const char *utf8 = PyUnicode_AsUTF8AndSize(raw, &utf8_len);
+ if (utf8 == NULL) {
+ Py_DECREF(raw);
+ return _PYTOK_READ_ERROR;
+ }
+ input.owner = raw;
+ input.data = (char *)utf8;
+ input.len = utf8_len;
+ input.ownership = _PYTOK_CHUNK_PYOBJECT;
+ if (input.len == 0) {
+ _PyTok_ChunkClear(&input);
+ if (finalize_decoding(tok) < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ continue;
+ }
+ }
+
+ if (reader->decoded_pos == reader->decoded_len &&
+ chunk_is_line(&input)) {
+ *chunk = input;
+ return _PYTOK_READ_LINE;
+ }
+
+ if (append_decoded(reader, input.data, input.len) < 0) {
+ _PyTok_ChunkClear(&input);
+ tok->done = E_NOMEM;
+ return _PYTOK_READ_ERROR;
+ }
+ _PyTok_ChunkClear(&input);
+ if (reader->decoded_pos < reader->decoded_len &&
+ reader->decoded[reader->decoded_len - 1] != '\n') {
+ int pending = _PyTok_DecoderHasBufferedInput(tok);
+ if (pending < 0) {
+ return _PYTOK_READ_ERROR;
+ }
+ if (!pending && append_implicit_newline(reader) < 0) {
+ tok->done = E_NOMEM;
+ return _PYTOK_READ_ERROR;
+ }
+ }
+ if (pop_decoded_line(reader, chunk)) {
+ return _PYTOK_READ_LINE;
+ }
+ }
+}
+
+static _PyTok_ReadResult
+next_interactive(struct tok_state *tok, _PyTok_Chunk *chunk)
+{
+ _PyTok_Reader *reader = tok->reader;
+ if (tok->interactive_underflow == IUNDERFLOW_STOP) {
+ return _PYTOK_READ_STOPPED;
+ }
+ char *input = PyOS_Readline(
+ tok->fp != NULL ? tok->fp : stdin, stdout, tok->prompt);
+ if (reader->nextprompt != NULL) {
+ tok->prompt = reader->nextprompt;
+ }
+ if (input == NULL) {
+ return _PYTOK_READ_INTERRUPT;
+ }
+ Py_ssize_t len = strlen(input);
+ if (len == 0) {
+ PyMem_Free(input);
+ return _PYTOK_READ_EOF;
+ }
+ _PyTok_Chunk decoded = {
+ .data = input,
+ .len = len,
+ .ownership = _PYTOK_CHUNK_PYMEM,
+ };
+ if (tok->encoding != NULL &&
+ _PyTok_DecodeOnce(
+ tok, &decoded, tok->encoding, NULL) < 0) {
+ _PyTok_ChunkClear(&decoded);
+ return _PYTOK_READ_ERROR;
+ }
+ chunk->data = _PyTok_NormalizeNewlines(
+ decoded.data, decoded.len, 0, 0,
+ &chunk->len, &chunk->implicit_newline);
+ _PyTok_ChunkClear(&decoded);
+ if (chunk->data == NULL) {
+ PyErr_NoMemory();
+ tok->done = E_NOMEM;
+ return _PYTOK_READ_ERROR;
+ }
+ chunk->ownership = _PYTOK_CHUNK_PYMEM;
+ return _PYTOK_READ_LINE;
+}
+
+static _PyTok_ReadResult
+reader_next(struct tok_state *tok, _PyTok_Chunk *chunk)
+{
+ *chunk = (_PyTok_Chunk){0};
+ switch (tok->reader->kind) {
+ case _PYTOK_READER_PREPARED:
+ return next_prepared(tok, chunk);
+ case _PYTOK_READER_FILE:
+ return next_file(tok, chunk);
+ case _PYTOK_READER_READLINE:
+ return next_readline(tok, chunk);
+ case _PYTOK_READER_INTERACTIVE:
+ return next_interactive(tok, chunk);
+ }
+ Py_UNREACHABLE();
+}
+
+int
+_PyTok_ReaderUnderflow(struct tok_state *tok)
+{
+ int prepared = tok->reader->kind == _PYTOK_READER_PREPARED;
+ int reset_buffer = !prepared && tok->start == NULL && !INSIDE_FSTRING(tok);
+
+ if (reset_buffer && tok->reader->kind != _PYTOK_READER_INTERACTIVE) {
+ tok->cur = tok->inp = tok->buf;
+ }
+
+ _PyTok_Chunk chunk;
+ _PyTok_ReadResult result = reader_next(tok, &chunk);
+ if (result != _PYTOK_READ_LINE) {
+ if (result == _PYTOK_READ_EOF) {
+ tok->done = E_EOF;
+ }
+ else if (result == _PYTOK_READ_STOPPED) {
+ tok->done = E_INTERACT_STOP;
+ }
+ else if (result == _PYTOK_READ_INTERRUPT) {
+ tok->done = E_INTR;
+ }
+ else {
+ tok->input_error = 1;
+ if (tok->done == E_OK) {
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_ERROR;
+ }
+ }
+ if (tok->reader->kind == _PYTOK_READER_INTERACTIVE &&
+ result != _PYTOK_READ_STOPPED) {
+ PySys_WriteStderr("\n");
+ }
+ return 0;
+ }
+
+ Py_ssize_t copy_len = chunk.len;
+ if (tok->reader->kind == _PYTOK_READER_INTERACTIVE &&
+ chunk.implicit_newline) {
+ copy_len--;
+ }
+ if (reset_buffer && tok->reader->kind == _PYTOK_READER_INTERACTIVE) {
+ tok->cur = tok->inp = tok->buf;
+ }
+ if (!prepared && !_PyLexer_tok_reserve_buf(tok, copy_len + 1)) {
+ _PyTok_ChunkClear(&chunk);
+ tok->input_error = 1;
+ return 0;
+ }
+ if (tok->reader->kind == _PYTOK_READER_INTERACTIVE &&
+ _PyTok_SourceAppendLine(&tok->source, chunk.data, chunk.len,
+ chunk.implicit_newline) < 0) {
+ _PyTok_ChunkClear(&chunk);
+ tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
+ ? E_NOMEM : E_ERROR;
+ tok->input_error = 1;
+ return 0;
+ }
+ if (tok->fp_interactive) {
+ tok->interactive_src_start = tok->source.bytes;
+ tok->interactive_src_end = tok->source.bytes + tok->source.len;
+ }
+ if (prepared) {
+ if (tok->start == NULL) {
+ tok->buf = tok->cur;
+ }
+ tok->inp = chunk.data + chunk.len;
+ }
+ else {
+ memcpy(tok->inp, chunk.data, (size_t)copy_len);
+ tok->inp += copy_len;
+ *tok->inp = '\0';
+ }
+ tok->implicit_newline = chunk.implicit_newline;
+
+ if (!prepared && tok->tok_mode_stack_index &&
+ !_PyLexer_update_ftstring_expr(tok, 0)) {
+ _PyTok_ChunkClear(&chunk);
+ tok->input_error = 1;
+ return 0;
+ }
+ ADVANCE_LINENO();
+ if (tok->reader->kind == _PYTOK_READER_FILE &&
+ (tok->encoding == NULL || strcmp(tok->encoding, "utf-8") == 0) &&
+ !_PyTokenizer_ensure_utf8(tok->cur, tok, tok->lineno)) {
+ _PyTok_ChunkClear(&chunk);
+ tok->input_error = 1;
+ return 0;
+ }
+ _PyTok_ChunkClear(&chunk);
+ return 1;
+}
+
+static struct tok_state *
+tokenizer_new_with_reader(_PyTok_ReaderKind kind)
+{
+ struct tok_state *tok = _PyTokenizer_tok_new();
+ if (tok == NULL) {
+ return NULL;
+ }
+ tok->reader = PyMem_Calloc(1, sizeof(*tok->reader));
+ if (tok->reader == NULL) {
+ PyErr_NoMemory();
+ _PyTokenizer_Free(tok);
+ return NULL;
+ }
+ tok->reader->kind = kind;
+ if (kind == _PYTOK_READER_PREPARED) {
+ return tok;
+ }
+ tok->buf = PyMem_Malloc(BUFSIZ);
+ if (tok->buf == NULL) {
+ PyErr_NoMemory();
+ _PyTokenizer_Free(tok);
+ return NULL;
+ }
+ tok->cur = tok->inp = tok->buf;
+ tok->end = tok->buf + BUFSIZ;
+ return tok;
+}
+
+static struct tok_state *
+tokenizer_from_string(const char *input, int utf8_only, int exec_input,
+ int preserve_crlf)
+{
+ struct tok_state *tok = tokenizer_new_with_reader(_PYTOK_READER_PREPARED);
+ if (tok == NULL) {
+ return NULL;
+ }
+ if (_PyTok_PrepareString(
+ tok, input, utf8_only, exec_input, preserve_crlf) < 0) {
+ _PyTokenizer_Free(tok);
+ return NULL;
+ }
+ tok->buf = tok->cur = tok->inp = tok->str;
+ tok->end = tok->buf;
+ return tok;
+}
+
+struct tok_state *
+_PyTokenizer_FromString(const char *input, int exec_input, int preserve_crlf)
+{
+ return tokenizer_from_string(input, 0, exec_input, preserve_crlf);
+}
+
+struct tok_state *
+_PyTokenizer_FromUTF8(const char *input, int exec_input, int preserve_crlf)
+{
+ return tokenizer_from_string(input, 1, exec_input, preserve_crlf);
+}
+
+struct tok_state *
+_PyTokenizer_FromReadline(PyObject *readline, const char *encoding)
+{
+ struct tok_state *tok = tokenizer_new_with_reader(_PYTOK_READER_READLINE);
+ if (tok == NULL) {
+ return NULL;
+ }
+ if (encoding != NULL && _PyTok_SetEncoding(tok, encoding) < 0) {
+ _PyTokenizer_Free(tok);
+ return NULL;
+ }
+ tok->reader->readline = Py_NewRef(readline);
+ return tok;
+}
+
+struct tok_state *
+_PyTokenizer_FromFile(FILE *fp, const char *encoding,
+ const char *ps1, const char *ps2)
+{
+ _PyTok_ReaderKind kind = ps1 != NULL || ps2 != NULL
+ ? _PYTOK_READER_INTERACTIVE : _PYTOK_READER_FILE;
+ struct tok_state *tok = tokenizer_new_with_reader(kind);
+ if (tok == NULL) {
+ return NULL;
+ }
+ if (encoding != NULL && _PyTok_SetEncoding(tok, encoding) < 0) {
+ _PyTokenizer_Free(tok);
+ return NULL;
+ }
+ tok->fp = fp;
+ tok->prompt = ps1;
+ tok->reader->nextprompt = ps2;
+ return tok;
+}
+
+#if defined(__wasi__) || (defined(__EMSCRIPTEN__) && (__EMSCRIPTEN_major__ >= 3))
+/* WASI has no dup(), and Emscripten's emulation is slow. */
+typedef union {
+ void *cookie;
+ int fd;
+} borrowed_fd;
+
+static ssize_t
+borrow_read(void *cookie, char *buffer, size_t size)
+{
+ borrowed_fd borrowed = {.cookie = cookie};
+ return read(borrowed.fd, buffer, size);
+}
+
+static FILE *
+fdopen_borrow(int fd)
+{
+ cookie_io_functions_t callbacks = {borrow_read, NULL, NULL, NULL};
+ borrowed_fd borrowed = {.fd = fd};
+ return fopencookie(borrowed.cookie, "r", callbacks);
+}
+#else
+static FILE *
+fdopen_borrow(int fd)
+{
+ int copy = _Py_dup(fd);
+ return copy < 0 ? NULL : fdopen(copy, "r");
+}
+#endif
+
+char *
+_PyTokenizer_FindEncodingFilename(int fd, PyObject *filename)
+{
+ FILE *fp = fdopen_borrow(fd);
+ if (fp == NULL) {
+ return NULL;
+ }
+ struct tok_state *tok = _PyTokenizer_FromFile(fp, NULL, NULL, NULL);
+ if (tok == NULL) {
+ fclose(fp);
+ return NULL;
+ }
+ tok->filename = filename != NULL
+ ? Py_NewRef(filename) : PyUnicode_FromString("<string>");
+ if (tok->filename == NULL) {
+ fclose(fp);
+ _PyTokenizer_Free(tok);
+ return NULL;
+ }
+ /* Reporting a warning here could recursively ask for the encoding. */
+ tok->report_warnings = 0;
+ while (tok->lineno < 2 && tok->done == E_OK) {
+ struct token token;
+ _PyToken_Init(&token);
+ _PyTokenizer_Get(tok, &token);
+ _PyToken_Free(&token);
+ }
+ fclose(fp);
+ char *encoding = tok->encoding == NULL
+ ? NULL : _PyTok_CopyBytes(tok->encoding, strlen(tok->encoding));
+ _PyTokenizer_Free(tok);
+ return encoding;
+}
diff --git a/Parser/tokenizer/reader.h b/Parser/tokenizer/reader.h
new file mode 100644
index 0000000..c27bc2a
--- /dev/null
+++ b/Parser/tokenizer/reader.h
@@ -0,0 +1,9 @@
+#ifndef Py_TOKENIZER_READER_H
+#define Py_TOKENIZER_READER_H
+
+struct tok_state;
+
+void _PyTok_ReaderFree(struct tok_state *);
+int _PyTok_ReaderUnderflow(struct tok_state *);
+
+#endif
diff --git a/Parser/tokenizer/reader_internal.h b/Parser/tokenizer/reader_internal.h
new file mode 100644
index 0000000..121d0f9
--- /dev/null
+++ b/Parser/tokenizer/reader_internal.h
@@ -0,0 +1,81 @@
+#ifndef Py_TOKENIZER_READER_INTERNAL_H
+#define Py_TOKENIZER_READER_INTERNAL_H
+
+#include "Python.h"
+
+typedef enum {
+ _PYTOK_READER_PREPARED,
+ _PYTOK_READER_FILE,
+ _PYTOK_READER_READLINE,
+ _PYTOK_READER_INTERACTIVE,
+} _PyTok_ReaderKind;
+
+typedef enum {
+ _PYTOK_READ_LINE,
+ _PYTOK_READ_EOF,
+ _PYTOK_READ_STOPPED,
+ _PYTOK_READ_INTERRUPT,
+ _PYTOK_READ_ERROR,
+} _PyTok_ReadResult;
+
+typedef enum {
+ _PYTOK_ENCODING_ERROR = -1,
+ _PYTOK_ENCODING_DONE,
+ _PYTOK_ENCODING_NEED_SECOND_LINE,
+} _PyTok_EncodingResult;
+
+typedef enum {
+ _PYTOK_CHUNK_BORROWED,
+ _PYTOK_CHUNK_PYMEM,
+ _PYTOK_CHUNK_PYOBJECT,
+} _PyTok_ChunkOwnership;
+
+typedef struct {
+ char *data;
+ Py_ssize_t len;
+ int implicit_newline;
+ PyObject *owner;
+ _PyTok_ChunkOwnership ownership;
+} _PyTok_Chunk;
+
+typedef struct _PyTok_Reader {
+ _PyTok_ReaderKind kind;
+ PyObject *readline;
+ PyObject *decoder;
+ const char *nextprompt;
+
+ char *file_buffer;
+ Py_ssize_t file_buffer_cap;
+ _PyTok_Chunk prefetched_lines[2];
+ int prefetched_index;
+ int prefetched_count;
+
+ char *decoded;
+ Py_ssize_t decoded_pos;
+ Py_ssize_t decoded_len;
+ Py_ssize_t decoded_cap;
+ int decoded_tail_is_implicit;
+
+ int file_initialized;
+ int file_eof;
+ int decoder_finalized;
+} _PyTok_Reader;
+
+struct tok_state;
+
+char *_PyTok_CopyBytes(const char *, Py_ssize_t);
+int _PyTok_DecodeOnce(
+ struct tok_state *, _PyTok_Chunk *, const char *, const char *);
+char *_PyTok_NormalizeNewlines(
+ const char *, Py_ssize_t, int, int, Py_ssize_t *, int *);
+void _PyTok_ChunkClear(_PyTok_Chunk *);
+int _PyTok_SetEncoding(struct tok_state *, const char *);
+_PyTok_EncodingResult _PyTok_DetectEncoding(
+ struct tok_state *, const _PyTok_Chunk *, const _PyTok_Chunk *, int,
+ Py_ssize_t *);
+int _PyTok_PrepareString(struct tok_state *, const char *, int, int, int);
+int _PyTok_StartDecoder(struct tok_state *, const char *);
+int _PyTok_DecodeChunk(struct tok_state *, _PyTok_Chunk *, int);
+int _PyTok_DecoderHasBufferedInput(struct tok_state *);
+
+#endif
diff --git a/Parser/tokenizer/readline_tokenizer.c b/Parser/tokenizer/readline_tokenizer.c
deleted file mode 100644
index 917f7b4..0000000
--- a/Parser/tokenizer/readline_tokenizer.c
+++ /dev/null
@@ -1,135 +0,0 @@
-#include "Python.h"
-#include "errcode.h"
-
-#include "helpers.h"
-#include "../lexer/lexer.h"
-#include "../lexer/state.h"
-#include "../lexer/buffer.h"
-
-static int
-tok_readline_string(struct tok_state* tok) {
- PyObject* line = NULL;
- PyObject* raw_line = PyObject_CallNoArgs(tok->readline);
- if (raw_line == NULL) {
- if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
- PyErr_Clear();
- return 1;
- }
- _PyTokenizer_error_ret(tok);
- goto error;
- }
- if(tok->encoding != NULL) {
- if (!PyBytes_Check(raw_line)) {
- PyErr_Format(PyExc_TypeError, "readline() returned a non-bytes object");
- _PyTokenizer_error_ret(tok);
- goto error;
- }
- line = PyUnicode_Decode(PyBytes_AS_STRING(raw_line), PyBytes_GET_SIZE(raw_line),
- tok->encoding, "replace");
- Py_CLEAR(raw_line);
- if (line == NULL) {
- _PyTokenizer_error_ret(tok);
- goto error;
- }
- } else {
- if(!PyUnicode_Check(raw_line)) {
- PyErr_Format(PyExc_TypeError, "readline() returned a non-string object");
- _PyTokenizer_error_ret(tok);
- goto error;
- }
- line = raw_line;
- raw_line = NULL;
- }
- Py_ssize_t buflen;
- const char* buf = PyUnicode_AsUTF8AndSize(line, &buflen);
- if (buf == NULL) {
- _PyTokenizer_error_ret(tok);
- goto error;
- }
-
- // Make room for the null terminator *and* potentially
- // an extra newline character that we may need to artificially
- // add.
- size_t buffer_size = buflen + 2;
- if (!_PyLexer_tok_reserve_buf(tok, buffer_size)) {
- goto error;
- }
- memcpy(tok->inp, buf, buflen);
- tok->inp += buflen;
- *tok->inp = '\0';
-
- tok->line_start = tok->cur;
- Py_DECREF(line);
- return 1;
-error:
- Py_XDECREF(raw_line);
- Py_XDECREF(line);
- return 0;
-}
-
-static int
-tok_underflow_readline(struct tok_state* tok) {
- assert(tok->decoding_state == STATE_NORMAL);
- assert(tok->fp == NULL && tok->input == NULL && tok->decoding_readline == NULL);
- if (tok->start == NULL && !INSIDE_FSTRING(tok)) {
- tok->cur = tok->inp = tok->buf;
- }
- if (!tok_readline_string(tok)) {
- return 0;
- }
- if (tok->inp == tok->cur) {
- tok->done = E_EOF;
- return 0;
- }
- tok->implicit_newline = 0;
- if (tok->inp[-1] != '\n') {
- assert(tok->inp + 1 < tok->end);
- /* Last line does not end in \n, fake one */
- *tok->inp++ = '\n';
- *tok->inp = '\0';
- tok->implicit_newline = 1;
- }
-
- if (tok->tok_mode_stack_index && !_PyLexer_update_ftstring_expr(tok, 0)) {
- return 0;
- }
-
- ADVANCE_LINENO();
- /* The default encoding is UTF-8, so make sure we don't have any
- non-UTF-8 sequences in it. */
- if (!tok->encoding && !_PyTokenizer_ensure_utf8(tok->cur, tok, tok->lineno)) {
- _PyTokenizer_error_ret(tok);
- return 0;
- }
- assert(tok->done == E_OK);
- return tok->done == E_OK;
-}
-
-struct tok_state *
-_PyTokenizer_FromReadline(PyObject* readline, const char* enc,
- int exec_input, int preserve_crlf)
-{
- struct tok_state *tok = _PyTokenizer_tok_new();
- if (tok == NULL)
- return NULL;
- if ((tok->buf = (char *)PyMem_Malloc(BUFSIZ)) == NULL) {
- _PyTokenizer_Free(tok);
- PyErr_NoMemory();
- return NULL;
- }
- tok->cur = tok->inp = tok->buf;
- tok->end = tok->buf + BUFSIZ;
- tok->fp = NULL;
- if (enc != NULL) {
- tok->encoding = _PyTokenizer_new_string(enc, strlen(enc), tok);
- if (!tok->encoding) {
- _PyTokenizer_Free(tok);
- return NULL;
- }
- }
- tok->decoding_state = STATE_NORMAL;
- tok->underflow = &tok_underflow_readline;
- Py_INCREF(readline);
- tok->readline = readline;
- return tok;
-}
diff --git a/Parser/tokenizer/string_tokenizer.c b/Parser/tokenizer/string_tokenizer.c
deleted file mode 100644
index 7f07cca..0000000
--- a/Parser/tokenizer/string_tokenizer.c
+++ /dev/null
@@ -1,148 +0,0 @@
-#include "Python.h"
-#include "errcode.h"
-
-#include "helpers.h"
-#include "../lexer/state.h"
-
-static int
-tok_underflow_string(struct tok_state *tok) {
- char *end = strchr(tok->inp, '\n');
- if (end != NULL) {
- end++;
- }
- else {
- end = strchr(tok->inp, '\0');
- if (end == tok->inp) {
- tok->done = E_EOF;
- return 0;
- }
- }
- if (tok->start == NULL) {
- tok->buf = tok->cur;
- }
- tok->line_start = tok->cur;
- ADVANCE_LINENO();
- tok->inp = end;
- return 1;
-}
-
-/* Fetch a byte from TOK, using the string buffer. */
-static int
-buf_getc(struct tok_state *tok) {
- return Py_CHARMASK(*tok->str++);
-}
-
-/* Unfetch a byte from TOK, using the string buffer. */
-static void
-buf_ungetc(int c, struct tok_state *tok) {
- tok->str--;
- assert(Py_CHARMASK(*tok->str) == c); /* tok->cur may point to read-only segment */
-}
-
-/* Set the readline function for TOK to ENC. For the string-based
- tokenizer, this means to just record the encoding. */
-static int
-buf_setreadl(struct tok_state *tok, const char* enc) {
- tok->enc = enc;
- return 1;
-}
-
-/* Decode a byte string STR for use as the buffer of TOK.
- Look for encoding declarations inside STR, and record them
- inside TOK. */
-static char *
-decode_str(const char *input, int single, struct tok_state *tok, int preserve_crlf)
-{
- PyObject* utf8 = NULL;
- char *str;
- const char *s;
- const char *newl[2] = {NULL, NULL};
- int lineno = 0;
- tok->input = str = _PyTokenizer_translate_newlines(input, single, preserve_crlf, tok);
- if (str == NULL)
- return NULL;
- tok->enc = NULL;
- tok->str = str;
- if (!_PyTokenizer_check_bom(buf_getc, buf_ungetc, buf_setreadl, tok))
- return _PyTokenizer_error_ret(tok);
- str = tok->str; /* string after BOM if any */
- assert(str);
- if (tok->enc != NULL) {
- utf8 = _PyTokenizer_translate_into_utf8(str, tok->enc);
- if (utf8 == NULL)
- return _PyTokenizer_error_ret(tok);
- str = PyBytes_AsString(utf8);
- }
- for (s = str;; s++) {
- if (*s == '\0') break;
- else if (*s == '\n') {
- assert(lineno < 2);
- newl[lineno] = s;
- lineno++;
- if (lineno == 2) break;
- }
- }
- tok->enc = NULL;
- /* need to check line 1 and 2 separately since check_coding_spec
- assumes a single line as input */
- if (newl[0]) {
- tok->lineno = 1;
- if (!_PyTokenizer_check_coding_spec(str, newl[0] - str, tok, buf_setreadl)) {
- return NULL;
- }
- if (tok->enc == NULL && tok->decoding_state != STATE_NORMAL && newl[1]) {
- tok->lineno = 2;
- if (!_PyTokenizer_check_coding_spec(newl[0]+1, newl[1] - newl[0],
- tok, buf_setreadl))
- return NULL;
- }
- }
- tok->lineno = 0;
- if (tok->enc != NULL) {
- assert(utf8 == NULL);
- utf8 = _PyTokenizer_translate_into_utf8(str, tok->enc);
- if (utf8 == NULL)
- return _PyTokenizer_error_ret(tok);
- str = PyBytes_AS_STRING(utf8);
- }
- else if (!_PyTokenizer_ensure_utf8(str, tok, 1)) {
- return _PyTokenizer_error_ret(tok);
- }
- if (utf8 != NULL) {
- char *translated = _PyTokenizer_translate_newlines(
- str, single, preserve_crlf, tok);
- if (translated == NULL) {
- Py_DECREF(utf8);
- return _PyTokenizer_error_ret(tok);
- }
- PyMem_Free(tok->input);
- tok->input = translated;
- str = translated;
- Py_CLEAR(utf8);
- }
- tok->str = str;
- assert(tok->decoding_buffer == NULL);
- tok->decoding_buffer = utf8; /* CAUTION */
- return str;
-}
-
-/* Set up tokenizer for string */
-struct tok_state *
-_PyTokenizer_FromString(const char *str, int exec_input, int preserve_crlf)
-{
- struct tok_state *tok = _PyTokenizer_tok_new();
- char *decoded;
-
- if (tok == NULL)
- return NULL;
- decoded = decode_str(str, exec_input, tok, preserve_crlf);
- if (decoded == NULL) {
- _PyTokenizer_Free(tok);
- return NULL;
- }
-
- tok->buf = tok->cur = tok->inp = decoded;
- tok->end = decoded;
- tok->underflow = &tok_underflow_string;
- return tok;
-}
diff --git a/Parser/tokenizer/tokenizer.h b/Parser/tokenizer/tokenizer.h
index 8fbeb2d..d8c8891 100644
--- a/Parser/tokenizer/tokenizer.h
+++ b/Parser/tokenizer/tokenizer.h
@@ -5,9 +5,12 @@
struct tok_state *_PyTokenizer_FromString(const char *, int, int);
struct tok_state *_PyTokenizer_FromUTF8(const char *, int, int);
-struct tok_state *_PyTokenizer_FromReadline(PyObject*, const char*, int, int);
+struct tok_state *_PyTokenizer_FromReadline(PyObject *, const char *);
struct tok_state *_PyTokenizer_FromFile(FILE *, const char*,
const char *, const char *);
+/* Return the declared encoding in PyMem-allocated storage, or NULL.
+ An exception is set on error. */
+char *_PyTokenizer_FindEncodingFilename(int, PyObject *);
#define tok_dump _Py_tok_dump
diff --git a/Parser/tokenizer/utf8_tokenizer.c b/Parser/tokenizer/utf8_tokenizer.c
deleted file mode 100644
index 1a925f4..0000000
--- a/Parser/tokenizer/utf8_tokenizer.c
+++ /dev/null
@@ -1,55 +0,0 @@
-#include "Python.h"
-#include "errcode.h"
-
-#include "helpers.h"
-#include "../lexer/state.h"
-
-static int
-tok_underflow_string(struct tok_state *tok) {
- char *end = strchr(tok->inp, '\n');
- if (end != NULL) {
- end++;
- }
- else {
- end = strchr(tok->inp, '\0');
- if (end == tok->inp) {
- tok->done = E_EOF;
- return 0;
- }
- }
- if (tok->start == NULL) {
- tok->buf = tok->cur;
- }
- tok->line_start = tok->cur;
- ADVANCE_LINENO();
- tok->inp = end;
- return 1;
-}
-
-/* Set up tokenizer for UTF-8 string */
-struct tok_state *
-_PyTokenizer_FromUTF8(const char *str, int exec_input, int preserve_crlf)
-{
- struct tok_state *tok = _PyTokenizer_tok_new();
- char *translated;
- if (tok == NULL)
- return NULL;
- tok->input = translated = _PyTokenizer_translate_newlines(str, exec_input, preserve_crlf, tok);
- if (translated == NULL) {
- _PyTokenizer_Free(tok);
- return NULL;
- }
- tok->decoding_state = STATE_NORMAL;
- tok->enc = NULL;
- tok->str = translated;
- tok->encoding = _PyTokenizer_new_string("utf-8", 5, tok);
- if (!tok->encoding) {
- _PyTokenizer_Free(tok);
- return NULL;
- }
-
- tok->buf = tok->cur = tok->inp = translated;
- tok->end = translated;
- tok->underflow = &tok_underflow_string;
- return tok;
-}
diff --git a/Python/Python-tokenize.c b/Python/Python-tokenize.c
index e6d39e4..762b7b3 100644
--- a/Python/Python-tokenize.c
+++ b/Python/Python-tokenize.c
@@ -66,7 +66,7 @@ tokenizeriter_new_impl(PyTypeObject *type, PyObject *readline,
if (filename == NULL) {
return NULL;
}
- self->tok = _PyTokenizer_FromReadline(readline, encoding, 1, 1);
+ self->tok = _PyTokenizer_FromReadline(readline, encoding);
if (self->tok == NULL) {
Py_DECREF(filename);
return NULL;
diff --git a/Python/errors.c b/Python/errors.c
index 48b03e5..eb14899 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -13,6 +13,8 @@
#include "pycore_traceback.h" // _PyTraceBack_FromFrame()
#include "pycore_unicodeobject.h" // _PyUnicode_Equal()
+#include "../Parser/tokenizer/tokenizer.h"
+
#ifdef MS_WINDOWS
# include <windows.h>
# include <winbase.h>
@@ -2050,9 +2052,6 @@ PyErr_ProgramText(const char *filename, int lineno)
return res;
}
-/* Function from Parser/tokenizer/file_tokenizer.c */
-extern char* _PyTokenizer_FindEncodingFilename(int, PyObject *);
-
PyObject *
_PyErr_ProgramDecodedTextObject(PyObject *filename, int lineno, const char* encoding)
{
diff --git a/Python/traceback.c b/Python/traceback.c
index fe6a465..c8b3dba 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -12,6 +12,7 @@
#include "pycore_traceback.h" // EXCEPTION_TB_HEADER
#include "frameobject.h" // PyFrame_New()
+#include "../Parser/tokenizer/tokenizer.h"
#include "osdefs.h" // SEP
#ifdef HAVE_UNISTD_H
@@ -57,9 +58,6 @@
#define MAX_FRAME_DEPTH 100
#define DEFAULT_MAX_NTHREADS 100
-/* Function from Parser/tokenizer/file_tokenizer.c */
-extern char* _PyTokenizer_FindEncodingFilename(int, PyObject *);
-
/*[clinic input]
class traceback "PyTracebackObject *" "&PyTraceback_Type"
[clinic start generated code]*/
diff --git a/Tools/c-analyzer/TODO b/Tools/c-analyzer/TODO
index 2077534..ec07ea6 100644
--- a/Tools/c-analyzer/TODO
+++ b/Tools/c-analyzer/TODO
@@ -427,8 +427,6 @@
Objects/unicodeobject.c:unicodeiter_reduce():PyId_iter _Py_IDENTIFIER(iter)
Objects/weakrefobject.c:proxy_bytes():PyId___bytes__ _Py_IDENTIFIER(__bytes__)
Objects/weakrefobject.c:weakref_repr():PyId___name__ _Py_IDENTIFIER(__name__)
-Parser/tokenizer/file_tokenizer.c:fp_setreadl():PyId_open _Py_IDENTIFIER(open)
-Parser/tokenizer/file_tokenizer.c:fp_setreadl():PyId_readline _Py_IDENTIFIER(readline)
Python/Python-ast.c:ast_type_reduce():PyId___dict__ _Py_IDENTIFIER(__dict__)
Python/Python-ast.c:make_type():PyId___module__ _Py_IDENTIFIER(__module__)
Python/_warnings.c:PyId_stderr _Py_IDENTIFIER(stderr)
diff --git a/Tools/peg_generator/pegen/build.py b/Tools/peg_generator/pegen/build.py
index 37883af..bfd8e43 100644
--- a/Tools/peg_generator/pegen/build.py
+++ b/Tools/peg_generator/pegen/build.py
@@ -129,10 +129,8 @@ def compile_c_extension(
str(MOD_DIR.parent.parent.parent / "Parser" / "lexer" / "state.c"),
str(MOD_DIR.parent.parent.parent / "Parser" / "lexer" / "string.c"),
str(MOD_DIR.parent.parent.parent / "Parser" / "lexer" / "buffer.c"),
- str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer" / "string_tokenizer.c"),
- str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer" / "file_tokenizer.c"),
- str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer" / "utf8_tokenizer.c"),
- str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer" / "readline_tokenizer.c"),
+ str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer" / "decoder.c"),
+ str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer" / "reader.c"),
str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer" / "helpers.c"),
str(MOD_DIR.parent.parent.parent / "Parser" / "pegen.c"),
str(MOD_DIR.parent.parent.parent / "Parser" / "pegen_errors.c"),