OILS / mycpp / mylib.py View on Github | oilshell.org

524 lines, 186 significant
1"""
2runtime.py
3"""
4from __future__ import print_function
5
6try:
7 import cStringIO
8except ImportError:
9 # Python 3 doesn't have cStringIO. Our yaks/ demo currently uses
10 # mycpp/mylib.py with Python 3.
11 cStringIO = None
12 import io
13
14import sys
15
16from pylib import collections_
17try:
18 import posix_ as posix
19except ImportError:
20 # Hack for tangled dependencies.
21 import os
22 posix = os
23
24from typing import (Tuple, List, Dict, Optional, Iterator, Any, TypeVar,
25 Generic, cast, TYPE_CHECKING)
26if TYPE_CHECKING:
27 from mycpp import mops
28
29# For conditional translation
30CPP = False
31PYTHON = True
32
33# Use POSIX name directly
34STDIN_FILENO = 0
35
36
37def MaybeCollect():
38 # type: () -> None
39 pass
40
41
42def NewDict():
43 # type: () -> Dict[str, Any]
44 """Make dictionaries ordered in Python, e.g. for JSON.
45
46 In C++, our Dict implementation should be ordered.
47 """
48 return collections_.OrderedDict()
49
50
51def log(msg, *args):
52 # type: (str, *Any) -> None
53 """Print debug output to stderr."""
54 if args:
55 msg = msg % args
56 print(msg, file=sys.stderr)
57
58
59def print_stderr(s):
60 # type: (str) -> None
61 """Print a message to stderr for the user.
62
63 This should be used sparingly, since it doesn't have location info, like
64 ui.ErrorFormatter does. We use it to print fatal I/O errors that were only
65 caught at the top level.
66 """
67 print(s, file=sys.stderr)
68
69
70#
71# Byte Operations avoid excessive allocations with string algorithms
72#
73
74
75def ByteAt(s, i):
76 # type: (str, int) -> int
77 """i must be in bounds."""
78
79 # This simplifies the C++ implementation
80 assert 0 <= i, 'No negative indices'
81 assert i < len(s), 'No negative indices'
82
83 return ord(s[i])
84
85
86def ByteEquals(byte, ch):
87 # type: (int, str) -> bool
88 assert len(ch) == 1, ch
89 assert 0 <= byte < 256, byte
90
91 return byte == ord(ch)
92
93
94def ByteInSet(byte, byte_set):
95 # type: (int, str) -> bool
96 assert 0 <= byte < 256, byte
97
98 return chr(byte) in byte_set
99
100
101def JoinBytes(byte_list):
102 # type: (List[int]) -> str
103
104 return ''.join(chr(b) for b in byte_list)
105
106
107#
108# Added for SparseArray
109#
110
111
112def BigIntSort(keys):
113 # type: (List[mops.BigInt]) -> None
114 keys.sort(key=lambda big: big.i)
115
116
117class File:
118 """
119 TODO: This should define a read/write interface, and then LineReader() and
120 Writer() can possibly inherit it, with runtime assertions
121
122 Then we allow downcasting from File -> LineReader, like we currently do in
123 C++ in gc_mylib.h.
124
125 Inheritance can't express the structural Reader/Writer pattern of Go, which
126 would be better. I suppose we could use File* everywhere, but having
127 fine-grained types is nicer. And there will be very few casts.
128 """
129 pass
130
131
132class LineReader:
133
134 def readline(self):
135 # type: () -> str
136 raise NotImplementedError()
137
138 def close(self):
139 # type: () -> None
140 raise NotImplementedError()
141
142 def isatty(self):
143 # type: () -> bool
144 raise NotImplementedError()
145
146
147if TYPE_CHECKING:
148
149 class BufLineReader(LineReader):
150
151 def __init__(self, s):
152 # type: (str) -> None
153 raise NotImplementedError()
154
155 def open(path):
156 # type: (str) -> LineReader
157
158 # TODO: should probably return mylib.File
159 # mylib.open() is currently only used in yaks/yaks_main and
160 # bin.osh_parse
161 raise NotImplementedError()
162
163else:
164 # Actual runtime
165 if cStringIO:
166 BufLineReader = cStringIO.StringIO
167 else: # Python 3
168 BufLineReader = io.StringIO
169
170 open = open
171
172
173class Writer:
174
175 def write(self, s):
176 # type: (str) -> None
177 raise NotImplementedError()
178
179 def flush(self):
180 # type: () -> None
181 raise NotImplementedError()
182
183 def isatty(self):
184 # type: () -> bool
185 raise NotImplementedError()
186
187 def close(self):
188 # type: () -> None
189 raise NotImplementedError()
190
191
192class BufWriter(Writer):
193 """Mimic StringIO API, but add clear() so we can reuse objects.
194
195 We can also add accelerators for directly writing numbers, to avoid
196 allocations when encoding JSON.
197 """
198
199 def __init__(self):
200 # type: () -> None
201 self.parts = []
202
203 def write(self, s):
204 # type: (str) -> None
205 self.parts.append(s)
206
207 def write_spaces(self, n):
208 # type: (int) -> None
209 """For JSON indenting. Avoid intermediate allocations in C++."""
210 self.parts.append(' ' * n)
211
212 def getvalue(self):
213 # type: () -> str
214 return ''.join(self.parts)
215
216 def clear(self):
217 # type: () -> None
218 del self.parts[:]
219
220 def close(self):
221 # type: () -> None
222
223 # No-op for now - we could invalidate write()?
224
225 pass
226
227
228def Stdout():
229 # type: () -> Writer
230 return sys.stdout
231
232
233def Stderr():
234 # type: () -> Writer
235 return sys.stderr
236
237
238def Stdin():
239 # type: () -> LineReader
240 return sys.stdin
241
242
243class switch(object):
244 """Translates to C switch on int.
245
246 with tagswitch(i) as case:
247 if case(42, 43):
248 print('hi')
249 elif case(99):
250 print('two')
251 else:
252 print('neither')
253 """
254
255 def __init__(self, value):
256 # type: (int) -> None
257 self.value = value
258
259 def __enter__(self):
260 # type: () -> switch
261 return self
262
263 def __exit__(self, type, value, traceback):
264 # type: (Any, Any, Any) -> bool
265 return False # Allows a traceback to occur
266
267 def __call__(self, *cases):
268 # type: (*Any) -> bool
269 return self.value in cases
270
271
272class str_switch(object):
273 """Translates to fast dispatch on string length, then memcmp()."""
274
275 def __init__(self, value):
276 # type: (str) -> None
277 self.value = value
278
279 def __enter__(self):
280 # type: () -> switch
281 return self
282
283 def __exit__(self, type, value, traceback):
284 # type: (Any, Any, Any) -> bool
285 return False # Allows a traceback to occur
286
287 def __call__(self, *cases):
288 # type: (*Any) -> bool
289 return self.value in cases
290
291
292class tagswitch(object):
293 """A ContextManager that translates to switch statement over ASDL types."""
294
295 def __init__(self, node):
296 # type: (Any) -> None
297 self.tag = node.tag()
298
299 def __enter__(self):
300 # type: () -> tagswitch
301 return self
302
303 def __exit__(self, type, value, traceback):
304 # type: (Any, Any, Any) -> bool
305 return False # Allows a traceback to occur
306
307 def __call__(self, *cases):
308 # type: (*Any) -> bool
309 return self.tag in cases
310
311
312if TYPE_CHECKING:
313 # Doesn't work
314 T = TypeVar('T')
315
316 class StackArray(Generic[T]):
317
318 def __init__(self):
319 self.items = [] # type: List[T]
320
321 def append(self, item):
322 # type: (T) -> None
323 self.items.append(item)
324
325 def pop(self):
326 # type: () -> T
327 return self.items.pop()
328
329 # Doesn't work, this is only for primitive types
330 #StackArray = NewType('StackArray', list)
331
332
333def MakeStackArray(item_type):
334 # type: (TypeVar) -> StackArray[item_type]
335 """
336 Convenience "constructor" used like this:
337
338 myarray = MakeStackArray(int)
339
340 The idiom could also be
341
342 myarray = cast('StackArray[int]', [])
343
344 But that's uglier.
345 """
346 return cast('StackArray[item_type]', [])
347
348
349if TYPE_CHECKING:
350 K = TypeVar('K')
351 V = TypeVar('V')
352
353
354def iteritems(d):
355 # type: (Dict[K, V]) -> Iterator[Tuple[K, V]]
356 """Make translation a bit easier."""
357 return d.iteritems()
358
359
360def split_once(s, delim):
361 # type: (str, str) -> Tuple[str, Optional[str]]
362 """Easier to call than split(s, 1) because of tuple unpacking."""
363
364 parts = s.split(delim, 1)
365 if len(parts) == 1:
366 no_str = None # type: Optional[str]
367 return s, no_str
368 else:
369 return parts[0], parts[1]
370
371
372def hex_lower(i):
373 # type: (int) -> str
374 return '%x' % i
375
376
377def dict_erase(d, key):
378 # type: (Dict[Any, Any], Any) -> None
379 """
380 Ensure that a key isn't in the Dict d. This makes C++ translation easier.
381 """
382 try:
383 del d[key]
384 except KeyError:
385 pass
386
387
388def str_cmp(s1, s2):
389 # type: (str, str) -> int
390 if s1 == s2:
391 return 0
392 if s1 < s2:
393 return -1
394 else:
395 return 1
396
397
398class UniqueObjects(object):
399 """A set of objects identified by their address in memory
400
401 Python's id(obj) returns the address of any object. But we don't simply
402 implement it, because it requires a uint64_t on 64-bit systems, while mycpp
403 only supports 'int'.
404
405 So we have a whole class.
406
407 Should be used for:
408
409 - Cycle detection when pretty printing, as Python's repr() does
410 - See CPython's Objects/object.c PyObject_Repr()
411 /* These methods are used to control infinite recursion in repr, str, print,
412 etc. Container objects that may recursively contain themselves,
413 e.g. builtin dictionaries and lists, should use Py_ReprEnter() and
414 Py_ReprLeave() to avoid infinite recursion.
415 */
416 - e.g. dictobject.c dict_repr() calls Py_ReprEnter() to print {...}
417 - In Python 2.7 a GLOBAL VAR is used
418
419 - It also checks for STACK OVERFLOW
420
421 - Packle serialization
422 """
423
424 def __init__(self):
425 # 64-bit id() -> small integer ID
426 self.addresses = {} # type: Dict[int, int]
427
428 def Contains(self, obj):
429 # type: (Any) -> bool
430 """ Convenience? """
431 return self.Get(obj) != -1
432
433 def MaybeAdd(self, obj):
434 # type: (Any) -> None
435 """ Convenience? """
436
437 # def AddNewObject(self, obj):
438 def Add(self, obj):
439 # type: (Any) -> None
440 """
441 Assert it isn't already there, and assign a new ID!
442
443 # Lib/pickle does:
444
445 self.memo[id(obj)] = memo_len, obj
446
447 I guess that's the object ID and a void*
448
449 Then it does:
450
451 x = self.memo.get(id(obj))
452
453 and
454
455 # If the object is already in the memo, this means it is
456 # recursive. In this case, throw away everything we put on the
457 # stack, and fetch the object back from the memo.
458 if id(obj) in self.memo:
459 write(POP + self.get(self.memo[id(obj)][0]))
460
461 BUT It only uses the numeric ID!
462 """
463 addr = id(obj)
464 assert addr not in self.addresses
465 self.addresses[addr] = len(self.addresses)
466
467 def Get(self, obj):
468 # type: (Any) -> int
469 """
470 Returns unique ID assigned
471
472 Returns -1 if it doesn't exist?
473 """
474 addr = id(obj)
475 return self.addresses.get(addr, -1)
476
477 # Note: self.memo.clear() doesn't appear to be used
478
479
480def probe(provider, name, *args):
481 # type: (str, str, Any) -> None
482 """Create a probe for use with profilers like linux perf and ebpf or dtrace."""
483 # Noop. Just a marker for mycpp to emit a DTRACE_PROBE()
484 return
485
486
487if 0:
488 # Prototype of Unix file descriptor I/O, compared with FILE* libc I/O.
489 # Doesn't seem like we need this now.
490
491 # Short versions of STDOUT_FILENO and STDERR_FILENO
492 kStdout = 1
493 kStderr = 2
494
495 def writeln(s, fd=kStdout):
496 # type: (str, int) -> None
497 """Write a line. The name is consistent with JavaScript writeln() and Rust.
498
499 e.g.
500 writeln("x = %d" % x, kStderr)
501
502 TODO: The Oil interpreter shouldn't use print() anywhere. Instead it can use
503 writeln(s) and writeln(s, kStderr)
504 """
505 posix.write(fd, s)
506 posix.write(fd, '\n')
507
508 class File(object):
509 """Custom file wrapper for Unix I/O like write() read()
510
511 Not C I/O like fwrite() fread(). There should be no flush().
512 """
513
514 def __init__(self, fd):
515 # type: (int) -> None
516 self.fd = fd
517
518 def write(self, s):
519 # type: (str) -> None
520 posix.write(self.fd, s)
521
522 def writeln(self, s):
523 # type: (str) -> None
524 writeln(s, fd=self.fd)