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

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