OILS / mycpp / mylib.py View on Github | oils.pub

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