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

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