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

502 lines, 180 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
176class BufWriter(Writer):
177 """Mimic StringIO API, but add clear() so we can reuse objects.
178
179 We can also add accelerators for directly writing numbers, to avoid
180 allocations when encoding JSON.
181 """
182
183 def __init__(self):
184 # type: () -> None
185 self.parts = []
186
187 def write(self, s):
188 # type: (str) -> None
189 self.parts.append(s)
190
191 def write_spaces(self, n):
192 # type: (int) -> None
193 """For JSON indenting. Avoid intermediate allocations in C++."""
194 self.parts.append(' ' * n)
195
196 def getvalue(self):
197 # type: () -> str
198 return ''.join(self.parts)
199
200 def clear(self):
201 # type: () -> None
202 del self.parts[:]
203
204
205def Stdout():
206 # type: () -> Writer
207 return sys.stdout
208
209
210def Stderr():
211 # type: () -> Writer
212 return sys.stderr
213
214
215def Stdin():
216 # type: () -> LineReader
217 return sys.stdin
218
219
220class switch(object):
221 """Translates to C switch on int.
222
223 with tagswitch(i) as case:
224 if case(42, 43):
225 print('hi')
226 elif case(99):
227 print('two')
228 else:
229 print('neither')
230 """
231
232 def __init__(self, value):
233 # type: (int) -> None
234 self.value = value
235
236 def __enter__(self):
237 # type: () -> switch
238 return self
239
240 def __exit__(self, type, value, traceback):
241 # type: (Any, Any, Any) -> bool
242 return False # Allows a traceback to occur
243
244 def __call__(self, *cases):
245 # type: (*Any) -> bool
246 return self.value in cases
247
248
249class str_switch(object):
250 """Translates to fast dispatch on string length, then memcmp()."""
251
252 def __init__(self, value):
253 # type: (str) -> None
254 self.value = value
255
256 def __enter__(self):
257 # type: () -> switch
258 return self
259
260 def __exit__(self, type, value, traceback):
261 # type: (Any, Any, Any) -> bool
262 return False # Allows a traceback to occur
263
264 def __call__(self, *cases):
265 # type: (*Any) -> bool
266 return self.value in cases
267
268
269class tagswitch(object):
270 """A ContextManager that translates to switch statement over ASDL types."""
271
272 def __init__(self, node):
273 # type: (Any) -> None
274 self.tag = node.tag()
275
276 def __enter__(self):
277 # type: () -> tagswitch
278 return self
279
280 def __exit__(self, type, value, traceback):
281 # type: (Any, Any, Any) -> bool
282 return False # Allows a traceback to occur
283
284 def __call__(self, *cases):
285 # type: (*Any) -> bool
286 return self.tag in cases
287
288
289if TYPE_CHECKING:
290 # Doesn't work
291 T = TypeVar('T')
292 class StackArray(Generic[T]):
293 def __init__(self):
294 self.items = [] # type: List[T]
295
296 def append(self, item):
297 # type: (T) -> None
298 self.items.append(item)
299
300 def pop(self):
301 # type: () -> T
302 return self.items.pop()
303
304 # Doesn't work, this is only for primitive types
305 #StackArray = NewType('StackArray', list)
306
307
308def MakeStackArray(item_type):
309 # type: (TypeVar) -> StackArray[item_type]
310 """
311 Convenience "constructor" used like this:
312
313 myarray = MakeStackArray(int)
314
315 The idiom could also be
316
317 myarray = cast('StackArray[int]', [])
318
319 But that's uglier.
320 """
321 return cast('StackArray[item_type]', [])
322
323
324if TYPE_CHECKING:
325 K = TypeVar('K')
326 V = TypeVar('V')
327
328
329def iteritems(d):
330 # type: (Dict[K, V]) -> Iterator[Tuple[K, V]]
331 """Make translation a bit easier."""
332 return d.iteritems()
333
334
335def split_once(s, delim):
336 # type: (str, str) -> Tuple[str, Optional[str]]
337 """Easier to call than split(s, 1) because of tuple unpacking."""
338
339 parts = s.split(delim, 1)
340 if len(parts) == 1:
341 no_str = None # type: Optional[str]
342 return s, no_str
343 else:
344 return parts[0], parts[1]
345
346
347def hex_lower(i):
348 # type: (int) -> str
349 return '%x' % i
350
351
352def hex_upper(i):
353 # type: (int) -> str
354 return '%X' % i
355
356
357def octal(i):
358 # type: (int) -> str
359 return '%o' % i
360
361
362def dict_erase(d, key):
363 # type: (Dict[Any, Any], Any) -> None
364 """
365 Ensure that a key isn't in the Dict d. This makes C++ translation easier.
366 """
367 try:
368 del d[key]
369 except KeyError:
370 pass
371
372
373def str_cmp(s1, s2):
374 # type: (str, str) -> int
375 if s1 == s2:
376 return 0
377 if s1 < s2:
378 return -1
379 else:
380 return 1
381
382
383class UniqueObjects(object):
384 """A set of objects identified by their address in memory
385
386 Python's id(obj) returns the address of any object. But we don't simply
387 implement it, because it requires a uint64_t on 64-bit systems, while mycpp
388 only supports 'int'.
389
390 So we have a whole class.
391
392 Should be used for:
393
394 - Cycle detection when pretty printing, as Python's repr() does
395 - See CPython's Objects/object.c PyObject_Repr()
396 /* These methods are used to control infinite recursion in repr, str, print,
397 etc. Container objects that may recursively contain themselves,
398 e.g. builtin dictionaries and lists, should use Py_ReprEnter() and
399 Py_ReprLeave() to avoid infinite recursion.
400 */
401 - e.g. dictobject.c dict_repr() calls Py_ReprEnter() to print {...}
402 - In Python 2.7 a GLOBAL VAR is used
403
404 - It also checks for STACK OVERFLOW
405
406 - Packle serialization
407 """
408
409 def __init__(self):
410 # 64-bit id() -> small integer ID
411 self.addresses = {} # type: Dict[int, int]
412
413 def Contains(self, obj):
414 # type: (Any) -> bool
415 """ Convenience? """
416 return self.Get(obj) != -1
417
418 def MaybeAdd(self, obj):
419 # type: (Any) -> None
420 """ Convenience? """
421
422 # def AddNewObject(self, obj):
423 def Add(self, obj):
424 # type: (Any) -> None
425 """
426 Assert it isn't already there, and assign a new ID!
427
428 # Lib/pickle does:
429
430 self.memo[id(obj)] = memo_len, obj
431
432 I guess that's the object ID and a void*
433
434 Then it does:
435
436 x = self.memo.get(id(obj))
437
438 and
439
440 # If the object is already in the memo, this means it is
441 # recursive. In this case, throw away everything we put on the
442 # stack, and fetch the object back from the memo.
443 if id(obj) in self.memo:
444 write(POP + self.get(self.memo[id(obj)][0]))
445
446 BUT It only uses the numeric ID!
447 """
448 addr = id(obj)
449 assert addr not in self.addresses
450 self.addresses[addr] = len(self.addresses)
451
452 def Get(self, obj):
453 # type: (Any) -> int
454 """
455 Returns unique ID assigned
456
457 Returns -1 if it doesn't exist?
458 """
459 addr = id(obj)
460 return self.addresses.get(addr, -1)
461
462 # Note: self.memo.clear() doesn't appear to be used
463
464
465if 0:
466 # Prototype of Unix file descriptor I/O, compared with FILE* libc I/O.
467 # Doesn't seem like we need this now.
468
469 # Short versions of STDOUT_FILENO and STDERR_FILENO
470 kStdout = 1
471 kStderr = 2
472
473 def writeln(s, fd=kStdout):
474 # type: (str, int) -> None
475 """Write a line. The name is consistent with JavaScript writeln() and Rust.
476
477 e.g.
478 writeln("x = %d" % x, kStderr)
479
480 TODO: The Oil interpreter shouldn't use print() anywhere. Instead it can use
481 writeln(s) and writeln(s, kStderr)
482 """
483 posix.write(fd, s)
484 posix.write(fd, '\n')
485
486 class File(object):
487 """Custom file wrapper for Unix I/O like write() read()
488
489 Not C I/O like fwrite() fread(). There should be no flush().
490 """
491
492 def __init__(self, fd):
493 # type: (int) -> None
494 self.fd = fd
495
496 def write(self, s):
497 # type: (str) -> None
498 posix.write(self.fd, s)
499
500 def writeln(self, s):
501 # type: (str) -> None
502 writeln(s, fd=self.fd)