Showing
5 changed files
with
676 additions
and
0 deletions
.gitignore
0 → 100644
codanim/__init__.py
0 → 100644
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 |
codanim/data.py
0 → 100644
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}" |
codanim/flow.py
0 → 100644
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 | + |
codanim/highlight.py
0 → 100644
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")) |
-
Please register or login to post a comment