OILS / mycpp / examples / scoped_resource.py View on Github | oils.pub

259 lines, 108 significant
1#!/usr/bin/env python2
2"""
3scoped_resource.py
4"""
5from __future__ import print_function
6
7import os
8import sys
9
10from mycpp import mylib
11from mycpp.mylib import log
12from typing import List, Optional, Any
13
14
15class MyError(Exception):
16
17 def __init__(self):
18 # type: () -> None
19 pass
20
21
22def Error(error):
23 # type: (bool) -> None
24 if error:
25 raise MyError()
26
27
28#class BadName(object):
29class ctx_BadName(object):
30
31 def __init__(self):
32 # type: () -> None
33 self.i = 42
34
35 def __exit__(self, type, value, traceback):
36 # type: (Any, Any, Any) -> None
37 self.i = 43
38
39
40class ctx_NoArgs(object):
41 """Regression for most vexing parse."""
42
43 def __init__(self):
44 # type: () -> None
45 print('> NoArgs')
46
47 def __enter__(self):
48 # type: () -> None
49 """no-op, but it has to exist to be used as context manager."""
50 pass
51
52 def __exit__(self, type, value, traceback):
53 # type: (Any, Any, Any) -> None
54 print('< NoArgs')
55
56
57class ctx_DirStack(object):
58
59 def __init__(self, state, entry):
60 # type: (DirStack, str) -> None
61 self.state = state
62 state.Push(entry)
63
64 # Bug #1986: add heap-allocated member of context manager
65 self.restored = [] # type: List[str]
66 self.restored.append('foo')
67 self.non_pointer_member = 42 # make sure we don't root this
68
69 def __enter__(self):
70 # type: () -> None
71 """no-op, but it has to exist to be used as context manager."""
72 pass
73
74 def __exit__(self, type, value, traceback):
75 # type: (Any, Any, Any) -> None
76 self.restored.pop()
77 self.state.Pop()
78
79
80class DirStack(object):
81 """For pushd/popd/dirs."""
82
83 def __init__(self):
84 # type: () -> None
85 self.stack = [] # type: List[str]
86 self.Reset() # Invariant: it always has at least ONE entry.
87
88 def Reset(self):
89 # type: () -> None
90 del self.stack[:]
91 self.stack.append('CWD')
92
93 def Push(self, entry):
94 # type: (str) -> None
95 self.stack.append(entry)
96
97 def Pop(self):
98 # type: () -> Optional[str]
99 if len(self.stack) <= 1:
100 return None
101 self.stack.pop() # remove last
102 return self.stack[-1] # return second to last
103
104 def Iter(self):
105 # type: () -> List[str]
106 """Iterate in reverse order."""
107 # mycpp REWRITE:
108 #return reversed(self.stack)
109 ret = [] # type: List[str]
110 ret.extend(self.stack)
111 ret.reverse()
112 return ret
113
114 def __repr__(self):
115 # type: () -> str
116 return repr(self.stack)
117
118
119# C++ translation
120#
121# class _ErrExit__Context; // forward declaration
122# class _ErrExit {
123# };
124#
125# class _ErrExit__Context {
126# _ErrExit__Context(_ErrExit* state) : state_(state) {
127# state->Push();
128# }
129# ~_ErrExit__Context() {
130# state->Pop();
131# }
132# };
133
134
135def DoWork(d, do_raise):
136 # type: (DirStack, bool) -> None
137
138 # problem
139 # with self.mem.ctx_Call(...)
140 # PushCall/PopCall
141 # with self.mem.ctx_Temp(...)
142 # PushTemp/PopCall
143 # with self.mem.ctx_Source(...)
144 # PushSource/PopSource
145 #
146 # Scope_Call
147 # Scope_Temp
148
149 # PROBLEM: WE LOST TYPE CHECKING!
150 #with e.Context('zz') as _:
151 with ctx_DirStack(d, 'foo') as _:
152 log(' in context stack %d', len(d.stack))
153 if do_raise:
154 Error(do_raise)
155
156
157def TestGoodRaise():
158 # type: () -> None
159
160 # Use cases:
161 #
162 # Many in cmd_exec.py
163 #
164 # fd_state.Push(...) and Pop
165 # BeginAlias, EndAlias
166 # PushSource, PopSource (opens files)
167 # source
168 # eval -- no file opened, but need to change the current file
169 # PushTemp, PopTemp for env bindings
170 # PushErrExit, PopErrExit
171 # loop_level in cmd_exec
172
173 d = DirStack()
174
175 for do_raise in [False, True]:
176 log('')
177 log('-> dir stack %d', len(d.stack))
178 try:
179 DoWork(d, do_raise)
180 except MyError:
181 log('exited with exception')
182 log('<- dir stack %d', len(d.stack))
183
184 # C++ translation
185 #
186 # _Errexit e;
187 # e.errexit = true;
188 #
189 # log("-> errexit %d", e.errexit)
190 # {
191 # _ErrExit__Context(e);
192 # log(" errexit %d", e.errexit)
193 # }
194 # log("<- errexit %d", e.errexit)
195
196 with ctx_NoArgs():
197 print('hi')
198
199
200class ctx_MaybePure(object):
201 """Regression for early return."""
202
203 def __init__(self):
204 # type: () -> None
205 self.member = 'bar'
206
207 def __enter__(self):
208 # type: () -> None
209 """no-op, but it has to exist to be used as context manager."""
210 pass
211
212 def __exit__(self, type, value, traceback):
213 # type: (Any, Any, Any) -> None
214
215 if self.member is not None:
216 # BUG FIX: early return used to cause GC rooting error
217 return
218
219
220def TestReturn():
221 # type: () -> None
222
223 i = 0
224 for j in xrange(1000):
225 with ctx_MaybePure():
226 i += 1
227 mylib.MaybeCollect()
228 print("i = %d" % i)
229
230
231def run_tests():
232 # type: () -> None
233 TestGoodRaise()
234
235 TestReturn()
236
237
238def run_benchmarks():
239 # type: () -> None
240 d = DirStack()
241 for i in xrange(1000000):
242 # Does NOT trigger the bug!
243 #mylib.MaybeCollect()
244 try:
245 with ctx_DirStack(d, 'foo') as _:
246 # Bug #1986: add collection in this loop
247 mylib.MaybeCollect()
248 if i % 10000 == 0:
249 raise MyError()
250 except MyError:
251 log('exception')
252
253
254if __name__ == '__main__':
255 if os.getenv('BENCHMARK'):
256 log('Benchmarking...')
257 run_benchmarks()
258 else:
259 run_tests()