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

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