Franck Pommereau

initial import

1 +*~
2 +,*
3 ++*
4 +*.aux
5 +*.log
6 +*.fdb_latexmk
7 +*.fls
8 +*.nav
9 +*.vrb
10 +*.out
11 +*.snm
12 +*.synctex.gz
13 +*.toc
14 +__pycache__
1 +class ExecEnv (dict) :
2 + def __init__ (self, *l, **k) :
3 + super().__init__(*l, **k)
4 + def __setitem__ (self, key, val) :
5 + old = self.get(key, None)
6 + try :
7 + old.set(val)
8 + except AttributeError :
9 + super().__setitem__(key, val)
10 + def __getitem__ (self, key) :
11 + old = self.get(key, None)
12 + try :
13 + return old.get()
14 + except AttributeError :
15 + return super().__getitem__(key)
16 + def exec (self, code) :
17 + exec(code, self)
18 + def eval (self, code) :
19 + return eval(code, self)
20 +
21 +class CAni (object) :
22 + _env = ExecEnv()
23 + @property
24 + def IP (self) :
25 + return self._env.get("IP", 1)
26 + @IP.setter
27 + def IP (self, val) :
28 + self._env["IP"] = val
29 + @property
30 + def RET (self) :
31 + return self._env.get("RET", None)
32 + @RET.setter
33 + def RET (self, val) :
34 + self._env["RET"] = val
35 + def exec (self, code) :
36 + self._env.exec(code)
37 + def eval (self, code) :
38 + RET = self.RET = self._env.eval(code)
39 + return RET
1 +from itertools import chain
2 +from inspect import cleandoc
3 +from collections import defaultdict
4 +from . import CAni
5 +
6 +class dopt (dict) :
7 + def __str__ (self) :
8 + return ",".join("%s=%s" % (k, v) if v is not None else k
9 + for (k, v) in self.items())
10 +
11 +_opposite = {"north": "south",
12 + "east": "west",
13 + "south": "north",
14 + "west": "east"}
15 +
16 +opp = _opposite.get
17 +
18 +class TikZ (object) :
19 + _defaults = {"tikzpicture": {},
20 + "pos": {},
21 + "value": {"minimum size": "1cm",
22 + "transform shape": None,
23 + "draw": None,
24 + "inner sep": "0pt",
25 + "outer sep": "0pt"},
26 + "valuescope": {},
27 + "aggregatescope": {},
28 + "arrayscope": {},
29 + "structscope": {},
30 + "heapscope": {},
31 + "valueread" : {"very thick": None,
32 + "draw": "blue!50!black",
33 + "fill": "blue!20"},
34 + "valuewrite": {"very thick": None,
35 + "draw": "red!50!black",
36 + "fill": "red!20"},
37 + "valuereadwrite": {"very thick": None,
38 + "draw": "purple!50!black",
39 + "fill": "purple!20"},
40 + "pointer": {"thick": None,
41 + "|-{Stealth[round]}": None},
42 + "aggregate": {"grow": "east",
43 + "ticks": "south"},
44 + "heap": {"grow": "left",
45 + "distance": "15mm"},
46 + "group": {"opacity": 0,
47 + "very thick": None,
48 + "inner sep": "0pt",
49 + "outer sep": "0pt"},
50 + "alloc": {},
51 + "ticks": {"gray": None,
52 + "scale": ".7"}}
53 + def __init__ (self, options, **default) :
54 + self._keys = []
55 + for key, val in chain(self._defaults.items(), default.items(), options.items()) :
56 + self.__dict__.setdefault(key, dopt()).update(val)
57 + self._keys.append(key)
58 + def __add__ (self, options) :
59 + return self.__class__(options, **self.dict())
60 + def __truediv__ (self, key) :
61 + new = __class__(self)
62 + setattr(new, key, dopt())
63 + return new
64 + def items (self) :
65 + for key in self._keys :
66 + yield key, getattr(self, key)
67 + def dict (self) :
68 + return dict(self.items())
69 +
70 +class CAniTikZ (CAni) :
71 + _nodeid = defaultdict(int)
72 + _defaults = {}
73 + def __init__ (self, tikz) :
74 + self.nodeid = tikz.pop("nodeid", None)
75 + if self.nodeid is None :
76 + char = self.__class__.__name__[0].lower()
77 + num = self._nodeid[char]
78 + self.nodeid = f"{char}{num}"
79 + self._nodeid[char] = num + 1
80 + self._o = TikZ(tikz, **self._defaults)
81 + def __matmul__ (self, key) :
82 + if key == 0 :
83 + return self
84 + else :
85 + raise ValueError("invalid index %r for %s object"
86 + % (key, self.__class__.__name__))
87 + def ptr (self) :
88 + return Pointer(self@0)
89 + def tex (self, **tikz) :
90 + opt = TikZ(tikz)
91 + return cleandoc(r"""\begin{{tikzpicture}}[{opt.tikzpicture}]
92 + {code}
93 + \end{{tikzpicture}}
94 + """).format(opt=opt,
95 + code="\n ".join(self.tikz(**tikz).splitlines()))
96 +
97 +class Pointer (CAniTikZ) :
98 + def __init__ (self, data) :
99 + self._d = data
100 + def val (self) :
101 + return self._d
102 + def __tikz__ (self, src, opt) :
103 + if self._d is None :
104 + return ""
105 + else :
106 + tgt = (self._d@0).nodeid
107 + return fr"\draw[{opt.pointer}] ({src}) -- ({tgt});"
108 +
109 +class Value (CAniTikZ) :
110 + def __init__ (self, init=None, **tikz) :
111 + super().__init__(tikz)
112 + self._h = [[init, self.IP, None]]
113 + self._r = set()
114 + self._w = set()
115 + def get (self) :
116 + self._r.add(self.IP)
117 + return self._h[-1][0]
118 + def set (self, val) :
119 + self._w.add(self.IP)
120 + if self._h[-1][0] == val :
121 + pass
122 + elif self.IP == self._h[-1][1] :
123 + self._h[-1][0] = val
124 + else :
125 + self._h[-1][2] = self.IP - 1
126 + self._h.append([val, self.IP, None])
127 + def stop (self) :
128 + if self.IP == self._h[-1][1] :
129 + self._h[-1][2] = self.IP
130 + else :
131 + self._h[-1][2] = self.IP - 1
132 + def tikz (self, **tikz) :
133 + tpl = r"""%% {classname} {nodeid}
134 + \begin{{scope}}[{opt.valuescope}]
135 + {node}
136 + {highlight}
137 + {states}
138 + \end{{scope}}
139 + %% /{classname} {nodeid}
140 + """
141 + opt = TikZ(tikz) + self._o
142 + self.stop()
143 + return cleandoc(tpl).format(classname=self.__class__.__name__,
144 + opt=opt,
145 + node=(self._node(opt)
146 + or "% skipped node"),
147 + nodeid=self.nodeid,
148 + highlight=("\n ".join(self._highlight(opt))
149 + or "% skipped reads/writes"),
150 + states=("\n ".join(self._states(opt))
151 + or "%s skipped states"))
152 + def _highlight (self, opt) :
153 + nodeid = self.nodeid
154 + for cat, steps in zip([opt.valueread, opt.valuewrite, opt.valuereadwrite],
155 + [self._r-self._w, self._w-self._r, self._w&self._r]) :
156 + if steps :
157 + when = ",".join(str(s) for s in sorted(steps))
158 + yield cleandoc(fr"""\only<{when}>{{
159 + \draw[{cat}] ({nodeid}.south west) rectangle ({nodeid}.north east);
160 + }}
161 + """)
162 + def _node (self, opt) :
163 + return fr"\node[{opt.value},{opt.pos}] ({self.nodeid}) {{}};"
164 + def _states (self, opt) :
165 + for value, start, stop in self._h :
166 + if value is not None :
167 + yield (r"\only<{start}-{stop}>{{ {state} }}"
168 + r"").format(start=start or 1,
169 + stop=stop or "",
170 + state=self._state(value, opt))
171 + def _state (self, value, opt) :
172 + try :
173 + return value.__tikz__(f"{self.nodeid}.center", opt)
174 + except :
175 + return f"\node at ({self.nodeid}) {{{value}\strut}};"
176 +
177 +class Aggregate (CAniTikZ) :
178 + def __init__ (self, init, **tikz) :
179 + super().__init__(tikz)
180 + if isinstance(init, int) :
181 + self._d = {k: Value(None, nodeid=f"{self.nodeid}/{k}", **tikz)
182 + for k in range(init)}
183 + elif isinstance(init, list) :
184 + self._d = {}
185 + for k, v in enumerate(init) :
186 + if isinstance(v, Value) :
187 + self._d[k] = v
188 + else :
189 + self._d[k] = Value(v, nodeid=f"{self.nodeid}/{k}", **tikz)
190 + elif isinstance(init, dict) :
191 + self._d = {}
192 + for k, v in init.items() :
193 + if isinstance(v, Value) :
194 + self._d[k] = v
195 + else :
196 + self._d[k] = Value(v, nodeid=f"{self.nodeid}/{k}", **tikz)
197 + items = list(self._d.values())
198 + self._first = items[0]
199 + self._last = items[-1]
200 + def __matmul__ (self, key) :
201 + if key in self._d :
202 + return self._d[key]
203 + elif key == 0 :
204 + return self._first
205 + elif key == -1 :
206 + return self._last
207 + else :
208 + raise ValueError("invalid index %r for %s object"
209 + % (key, self.__class__.__name__))
210 + def __getitem__ (self, key) :
211 + return self._d[key].get()
212 + def __setitem__ (self, key, val) :
213 + self._d[key].set(val)
214 + def __len__ (self) :
215 + return len(self.d)
216 + def stop (self) :
217 + for v in self._d.values() :
218 + v.stop()
219 + def tikz (self, **tikz) :
220 + tpl = r"""%% {classname} {nodeid}
221 + \begin{{scope}}[{opt.aggregatescope}]
222 + {nodes}
223 + {ticks}
224 + {highlight}
225 + {states}
226 + \end{{scope}}
227 + %% /{classname} {nodeid}
228 + """
229 + opt = TikZ(tikz) + self._o
230 + self.stop()
231 + return cleandoc(tpl).format(classname=self.__class__.__name__,
232 + nodeid=self.nodeid,
233 + opt=opt,
234 + nodes="\n ".join(self._nodes(opt)),
235 + ticks=("\n ".join(self._ticks(opt))
236 + or "% skipped ticks"),
237 + highlight=("\n ".join(self._highlight(opt))
238 + or "% skipped reads/writes"),
239 + states=("\n ".join(self._states(opt))
240 + or "% skipped states"))
241 + def _nodes (self, opt) :
242 + grow = opt.aggregate["grow"]
243 + anchor = opp(grow)
244 + prev = None
245 + for key, val in self._d.items() :
246 + if prev is None :
247 + yield val._node(opt)
248 + opt = (opt / "pos") + {"value": {"anchor": anchor},
249 + "pos": {"at": f"({val.nodeid}.{grow})"}}
250 + else :
251 + yield val._node(opt)
252 + prev = val
253 + yield (r"\node[{opt.group},fit=({first}) ({last})] ({nodeid}) {{}};"
254 + r"").format(opt=opt,
255 + nodeid=self.nodeid,
256 + first=(self@0).nodeid,
257 + last=(self@-1).nodeid)
258 + def _ticks (self, opt) :
259 + ticks_side = opt.aggregate.get("ticks", None)
260 + if not ticks_side :
261 + return
262 + ticks_anchor = opp(ticks_side)
263 + for key, val in self._d.items() :
264 + yield (r"\node[{opt.ticks},anchor={anchor}] at ({nodeid}.{side})"
265 + r" {{{tick}}};"
266 + r"").format(opt=opt,
267 + anchor=ticks_anchor,
268 + nodeid=val.nodeid,
269 + side=ticks_side,
270 + tick=self._tick(key, opt))
271 + def _tick (self, key, opt) :
272 + return fr"{key}\strut"
273 + def _highlight (self, opt) :
274 + for access, steps in zip([opt.valueread, opt.valuewrite, opt.valuereadwrite],
275 + [lambda v: v._r - v._w,
276 + lambda v: v._w - v._r,
277 + lambda v: v._w & v._r]) :
278 + anim = defaultdict(list)
279 + for key, val in self._d.items() :
280 + for s in steps(val) :
281 + anim[s].append(key)
282 + mina = defaultdict(set)
283 + for step, keys in anim.items() :
284 + mina[tuple(sorted(keys))].add(step)
285 + def minstep (item) :
286 + return tuple(sorted(item[1]))
287 + for info, steps in sorted(mina.items(), key=minstep) :
288 + yield (r"\only<{steps}>{{"
289 + r"").format(steps=",".join(str(s) for s in steps))
290 + for key in info :
291 + nodeid = (self@key).nodeid
292 + yield (fr"\draw[{access}] ({nodeid}.south west) rectangle"
293 + fr" ({nodeid}.north east);")
294 + yield "}"
295 + def _states (self, opt) :
296 + anim = defaultdict(list)
297 + for key, val in self._d.items() :
298 + for value, start, stop in val._h :
299 + anim[start,stop].append((key, value))
300 + def firstlargest (item) :
301 + return (item[0][0], -item[0][1])
302 + for (start, stop), info in sorted(anim.items(), key=firstlargest) :
303 + if all(v is None for _, v in info) :
304 + continue
305 + yield (r"\only<{start}-{stop}>{{"
306 + r"").format(start=start, stop=stop)
307 + for key, val in info :
308 + if val is not None :
309 + nodeid = (self@key).nodeid
310 + try :
311 + yield " " + val.__tikz__(f"{nodeid}.center", opt)
312 + except :
313 + yield fr" \node at ({nodeid}) {{{val}}};"
314 + yield "}"
315 +
316 +class Array (Aggregate) :
317 + _defaults = {"aggregate": {"index": "west"}}
318 + def __init__ (self, init, index=[], **tikz) :
319 + super().__init__(init, **tikz)
320 + self._o.aggregatescope = self._o.arrayscope
321 + # register index
322 + def _ticks (self, opt) :
323 + for t in super()._ticks(opt) :
324 + yield t
325 + # yield index
326 +
327 +class Struct (Aggregate) :
328 + _defaults = {"aggregate": {"grow": "south",
329 + "ticks": "west"}}
330 + def __init__ (self, init, **tikz) :
331 + super().__init__(init, **tikz)
332 + self._o.aggregatescope = self._o.structscope
333 + def _tick (self, key, opt) :
334 + return fr".{key}\strut"
335 +
336 +class Heap (CAniTikZ) :
337 + _defaults = {"group": {"opacity": 0,
338 + "inner sep": "5mm"}}
339 + def __init__ (self, **tikz) :
340 + super().__init__(tikz)
341 + self._alloc = {}
342 + self._freed = {}
343 + def new (self, data) :
344 + self._alloc[data.nodeid] = [data, self.IP, ""]
345 + return Pointer(data)
346 + def free (self, ptr) :
347 + data = ptr.get()
348 + l = self._freed[data.nodeid] = self._alloc.pop(data.nodeid)
349 + l[-1] = self.IP
350 + ptr.set(None)
351 + def tikz (self, **tikz) :
352 + opt = TikZ(tikz) + self._o
353 + classname = self.__class__.__name__
354 + nodeid = self.nodeid
355 + return (f"%% {classname} {nodeid}\n"
356 + + "\n".join(self._tikz(opt))
357 + + f"\n%% /{classname} {nodeid}")
358 + def _tikz (self, opt) :
359 + fit = []
360 + yield fr"\begin{{scope}}[{opt.heapscope}]"
361 + for data, start, stop in chain(self._alloc.values(), self._freed.values()) :
362 + fit.append(data.nodeid)
363 + yield fr" \uncover<{start}-{stop}>{{"
364 + yield fr" %% allocated data"
365 + yield fr" \begin{{scope}}[{opt.alloc}]"
366 + for line in data.tikz(**opt.dict()).splitlines() :
367 + yield " " + line
368 + yield r" \end{scope}"
369 + yield r" }"
370 + opt = opt + {"pos": {opt.heap["grow"]:
371 + "{dist} of {prev}".format(dist=opt.heap["distance"],
372 + prev=(data@0).nodeid)}}
373 + children = " ".join(f"({nid})" for nid in fit)
374 + yield fr" \node[{opt.group},fit={children}] ({self.nodeid}) {{}};"
375 + yield r"\end{scope}"
1 +from inspect import Signature, Parameter
2 +
3 +from . import CAni
4 +from .highlight import pygmentize
5 +
6 +class _CODE (CAni) :
7 + _fields = []
8 + _options = []
9 + def __init__ (self, *l, **k) :
10 + params = []
11 + for i, name in enumerate(self._fields) :
12 + if name[0] == "*" :
13 + self._fields = self._fields[:]
14 + self._fields[i] = name[1:]
15 + params.append(Parameter(name[1:], Parameter.VAR_POSITIONAL))
16 + else :
17 + params.append(Parameter(name, Parameter.POSITIONAL_OR_KEYWORD))
18 + for name in self._options :
19 + params.append(Parameter(name, Parameter.POSITIONAL_OR_KEYWORD, default=None))
20 + params.append(Parameter("src", Parameter.KEYWORD_ONLY, default=None))
21 + sig = Signature(params)
22 + args = sig.bind(*l, **k)
23 + args.apply_defaults()
24 + for key, val in args.arguments.items() :
25 + setattr(self, key, val)
26 + self._at = set()
27 + def __str__ (self) :
28 + content = []
29 + for key, val in self.items() :
30 + if isinstance(val, _CODE) :
31 + content.append((key, str(val)))
32 + else :
33 + content.append((key, repr(val)))
34 + return "%s(%s)" % (self.__class__.__name__,
35 + ", ".join("%s=%r" % item for item in content))
36 + def items (self) :
37 + for field in self._fields :
38 + yield field, getattr(self, field)
39 + for field in self._options :
40 + member = getattr(self, field, None)
41 + if member is not None :
42 + yield field, member
43 + def source (self) :
44 + sub = {}
45 + for key, val in self.items() :
46 + if isinstance(val, _CODE) :
47 + sub[key] = val.source()
48 + else :
49 + sub[key] = val
50 + return self.src.format(**sub)
51 + def tex (self) :
52 + sub = self.src.format(**{key : "$" for key, val in self.items()}).split("$")
53 + parts = [pygmentize(sub[0])]
54 + for (key, val), txt in zip(self.items(), sub[1:]) :
55 + if isinstance(val, _CODE) :
56 + parts.append(val.tex())
57 + else :
58 + parts.append(pygmentize(str(val)))
59 + parts.append(pygmentize(txt))
60 + tex = "".join(parts)
61 + if self._at :
62 + return r"\onlyhl{%s}{" % ",".join(str(i) for i in self._at) + tex + "}"
63 + else :
64 + return tex
65 +
66 +class BLOCK (_CODE) :
67 + _fields = ["*body"]
68 + def __call__ (self) :
69 + self._at.add(self.IP)
70 + for code in self.body :
71 + code()
72 + def source (self) :
73 + return "".join(b.source() for b in self.body)
74 + def tex (self) :
75 + return "".join(b.tex() for b in self.body)
76 +
77 +class STMT (_CODE) :
78 + _fields = ["*steps"]
79 + def __call__ (self) :
80 + for s in self.steps :
81 + self._at.add(self.IP)
82 + self.exec(s)
83 + self.IP += 1
84 +
85 +class EXPR (_CODE) :
86 + _fields = ["expr"]
87 + def __init__ (self, *l, **k) :
88 + super().__init__(*l, **k)
89 + if self.src is None :
90 + self.src = self.expr
91 + def __call__ (self) :
92 + self._at.add(self.IP)
93 + self.eval(self.expr)
94 + self.IP += 1
95 +
96 +class PY (_CODE) :
97 + _fields = ["py"]
98 + def __call__ (self) :
99 + self.exec(self.py)
100 + def tex (self) :
101 + return ""
102 + def source (self) :
103 + return ""
104 +
105 +class ENV (_CODE) :
106 + _fields = ["name", "value"]
107 + def __call__ (self) :
108 + self._env[self.name] = self.value
109 + def tex (self) :
110 + return ""
111 + def source (self) :
112 + return ""
113 +
114 +class WS (_CODE) :
115 + _fields = []
116 + def __init__ (self, src) :
117 + super().__init__(src=src)
118 + def __call__ (self) :
119 + pass
120 + def tex (self) :
121 + return self.src
122 +
123 +class XDECL (_CODE) :
124 + _fields = ["*names"]
125 + def __call__ (self) :
126 + for name in self.names :
127 + self._env[name] = None
128 + self._at.add(self.IP)
129 + self.IP += 1
130 +
131 +class DECL (_CODE) :
132 + _fields = ["name"]
133 + _options = ["init"]
134 + def __call__ (self) :
135 + if self.init is not None :
136 + self.init()
137 + self._env[self.name] = self.RET
138 + else :
139 + self._env[self.name] = None
140 + self._at.add(self.IP)
141 + self.IP += 1
142 + def tex (self) :
143 + src = super().tex()
144 + if self.animate is None :
145 + return src
146 + else :
147 + return src + " " + "".join(self._tex())
148 + def _tex (self) :
149 + tail = r"\PY{{c+c1}}{{/* {value} */}}"
150 + for value, start, stop in self._cell.hist :
151 + if value is not None :
152 + yield (r"\onlyshow{{{start}-{stop}}}{{{value}}}"
153 + r"").format(start=start or 1,
154 + stop=stop or "",
155 + value=tail.format(value=value))
156 +
157 +class BreakLoop (Exception) :
158 + def __init__ (self) :
159 + super().__init__()
160 +
161 +class BREAK (_CODE) :
162 + def __call__ (self) :
163 + self._at.add(self.IP)
164 + self.IP += 1
165 + raise BreakLoop()
166 +
167 +class FunctionReturn (Exception) :
168 + def __init__ (self, RET) :
169 + super().__init__()
170 + self.RET = RET
171 +
172 +class RETURN (_CODE) :
173 + _fields = ["value"]
174 + def __call__ (self) :
175 + self.value()
176 + self._at.add(self.IP)
177 + self.IP += 1
178 + raise FunctionReturn(self.RET)
179 +
180 +class IF (_CODE) :
181 + _fields = ["cond", "then"]
182 + _options = ["otherwise"]
183 + def __call__ (self) :
184 + self.cond()
185 + if self.RET :
186 + self.then()
187 + elif self.otherwise is not None :
188 + self.otherwise()
189 +
190 +class WHILE (_CODE) :
191 + _fields = ["cond", "body"]
192 + def __call__ (self) :
193 + try :
194 + while True :
195 + self.cond()
196 + if not self.RET :
197 + break
198 + self.body()
199 + except BreakLoop :
200 + return
201 +
202 +class DO (_CODE) :
203 + _fields = ["body", "cond"]
204 + def __call__ (self) :
205 + try :
206 + while True :
207 + self.body()
208 + self.cond()
209 + if not self.RET :
210 + break
211 + except BreakLoop :
212 + pass
213 +
214 +class FOR (_CODE) :
215 + _fields = ["init", "cond", "step", "body"]
216 + def __call__ (self) :
217 + self.init()
218 + try :
219 + while True :
220 + self.cond()
221 + if not self.RET :
222 + break
223 + self.body()
224 + self.step()
225 + except BreakLoop :
226 + pass
227 +
228 +class FUNC (_CODE) :
229 + _fields = ["body"]
230 + def __call__ (self) :
231 + try :
232 + self.body()
233 + except FunctionReturn as exc :
234 + self._env["RET"] = exc.RET
235 +
1 +from pygments import highlight as _pygmentize
2 +import pygments.lexers, pygments.formatters
3 +
4 +##
5 +## code pretty-printing
6 +##
7 +
8 +_lexer = pygments.lexers.get_lexer_by_name("C")
9 +_formatter = pygments.formatters.get_formatter_by_name("latex")
10 +
11 +def pygmentize (src) :
12 + return "\n".join("\n".join(_pygmentize(line, _lexer, _formatter).splitlines()[1:-1])
13 + for line in src.split("\n"))