Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Parser/myreadline.c
12 views
1
2
/* Readline interface for tokenizer.c and [raw_]input() in bltinmodule.c.
3
By default, or when stdin is not a tty device, we have a super
4
simple my_readline function using fgets.
5
Optionally, we can use the GNU readline library.
6
my_readline() has a different return value from GNU readline():
7
- NULL if an interrupt occurred or if an error occurred
8
- a malloc'ed empty string if EOF was read
9
- a malloc'ed string ending in \n normally
10
*/
11
12
#include "Python.h"
13
#include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH
14
#include "pycore_pystate.h" // _PyThreadState_GET()
15
#ifdef MS_WINDOWS
16
# ifndef WIN32_LEAN_AND_MEAN
17
# define WIN32_LEAN_AND_MEAN
18
# endif
19
# include "windows.h"
20
#endif /* MS_WINDOWS */
21
22
23
PyThreadState* _PyOS_ReadlineTState = NULL;
24
25
static PyThread_type_lock _PyOS_ReadlineLock = NULL;
26
27
int (*PyOS_InputHook)(void) = NULL;
28
29
/* This function restarts a fgets() after an EINTR error occurred
30
except if _PyOS_InterruptOccurred() returns true. */
31
32
static int
33
my_fgets(PyThreadState* tstate, char *buf, int len, FILE *fp)
34
{
35
#ifdef MS_WINDOWS
36
HANDLE handle;
37
_Py_BEGIN_SUPPRESS_IPH
38
handle = (HANDLE)_get_osfhandle(fileno(fp));
39
_Py_END_SUPPRESS_IPH
40
41
/* bpo-40826: fgets(fp) does crash if fileno(fp) is closed */
42
if (handle == INVALID_HANDLE_VALUE) {
43
return -1; /* EOF */
44
}
45
#endif
46
47
while (1) {
48
if (PyOS_InputHook != NULL &&
49
// GH-104668: See PyOS_ReadlineFunctionPointer's comment below...
50
_Py_IsMainInterpreter(tstate->interp))
51
{
52
(void)(PyOS_InputHook)();
53
}
54
55
errno = 0;
56
clearerr(fp);
57
char *p = fgets(buf, len, fp);
58
if (p != NULL) {
59
return 0; /* No error */
60
}
61
int err = errno;
62
63
#ifdef MS_WINDOWS
64
/* Ctrl-C anywhere on the line or Ctrl-Z if the only character
65
on a line will set ERROR_OPERATION_ABORTED. Under normal
66
circumstances Ctrl-C will also have caused the SIGINT handler
67
to fire which will have set the event object returned by
68
_PyOS_SigintEvent. This signal fires in another thread and
69
is not guaranteed to have occurred before this point in the
70
code.
71
72
Therefore: check whether the event is set with a small timeout.
73
If it is, assume this is a Ctrl-C and reset the event. If it
74
isn't set assume that this is a Ctrl-Z on its own and drop
75
through to check for EOF.
76
*/
77
if (GetLastError()==ERROR_OPERATION_ABORTED) {
78
HANDLE hInterruptEvent = _PyOS_SigintEvent();
79
switch (WaitForSingleObjectEx(hInterruptEvent, 10, FALSE)) {
80
case WAIT_OBJECT_0:
81
ResetEvent(hInterruptEvent);
82
return 1; /* Interrupt */
83
case WAIT_FAILED:
84
return -2; /* Error */
85
}
86
}
87
#endif /* MS_WINDOWS */
88
89
if (feof(fp)) {
90
clearerr(fp);
91
return -1; /* EOF */
92
}
93
94
#ifdef EINTR
95
if (err == EINTR) {
96
PyEval_RestoreThread(tstate);
97
int s = PyErr_CheckSignals();
98
PyEval_SaveThread();
99
100
if (s < 0) {
101
return 1;
102
}
103
/* try again */
104
continue;
105
}
106
#endif
107
108
if (_PyOS_InterruptOccurred(tstate)) {
109
return 1; /* Interrupt */
110
}
111
return -2; /* Error */
112
}
113
/* NOTREACHED */
114
}
115
116
#ifdef HAVE_WINDOWS_CONSOLE_IO
117
/* Readline implementation using ReadConsoleW */
118
119
extern char _get_console_type(HANDLE handle);
120
121
char *
122
_PyOS_WindowsConsoleReadline(PyThreadState *tstate, HANDLE hStdIn)
123
{
124
static wchar_t wbuf_local[1024 * 16];
125
const DWORD chunk_size = 1024;
126
127
DWORD n_read, total_read, wbuflen, u8len;
128
wchar_t *wbuf;
129
char *buf = NULL;
130
int err = 0;
131
132
n_read = (DWORD)-1;
133
total_read = 0;
134
wbuf = wbuf_local;
135
wbuflen = sizeof(wbuf_local) / sizeof(wbuf_local[0]) - 1;
136
while (1) {
137
if (PyOS_InputHook != NULL &&
138
// GH-104668: See PyOS_ReadlineFunctionPointer's comment below...
139
_Py_IsMainInterpreter(tstate->interp))
140
{
141
(void)(PyOS_InputHook)();
142
}
143
if (!ReadConsoleW(hStdIn, &wbuf[total_read], wbuflen - total_read, &n_read, NULL)) {
144
err = GetLastError();
145
goto exit;
146
}
147
if (n_read == (DWORD)-1 && (err = GetLastError()) == ERROR_OPERATION_ABORTED) {
148
break;
149
}
150
if (n_read == 0) {
151
int s;
152
err = GetLastError();
153
if (err != ERROR_OPERATION_ABORTED)
154
goto exit;
155
err = 0;
156
HANDLE hInterruptEvent = _PyOS_SigintEvent();
157
if (WaitForSingleObjectEx(hInterruptEvent, 100, FALSE)
158
== WAIT_OBJECT_0) {
159
ResetEvent(hInterruptEvent);
160
PyEval_RestoreThread(tstate);
161
s = PyErr_CheckSignals();
162
PyEval_SaveThread();
163
if (s < 0) {
164
goto exit;
165
}
166
}
167
break;
168
}
169
170
total_read += n_read;
171
if (total_read == 0 || wbuf[total_read - 1] == L'\n') {
172
break;
173
}
174
wbuflen += chunk_size;
175
if (wbuf == wbuf_local) {
176
wbuf[total_read] = '\0';
177
wbuf = (wchar_t*)PyMem_RawMalloc(wbuflen * sizeof(wchar_t));
178
if (wbuf) {
179
wcscpy_s(wbuf, wbuflen, wbuf_local);
180
}
181
else {
182
PyEval_RestoreThread(tstate);
183
PyErr_NoMemory();
184
PyEval_SaveThread();
185
goto exit;
186
}
187
}
188
else {
189
wchar_t *tmp = PyMem_RawRealloc(wbuf, wbuflen * sizeof(wchar_t));
190
if (tmp == NULL) {
191
PyEval_RestoreThread(tstate);
192
PyErr_NoMemory();
193
PyEval_SaveThread();
194
goto exit;
195
}
196
wbuf = tmp;
197
}
198
}
199
200
if (wbuf[0] == '\x1a') {
201
buf = PyMem_RawMalloc(1);
202
if (buf) {
203
buf[0] = '\0';
204
}
205
else {
206
PyEval_RestoreThread(tstate);
207
PyErr_NoMemory();
208
PyEval_SaveThread();
209
}
210
goto exit;
211
}
212
213
u8len = WideCharToMultiByte(CP_UTF8, 0,
214
wbuf, total_read,
215
NULL, 0,
216
NULL, NULL);
217
buf = PyMem_RawMalloc(u8len + 1);
218
if (buf == NULL) {
219
PyEval_RestoreThread(tstate);
220
PyErr_NoMemory();
221
PyEval_SaveThread();
222
goto exit;
223
}
224
225
u8len = WideCharToMultiByte(CP_UTF8, 0,
226
wbuf, total_read,
227
buf, u8len,
228
NULL, NULL);
229
buf[u8len] = '\0';
230
231
exit:
232
if (wbuf != wbuf_local) {
233
PyMem_RawFree(wbuf);
234
}
235
236
if (err) {
237
PyEval_RestoreThread(tstate);
238
PyErr_SetFromWindowsErr(err);
239
PyEval_SaveThread();
240
}
241
return buf;
242
}
243
244
#endif /* HAVE_WINDOWS_CONSOLE_IO */
245
246
247
/* Readline implementation using fgets() */
248
249
char *
250
PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
251
{
252
size_t n;
253
char *p, *pr;
254
PyThreadState *tstate = _PyOS_ReadlineTState;
255
assert(tstate != NULL);
256
257
#ifdef HAVE_WINDOWS_CONSOLE_IO
258
const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp);
259
if (!config->legacy_windows_stdio && sys_stdin == stdin) {
260
HANDLE hStdIn, hStdErr;
261
262
hStdIn = _Py_get_osfhandle_noraise(fileno(sys_stdin));
263
hStdErr = _Py_get_osfhandle_noraise(fileno(stderr));
264
265
if (_get_console_type(hStdIn) == 'r') {
266
fflush(sys_stdout);
267
if (prompt) {
268
if (_get_console_type(hStdErr) == 'w') {
269
wchar_t *wbuf;
270
int wlen;
271
wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1,
272
NULL, 0);
273
if (wlen) {
274
wbuf = PyMem_RawMalloc(wlen * sizeof(wchar_t));
275
if (wbuf == NULL) {
276
PyEval_RestoreThread(tstate);
277
PyErr_NoMemory();
278
PyEval_SaveThread();
279
return NULL;
280
}
281
wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1,
282
wbuf, wlen);
283
if (wlen) {
284
DWORD n;
285
fflush(stderr);
286
/* wlen includes null terminator, so subtract 1 */
287
WriteConsoleW(hStdErr, wbuf, wlen - 1, &n, NULL);
288
}
289
PyMem_RawFree(wbuf);
290
}
291
} else {
292
fprintf(stderr, "%s", prompt);
293
fflush(stderr);
294
}
295
}
296
clearerr(sys_stdin);
297
return _PyOS_WindowsConsoleReadline(tstate, hStdIn);
298
}
299
}
300
#endif
301
302
fflush(sys_stdout);
303
if (prompt) {
304
fprintf(stderr, "%s", prompt);
305
}
306
fflush(stderr);
307
308
n = 0;
309
p = NULL;
310
do {
311
size_t incr = (n > 0) ? n + 2 : 100;
312
if (incr > INT_MAX) {
313
PyMem_RawFree(p);
314
PyEval_RestoreThread(tstate);
315
PyErr_SetString(PyExc_OverflowError, "input line too long");
316
PyEval_SaveThread();
317
return NULL;
318
}
319
pr = (char *)PyMem_RawRealloc(p, n + incr);
320
if (pr == NULL) {
321
PyMem_RawFree(p);
322
PyEval_RestoreThread(tstate);
323
PyErr_NoMemory();
324
PyEval_SaveThread();
325
return NULL;
326
}
327
p = pr;
328
int err = my_fgets(tstate, p + n, (int)incr, sys_stdin);
329
if (err == 1) {
330
// Interrupt
331
PyMem_RawFree(p);
332
return NULL;
333
} else if (err != 0) {
334
// EOF or error
335
p[n] = '\0';
336
break;
337
}
338
n += strlen(p + n);
339
} while (p[n-1] != '\n');
340
341
pr = (char *)PyMem_RawRealloc(p, n+1);
342
if (pr == NULL) {
343
PyMem_RawFree(p);
344
PyEval_RestoreThread(tstate);
345
PyErr_NoMemory();
346
PyEval_SaveThread();
347
return NULL;
348
}
349
return pr;
350
}
351
352
353
/* By initializing this function pointer, systems embedding Python can
354
override the readline function.
355
356
Note: Python expects in return a buffer allocated with PyMem_Malloc. */
357
358
char *(*PyOS_ReadlineFunctionPointer)(FILE *, FILE *, const char *) = NULL;
359
360
361
/* Interface used by tokenizer.c and bltinmodule.c */
362
363
char *
364
PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
365
{
366
char *rv, *res;
367
size_t len;
368
369
PyThreadState *tstate = _PyThreadState_GET();
370
if (_PyOS_ReadlineTState == tstate) {
371
PyErr_SetString(PyExc_RuntimeError,
372
"can't re-enter readline");
373
return NULL;
374
}
375
376
377
if (PyOS_ReadlineFunctionPointer == NULL) {
378
PyOS_ReadlineFunctionPointer = PyOS_StdioReadline;
379
}
380
381
if (_PyOS_ReadlineLock == NULL) {
382
_PyOS_ReadlineLock = PyThread_allocate_lock();
383
if (_PyOS_ReadlineLock == NULL) {
384
PyErr_SetString(PyExc_MemoryError, "can't allocate lock");
385
return NULL;
386
}
387
}
388
389
_PyOS_ReadlineTState = tstate;
390
Py_BEGIN_ALLOW_THREADS
391
PyThread_acquire_lock(_PyOS_ReadlineLock, 1);
392
393
/* This is needed to handle the unlikely case that the
394
* interpreter is in interactive mode *and* stdin/out are not
395
* a tty. This can happen, for example if python is run like
396
* this: python -i < test1.py
397
*/
398
if (!isatty(fileno(sys_stdin)) || !isatty(fileno(sys_stdout)) ||
399
// GH-104668: Don't call global callbacks like PyOS_InputHook or
400
// PyOS_ReadlineFunctionPointer from subinterpreters, since it seems
401
// like there's no good way for users (like readline and tkinter) to
402
// avoid using global state to manage them. Plus, we generally don't
403
// want to cause trouble for libraries that don't know/care about
404
// subinterpreter support. If libraries really need better APIs that
405
// work per-interpreter and have ways to access module state, we can
406
// certainly add them later (but for now we'll cross our fingers and
407
// hope that nobody actually cares):
408
!_Py_IsMainInterpreter(tstate->interp))
409
{
410
rv = PyOS_StdioReadline(sys_stdin, sys_stdout, prompt);
411
}
412
else {
413
rv = (*PyOS_ReadlineFunctionPointer)(sys_stdin, sys_stdout, prompt);
414
}
415
Py_END_ALLOW_THREADS
416
417
PyThread_release_lock(_PyOS_ReadlineLock);
418
419
_PyOS_ReadlineTState = NULL;
420
421
if (rv == NULL)
422
return NULL;
423
424
len = strlen(rv) + 1;
425
res = PyMem_Malloc(len);
426
if (res != NULL) {
427
memcpy(res, rv, len);
428
}
429
else {
430
PyErr_NoMemory();
431
}
432
PyMem_RawFree(rv);
433
434
return res;
435
}
436
437