lean-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lean_python/__init__.py +6 -0
- lean_python/cli.py +50 -0
- lean_python/embed.py +131 -0
- lean_python/io.py +15 -0
- lean_python/io_externs.py +1192 -0
- lean_python/marshal.py +727 -0
- lean_python/models.py +297 -0
- lean_python/runtime.py +1400 -0
- lean_python/sync.py +265 -0
- lean_python/types.py +33 -0
- lean_python-0.1.0.dist-info/METADATA +231 -0
- lean_python-0.1.0.dist-info/RECORD +15 -0
- lean_python-0.1.0.dist-info/WHEEL +4 -0
- lean_python-0.1.0.dist-info/entry_points.txt +2 -0
- lean_python-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,1192 @@
|
|
|
1
|
+
"""IO externs of the runtime: results, handles and streams, ``ST.Ref``, the file system, the
|
|
2
|
+
environment of the process, child processes, promises and tasks of ``BaseIO`` actions.
|
|
3
|
+
|
|
4
|
+
In Lean 4.35 an ``IO`` action returns an ``EST.Out`` (``ok`` = constructor 0, ``error`` =
|
|
5
|
+
constructor 1, one object field), while a ``BaseIO``/``ST`` action returns its value itself.
|
|
6
|
+
The world parameter has the IR type ``void`` and is not passed to externs; closures over IO
|
|
7
|
+
actions receive it as ``0``. The behavior follows Lean's ``io.cpp`` and ``process.cpp``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import errno as E
|
|
11
|
+
import fcntl
|
|
12
|
+
import os
|
|
13
|
+
import signal
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
from lean_python.runtime import (
|
|
20
|
+
Closure,
|
|
21
|
+
Ctor,
|
|
22
|
+
LArray,
|
|
23
|
+
LByteArray,
|
|
24
|
+
Task,
|
|
25
|
+
lean_apply,
|
|
26
|
+
lean_dec,
|
|
27
|
+
lean_inc,
|
|
28
|
+
lean_internal_panic,
|
|
29
|
+
lean_mark_persistent,
|
|
30
|
+
register_external,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Results
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def io_ok(a):
|
|
38
|
+
return Ctor(0, [a])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def io_error(e):
|
|
42
|
+
return Ctor(1, [e])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# IO.Error
|
|
47
|
+
|
|
48
|
+
ALREADY_EXISTS = 0
|
|
49
|
+
OTHER_ERROR = 1
|
|
50
|
+
RESOURCE_BUSY = 2
|
|
51
|
+
RESOURCE_VANISHED = 3
|
|
52
|
+
UNSUPPORTED_OPERATION = 4
|
|
53
|
+
HARDWARE_FAULT = 5
|
|
54
|
+
UNSATISFIED_CONSTRAINTS = 6
|
|
55
|
+
ILLEGAL_OPERATION = 7
|
|
56
|
+
PROTOCOL_ERROR = 8
|
|
57
|
+
TIME_EXPIRED = 9
|
|
58
|
+
INTERRUPTED = 10
|
|
59
|
+
NO_FILE_OR_DIRECTORY = 11
|
|
60
|
+
INVALID_ARGUMENT = 12
|
|
61
|
+
PERMISSION_DENIED = 13
|
|
62
|
+
RESOURCE_EXHAUSTED = 14
|
|
63
|
+
INAPPROPRIATE_TYPE = 15
|
|
64
|
+
NO_SUCH_THING = 16
|
|
65
|
+
UNEXPECTED_EOF = 17
|
|
66
|
+
USER_ERROR = 18
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _has_filename(tag):
|
|
70
|
+
return tag == ALREADY_EXISTS or INTERRUPTED <= tag <= NO_SUCH_THING
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _mk_error(tag, fname, code, details):
|
|
74
|
+
if _has_filename(tag):
|
|
75
|
+
if fname is None:
|
|
76
|
+
f = 0
|
|
77
|
+
elif tag in (INTERRUPTED, NO_FILE_OR_DIRECTORY):
|
|
78
|
+
f = fname
|
|
79
|
+
else:
|
|
80
|
+
f = Ctor(1, [fname])
|
|
81
|
+
r = Ctor(tag, [f, details])
|
|
82
|
+
r.sc[16] = code
|
|
83
|
+
return r
|
|
84
|
+
r = Ctor(tag, [details])
|
|
85
|
+
r.sc[8] = code
|
|
86
|
+
return r
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def mk_user_error(msg):
|
|
90
|
+
return Ctor(USER_ERROR, [msg])
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _embedded_nul_error(s):
|
|
94
|
+
return _mk_error(INVALID_ARGUMENT, s, E.EINVAL, b"string contains NUL bytes")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _file_not_found_error(fname):
|
|
98
|
+
return _mk_error(NO_FILE_OR_DIRECTORY, fname, E.ENOENT, b"")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_UV_MESSAGES = [
|
|
102
|
+
("E2BIG", "argument list too long"),
|
|
103
|
+
("EACCES", "permission denied"),
|
|
104
|
+
("EADDRINUSE", "address already in use"),
|
|
105
|
+
("EADDRNOTAVAIL", "address not available"),
|
|
106
|
+
("EAFNOSUPPORT", "address family not supported"),
|
|
107
|
+
("EAGAIN", "resource temporarily unavailable"),
|
|
108
|
+
("EBADF", "bad file descriptor"),
|
|
109
|
+
("EBUSY", "resource busy or locked"),
|
|
110
|
+
("ECONNABORTED", "software caused connection abort"),
|
|
111
|
+
("ECONNREFUSED", "connection refused"),
|
|
112
|
+
("ECONNRESET", "connection reset by peer"),
|
|
113
|
+
("EDESTADDRREQ", "destination address required"),
|
|
114
|
+
("EEXIST", "file already exists"),
|
|
115
|
+
("EFAULT", "bad address in system call argument"),
|
|
116
|
+
("EFBIG", "file too large"),
|
|
117
|
+
("EHOSTUNREACH", "host is unreachable"),
|
|
118
|
+
("EILSEQ", "illegal byte sequence"),
|
|
119
|
+
("EINTR", "interrupted system call"),
|
|
120
|
+
("EINVAL", "invalid argument"),
|
|
121
|
+
("EIO", "i/o error"),
|
|
122
|
+
("EISCONN", "socket is already connected"),
|
|
123
|
+
("EISDIR", "illegal operation on a directory"),
|
|
124
|
+
("ELOOP", "too many symbolic links encountered"),
|
|
125
|
+
("EMFILE", "too many open files"),
|
|
126
|
+
("EMLINK", "too many links"),
|
|
127
|
+
("EMSGSIZE", "message too long"),
|
|
128
|
+
("ENAMETOOLONG", "name too long"),
|
|
129
|
+
("ENETDOWN", "network is down"),
|
|
130
|
+
("ENETUNREACH", "network is unreachable"),
|
|
131
|
+
("ENFILE", "file table overflow"),
|
|
132
|
+
("ENOBUFS", "no buffer space available"),
|
|
133
|
+
("ENODEV", "no such device"),
|
|
134
|
+
("ENOENT", "no such file or directory"),
|
|
135
|
+
("ENOMEM", "not enough memory"),
|
|
136
|
+
("ENOPROTOOPT", "protocol not available"),
|
|
137
|
+
("ENOSPC", "no space left on device"),
|
|
138
|
+
("ENOSYS", "function not implemented"),
|
|
139
|
+
("ENOTCONN", "socket is not connected"),
|
|
140
|
+
("ENOTDIR", "not a directory"),
|
|
141
|
+
("ENOTEMPTY", "directory not empty"),
|
|
142
|
+
("ENOTSOCK", "socket operation on non-socket"),
|
|
143
|
+
("ENOTTY", "inappropriate ioctl for device"),
|
|
144
|
+
("ENXIO", "no such device or address"),
|
|
145
|
+
("EOPNOTSUPP", "operation not supported on socket"),
|
|
146
|
+
("EPERM", "operation not permitted"),
|
|
147
|
+
("EPIPE", "broken pipe"),
|
|
148
|
+
("EPROTO", "protocol error"),
|
|
149
|
+
("EPROTONOSUPPORT", "protocol not supported"),
|
|
150
|
+
("EPROTOTYPE", "protocol wrong type for socket"),
|
|
151
|
+
("ERANGE", "result too large"),
|
|
152
|
+
("EROFS", "read-only file system"),
|
|
153
|
+
("ESPIPE", "invalid seek"),
|
|
154
|
+
("ESRCH", "no such process"),
|
|
155
|
+
("ETIMEDOUT", "connection timed out"),
|
|
156
|
+
("ETXTBSY", "text file is busy"),
|
|
157
|
+
("EXDEV", "cross-device link not permitted"),
|
|
158
|
+
("ENODATA", "no data available"),
|
|
159
|
+
]
|
|
160
|
+
_UV = {}
|
|
161
|
+
for _name, _msg in _UV_MESSAGES:
|
|
162
|
+
if hasattr(E, _name):
|
|
163
|
+
_UV.setdefault(getattr(E, _name), _msg)
|
|
164
|
+
|
|
165
|
+
_APPROX = {}
|
|
166
|
+
for _src, _dst in (
|
|
167
|
+
("ENOMSG", "ENODATA"),
|
|
168
|
+
("ENOEXEC", "EINVAL"),
|
|
169
|
+
("EDOM", "EINVAL"),
|
|
170
|
+
("ENOSTR", "EINVAL"),
|
|
171
|
+
("EBADMSG", "EPROTO"),
|
|
172
|
+
("ECHILD", "ESRCH"),
|
|
173
|
+
("EDEADLK", "EBUSY"),
|
|
174
|
+
("EIDRM", "EPIPE"),
|
|
175
|
+
("EINPROGRESS", "EISCONN"),
|
|
176
|
+
("ENETRESET", "ECONNRESET"),
|
|
177
|
+
("ENOLINK", "ECONNRESET"),
|
|
178
|
+
("ENOLCK", "EAGAIN"),
|
|
179
|
+
("ENOSR", "ENOBUFS"),
|
|
180
|
+
("ETIME", "ETIMEDOUT"),
|
|
181
|
+
):
|
|
182
|
+
if hasattr(E, _src) and hasattr(E, _dst):
|
|
183
|
+
_APPROX[getattr(E, _src)] = getattr(E, _dst)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _codes(*names):
|
|
187
|
+
return {getattr(E, n) for n in names if hasattr(E, n)}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
_KINDS = [
|
|
191
|
+
(_codes("EINTR"), INTERRUPTED),
|
|
192
|
+
(
|
|
193
|
+
_codes("ELOOP", "ENAMETOOLONG", "EDESTADDRREQ", "EBADF", "EINVAL", "EILSEQ", "ENOTCONN", "ENOTSOCK"),
|
|
194
|
+
INVALID_ARGUMENT,
|
|
195
|
+
),
|
|
196
|
+
(_codes("ENOENT"), NO_FILE_OR_DIRECTORY),
|
|
197
|
+
(_codes("EACCES", "EROFS", "ECONNABORTED", "EFBIG", "EPERM"), PERMISSION_DENIED),
|
|
198
|
+
(
|
|
199
|
+
_codes("EMFILE", "ENFILE", "ENOSPC", "E2BIG", "EAGAIN", "EMLINK", "EMSGSIZE", "ENOBUFS", "ENOMEM"),
|
|
200
|
+
RESOURCE_EXHAUSTED,
|
|
201
|
+
),
|
|
202
|
+
(_codes("EISDIR", "ENOTDIR"), INAPPROPRIATE_TYPE),
|
|
203
|
+
(_codes("ENXIO", "EHOSTUNREACH", "ENETUNREACH", "ECONNREFUSED", "ENODATA", "ESRCH"), NO_SUCH_THING),
|
|
204
|
+
(_codes("EEXIST", "EISCONN"), ALREADY_EXISTS),
|
|
205
|
+
(_codes("EIO"), HARDWARE_FAULT),
|
|
206
|
+
(_codes("ENOTEMPTY"), UNSATISFIED_CONSTRAINTS),
|
|
207
|
+
(_codes("ENOTTY"), ILLEGAL_OPERATION),
|
|
208
|
+
(_codes("ECONNRESET", "ENETDOWN", "EPIPE"), RESOURCE_VANISHED),
|
|
209
|
+
(_codes("EPROTO", "EPROTONOSUPPORT", "EPROTOTYPE"), PROTOCOL_ERROR),
|
|
210
|
+
(_codes("ETIMEDOUT"), TIME_EXPIRED),
|
|
211
|
+
(_codes("EBUSY", "ETXTBSY", "EADDRINUSE"), RESOURCE_BUSY),
|
|
212
|
+
(
|
|
213
|
+
_codes(
|
|
214
|
+
"EADDRNOTAVAIL",
|
|
215
|
+
"EAFNOSUPPORT",
|
|
216
|
+
"ENODEV",
|
|
217
|
+
"ENOPROTOOPT",
|
|
218
|
+
"ENOSYS",
|
|
219
|
+
"EOPNOTSUPP",
|
|
220
|
+
"ERANGE",
|
|
221
|
+
"ESPIPE",
|
|
222
|
+
"EXDEV",
|
|
223
|
+
),
|
|
224
|
+
UNSUPPORTED_OPERATION,
|
|
225
|
+
),
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def decode_error(errnum, approximate, fname=None):
|
|
230
|
+
"""The ``IO.Error`` of the OS error ``errnum`` (``decode_uv_error_impl``)."""
|
|
231
|
+
e = _APPROX.get(errnum, errnum) if approximate else errnum
|
|
232
|
+
msg = _UV.get(e)
|
|
233
|
+
if msg is None:
|
|
234
|
+
uv = 0
|
|
235
|
+
msg = f"Unknown system error {-errnum}"
|
|
236
|
+
else:
|
|
237
|
+
uv = e
|
|
238
|
+
tag = OTHER_ERROR
|
|
239
|
+
if uv != 0:
|
|
240
|
+
for codes, kind in _KINDS:
|
|
241
|
+
if uv in codes:
|
|
242
|
+
tag = kind
|
|
243
|
+
break
|
|
244
|
+
if tag in (INTERRUPTED, NO_FILE_OR_DIRECTORY):
|
|
245
|
+
f = fname if fname is not None else b""
|
|
246
|
+
elif _has_filename(tag):
|
|
247
|
+
f = fname
|
|
248
|
+
else:
|
|
249
|
+
f = None
|
|
250
|
+
return _mk_error(tag, f, errnum, msg.encode())
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _of_os(ex, fname=None, approximate=True):
|
|
254
|
+
if isinstance(ex, OSError) and ex.errno is not None:
|
|
255
|
+
return decode_error(ex.errno, approximate, fname)
|
|
256
|
+
return _mk_error(OTHER_ERROR, None, 0, str(ex).encode())
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _err(ex, fname=None, approximate=True):
|
|
260
|
+
return io_error(_of_os(ex, fname, approximate))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _decap(s):
|
|
264
|
+
return s[:1].lower() + s[1:] if s else s
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _fopen_error(gist, fname, code, details):
|
|
268
|
+
if details is not None:
|
|
269
|
+
return f"{_decap(gist)} (error code: {code}, {_decap(details)})\n file: {fname}"
|
|
270
|
+
return f"{_decap(gist)} (error code: {code})\n file: {fname}"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _other_error(gist, code, details):
|
|
274
|
+
if details is not None:
|
|
275
|
+
return f"{_decap(gist)} (error code: {code}, {_decap(details)})"
|
|
276
|
+
return f"{_decap(gist)} (error code: {code})"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _str(b):
|
|
280
|
+
return b.decode("utf-8", "surrogatepass")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def io_error_to_string(e):
|
|
284
|
+
"""``IO.Error.toString``."""
|
|
285
|
+
if isinstance(e, int):
|
|
286
|
+
return "end of file"
|
|
287
|
+
tag = e.tag
|
|
288
|
+
if tag == USER_ERROR:
|
|
289
|
+
return _str(e.objs[0])
|
|
290
|
+
if _has_filename(tag):
|
|
291
|
+
f = e.objs[0]
|
|
292
|
+
if tag in (INTERRUPTED, NO_FILE_OR_DIRECTORY):
|
|
293
|
+
fname = _str(f)
|
|
294
|
+
elif isinstance(f, int):
|
|
295
|
+
fname = None
|
|
296
|
+
else:
|
|
297
|
+
fname = _str(f.objs[0])
|
|
298
|
+
details = _str(e.objs[1])
|
|
299
|
+
code = e.sc[16]
|
|
300
|
+
else:
|
|
301
|
+
fname = None
|
|
302
|
+
details = _str(e.objs[0])
|
|
303
|
+
code = e.sc[8]
|
|
304
|
+
|
|
305
|
+
def with_file(gist, d):
|
|
306
|
+
if fname is not None:
|
|
307
|
+
return _fopen_error(gist, fname, code, d)
|
|
308
|
+
return _other_error(gist, code, d)
|
|
309
|
+
|
|
310
|
+
d = details
|
|
311
|
+
table = {
|
|
312
|
+
INAPPROPRIATE_TYPE: lambda: with_file("inappropriate type", d),
|
|
313
|
+
INTERRUPTED: lambda: with_file("interrupted system call", d),
|
|
314
|
+
INVALID_ARGUMENT: lambda: with_file("invalid argument", d),
|
|
315
|
+
NO_FILE_OR_DIRECTORY: lambda: with_file("no such file or directory", None),
|
|
316
|
+
NO_SUCH_THING: lambda: with_file("no such thing", d),
|
|
317
|
+
PERMISSION_DENIED: lambda: with_file(details, None),
|
|
318
|
+
RESOURCE_EXHAUSTED: lambda: with_file("resource exhausted", d),
|
|
319
|
+
ALREADY_EXISTS: lambda: with_file("already exists", d),
|
|
320
|
+
OTHER_ERROR: lambda: _other_error(details, code, None),
|
|
321
|
+
RESOURCE_BUSY: lambda: _other_error("resource busy", code, d),
|
|
322
|
+
RESOURCE_VANISHED: lambda: _other_error("resource vanished", code, d),
|
|
323
|
+
HARDWARE_FAULT: lambda: _other_error("hardware fault", code, None),
|
|
324
|
+
ILLEGAL_OPERATION: lambda: _other_error("illegal operation", code, d),
|
|
325
|
+
PROTOCOL_ERROR: lambda: _other_error("protocol error", code, d),
|
|
326
|
+
TIME_EXPIRED: lambda: _other_error("time expired", code, d),
|
|
327
|
+
UNSATISFIED_CONSTRAINTS: lambda: _other_error("directory not empty", code, None),
|
|
328
|
+
UNSUPPORTED_OPERATION: lambda: _other_error("unsupported operation", code, d),
|
|
329
|
+
}
|
|
330
|
+
f = table.get(tag)
|
|
331
|
+
return f() if f else ""
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def lean_mk_io_user_error(msg):
|
|
335
|
+
return mk_user_error(msg)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def lean_decode_io_error(errnum, fname):
|
|
339
|
+
return decode_error(errnum, True, fname)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def lean_io_result_show_error(r):
|
|
343
|
+
sys.stdout.flush()
|
|
344
|
+
sys.stderr.write(f"uncaught exception: {io_error_to_string(r.objs[0])}\n")
|
|
345
|
+
sys.stderr.flush()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ---------------------------------------------------------------------------
|
|
349
|
+
# Handles
|
|
350
|
+
|
|
351
|
+
STDIN = 0
|
|
352
|
+
STDOUT = 1
|
|
353
|
+
STDERR = 2
|
|
354
|
+
|
|
355
|
+
_READ_CHUNK = 8192
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class Handle:
|
|
359
|
+
"""An ``IO.FS.Handle``: a standard stream (``std``) or a file descriptor with a read buffer."""
|
|
360
|
+
|
|
361
|
+
__slots__ = ("buf", "fd", "pos", "rc", "std")
|
|
362
|
+
|
|
363
|
+
def __init__(self, std=None, fd=None):
|
|
364
|
+
self.rc = 1
|
|
365
|
+
self.std = std
|
|
366
|
+
self.fd = fd
|
|
367
|
+
self.buf = b""
|
|
368
|
+
self.pos = 0
|
|
369
|
+
|
|
370
|
+
def close(self):
|
|
371
|
+
fd = self.fd
|
|
372
|
+
if fd is not None:
|
|
373
|
+
self.fd = None
|
|
374
|
+
try:
|
|
375
|
+
os.close(fd)
|
|
376
|
+
except OSError:
|
|
377
|
+
pass
|
|
378
|
+
|
|
379
|
+
def __del__(self):
|
|
380
|
+
self.close()
|
|
381
|
+
|
|
382
|
+
def fill(self):
|
|
383
|
+
if self.pos < len(self.buf):
|
|
384
|
+
return True
|
|
385
|
+
self.pos = 0
|
|
386
|
+
while True:
|
|
387
|
+
try:
|
|
388
|
+
self.buf = os.read(self.fd, _READ_CHUNK)
|
|
389
|
+
return len(self.buf) > 0
|
|
390
|
+
except InterruptedError:
|
|
391
|
+
continue
|
|
392
|
+
except OSError:
|
|
393
|
+
self.buf = b""
|
|
394
|
+
raise
|
|
395
|
+
|
|
396
|
+
def read(self, n):
|
|
397
|
+
out = bytearray()
|
|
398
|
+
while len(out) < n:
|
|
399
|
+
if not self.fill():
|
|
400
|
+
break
|
|
401
|
+
k = min(len(self.buf) - self.pos, n - len(out))
|
|
402
|
+
out += self.buf[self.pos : self.pos + k]
|
|
403
|
+
self.pos += k
|
|
404
|
+
return bytes(out)
|
|
405
|
+
|
|
406
|
+
def get_line(self):
|
|
407
|
+
line = bytearray()
|
|
408
|
+
while self.fill():
|
|
409
|
+
rest = self.buf[self.pos :]
|
|
410
|
+
i = rest.find(b"\n")
|
|
411
|
+
if i >= 0:
|
|
412
|
+
line += rest[: i + 1]
|
|
413
|
+
self.pos += i + 1
|
|
414
|
+
break
|
|
415
|
+
line += rest
|
|
416
|
+
self.pos = len(self.buf)
|
|
417
|
+
return bytes(line)
|
|
418
|
+
|
|
419
|
+
def unread(self):
|
|
420
|
+
ahead = len(self.buf) - self.pos
|
|
421
|
+
self.buf = b""
|
|
422
|
+
self.pos = 0
|
|
423
|
+
if ahead > 0:
|
|
424
|
+
os.lseek(self.fd, -ahead, os.SEEK_CUR)
|
|
425
|
+
|
|
426
|
+
def write(self, data):
|
|
427
|
+
self.unread()
|
|
428
|
+
view = memoryview(data)
|
|
429
|
+
while view:
|
|
430
|
+
n = os.write(self.fd, view)
|
|
431
|
+
view = view[n:]
|
|
432
|
+
|
|
433
|
+
def rewind(self):
|
|
434
|
+
self.buf = b""
|
|
435
|
+
self.pos = 0
|
|
436
|
+
os.lseek(self.fd, 0, os.SEEK_SET)
|
|
437
|
+
|
|
438
|
+
def truncate(self):
|
|
439
|
+
self.unread()
|
|
440
|
+
at = os.lseek(self.fd, 0, os.SEEK_CUR)
|
|
441
|
+
os.ftruncate(self.fd, at)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
register_external(Handle, Handle.close)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _bad(code):
|
|
448
|
+
return OSError(code, os.strerror(code))
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _write_to(h, data):
|
|
452
|
+
if h.std == STDOUT:
|
|
453
|
+
sys.stdout.flush()
|
|
454
|
+
sys.stdout.buffer.write(data)
|
|
455
|
+
elif h.std == STDERR:
|
|
456
|
+
sys.stderr.flush()
|
|
457
|
+
sys.stderr.buffer.write(data)
|
|
458
|
+
elif h.std == STDIN:
|
|
459
|
+
raise _bad(E.EBADF)
|
|
460
|
+
else:
|
|
461
|
+
h.write(data)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _unit(fn):
|
|
465
|
+
try:
|
|
466
|
+
fn()
|
|
467
|
+
return io_ok(0)
|
|
468
|
+
except OSError as ex:
|
|
469
|
+
return _err(ex)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _path(p):
|
|
473
|
+
if b"\0" in p:
|
|
474
|
+
return None
|
|
475
|
+
return p
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
_MODE_FLAGS = {
|
|
479
|
+
0: os.O_RDONLY,
|
|
480
|
+
1: os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
|
|
481
|
+
2: os.O_WRONLY | os.O_CREAT | os.O_EXCL,
|
|
482
|
+
3: os.O_RDWR,
|
|
483
|
+
4: os.O_WRONLY | os.O_APPEND | os.O_CREAT,
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def lean_io_prim_handle_mk(fname, mode):
|
|
488
|
+
p = _path(fname)
|
|
489
|
+
if p is None:
|
|
490
|
+
return io_error(_embedded_nul_error(fname))
|
|
491
|
+
try:
|
|
492
|
+
fd = os.open(p, _MODE_FLAGS.get(mode, _MODE_FLAGS[4]) | os.O_CLOEXEC, 0o666)
|
|
493
|
+
except OSError as ex:
|
|
494
|
+
return _err(ex, fname)
|
|
495
|
+
return io_ok(Handle(fd=fd))
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def lean_io_prim_handle_flush(h):
|
|
499
|
+
def go():
|
|
500
|
+
if h.std == STDOUT:
|
|
501
|
+
sys.stdout.flush()
|
|
502
|
+
sys.stdout.buffer.flush()
|
|
503
|
+
elif h.std == STDERR:
|
|
504
|
+
sys.stderr.flush()
|
|
505
|
+
sys.stderr.buffer.flush()
|
|
506
|
+
|
|
507
|
+
return _unit(go)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def lean_io_prim_handle_put_str(h, s):
|
|
511
|
+
return _unit(lambda: _write_to(h, s))
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def lean_io_prim_handle_write(h, buf):
|
|
515
|
+
return _unit(lambda: _write_to(h, bytes(buf.data)))
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def lean_io_prim_handle_get_line(h):
|
|
519
|
+
try:
|
|
520
|
+
if h.std == STDIN:
|
|
521
|
+
line = sys.stdin.buffer.readline()
|
|
522
|
+
elif h.std is None:
|
|
523
|
+
line = h.get_line()
|
|
524
|
+
else:
|
|
525
|
+
raise _bad(E.EBADF)
|
|
526
|
+
except OSError as ex:
|
|
527
|
+
return _err(ex)
|
|
528
|
+
return io_ok(line)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def lean_io_prim_handle_read(h, nbytes):
|
|
532
|
+
try:
|
|
533
|
+
if h.std in (STDOUT, STDERR):
|
|
534
|
+
raise _bad(E.EBADF)
|
|
535
|
+
if h.std == STDIN:
|
|
536
|
+
data = sys.stdin.buffer.read1(nbytes) if nbytes else b""
|
|
537
|
+
else:
|
|
538
|
+
data = h.read(nbytes)
|
|
539
|
+
except OSError as ex:
|
|
540
|
+
return _err(ex)
|
|
541
|
+
return io_ok(LByteArray(bytearray(data)))
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def lean_io_prim_handle_is_tty(h):
|
|
545
|
+
fd = h.std if h.std is not None else h.fd
|
|
546
|
+
return os.isatty(fd)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def lean_io_prim_handle_rewind(h):
|
|
550
|
+
def go():
|
|
551
|
+
if h.std is not None:
|
|
552
|
+
raise _bad(E.ESPIPE)
|
|
553
|
+
h.rewind()
|
|
554
|
+
|
|
555
|
+
return _unit(go)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def lean_io_prim_handle_truncate(h):
|
|
559
|
+
def go():
|
|
560
|
+
if h.std is not None:
|
|
561
|
+
raise _bad(E.EINVAL)
|
|
562
|
+
h.truncate()
|
|
563
|
+
|
|
564
|
+
return _unit(go)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _fd(h):
|
|
568
|
+
return h.std if h.std is not None else h.fd
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def lean_io_prim_handle_lock(h, exclusive):
|
|
572
|
+
return _unit(lambda: fcntl.flock(_fd(h), fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH))
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def lean_io_prim_handle_try_lock(h, exclusive):
|
|
576
|
+
op = fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH
|
|
577
|
+
try:
|
|
578
|
+
fcntl.flock(_fd(h), op | fcntl.LOCK_NB)
|
|
579
|
+
return io_ok(1)
|
|
580
|
+
except BlockingIOError:
|
|
581
|
+
return io_ok(0)
|
|
582
|
+
except OSError as ex:
|
|
583
|
+
if ex.errno in (E.EWOULDBLOCK, E.EAGAIN):
|
|
584
|
+
return io_ok(0)
|
|
585
|
+
return _err(ex)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def lean_io_prim_handle_unlock(h):
|
|
589
|
+
return _unit(lambda: fcntl.flock(_fd(h), fcntl.LOCK_UN))
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
# ---------------------------------------------------------------------------
|
|
593
|
+
# Streams (``IO.FS.Stream.ofHandle``)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _stream_flush(h, _w):
|
|
597
|
+
return lean_io_prim_handle_flush(h)
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def _stream_read(h, n, _w):
|
|
601
|
+
return lean_io_prim_handle_read(h, n)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _stream_write(h, b, _w):
|
|
605
|
+
return lean_io_prim_handle_write(h, b)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _stream_get_line(h, _w):
|
|
609
|
+
return lean_io_prim_handle_get_line(h)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def _stream_put_str(h, s, _w):
|
|
613
|
+
return lean_io_prim_handle_put_str(h, s)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _stream_is_tty(h, _w):
|
|
617
|
+
return lean_io_prim_handle_is_tty(h)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def lean_stream_of_handle(h):
|
|
621
|
+
return Ctor(
|
|
622
|
+
0,
|
|
623
|
+
[
|
|
624
|
+
Closure(_stream_flush, 2, [h]),
|
|
625
|
+
Closure(_stream_read, 3, [h]),
|
|
626
|
+
Closure(_stream_write, 3, [h]),
|
|
627
|
+
Closure(_stream_get_line, 2, [h]),
|
|
628
|
+
Closure(_stream_put_str, 3, [h]),
|
|
629
|
+
Closure(_stream_is_tty, 2, [h]),
|
|
630
|
+
],
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
_streams = threading.local()
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _current(name, std):
|
|
638
|
+
s = getattr(_streams, name, None)
|
|
639
|
+
if s is None:
|
|
640
|
+
s = lean_mark_persistent(lean_stream_of_handle(Handle(std=std)))
|
|
641
|
+
setattr(_streams, name, s)
|
|
642
|
+
return s
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _swap(name, std, h):
|
|
646
|
+
old = _current(name, std)
|
|
647
|
+
setattr(_streams, name, lean_mark_persistent(h))
|
|
648
|
+
return old
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def lean_get_stdin():
|
|
652
|
+
return _current("stdin", STDIN)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def lean_get_stdout():
|
|
656
|
+
return _current("stdout", STDOUT)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def lean_get_stderr():
|
|
660
|
+
return _current("stderr", STDERR)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def lean_get_set_stdin(h):
|
|
664
|
+
return _swap("stdin", STDIN, h)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def lean_get_set_stdout(h):
|
|
668
|
+
return _swap("stdout", STDOUT, h)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def lean_get_set_stderr(h):
|
|
672
|
+
return _swap("stderr", STDERR, h)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
# ---------------------------------------------------------------------------
|
|
676
|
+
# ST.Ref
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
class STRef:
|
|
680
|
+
__slots__ = ("value",)
|
|
681
|
+
|
|
682
|
+
def __init__(self, value):
|
|
683
|
+
self.value = value
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def lean_st_mk_ref(a):
|
|
687
|
+
return STRef(a)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def lean_st_ref_get(r):
|
|
691
|
+
v = r.value
|
|
692
|
+
if v is None:
|
|
693
|
+
_internal("null reference read")
|
|
694
|
+
lean_inc(v)
|
|
695
|
+
return v
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def lean_st_ref_take(r):
|
|
699
|
+
v = r.value
|
|
700
|
+
if v is None:
|
|
701
|
+
_internal("null reference read")
|
|
702
|
+
r.value = None
|
|
703
|
+
return v
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def lean_st_ref_put(r, a):
|
|
707
|
+
r.value = a
|
|
708
|
+
return 0
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def lean_st_ref_swap(r, a):
|
|
712
|
+
old = r.value
|
|
713
|
+
if old is None:
|
|
714
|
+
_internal("null reference read")
|
|
715
|
+
r.value = a
|
|
716
|
+
return old
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def lean_st_ref_ptr_eq(r1, r2):
|
|
720
|
+
return r1 is r2
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _internal(msg):
|
|
724
|
+
lean_internal_panic(msg)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
# ---------------------------------------------------------------------------
|
|
728
|
+
# Clocks
|
|
729
|
+
|
|
730
|
+
_MONO_BASE = time.monotonic_ns()
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
def lean_io_mono_ms_now():
|
|
734
|
+
return (time.monotonic_ns() - _MONO_BASE) // 1_000_000 + 1
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def lean_io_mono_nanos_now():
|
|
738
|
+
return time.monotonic_ns() - _MONO_BASE + 1
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
# ---------------------------------------------------------------------------
|
|
742
|
+
# Platform
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def lean_system_platform_windows(_unit=0):
|
|
746
|
+
return sys.platform == "win32"
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def lean_system_platform_osx(_unit=0):
|
|
750
|
+
return sys.platform == "darwin"
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def lean_system_platform_linux(_unit=0):
|
|
754
|
+
return sys.platform.startswith("linux")
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def lean_system_platform_emscripten(_unit=0):
|
|
758
|
+
return sys.platform == "emscripten"
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def lean_internal_get_hardware_concurrency(_unit=0):
|
|
762
|
+
return os.cpu_count() or 0
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
# ---------------------------------------------------------------------------
|
|
766
|
+
# Files and directories
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def _with_path(p, fn):
|
|
770
|
+
if b"\0" in p:
|
|
771
|
+
return io_error(_embedded_nul_error(p))
|
|
772
|
+
return fn(p)
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _unit_or(fn, fname, approximate=True):
|
|
776
|
+
try:
|
|
777
|
+
fn()
|
|
778
|
+
return io_ok(0)
|
|
779
|
+
except OSError as ex:
|
|
780
|
+
return _err(ex, fname, approximate)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def lean_io_read_dir(d):
|
|
784
|
+
def go(p):
|
|
785
|
+
try:
|
|
786
|
+
names = os.listdir(p)
|
|
787
|
+
except OSError as ex:
|
|
788
|
+
return _err(ex, d)
|
|
789
|
+
return io_ok(LArray([Ctor(0, [d, n]) for n in names]))
|
|
790
|
+
|
|
791
|
+
return _with_path(d, go)
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def _system_time(sec, nsec):
|
|
795
|
+
o = Ctor(0, [sec])
|
|
796
|
+
o.sc[8] = nsec
|
|
797
|
+
return o
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def _metadata_obj(st):
|
|
801
|
+
import stat
|
|
802
|
+
|
|
803
|
+
o = Ctor(
|
|
804
|
+
0,
|
|
805
|
+
[
|
|
806
|
+
_system_time(int(st.st_atime_ns // 1_000_000_000), st.st_atime_ns % 1_000_000_000),
|
|
807
|
+
_system_time(int(st.st_mtime_ns // 1_000_000_000), st.st_mtime_ns % 1_000_000_000),
|
|
808
|
+
],
|
|
809
|
+
)
|
|
810
|
+
o.sc[16] = st.st_size
|
|
811
|
+
o.sc[24] = st.st_nlink
|
|
812
|
+
m = st.st_mode
|
|
813
|
+
if stat.S_ISDIR(m):
|
|
814
|
+
ty = 0
|
|
815
|
+
elif stat.S_ISREG(m):
|
|
816
|
+
ty = 1
|
|
817
|
+
elif stat.S_ISLNK(m):
|
|
818
|
+
ty = 2
|
|
819
|
+
else:
|
|
820
|
+
ty = 3
|
|
821
|
+
o.sc[32] = ty
|
|
822
|
+
return o
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def lean_io_metadata(fname):
|
|
826
|
+
def go(p):
|
|
827
|
+
try:
|
|
828
|
+
return io_ok(_metadata_obj(os.stat(p)))
|
|
829
|
+
except OSError as ex:
|
|
830
|
+
return _err(ex, fname, False)
|
|
831
|
+
|
|
832
|
+
return _with_path(fname, go)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def lean_io_symlink_metadata(fname):
|
|
836
|
+
def go(p):
|
|
837
|
+
try:
|
|
838
|
+
return io_ok(_metadata_obj(os.lstat(p)))
|
|
839
|
+
except OSError as ex:
|
|
840
|
+
return _err(ex, fname, False)
|
|
841
|
+
|
|
842
|
+
return _with_path(fname, go)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def lean_io_create_dir(p):
|
|
846
|
+
return _with_path(p, lambda q: _unit_or(lambda: os.mkdir(q, 0o777), p))
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def lean_io_remove_dir(p):
|
|
850
|
+
return _with_path(p, lambda q: _unit_or(lambda: os.rmdir(q), p))
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def lean_io_remove_file(p):
|
|
854
|
+
return _with_path(p, lambda q: _unit_or(lambda: os.unlink(q), p, False))
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def lean_io_rename(src, dst):
|
|
858
|
+
if b"\0" in src:
|
|
859
|
+
return io_error(_embedded_nul_error(src))
|
|
860
|
+
if b"\0" in dst:
|
|
861
|
+
return io_error(_embedded_nul_error(dst))
|
|
862
|
+
try:
|
|
863
|
+
os.rename(src, dst)
|
|
864
|
+
return io_ok(0)
|
|
865
|
+
except OSError as ex:
|
|
866
|
+
return _err(ex, src + b" and/or " + dst)
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def lean_io_hard_link(orig, link):
|
|
870
|
+
if b"\0" in orig:
|
|
871
|
+
return io_error(_embedded_nul_error(orig))
|
|
872
|
+
if b"\0" in link:
|
|
873
|
+
return io_error(_embedded_nul_error(link))
|
|
874
|
+
return _unit_or(lambda: os.link(orig, link), orig, False)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def lean_io_realpath(fname):
|
|
878
|
+
def go(p):
|
|
879
|
+
try:
|
|
880
|
+
return io_ok(os.path.realpath(p, strict=True))
|
|
881
|
+
except OSError:
|
|
882
|
+
return io_error(_file_not_found_error(fname))
|
|
883
|
+
|
|
884
|
+
return _with_path(fname, go)
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
def lean_chmod(fname, mode):
|
|
888
|
+
return _with_path(fname, lambda p: _unit_or(lambda: os.chmod(p, mode), fname))
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _temp_dir():
|
|
892
|
+
for v in ("TMPDIR", "TMP", "TEMP", "TEMPDIR"):
|
|
893
|
+
d = os.environb.get(v.encode())
|
|
894
|
+
if d:
|
|
895
|
+
break
|
|
896
|
+
else:
|
|
897
|
+
d = b"/tmp"
|
|
898
|
+
if len(d) > 1 and d.endswith(b"/"):
|
|
899
|
+
d = d[:-1]
|
|
900
|
+
return d
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def lean_io_create_tempfile():
|
|
904
|
+
try:
|
|
905
|
+
fd, path = tempfile.mkstemp(prefix=b"tmp.", dir=_temp_dir())
|
|
906
|
+
except OSError as ex:
|
|
907
|
+
return _err(ex, None, False)
|
|
908
|
+
return io_ok(Ctor(0, [Handle(fd=fd), path]))
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def lean_io_create_tempdir():
|
|
912
|
+
try:
|
|
913
|
+
path = tempfile.mkdtemp(prefix=b"tmp.", dir=_temp_dir())
|
|
914
|
+
except OSError as ex:
|
|
915
|
+
return _err(ex, None, False)
|
|
916
|
+
return io_ok(path)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
# ---------------------------------------------------------------------------
|
|
920
|
+
# The process
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def lean_io_getenv(var):
|
|
924
|
+
if b"\0" in var:
|
|
925
|
+
return 0
|
|
926
|
+
v = os.environb.get(var)
|
|
927
|
+
return 0 if v is None else Ctor(1, [v])
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def lean_io_app_path():
|
|
931
|
+
try:
|
|
932
|
+
return io_ok(os.path.realpath(os.fsencode(sys.executable), strict=True))
|
|
933
|
+
except OSError:
|
|
934
|
+
return io_error(mk_user_error(b"failed to locate application"))
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def lean_io_current_dir():
|
|
938
|
+
try:
|
|
939
|
+
return io_ok(os.getcwdb())
|
|
940
|
+
except OSError:
|
|
941
|
+
return io_error(mk_user_error(b"failed to retrieve current working directory"))
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def lean_io_process_get_current_dir():
|
|
945
|
+
try:
|
|
946
|
+
return io_ok(os.getcwdb())
|
|
947
|
+
except OSError as ex:
|
|
948
|
+
return _err(ex)
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
def lean_io_process_set_current_dir(p):
|
|
952
|
+
return _with_path(p, lambda q: _unit_or(lambda: os.chdir(q), p))
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
def lean_io_process_get_pid():
|
|
956
|
+
return os.getpid()
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def lean_io_get_tid():
|
|
960
|
+
return threading.get_native_id()
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def lean_io_get_random_bytes(n):
|
|
964
|
+
if n == 0:
|
|
965
|
+
return io_ok(LByteArray(bytearray()))
|
|
966
|
+
try:
|
|
967
|
+
return io_ok(LByteArray(bytearray(os.urandom(n))))
|
|
968
|
+
except OSError as ex:
|
|
969
|
+
return _err(ex)
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def lean_io_exit(code):
|
|
973
|
+
sys.stdout.flush()
|
|
974
|
+
sys.stderr.flush()
|
|
975
|
+
os._exit(code)
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def lean_io_force_exit(code):
|
|
979
|
+
os._exit(code)
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
def lean_runtime_mark_persistent(a):
|
|
983
|
+
return lean_mark_persistent(a)
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
def lean_runtime_mark_multi_threaded(a):
|
|
987
|
+
return a
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
# ---------------------------------------------------------------------------
|
|
991
|
+
# Child processes
|
|
992
|
+
|
|
993
|
+
PIPED = 0
|
|
994
|
+
INHERIT = 1
|
|
995
|
+
NULL = 2
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def _cstr(b):
|
|
999
|
+
i = b.find(b"\0")
|
|
1000
|
+
return b if i < 0 else b[:i]
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def lean_io_process_spawn(args):
|
|
1004
|
+
cfg = args.objs[0]
|
|
1005
|
+
modes = [cfg.sc.get(0, INHERIT), cfg.sc.get(1, INHERIT), cfg.sc.get(2, INHERIT)]
|
|
1006
|
+
cmd = _cstr(args.objs[1])
|
|
1007
|
+
argv = [cmd] + [_cstr(a) for a in args.objs[2].data]
|
|
1008
|
+
cwd = args.objs[3]
|
|
1009
|
+
env = args.objs[4]
|
|
1010
|
+
inherit_env = bool(args.sc.get(40, 1))
|
|
1011
|
+
do_setsid = bool(args.sc.get(41, 0))
|
|
1012
|
+
sys.stdout.flush()
|
|
1013
|
+
sys.stderr.flush()
|
|
1014
|
+
|
|
1015
|
+
pipes = [None, None, None]
|
|
1016
|
+
try:
|
|
1017
|
+
for i in range(3):
|
|
1018
|
+
if modes[i] == PIPED:
|
|
1019
|
+
pipes[i] = os.pipe2(os.O_CLOEXEC)
|
|
1020
|
+
except OSError as ex:
|
|
1021
|
+
return _err(ex)
|
|
1022
|
+
|
|
1023
|
+
child_env = dict(os.environb) if inherit_env else {}
|
|
1024
|
+
for pair in env.data:
|
|
1025
|
+
k = _cstr(pair.objs[0])
|
|
1026
|
+
v = pair.objs[1]
|
|
1027
|
+
if isinstance(v, int):
|
|
1028
|
+
child_env.pop(k, None)
|
|
1029
|
+
else:
|
|
1030
|
+
child_env[k] = _cstr(v.objs[0])
|
|
1031
|
+
cwd_c = None if isinstance(cwd, int) else _cstr(cwd.objs[0])
|
|
1032
|
+
cwd_msg = b"could not change directory to " + (cwd_c or b"") + b"\n"
|
|
1033
|
+
exec_msg = b"could not execute external process '" + cmd + b"'\n"
|
|
1034
|
+
|
|
1035
|
+
try:
|
|
1036
|
+
pid = os.fork()
|
|
1037
|
+
except OSError as ex:
|
|
1038
|
+
for p in pipes:
|
|
1039
|
+
if p:
|
|
1040
|
+
os.close(p[0])
|
|
1041
|
+
os.close(p[1])
|
|
1042
|
+
return _err(ex)
|
|
1043
|
+
if pid == 0:
|
|
1044
|
+
try:
|
|
1045
|
+
for i, target in enumerate((0, 1, 2)):
|
|
1046
|
+
p = pipes[i]
|
|
1047
|
+
if p is not None:
|
|
1048
|
+
r, w = p
|
|
1049
|
+
if target == 0:
|
|
1050
|
+
os.dup2(r, 0)
|
|
1051
|
+
os.close(w)
|
|
1052
|
+
else:
|
|
1053
|
+
os.dup2(w, target)
|
|
1054
|
+
os.close(r)
|
|
1055
|
+
elif modes[i] == NULL:
|
|
1056
|
+
fd = os.open(b"/dev/null", os.O_RDONLY if target == 0 else os.O_WRONLY)
|
|
1057
|
+
os.dup2(fd, target)
|
|
1058
|
+
if cwd_c is not None:
|
|
1059
|
+
try:
|
|
1060
|
+
os.chdir(cwd_c)
|
|
1061
|
+
except OSError:
|
|
1062
|
+
os.write(2, cwd_msg)
|
|
1063
|
+
os._exit(255)
|
|
1064
|
+
if do_setsid:
|
|
1065
|
+
os.setsid()
|
|
1066
|
+
try:
|
|
1067
|
+
os.execvpe(cmd, argv, child_env)
|
|
1068
|
+
except OSError:
|
|
1069
|
+
pass
|
|
1070
|
+
os.write(2, exec_msg)
|
|
1071
|
+
finally:
|
|
1072
|
+
os._exit(255)
|
|
1073
|
+
|
|
1074
|
+
handles = []
|
|
1075
|
+
for i, p in enumerate(pipes):
|
|
1076
|
+
if p is None:
|
|
1077
|
+
handles.append(0)
|
|
1078
|
+
continue
|
|
1079
|
+
r, w = p
|
|
1080
|
+
ours, theirs = (w, r) if i == 0 else (r, w)
|
|
1081
|
+
os.close(theirs)
|
|
1082
|
+
handles.append(Handle(fd=ours))
|
|
1083
|
+
child = Ctor(0, handles)
|
|
1084
|
+
child.sc[24] = pid
|
|
1085
|
+
child.sc[28] = 1 if do_setsid else 0
|
|
1086
|
+
return io_ok(child)
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
def _exit_code(status):
|
|
1090
|
+
if os.WIFEXITED(status):
|
|
1091
|
+
return os.WEXITSTATUS(status)
|
|
1092
|
+
return 128 + os.WTERMSIG(status)
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
def lean_io_process_child_wait(_cfg, child):
|
|
1096
|
+
try:
|
|
1097
|
+
_, status = os.waitpid(child.sc[24], 0)
|
|
1098
|
+
except OSError as ex:
|
|
1099
|
+
return _err(ex)
|
|
1100
|
+
return io_ok(_exit_code(status))
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
def lean_io_process_child_try_wait(_cfg, child):
|
|
1104
|
+
try:
|
|
1105
|
+
pid, status = os.waitpid(child.sc[24], os.WNOHANG)
|
|
1106
|
+
except OSError as ex:
|
|
1107
|
+
return _err(ex)
|
|
1108
|
+
if pid == 0:
|
|
1109
|
+
return io_ok(0)
|
|
1110
|
+
return io_ok(Ctor(1, [_exit_code(status)]))
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
def lean_io_process_child_kill(_cfg, child):
|
|
1114
|
+
pid = child.sc[24]
|
|
1115
|
+
try:
|
|
1116
|
+
if child.sc.get(28, 0):
|
|
1117
|
+
os.killpg(pid, signal.SIGKILL)
|
|
1118
|
+
else:
|
|
1119
|
+
os.kill(pid, signal.SIGKILL)
|
|
1120
|
+
except OSError as ex:
|
|
1121
|
+
return _err(ex)
|
|
1122
|
+
return io_ok(0)
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def lean_io_process_child_pid(_cfg, child):
|
|
1126
|
+
return child.sc[24]
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def lean_io_process_child_take_stdin(_cfg, child):
|
|
1130
|
+
out, err = child.objs[1], child.objs[2]
|
|
1131
|
+
lean_inc(out)
|
|
1132
|
+
lean_inc(err)
|
|
1133
|
+
rest = Ctor(0, [0, out, err])
|
|
1134
|
+
rest.sc[24] = child.sc[24]
|
|
1135
|
+
rest.sc[28] = child.sc.get(28, 0)
|
|
1136
|
+
stdin = child.objs[0]
|
|
1137
|
+
lean_inc(stdin)
|
|
1138
|
+
lean_dec(child)
|
|
1139
|
+
return io_ok(Ctor(0, [stdin, rest]))
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
# ---------------------------------------------------------------------------
|
|
1143
|
+
# Tasks of BaseIO actions and promises (tasks run synchronously)
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
def lean_io_as_task(act, prio=0):
|
|
1147
|
+
return Task(lean_mark_persistent(lean_apply(act, [0])))
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
def lean_io_map_task(f, t, prio=0, sync=0):
|
|
1151
|
+
return Task(lean_mark_persistent(lean_apply(f, [t.value, 0])))
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
def lean_io_bind_task(t, f, prio=0, sync=0):
|
|
1155
|
+
return lean_apply(f, [t.value, 0])
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
def lean_io_check_canceled():
|
|
1159
|
+
return False
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
def lean_io_cancel(t):
|
|
1163
|
+
return 0
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def lean_io_get_task_state(t):
|
|
1167
|
+
return 1 if t.value is None else 2
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
def lean_io_wait_any(tasks):
|
|
1171
|
+
return tasks.objs[0].value
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
class Promise:
|
|
1175
|
+
__slots__ = ("task",)
|
|
1176
|
+
|
|
1177
|
+
def __init__(self):
|
|
1178
|
+
self.task = Task(None)
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def lean_io_promise_new():
|
|
1182
|
+
return Promise()
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
def lean_io_promise_resolve(value, p):
|
|
1186
|
+
if p.task.value is None:
|
|
1187
|
+
p.task.value = lean_mark_persistent(Ctor(1, [value]))
|
|
1188
|
+
return 0
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def lean_io_promise_result_opt(p):
|
|
1192
|
+
return p.task
|