Showing
20 changed files
with
455 additions
and
238 deletions
1 | -"""SNAKES is the Net Algebra Kit for Editors and Simulators | 1 | +"""SNAKES library is organised into three parts: |
2 | - | 2 | + |
3 | -SNAKES is a Python library allowing to model all sorts of Petri nets | 3 | + * the core library is package `snakes` and its modules, among which |
4 | -and to execute them. It is very general as most Petri nets annotations | 4 | + `snakes.nets` is the one to work with Petri nets while the others |
5 | -can be arbitrary Python expressions while most values can be arbitrary | 5 | + can be seen as auxiliary modules |
6 | -Python objects. | 6 | + * the plugin system and the plugins themselves reside into package |
7 | - | 7 | + `snakes.plugins` |
8 | -SNAKES can be further extended with plugins, several ones being | 8 | + * auxiliary tools are kept into other sub-packages: `snakes.lang` |
9 | -already provided, in particular two plugins implement the Petri nets | 9 | + for all the material related to parsing Python and other |
10 | -compositions defined for the Petri Box Calculus and its successors. | 10 | + languages, `snakes.utils` for various utilities like the ABCD |
11 | + compiler | ||
11 | 12 | ||
12 | @author: Franck Pommereau | 13 | @author: Franck Pommereau |
13 | @organization: University of Evry/Paris-Saclay | 14 | @organization: University of Evry/Paris-Saclay |
14 | @copyright: (C) 2005-2013 Franck Pommereau | 15 | @copyright: (C) 2005-2013 Franck Pommereau |
15 | @license: GNU Lesser General Public Licence (aka. GNU LGPL), see the | 16 | @license: GNU Lesser General Public Licence (aka. GNU LGPL), see the |
16 | - file `doc/COPYING` in the distribution or visit [the GNU web | 17 | + file `doc/COPYING` in the distribution or visit the [GNU web |
17 | site](http://www.gnu.org/licenses/licenses.html#LGPL) | 18 | site](http://www.gnu.org/licenses/licenses.html#LGPL) |
18 | @contact: franck.pommereau@ibisc.univ-evry.fr | 19 | @contact: franck.pommereau@ibisc.univ-evry.fr |
19 | """ | 20 | """ |
... | @@ -21,8 +22,13 @@ compositions defined for the Petri Box Calculus and its successors. | ... | @@ -21,8 +22,13 @@ compositions defined for the Petri Box Calculus and its successors. |
21 | version = "0.9.16" | 22 | version = "0.9.16" |
22 | defaultencoding = "utf-8" | 23 | defaultencoding = "utf-8" |
23 | 24 | ||
25 | +"""## Module `snakes` | ||
26 | + | ||
27 | +This module only provides the exceptions used throughout SNAKES. | ||
28 | +""" | ||
29 | + | ||
24 | class SnakesError (Exception) : | 30 | class SnakesError (Exception) : |
25 | - "An error in SNAKES" | 31 | + "Generic error in SNAKES" |
26 | pass | 32 | pass |
27 | 33 | ||
28 | class ConstraintError (SnakesError) : | 34 | class ConstraintError (SnakesError) : | ... | ... |
1 | -"""Basic data types and functions used in SNAKES | 1 | +"""Basic data types and related functions used in SNAKES |
2 | """ | 2 | """ |
3 | 3 | ||
4 | import operator, inspect | 4 | import operator, inspect |
... | @@ -8,7 +8,8 @@ from snakes.hashables import hdict | ... | @@ -8,7 +8,8 @@ from snakes.hashables import hdict |
8 | from snakes.pnml import Tree | 8 | from snakes.pnml import Tree |
9 | 9 | ||
10 | def cross (sets) : | 10 | def cross (sets) : |
11 | - """Cross-product. | 11 | + """Cross-product of some iterable collections (typically, `list` |
12 | + or `set`). | ||
12 | 13 | ||
13 | >>> list(cross([[1, 2], [3, 4, 5]])) | 14 | >>> list(cross([[1, 2], [3, 4, 5]])) |
14 | [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)] | 15 | [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)] |
... | @@ -21,10 +22,9 @@ def cross (sets) : | ... | @@ -21,10 +22,9 @@ def cross (sets) : |
21 | [] | 22 | [] |
22 | 23 | ||
23 | @param sets: the sets of values to use | 24 | @param sets: the sets of values to use |
24 | - @type sets: `iterable(iterable(object))` | 25 | + @type sets: `iterable` |
25 | - @return: the `list` of obtained tuples (lists are used to allow | 26 | + @return: an iterator over the tuples in the cross-product |
26 | - unhashable objects) | 27 | + @rtype: `generator` |
27 | - @rtype: `generator(tuple(object))` | ||
28 | """ | 28 | """ |
29 | if len(sets) == 0 : | 29 | if len(sets) == 0 : |
30 | pass | 30 | pass |
... | @@ -65,8 +65,8 @@ def iterate (value) : | ... | @@ -65,8 +65,8 @@ def iterate (value) : |
65 | class WordSet (set) : | 65 | class WordSet (set) : |
66 | """A set of words being able to generate fresh words. | 66 | """A set of words being able to generate fresh words. |
67 | """ | 67 | """ |
68 | - def fresh (self, add=False, min=1, allowed="abcdefghijklmnopqrstuvwxyz", | 68 | + def fresh (self, add=False, min=1, base="", |
69 | - base="") : | 69 | + allowed="abcdefghijklmnopqrstuvwxyz") : |
70 | """Create a fresh word (ie, which is not in the set). | 70 | """Create a fresh word (ie, which is not in the set). |
71 | 71 | ||
72 | >>> w = WordSet(['foo', 'bar']) | 72 | >>> w = WordSet(['foo', 'bar']) |
... | @@ -87,6 +87,8 @@ class WordSet (set) : | ... | @@ -87,6 +87,8 @@ class WordSet (set) : |
87 | @type min: `int` | 87 | @type min: `int` |
88 | @param allowed: characters allowed in the new word | 88 | @param allowed: characters allowed in the new word |
89 | @type allowed: `str` | 89 | @type allowed: `str` |
90 | + @param base: prefix of generated words | ||
91 | + @type base: `str` | ||
90 | """ | 92 | """ |
91 | if base : | 93 | if base : |
92 | result = [base] + [allowed[0]] * max(0, min - len(base)) | 94 | result = [base] + [allowed[0]] * max(0, min - len(base)) |
... | @@ -118,7 +120,7 @@ class MultiSet (hdict) : | ... | @@ -118,7 +120,7 @@ class MultiSet (hdict) : |
118 | 120 | ||
119 | MultiSets support various operations, in particular: addition | 121 | MultiSets support various operations, in particular: addition |
120 | (`+`), substraction (`-`), multiplication by a non negative | 122 | (`+`), substraction (`-`), multiplication by a non negative |
121 | - integer (`*k`), comparisons (`<`, `>`, etc.), length (`len`) | 123 | + integer (`*k`), comparisons (`<`, `>`, etc.), length (`len`). |
122 | """ | 124 | """ |
123 | def __init__ (self, values=[]) : | 125 | def __init__ (self, values=[]) : |
124 | """Initialise the multiset, adding values to it. | 126 | """Initialise the multiset, adding values to it. |
... | @@ -128,17 +130,18 @@ class MultiSet (hdict) : | ... | @@ -128,17 +130,18 @@ class MultiSet (hdict) : |
128 | >>> MultiSet() | 130 | >>> MultiSet() |
129 | MultiSet([]) | 131 | MultiSet([]) |
130 | 132 | ||
131 | - @param values: a single value or an iterable object holding | 133 | + @param values: a single value or an iterable collection of |
132 | values (strings are not iterated) | 134 | values (strings are not iterated) |
133 | - @type values: any atomic object (`str` included) or an | 135 | + @type values: `object` |
134 | - iterable object | ||
135 | """ | 136 | """ |
136 | self.add(values) | 137 | self.add(values) |
137 | def copy (self) : | 138 | def copy (self) : |
138 | """Copy a `MultiSet` | 139 | """Copy a `MultiSet` |
139 | 140 | ||
140 | - >>> MultiSet([1, 2, 3, 1, 2]).copy() | 141 | + >>> m1 = MultiSet([1, 2, 3, 1, 2]) |
141 | - MultiSet([...]) | 142 | + >>> m2 = m1.copy() |
143 | + >>> m1 == m2 and m1 is not m2 | ||
144 | + True | ||
142 | 145 | ||
143 | @return: a copy of the multiset | 146 | @return: a copy of the multiset |
144 | @rtype: `MultiSet` | 147 | @rtype: `MultiSet` |
... | @@ -147,6 +150,7 @@ class MultiSet (hdict) : | ... | @@ -147,6 +150,7 @@ class MultiSet (hdict) : |
147 | result.update(self) | 150 | result.update(self) |
148 | return result | 151 | return result |
149 | __pnmltag__ = "multiset" | 152 | __pnmltag__ = "multiset" |
153 | + # apidoc skip | ||
150 | def __pnmldump__ (self) : | 154 | def __pnmldump__ (self) : |
151 | """ | 155 | """ |
152 | >>> MultiSet([1, 2, 3, 4, 1, 2]).__pnmldump__() | 156 | >>> MultiSet([1, 2, 3, 4, 1, 2]).__pnmldump__() |
... | @@ -202,6 +206,7 @@ class MultiSet (hdict) : | ... | @@ -202,6 +206,7 @@ class MultiSet (hdict) : |
202 | Tree("value", None, Tree.from_obj(value)), | 206 | Tree("value", None, Tree.from_obj(value)), |
203 | Tree("multiplicity", str(self[value])))) | 207 | Tree("multiplicity", str(self[value])))) |
204 | return Tree(self.__pnmltag__, None, *nodes) | 208 | return Tree(self.__pnmltag__, None, *nodes) |
209 | + # apidoc skip | ||
205 | @classmethod | 210 | @classmethod |
206 | def __pnmlload__ (cls, tree) : | 211 | def __pnmlload__ (cls, tree) : |
207 | """Load a multiset from its PNML representation | 212 | """Load a multiset from its PNML representation |
... | @@ -220,9 +225,10 @@ class MultiSet (hdict) : | ... | @@ -220,9 +225,10 @@ class MultiSet (hdict) : |
220 | """Add a single value `times` times. | 225 | """Add a single value `times` times. |
221 | 226 | ||
222 | @param value: the value to add | 227 | @param value: the value to add |
223 | - @type value: any object | 228 | + @type value: `object` |
224 | @param times: the number of times that `value` has to be added | 229 | @param times: the number of times that `value` has to be added |
225 | @type times: non negative `int` | 230 | @type times: non negative `int` |
231 | + @raise ValueError: when `times` is negative | ||
226 | """ | 232 | """ |
227 | if times < 0 : | 233 | if times < 0 : |
228 | raise ValueError("negative values are forbidden") | 234 | raise ValueError("negative values are forbidden") |
... | @@ -242,10 +248,11 @@ class MultiSet (hdict) : | ... | @@ -242,10 +248,11 @@ class MultiSet (hdict) : |
242 | [1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 5] | 248 | [1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 5] |
243 | 249 | ||
244 | @param values: the values to add or a single value to add | 250 | @param values: the values to add or a single value to add |
245 | - @type values: any atomic object (`str` included) or an | 251 | + @type values: `object` |
246 | - iterable object | ||
247 | @param times: the number of times each value should be added | 252 | @param times: the number of times each value should be added |
248 | - @type times: non negative `int` | 253 | + (must be non-negative) |
254 | + @type times: `int` | ||
255 | + @raise ValueError: when `times` is negative | ||
249 | """ | 256 | """ |
250 | self.__mutable__() | 257 | self.__mutable__() |
251 | for value in iterate(values) : | 258 | for value in iterate(values) : |
... | @@ -258,6 +265,7 @@ class MultiSet (hdict) : | ... | @@ -258,6 +265,7 @@ class MultiSet (hdict) : |
258 | @param times: the number of times that `value` has to be | 265 | @param times: the number of times that `value` has to be |
259 | removed | 266 | removed |
260 | @type times: non negative `int` | 267 | @type times: non negative `int` |
268 | + @raise ValueError: when `times` is negative | ||
261 | """ | 269 | """ |
262 | if times < 0 : | 270 | if times < 0 : |
263 | raise ValueError("negative values are forbidden") | 271 | raise ValueError("negative values are forbidden") |
... | @@ -269,8 +277,7 @@ class MultiSet (hdict) : | ... | @@ -269,8 +277,7 @@ class MultiSet (hdict) : |
269 | def remove (self, values, times=1) : | 277 | def remove (self, values, times=1) : |
270 | """Remove values to the multiset. | 278 | """Remove values to the multiset. |
271 | 279 | ||
272 | - >>> m = MultiSet() | 280 | + >>> m = MultiSet([1, 2, 2, 3] * 2) |
273 | - >>> m.add([1, 2, 2, 3], 2) | ||
274 | >>> list(sorted(m.items())) | 281 | >>> list(sorted(m.items())) |
275 | [1, 1, 2, 2, 2, 2, 3, 3] | 282 | [1, 1, 2, 2, 2, 2, 3, 3] |
276 | >>> m.remove(2, 3) | 283 | >>> m.remove(2, 3) |
... | @@ -282,10 +289,11 @@ class MultiSet (hdict) : | ... | @@ -282,10 +289,11 @@ class MultiSet (hdict) : |
282 | 289 | ||
283 | @param values: the values to remove or a single value to | 290 | @param values: the values to remove or a single value to |
284 | remove | 291 | remove |
285 | - @type values: any atomic object (`str` included) or an | 292 | + @type values: `object` |
286 | - iterable object | ||
287 | @param times: the number of times each value should be removed | 293 | @param times: the number of times each value should be removed |
288 | - @type times: non negative `int` | 294 | + (must be non negative) |
295 | + @type times: `int` | ||
296 | + @raise ValueError: when `times` is negative | ||
289 | """ | 297 | """ |
290 | self.__mutable__() | 298 | self.__mutable__() |
291 | for value in iterate(values) : | 299 | for value in iterate(values) : |
... | @@ -303,22 +311,21 @@ class MultiSet (hdict) : | ... | @@ -303,22 +311,21 @@ class MultiSet (hdict) : |
303 | """ | 311 | """ |
304 | return self.get(value, 0) | 312 | return self.get(value, 0) |
305 | def __iter__ (self) : | 313 | def __iter__ (self) : |
306 | - """Iterate over the values (with repetitions). | 314 | + """Iterate over the values, _including repetitions_. Use |
307 | - | 315 | + `MultiSet.keys` to ignore repetitions. |
308 | - Use `MultiSet.keys` to ignore repetitions. | ||
309 | 316 | ||
310 | >>> list(sorted(iter(MultiSet([1, 2, 3, 1, 2])))) | 317 | >>> list(sorted(iter(MultiSet([1, 2, 3, 1, 2])))) |
311 | [1, 1, 2, 2, 3] | 318 | [1, 1, 2, 2, 3] |
312 | 319 | ||
313 | @return: an iterator on the elements | 320 | @return: an iterator on the elements |
314 | - @rtype: `iterator` | 321 | + @rtype: `generator` |
315 | """ | 322 | """ |
316 | for value in dict.__iter__(self) : | 323 | for value in dict.__iter__(self) : |
317 | for count in range(self[value]) : | 324 | for count in range(self[value]) : |
318 | yield value | 325 | yield value |
319 | def items (self) : | 326 | def items (self) : |
320 | """Return the list of items with repetitions. The list without | 327 | """Return the list of items with repetitions. The list without |
321 | - repetitions can be retrieved with the `key` method. | 328 | + repetitions can be retrieved with `MultiSet.key`. |
322 | 329 | ||
323 | >>> m = MultiSet([1, 2, 2, 3]) | 330 | >>> m = MultiSet([1, 2, 2, 3]) |
324 | >>> list(sorted(m.items())) | 331 | >>> list(sorted(m.items())) |
... | @@ -326,7 +333,7 @@ class MultiSet (hdict) : | ... | @@ -326,7 +333,7 @@ class MultiSet (hdict) : |
326 | >>> list(sorted(m.keys())) | 333 | >>> list(sorted(m.keys())) |
327 | [1, 2, 3] | 334 | [1, 2, 3] |
328 | 335 | ||
329 | - @return: list of items with repetitions | 336 | + @return: list of items including repetitions |
330 | @rtype: `list` | 337 | @rtype: `list` |
331 | """ | 338 | """ |
332 | return list(iter(self)) | 339 | return list(iter(self)) |
... | @@ -352,7 +359,7 @@ class MultiSet (hdict) : | ... | @@ -352,7 +359,7 @@ class MultiSet (hdict) : |
352 | """ | 359 | """ |
353 | return "MultiSet([%s])" % ", ".join(repr(x) for x in self) | 360 | return "MultiSet([%s])" % ", ".join(repr(x) for x in self) |
354 | def __len__ (self) : | 361 | def __len__ (self) : |
355 | - """Return the number of elements, including repetitions. | 362 | + """Return the number of elements, _including repetitions_. |
356 | 363 | ||
357 | >>> len(MultiSet([1, 2] * 3)) | 364 | >>> len(MultiSet([1, 2] * 3)) |
358 | 6 | 365 | 6 |
... | @@ -364,7 +371,7 @@ class MultiSet (hdict) : | ... | @@ -364,7 +371,7 @@ class MultiSet (hdict) : |
364 | else : | 371 | else : |
365 | return reduce(operator.add, self.values()) | 372 | return reduce(operator.add, self.values()) |
366 | def size (self) : | 373 | def size (self) : |
367 | - """Return the number of elements, excluding repetitions. | 374 | + """Return the number of elements, _excluding repetitions_. |
368 | 375 | ||
369 | >>> MultiSet([1, 2] * 3).size() | 376 | >>> MultiSet([1, 2] * 3).size() |
370 | 2 | 377 | 2 |
... | @@ -387,7 +394,8 @@ class MultiSet (hdict) : | ... | @@ -387,7 +394,8 @@ class MultiSet (hdict) : |
387 | result._add(value, times) | 394 | result._add(value, times) |
388 | return result | 395 | return result |
389 | def __sub__ (self, other) : | 396 | def __sub__ (self, other) : |
390 | - """Substract two multisets. | 397 | + """Substract two multisets. The second multiset must be |
398 | + smaller than the first one. | ||
391 | 399 | ||
392 | >>> MultiSet([1, 2, 3]) - MultiSet([2, 3]) | 400 | >>> MultiSet([1, 2, 3]) - MultiSet([2, 3]) |
393 | MultiSet([1]) | 401 | MultiSet([1]) |
... | @@ -399,6 +407,7 @@ class MultiSet (hdict) : | ... | @@ -399,6 +407,7 @@ class MultiSet (hdict) : |
399 | @param other: the multiset to substract | 407 | @param other: the multiset to substract |
400 | @type other: `MultiSet` | 408 | @type other: `MultiSet` |
401 | @rtype: `MultiSet` | 409 | @rtype: `MultiSet` |
410 | + @raise ValueError: when the second multiset is not smaller | ||
402 | """ | 411 | """ |
403 | result = self.copy() | 412 | result = self.copy() |
404 | for value, times in dict.items(other) : | 413 | for value, times in dict.items(other) : |
... | @@ -407,12 +416,13 @@ class MultiSet (hdict) : | ... | @@ -407,12 +416,13 @@ class MultiSet (hdict) : |
407 | def __mul__ (self, other) : | 416 | def __mul__ (self, other) : |
408 | """Multiplication by a non-negative integer. | 417 | """Multiplication by a non-negative integer. |
409 | 418 | ||
410 | - >>> MultiSet([1, 2]) * 3 | 419 | + >>> MultiSet([1, 2]) * 3 == MultiSet([1, 2] * 3) |
411 | - MultiSet([...]) | 420 | + True |
412 | 421 | ||
413 | @param other: the integer to multiply | 422 | @param other: the integer to multiply |
414 | @type other: non-negative `int` | 423 | @type other: non-negative `int` |
415 | @rtype: `MultiSet` | 424 | @rtype: `MultiSet` |
425 | + @raise ValueError: when `other` is negative | ||
416 | """ | 426 | """ |
417 | if other < 0 : | 427 | if other < 0 : |
418 | raise ValueError("negative values are forbidden") | 428 | raise ValueError("negative values are forbidden") |
... | @@ -460,7 +470,9 @@ class MultiSet (hdict) : | ... | @@ -460,7 +470,9 @@ class MultiSet (hdict) : |
460 | """ | 470 | """ |
461 | return not(self == other) | 471 | return not(self == other) |
462 | def __lt__ (self, other) : | 472 | def __lt__ (self, other) : |
463 | - """Test for strict inclusion. | 473 | + """Test for strict inclusion. A multiset `A` is strictly |
474 | + included in a multiset `B` iff every element in `A` is also in | ||
475 | + `B` but less repetitions `A` than in `B`. | ||
464 | 476 | ||
465 | >>> MultiSet([1, 2, 3]) < MultiSet([1, 2, 3, 4]) | 477 | >>> MultiSet([1, 2, 3]) < MultiSet([1, 2, 3, 4]) |
466 | True | 478 | True |
... | @@ -488,7 +500,7 @@ class MultiSet (hdict) : | ... | @@ -488,7 +500,7 @@ class MultiSet (hdict) : |
488 | result = True | 500 | result = True |
489 | return result or (dict.__len__(self) < dict.__len__(other)) | 501 | return result or (dict.__len__(self) < dict.__len__(other)) |
490 | def __le__ (self, other) : | 502 | def __le__ (self, other) : |
491 | - """Test for inclusion inclusion. | 503 | + """Test for inclusion. |
492 | 504 | ||
493 | >>> MultiSet([1, 2, 3]) <= MultiSet([1, 2, 3, 4]) | 505 | >>> MultiSet([1, 2, 3]) <= MultiSet([1, 2, 3, 4]) |
494 | True | 506 | True |
... | @@ -551,7 +563,8 @@ class MultiSet (hdict) : | ... | @@ -551,7 +563,8 @@ class MultiSet (hdict) : |
551 | """ | 563 | """ |
552 | return other.__le__(self) | 564 | return other.__le__(self) |
553 | def domain (self) : | 565 | def domain (self) : |
554 | - """Return the domain of the multiset | 566 | + """Return the domain of the multiset, that is, the set of |
567 | + elements that occurr at least once in the multiset. | ||
555 | 568 | ||
556 | >>> list(sorted((MultiSet([1, 2, 3, 4]) + MultiSet([1, 2, 3])).domain())) | 569 | >>> list(sorted((MultiSet([1, 2, 3, 4]) + MultiSet([1, 2, 3])).domain())) |
557 | [1, 2, 3, 4] | 570 | [1, 2, 3, 4] |
... | @@ -589,6 +602,7 @@ class Substitution (object) : | ... | @@ -589,6 +602,7 @@ class Substitution (object) : |
589 | Substitution(...) | 602 | Substitution(...) |
590 | """ | 603 | """ |
591 | self._dict = dict(*largs, **dargs) | 604 | self._dict = dict(*largs, **dargs) |
605 | + # apidoc skip | ||
592 | def __hash__ (self) : | 606 | def __hash__ (self) : |
593 | """ | 607 | """ |
594 | >>> hash(Substitution(x=1, y=2)) == hash(Substitution(y=2, x=1)) | 608 | >>> hash(Substitution(x=1, y=2)) == hash(Substitution(y=2, x=1)) |
... | @@ -599,7 +613,8 @@ class Substitution (object) : | ... | @@ -599,7 +613,8 @@ class Substitution (object) : |
599 | (hash(i) for i in self._dict.items()), | 613 | (hash(i) for i in self._dict.items()), |
600 | 153913524) | 614 | 153913524) |
601 | def __eq__ (self, other) : | 615 | def __eq__ (self, other) : |
602 | - """ | 616 | + """Test for equality. |
617 | + | ||
603 | >>> Substitution(x=1, y=2) == Substitution(y=2, x=1) | 618 | >>> Substitution(x=1, y=2) == Substitution(y=2, x=1) |
604 | True | 619 | True |
605 | >>> Substitution(x=1, y=2) == Substitution(y=1, x=1) | 620 | >>> Substitution(x=1, y=2) == Substitution(y=1, x=1) |
... | @@ -610,7 +625,8 @@ class Substitution (object) : | ... | @@ -610,7 +625,8 @@ class Substitution (object) : |
610 | except : | 625 | except : |
611 | return False | 626 | return False |
612 | def __ne__ (self, other) : | 627 | def __ne__ (self, other) : |
613 | - """ | 628 | + """Test for inequality. |
629 | + | ||
614 | >>> Substitution(x=1, y=2) != Substitution(y=2, x=1) | 630 | >>> Substitution(x=1, y=2) != Substitution(y=2, x=1) |
615 | False | 631 | False |
616 | >>> Substitution(x=1, y=2) != Substitution(y=1, x=1) | 632 | >>> Substitution(x=1, y=2) != Substitution(y=1, x=1) |
... | @@ -618,6 +634,7 @@ class Substitution (object) : | ... | @@ -618,6 +634,7 @@ class Substitution (object) : |
618 | """ | 634 | """ |
619 | return not self.__eq__(other) | 635 | return not self.__eq__(other) |
620 | __pnmltag__ = "substitution" | 636 | __pnmltag__ = "substitution" |
637 | + # apidoc skip | ||
621 | def __pnmldump__ (self) : | 638 | def __pnmldump__ (self) : |
622 | """Dumps a substitution to a PNML tree | 639 | """Dumps a substitution to a PNML tree |
623 | 640 | ||
... | @@ -658,6 +675,7 @@ class Substitution (object) : | ... | @@ -658,6 +675,7 @@ class Substitution (object) : |
658 | Tree("value", None, | 675 | Tree("value", None, |
659 | Tree.from_obj(value)))) | 676 | Tree.from_obj(value)))) |
660 | return Tree(self.__pnmltag__, None, *nodes) | 677 | return Tree(self.__pnmltag__, None, *nodes) |
678 | + # apidoc skip | ||
661 | @classmethod | 679 | @classmethod |
662 | def __pnmlload__ (cls, tree) : | 680 | def __pnmlload__ (cls, tree) : |
663 | """Load a substitution from its PNML representation | 681 | """Load a substitution from its PNML representation |
... | @@ -678,7 +696,8 @@ class Substitution (object) : | ... | @@ -678,7 +696,8 @@ class Substitution (object) : |
678 | result._dict[name] = value | 696 | result._dict[name] = value |
679 | return result | 697 | return result |
680 | def items (self) : | 698 | def items (self) : |
681 | - """Return the list of pairs (name, value). | 699 | + """Return the list of pairs `(name, value)` such that the |
700 | + substitution maps each `name` to the correspondign `value`. | ||
682 | 701 | ||
683 | >>> Substitution(x=1, y=2).items() | 702 | >>> Substitution(x=1, y=2).items() |
684 | [('...', ...), ('...', ...)] | 703 | [('...', ...), ('...', ...)] |
... | @@ -698,17 +717,17 @@ class Substitution (object) : | ... | @@ -698,17 +717,17 @@ class Substitution (object) : |
698 | """ | 717 | """ |
699 | return set(self._dict.keys()) | 718 | return set(self._dict.keys()) |
700 | def image (self) : | 719 | def image (self) : |
701 | - """Return the set of values associated to the names. | 720 | + """Return the list of values associated to the names. |
702 | 721 | ||
703 | >>> list(sorted(Substitution(x=1, y=2).image())) | 722 | >>> list(sorted(Substitution(x=1, y=2).image())) |
704 | [1, 2] | 723 | [1, 2] |
705 | 724 | ||
706 | - @return: the set of values associated to names | 725 | + @return: the list of values associated to names |
707 | - @rtype: `set` | 726 | + @rtype: `list` |
708 | """ | 727 | """ |
709 | - return set(self._dict.values()) | 728 | + return list(self._dict.values()) |
710 | def __contains__ (self, name) : | 729 | def __contains__ (self, name) : |
711 | - """Test if a name is mapped. | 730 | + """Test if a name is mapped by the substitution. |
712 | 731 | ||
713 | >>> 'x' in Substitution(x=1, y=2) | 732 | >>> 'x' in Substitution(x=1, y=2) |
714 | True | 733 | True |
... | @@ -758,8 +777,8 @@ class Substitution (object) : | ... | @@ -758,8 +777,8 @@ class Substitution (object) : |
758 | def dict (self) : | 777 | def dict (self) : |
759 | """Return the mapping as a dictionnary. | 778 | """Return the mapping as a dictionnary. |
760 | 779 | ||
761 | - >>> Substitution(x=1, y=2).dict() | 780 | + >>> Substitution(x=1, y=2).dict() == {'x': 1, 'y': 2} |
762 | - {'...': ..., '...': ...} | 781 | + True |
763 | 782 | ||
764 | @return: a dictionnary that does the same mapping as the | 783 | @return: a dictionnary that does the same mapping as the |
765 | substitution | 784 | substitution |
... | @@ -767,10 +786,12 @@ class Substitution (object) : | ... | @@ -767,10 +786,12 @@ class Substitution (object) : |
767 | """ | 786 | """ |
768 | return self._dict.copy() | 787 | return self._dict.copy() |
769 | def copy (self) : | 788 | def copy (self) : |
770 | - """Copy the mapping. | 789 | + """Return a distinct copy of the mapping. |
771 | 790 | ||
772 | - >>> Substitution(x=1, y=2).copy() | 791 | + >>> s1 = Substitution(x=1, y=2) |
773 | - Substitution(...) | 792 | + >>> s2 = s1.copy() |
793 | + >>> s1 == s2 and s1 is not s2 | ||
794 | + True | ||
774 | 795 | ||
775 | @return: a copy of the substitution | 796 | @return: a copy of the substitution |
776 | @rtype: `Substitution` | 797 | @rtype: `Substitution` |
... | @@ -793,8 +814,6 @@ class Substitution (object) : | ... | @@ -793,8 +814,6 @@ class Substitution (object) : |
793 | def __getitem__ (self, var) : | 814 | def __getitem__ (self, var) : |
794 | """Return the mapped value. | 815 | """Return the mapped value. |
795 | 816 | ||
796 | - Fails with `DomainError` if `var` is not mapped. | ||
797 | - | ||
798 | >>> s = Substitution(x=1, y=2) | 817 | >>> s = Substitution(x=1, y=2) |
799 | >>> s['x'] | 818 | >>> s['x'] |
800 | 1 | 819 | 1 |
... | @@ -813,9 +832,8 @@ class Substitution (object) : | ... | @@ -813,9 +832,8 @@ class Substitution (object) : |
813 | except KeyError : | 832 | except KeyError : |
814 | raise DomainError("unbound variable '%s'" % var) | 833 | raise DomainError("unbound variable '%s'" % var) |
815 | def __call__ (self, var) : | 834 | def __call__ (self, var) : |
816 | - """Return the mapped value. | 835 | + """Return the mapped value or `var` itself if it is not |
817 | - | 836 | + mapped. |
818 | - Never fails but return `var` if it is not mapped. | ||
819 | 837 | ||
820 | >>> s = Substitution(x=1, y=2) | 838 | >>> s = Substitution(x=1, y=2) |
821 | >>> s('x') | 839 | >>> s('x') |
... | @@ -835,7 +853,6 @@ class Substitution (object) : | ... | @@ -835,7 +853,6 @@ class Substitution (object) : |
835 | return var | 853 | return var |
836 | def __add__ (self, other) : | 854 | def __add__ (self, other) : |
837 | """Add two substitution. | 855 | """Add two substitution. |
838 | - | ||
839 | Fails with `DomainError` if the two substitutions map a same | 856 | Fails with `DomainError` if the two substitutions map a same |
840 | name to different values. | 857 | name to different values. |
841 | 858 | ||
... | @@ -850,7 +867,7 @@ class Substitution (object) : | ... | @@ -850,7 +867,7 @@ class Substitution (object) : |
850 | @type other: `Substitution` | 867 | @type other: `Substitution` |
851 | @return: the union of the substitutions | 868 | @return: the union of the substitutions |
852 | @rtype: `Substitution` | 869 | @rtype: `Substitution` |
853 | - @raise DomainError: when one name is mapped to distinct values | 870 | + @raise DomainError: when a name is inconsistently mapped |
854 | """ | 871 | """ |
855 | for var in self : | 872 | for var in self : |
856 | if var in other and (self[var] != other[var]) : | 873 | if var in other and (self[var] != other[var]) : |
... | @@ -860,8 +877,8 @@ class Substitution (object) : | ... | @@ -860,8 +877,8 @@ class Substitution (object) : |
860 | return s | 877 | return s |
861 | def __mul__ (self, other) : | 878 | def __mul__ (self, other) : |
862 | """Compose two substitutions. | 879 | """Compose two substitutions. |
863 | - | 880 | + The composition of `f` and `g` is such that `(f*g)(x)` is |
864 | - The composition of f and g is such that (f*g)(x) = f(g(x)). | 881 | + `f(g(x))`. |
865 | 882 | ||
866 | >>> f = Substitution(a=1, d=3, y=5) | 883 | >>> f = Substitution(a=1, d=3, y=5) |
867 | >>> g = Substitution(b='d', c=2, e=4, y=6) | 884 | >>> g = Substitution(b='d', c=2, e=4, y=6) |
... | @@ -886,7 +903,8 @@ class Symbol (object) : | ... | @@ -886,7 +903,8 @@ class Symbol (object) : |
886 | """If `export` is `True`, the created symbol is exported under | 903 | """If `export` is `True`, the created symbol is exported under |
887 | its name. If `export` is `False`, no export is made. Finally, | 904 | its name. If `export` is `False`, no export is made. Finally, |
888 | if `export` is a string, it specifies the name of the exported | 905 | if `export` is a string, it specifies the name of the exported |
889 | - symbol. | 906 | + symbol. Exporting the name is made by adding it to the |
907 | + caller's global dict. | ||
890 | 908 | ||
891 | @param name: the name (or value of the symbol) | 909 | @param name: the name (or value of the symbol) |
892 | @type name: `str` | 910 | @type name: `str` |
... | @@ -915,6 +933,7 @@ class Symbol (object) : | ... | @@ -915,6 +933,7 @@ class Symbol (object) : |
915 | if export : | 933 | if export : |
916 | inspect.stack()[1][0].f_globals[export] = self | 934 | inspect.stack()[1][0].f_globals[export] = self |
917 | __pnmltag__ = "symbol" | 935 | __pnmltag__ = "symbol" |
936 | + # apidoc skip | ||
918 | def __pnmldump__ (self) : | 937 | def __pnmldump__ (self) : |
919 | """ | 938 | """ |
920 | >>> Symbol('egg', 'spam').__pnmldump__() | 939 | >>> Symbol('egg', 'spam').__pnmldump__() |
... | @@ -946,6 +965,7 @@ class Symbol (object) : | ... | @@ -946,6 +965,7 @@ class Symbol (object) : |
946 | else : | 965 | else : |
947 | children = [Tree.from_obj(self._export)] | 966 | children = [Tree.from_obj(self._export)] |
948 | return Tree(self.__pnmltag__, None, *children, **dict(name=self.name)) | 967 | return Tree(self.__pnmltag__, None, *children, **dict(name=self.name)) |
968 | + # apidoc skip | ||
949 | @classmethod | 969 | @classmethod |
950 | def __pnmlload__ (cls, tree) : | 970 | def __pnmlload__ (cls, tree) : |
951 | """ | 971 | """ |
... | @@ -963,7 +983,9 @@ class Symbol (object) : | ... | @@ -963,7 +983,9 @@ class Symbol (object) : |
963 | export = name | 983 | export = name |
964 | return cls(name, export) | 984 | return cls(name, export) |
965 | def __eq__ (self, other) : | 985 | def __eq__ (self, other) : |
966 | - """ | 986 | + """Test for equality of two symbols, which is the equality of |
987 | + their names. | ||
988 | + | ||
967 | >>> Symbol('foo', 'bar') == Symbol('foo') | 989 | >>> Symbol('foo', 'bar') == Symbol('foo') |
968 | True | 990 | True |
969 | >>> Symbol('egg') == Symbol('spam') | 991 | >>> Symbol('egg') == Symbol('spam') |
... | @@ -975,13 +997,15 @@ class Symbol (object) : | ... | @@ -975,13 +997,15 @@ class Symbol (object) : |
975 | except AttributeError : | 997 | except AttributeError : |
976 | return False | 998 | return False |
977 | def __ne__ (self, other) : | 999 | def __ne__ (self, other) : |
978 | - """ | 1000 | + """Test for inequality. |
1001 | + | ||
979 | >>> Symbol('foo', 'bar') != Symbol('foo') | 1002 | >>> Symbol('foo', 'bar') != Symbol('foo') |
980 | False | 1003 | False |
981 | >>> Symbol('egg') != Symbol('spam') | 1004 | >>> Symbol('egg') != Symbol('spam') |
982 | True | 1005 | True |
983 | """ | 1006 | """ |
984 | return not (self == other) | 1007 | return not (self == other) |
1008 | + # apidoc skip | ||
985 | def __hash__ (self) : | 1009 | def __hash__ (self) : |
986 | """ | 1010 | """ |
987 | >>> hash(Symbol('foo', 'bar')) == hash(Symbol('foo')) | 1011 | >>> hash(Symbol('foo', 'bar')) == hash(Symbol('foo')) |
... | @@ -989,13 +1013,15 @@ class Symbol (object) : | ... | @@ -989,13 +1013,15 @@ class Symbol (object) : |
989 | """ | 1013 | """ |
990 | return hash((self.__class__.__name__, self.name)) | 1014 | return hash((self.__class__.__name__, self.name)) |
991 | def __str__ (self) : | 1015 | def __str__ (self) : |
992 | - """ | 1016 | + """Short string representation |
1017 | + | ||
993 | >>> str(Symbol('foo')) | 1018 | >>> str(Symbol('foo')) |
994 | 'foo' | 1019 | 'foo' |
995 | """ | 1020 | """ |
996 | return self.name | 1021 | return self.name |
997 | def __repr__ (self) : | 1022 | def __repr__ (self) : |
998 | - """ | 1023 | + """String representation suitable for `eval` |
1024 | + | ||
999 | >>> Symbol('foo') | 1025 | >>> Symbol('foo') |
1000 | Symbol('foo') | 1026 | Symbol('foo') |
1001 | >>> Symbol('egg', 'spam') | 1027 | >>> Symbol('egg', 'spam') | ... | ... |
1 | -"""Hashable mutable objets. | 1 | +"""This module proposes hashable version of the mutable containers |
2 | - | ||
3 | -This module proposes hashable version of the mutable containers | ||
4 | `list`, `dict` and `set`, called respectively `hlist`, `hdict` and | 2 | `list`, `dict` and `set`, called respectively `hlist`, `hdict` and |
5 | -`hset`. After one object has been hashed, it becomes not mutable and | 3 | +`hset`. |
6 | -raises a ValueError if a method which changes the object (let call a | 4 | + |
7 | -_mutation_ such a method) is invoqued. The object can be then un- | 5 | +After one object has been hashed, it becomes not mutable and raises a |
8 | -hashed by calling `unhash` on it so that it becomes mutable again. | 6 | +`ValueError` if a method which changes the object (let call a |
7 | +_mutation_ such a method) is invoqued. The object can be then | ||
8 | +un-hashed by calling `unhash` on it so that it becomes mutable again. | ||
9 | Notice that this may cause troubles if the object is stored in a set | 9 | Notice that this may cause troubles if the object is stored in a set |
10 | or dict which uses its hashcode to locate it. Notice also that | 10 | or dict which uses its hashcode to locate it. Notice also that |
11 | hashable containers cannot be hashed if they contain non hashable | 11 | hashable containers cannot be hashed if they contain non hashable |
... | @@ -53,7 +53,8 @@ from operator import xor | ... | @@ -53,7 +53,8 @@ from operator import xor |
53 | from snakes.compat import * | 53 | from snakes.compat import * |
54 | 54 | ||
55 | def unhash (obj) : | 55 | def unhash (obj) : |
56 | - """Make the object hashable again. | 56 | + """Make the object mutable again. This should be used with |
57 | + caution, especially if the object is stored in a dict or a set. | ||
57 | 58 | ||
58 | >>> l = hlist(range(3)) | 59 | >>> l = hlist(range(3)) |
59 | >>> _ = hash(l) | 60 | >>> _ = hash(l) |
... | @@ -72,6 +73,7 @@ def unhash (obj) : | ... | @@ -72,6 +73,7 @@ def unhash (obj) : |
72 | except : | 73 | except : |
73 | pass | 74 | pass |
74 | 75 | ||
76 | +# apidoc skip | ||
75 | def hashable (cls) : | 77 | def hashable (cls) : |
76 | """Wrap methods in a class in order to make it hashable. | 78 | """Wrap methods in a class in order to make it hashable. |
77 | """ | 79 | """ |
... | @@ -122,7 +124,7 @@ def hashable (cls) : | ... | @@ -122,7 +124,7 @@ def hashable (cls) : |
122 | return cls | 124 | return cls |
123 | 125 | ||
124 | class hlist (list) : | 126 | class hlist (list) : |
125 | - """Hashable lists. | 127 | + """Hashable lists. They support all standard methods from `list`. |
126 | 128 | ||
127 | >>> l = hlist(range(5)) | 129 | >>> l = hlist(range(5)) |
128 | >>> l | 130 | >>> l |
... | @@ -138,6 +140,7 @@ class hlist (list) : | ... | @@ -138,6 +140,7 @@ class hlist (list) : |
138 | >>> unhash(l) | 140 | >>> unhash(l) |
139 | >>> l.append(6) | 141 | >>> l.append(6) |
140 | """ | 142 | """ |
143 | + # apidoc stop | ||
141 | def __add__ (self, other) : | 144 | def __add__ (self, other) : |
142 | return self.__class__(list.__add__(self, other)) | 145 | return self.__class__(list.__add__(self, other)) |
143 | def __delitem__ (self, item) : | 146 | def __delitem__ (self, item) : |
... | @@ -200,7 +203,8 @@ class hlist (list) : | ... | @@ -200,7 +203,8 @@ class hlist (list) : |
200 | hlist = hashable(hlist) | 203 | hlist = hashable(hlist) |
201 | 204 | ||
202 | class hdict (dict) : | 205 | class hdict (dict) : |
203 | - """Hashable dictionnaries. | 206 | + """Hashable dictionnaries. They support all standard methods from |
207 | + `dict`. | ||
204 | 208 | ||
205 | >>> l = hlist(range(5)) | 209 | >>> l = hlist(range(5)) |
206 | >>> d = hdict([(l, 0)]) | 210 | >>> d = hdict([(l, 0)]) |
... | @@ -229,6 +233,7 @@ class hdict (dict) : | ... | @@ -229,6 +233,7 @@ class hdict (dict) : |
229 | >>> d.pop(l) | 233 | >>> d.pop(l) |
230 | 0 | 234 | 0 |
231 | """ | 235 | """ |
236 | + # apidoc stop | ||
232 | def __delitem__ (self, key) : | 237 | def __delitem__ (self, key) : |
233 | self.__mutable__() | 238 | self.__mutable__() |
234 | dict.__delitem__(self, key) | 239 | dict.__delitem__(self, key) |
... | @@ -271,7 +276,7 @@ class hdict (dict) : | ... | @@ -271,7 +276,7 @@ class hdict (dict) : |
271 | hdict = hashable(hdict) | 276 | hdict = hashable(hdict) |
272 | 277 | ||
273 | class hset (set) : | 278 | class hset (set) : |
274 | - """Hashable sets. | 279 | + """Hashable sets. They support all standard methods from `set`. |
275 | 280 | ||
276 | >>> s = hset() | 281 | >>> s = hset() |
277 | >>> l = hlist(range(5)) | 282 | >>> l = hlist(range(5)) |
... | @@ -298,6 +303,7 @@ class hset (set) : | ... | @@ -298,6 +303,7 @@ class hset (set) : |
298 | >>> unhash(s) | 303 | >>> unhash(s) |
299 | >>> s.discard(l) | 304 | >>> s.discard(l) |
300 | """ | 305 | """ |
306 | + # apidoc stop | ||
301 | def __and__ (self, other) : | 307 | def __and__ (self, other) : |
302 | return self.__class__(set.__and__(self, other)) | 308 | return self.__class__(set.__and__(self, other)) |
303 | def __hash__ (self) : | 309 | def __hash__ (self) : | ... | ... |
1 | +"""This package is dedicated to parse and work with various languages, | ||
2 | +in particular Python itself and ABCD. These are mainly utilities for | ||
3 | +internal use in SNAKES, however, they may be of general interest | ||
4 | +independently of SNAKES. | ||
5 | + | ||
6 | +@todo: add documentation about how to use parsing and similar services | ||
7 | +""" | ||
8 | + | ||
1 | import sys | 9 | import sys |
2 | if sys.version_info[:2] in ((2, 6), (2, 7)) : | 10 | if sys.version_info[:2] in ((2, 6), (2, 7)) : |
3 | import ast | 11 | import ast |
... | @@ -14,9 +22,34 @@ else : | ... | @@ -14,9 +22,34 @@ else : |
14 | 22 | ||
15 | sys.modules["snkast"] = ast | 23 | sys.modules["snkast"] = ast |
16 | 24 | ||
25 | +"""### Module `ast` ### | ||
26 | + | ||
27 | +The module `ast` exported by `snakes.lang` is similar to Python's | ||
28 | +standard `ast` module (starting from version 2.6) but is available and | ||
29 | +uniform on every implementation of Python supported by SNAKES: CPython | ||
30 | +from 2.5, Jython and PyPy. (In general, these modules are available in | ||
31 | +these implementation - except CPython 2.5 - but with slight | ||
32 | +differences, so using `snakes.lang.ast` can be seen as a portable | ||
33 | +implementation.) | ||
34 | + | ||
35 | +Notice that this module is _not_ available for direct import but is | ||
36 | +exposed as a member of `snakes.lang`. Moreover, when `snakes.lang` is | ||
37 | +loaded, this module `ast` is also loaded as `snkast` in `sys.modules`, | ||
38 | +this allows to have both versions from Python and SNAKES | ||
39 | +simultaneously. | ||
40 | + | ||
41 | +>>> import snakes.lang.ast as ast | ||
42 | +ImportError ... | ||
43 | + ... | ||
44 | +ImportError: No module named ast | ||
45 | +>>> from snakes.lang import ast | ||
46 | +>>> import snkast | ||
47 | +""" | ||
48 | + | ||
17 | from . import unparse as _unparse | 49 | from . import unparse as _unparse |
18 | from snakes.compat import * | 50 | from snakes.compat import * |
19 | 51 | ||
52 | +# apidoc skip | ||
20 | class Names (ast.NodeVisitor) : | 53 | class Names (ast.NodeVisitor) : |
21 | def __init__ (self) : | 54 | def __init__ (self) : |
22 | ast.NodeVisitor.__init__(self) | 55 | ast.NodeVisitor.__init__(self) |
... | @@ -25,16 +58,24 @@ class Names (ast.NodeVisitor) : | ... | @@ -25,16 +58,24 @@ class Names (ast.NodeVisitor) : |
25 | self.names.add(node.id) | 58 | self.names.add(node.id) |
26 | 59 | ||
27 | def getvars (expr) : | 60 | def getvars (expr) : |
28 | - """ | 61 | + """Return the set of variables (or names in general) involved in a |
62 | + Python expression. | ||
63 | + | ||
29 | >>> list(sorted(getvars('x+y<z'))) | 64 | >>> list(sorted(getvars('x+y<z'))) |
30 | ['x', 'y', 'z'] | 65 | ['x', 'y', 'z'] |
31 | >>> list(sorted(getvars('x+y<z+f(3,t)'))) | 66 | >>> list(sorted(getvars('x+y<z+f(3,t)'))) |
32 | ['f', 't', 'x', 'y', 'z'] | 67 | ['f', 't', 'x', 'y', 'z'] |
68 | + | ||
69 | + @param expr: a Python expression | ||
70 | + @type expr: `str` | ||
71 | + @return: the set of variable names as strings | ||
72 | + @rtype: `set` | ||
33 | """ | 73 | """ |
34 | names = Names() | 74 | names = Names() |
35 | names.visit(ast.parse(expr)) | 75 | names.visit(ast.parse(expr)) |
36 | return names.names - set(['None', 'True', 'False']) | 76 | return names.names - set(['None', 'True', 'False']) |
37 | 77 | ||
78 | +# apidoc skip | ||
38 | class Unparser(_unparse.Unparser) : | 79 | class Unparser(_unparse.Unparser) : |
39 | boolops = {"And": 'and', "Or": 'or'} | 80 | boolops = {"And": 'and', "Or": 'or'} |
40 | def _Interactive (self, tree) : | 81 | def _Interactive (self, tree) : |
... | @@ -58,11 +99,13 @@ class Unparser(_unparse.Unparser) : | ... | @@ -58,11 +99,13 @@ class Unparser(_unparse.Unparser) : |
58 | self.dispatch(tree.body) | 99 | self.dispatch(tree.body) |
59 | self.leave() | 100 | self.leave() |
60 | 101 | ||
102 | +# apidoc skip | ||
61 | def unparse (st) : | 103 | def unparse (st) : |
62 | output = io.StringIO() | 104 | output = io.StringIO() |
63 | Unparser(st, output) | 105 | Unparser(st, output) |
64 | return output.getvalue().strip() | 106 | return output.getvalue().strip() |
65 | 107 | ||
108 | +# apidoc skip | ||
66 | class Renamer (ast.NodeTransformer) : | 109 | class Renamer (ast.NodeTransformer) : |
67 | def __init__ (self, map_names) : | 110 | def __init__ (self, map_names) : |
68 | ast.NodeTransformer.__init__(self) | 111 | ast.NodeTransformer.__init__(self) |
... | @@ -96,7 +139,8 @@ class Renamer (ast.NodeTransformer) : | ... | @@ -96,7 +139,8 @@ class Renamer (ast.NodeTransformer) : |
96 | ctx=ast.Load()), node) | 139 | ctx=ast.Load()), node) |
97 | 140 | ||
98 | def rename (expr, map={}, **ren) : | 141 | def rename (expr, map={}, **ren) : |
99 | - """ | 142 | + """Rename variables (ie, names) in a Python expression |
143 | + | ||
100 | >>> rename('x+y<z', x='t') | 144 | >>> rename('x+y<z', x='t') |
101 | '((t + y) < z)' | 145 | '((t + y) < z)' |
102 | >>> rename('x+y<z+f(3,t)', f='g', t='z', z='t') | 146 | >>> rename('x+y<z+f(3,t)', f='g', t='z', z='t') |
... | @@ -105,12 +149,22 @@ def rename (expr, map={}, **ren) : | ... | @@ -105,12 +149,22 @@ def rename (expr, map={}, **ren) : |
105 | '[(x + y) for x in range(3)]' | 149 | '[(x + y) for x in range(3)]' |
106 | >>> rename('[x+y for x in range(3)]', y='z') | 150 | >>> rename('[x+y for x in range(3)]', y='z') |
107 | '[(x + z) for x in range(3)]' | 151 | '[(x + z) for x in range(3)]' |
152 | + | ||
153 | + @param expr: a Python expression | ||
154 | + @type expr: `str` | ||
155 | + @param map: a mapping from old to new names (`str` to `str`) | ||
156 | + @type map: `dict` | ||
157 | + @param ren: additional mapping of old to new names | ||
158 | + @type ren: `str` | ||
159 | + @return: the new expression | ||
160 | + @rtype: `str` | ||
108 | """ | 161 | """ |
109 | map_names = dict(map) | 162 | map_names = dict(map) |
110 | map_names.update(ren) | 163 | map_names.update(ren) |
111 | transf = Renamer(map_names) | 164 | transf = Renamer(map_names) |
112 | return unparse(transf.visit(ast.parse(expr))) | 165 | return unparse(transf.visit(ast.parse(expr))) |
113 | 166 | ||
167 | +# apidoc skip | ||
114 | class Binder (Renamer) : | 168 | class Binder (Renamer) : |
115 | def visit_Name (self, node) : | 169 | def visit_Name (self, node) : |
116 | if node.id in self.map[-1] : | 170 | if node.id in self.map[-1] : |
... | @@ -119,7 +173,10 @@ class Binder (Renamer) : | ... | @@ -119,7 +173,10 @@ class Binder (Renamer) : |
119 | return node | 173 | return node |
120 | 174 | ||
121 | def bind (expr, map={}, **ren) : | 175 | def bind (expr, map={}, **ren) : |
122 | - """ | 176 | + """Replace variables (ie, names) in an expression with other |
177 | + expressions. The replacements should be provided as `ast` nodes, | ||
178 | + and so could be arbitrary expression. | ||
179 | + | ||
123 | >>> bind('x+y<z', x=ast.Num(n=2)) | 180 | >>> bind('x+y<z', x=ast.Num(n=2)) |
124 | '((2 + y) < z)' | 181 | '((2 + y) < z)' |
125 | >>> bind('x+y<z', y=ast.Num(n=2)) | 182 | >>> bind('x+y<z', y=ast.Num(n=2)) |
... | @@ -128,6 +185,15 @@ def bind (expr, map={}, **ren) : | ... | @@ -128,6 +185,15 @@ def bind (expr, map={}, **ren) : |
128 | '[(x + y) for x in range(3)]' | 185 | '[(x + y) for x in range(3)]' |
129 | >>> bind('[x+y for x in range(3)]', y=ast.Num(n=2)) | 186 | >>> bind('[x+y for x in range(3)]', y=ast.Num(n=2)) |
130 | '[(x + 2) for x in range(3)]' | 187 | '[(x + 2) for x in range(3)]' |
188 | + | ||
189 | + @param expr: a Python expression | ||
190 | + @type expr: `str` | ||
191 | + @param map: a mapping from old to new names (`str` to `ast.AST`) | ||
192 | + @type map: `dict` | ||
193 | + @param ren: additional mapping of old to new names | ||
194 | + @type ren: `ast.AST` | ||
195 | + @return: the new expression | ||
196 | + @rtype: `str` | ||
131 | """ | 197 | """ |
132 | map_names = dict(map) | 198 | map_names = dict(map) |
133 | map_names.update(ren) | 199 | map_names.update(ren) | ... | ... |
... | @@ -2,6 +2,8 @@ | ... | @@ -2,6 +2,8 @@ |
2 | 2 | ||
3 | This module holds the various Petri net elements: arcs, places, | 3 | This module holds the various Petri net elements: arcs, places, |
4 | transitions, markings, nets themselves and marking graphs. | 4 | transitions, markings, nets themselves and marking graphs. |
5 | + | ||
6 | +@todo: revise documentation | ||
5 | """ | 7 | """ |
6 | 8 | ||
7 | import re, operator, inspect | 9 | import re, operator, inspect | ... | ... |
1 | -"""A plugins system. | 1 | +"""This package implements SNAKES plugin system. SNAKES plugins |
2 | +themselves are available as modules within the package. | ||
3 | + | ||
4 | +Examples below are based on plugin `hello` that is distributed with | ||
5 | +SNAKES to be used as an exemple of how to build a plugin. It extends | ||
6 | +class `PetriNet` adding a method `hello` that says hello displaying | ||
7 | +the name of the net. | ||
8 | + | ||
9 | +## Loading plugins ## | ||
2 | 10 | ||
3 | The first example shows how to load a plugin: we load | 11 | The first example shows how to load a plugin: we load |
4 | `snakes.plugins.hello` and plug it into `snakes.nets`, which results | 12 | `snakes.plugins.hello` and plug it into `snakes.nets`, which results |
5 | -in a new module that actually `snakes.nets` extended by | 13 | +in a new module that is actually `snakes.nets` extended by |
6 | `snakes.plugins.hello`. | 14 | `snakes.plugins.hello`. |
7 | 15 | ||
8 | >>> import snakes.plugins as plugins | 16 | >>> import snakes.plugins as plugins |
... | @@ -18,11 +26,6 @@ The next example shows how to simulate the effect of `import module`: | ... | @@ -18,11 +26,6 @@ The next example shows how to simulate the effect of `import module`: |
18 | we give to `load` a thrid argument that is the name of the created | 26 | we give to `load` a thrid argument that is the name of the created |
19 | module, from which it becomes possible to import names or `*`. | 27 | module, from which it becomes possible to import names or `*`. |
20 | 28 | ||
21 | -**Warning:** this feature will not work `load` is not called from the | ||
22 | -module where we then do the `from ... import ...`. This is exactly the | ||
23 | -same when, from a module `foo` that you load a module `bar`: if `bar` | ||
24 | -loads other modules they will not be imported in `foo`. | ||
25 | - | ||
26 | >>> plugins.load('hello', 'snakes.nets', 'another_version') | 29 | >>> plugins.load('hello', 'snakes.nets', 'another_version') |
27 | <module ...> | 30 | <module ...> |
28 | >>> from another_version import PetriNet | 31 | >>> from another_version import PetriNet |
... | @@ -33,12 +36,23 @@ Hello from another net | ... | @@ -33,12 +36,23 @@ Hello from another net |
33 | >>> n.hello() | 36 | >>> n.hello() |
34 | Hi, this is yet another net! | 37 | Hi, this is yet another net! |
35 | 38 | ||
36 | -How to define a plugin is explained in the example `hello`. | 39 | +The last example shows how to load several plugins at once, instead of |
40 | +giving one plugin name, we just need to give a list of plugin names. | ||
41 | + | ||
42 | +>>> plugins.load(['hello', 'pos'], 'snakes.nets', 'mynets') | ||
43 | +<module ...> | ||
44 | +>>> from mynets import PetriNet | ||
45 | +>>> n = PetriNet('a net') | ||
46 | +>>> n.hello() # works thanks to plugin `hello` | ||
47 | +Hello from a net | ||
48 | +>>> n.bbox() # works thanks to plugin `pos` | ||
49 | +((0, 0), (0, 0)) | ||
37 | """ | 50 | """ |
38 | 51 | ||
39 | import imp, sys, inspect | 52 | import imp, sys, inspect |
40 | from functools import wraps | 53 | from functools import wraps |
41 | 54 | ||
55 | +# apidoc skip | ||
42 | def update (module, objects) : | 56 | def update (module, objects) : |
43 | """Update a module content | 57 | """Update a module content |
44 | """ | 58 | """ |
... | @@ -54,48 +68,17 @@ def update (module, objects) : | ... | @@ -54,48 +68,17 @@ def update (module, objects) : |
54 | else : | 68 | else : |
55 | raise ValueError("cannot plug '%r'" % obj) | 69 | raise ValueError("cannot plug '%r'" % obj) |
56 | 70 | ||
57 | -def build (name, module, *objects) : | ||
58 | - """Builds an extended module. | ||
59 | - | ||
60 | - The parameter `module` is exactly that taken by the function | ||
61 | - `extend` of a plugin. This list argument `objects` holds all the | ||
62 | - objects, constructed in `extend`, that are extensions of objects | ||
63 | - from `module`. The resulting value should be returned by `extend`. | ||
64 | - | ||
65 | - @param name: the name of the constructed module | ||
66 | - @type name: `str` | ||
67 | - @param module: the extended module | ||
68 | - @type module: `module` | ||
69 | - @param objects: the sub-objects | ||
70 | - @type objects: each is a class object | ||
71 | - @return: the new module | ||
72 | - @rtype: `module` | ||
73 | - """ | ||
74 | - result = imp.new_module(name) | ||
75 | - result.__dict__.update(module.__dict__) | ||
76 | - update(result, objects) | ||
77 | - result.__plugins__ = (module.__dict__.get("__plugins__", | ||
78 | - (module.__name__,)) | ||
79 | - + (name,)) | ||
80 | - for obj in objects : | ||
81 | - if inspect.isclass(obj) : | ||
82 | - obj.__plugins__ = result.__plugins__ | ||
83 | - return result | ||
84 | - | ||
85 | def load (plugins, base, name=None) : | 71 | def load (plugins, base, name=None) : |
86 | - """Load plugins. | 72 | + """Load plugins, `plugins` can be a single plugin name, a module |
87 | - | 73 | + or a list of such values. If `name` is not `None`, the extended |
88 | - `plugins` can be a single plugin name or module or a list of such | 74 | + module is loaded as `name` in `sys.modules` as well as in the |
89 | - values. If `name` is not `None`, the extended module is loaded ad | 75 | + global environment from which `load` was called. |
90 | - `name` in `sys.modules` as well as in the global environment from | ||
91 | - which `load` was called. | ||
92 | 76 | ||
93 | @param plugins: the module that implements the plugin, or its | 77 | @param plugins: the module that implements the plugin, or its |
94 | - name, or a collection of such values | 78 | + name, or a collection (eg, list, tuple) of such values |
95 | - @type plugins: `str` or `module`, or a `list`/`tuple`/... of such | 79 | + @type plugins: `object` |
96 | - values | ||
97 | @param base: the module being extended or its name | 80 | @param base: the module being extended or its name |
98 | - @type base: `str` or `module` | 81 | + @type base: `object` |
99 | @param name: the name of the created module | 82 | @param name: the name of the created module |
100 | @type name: `str` | 83 | @type name: `str` |
101 | @return: the extended module | 84 | @return: the extended module |
... | @@ -125,17 +108,45 @@ def load (plugins, base, name=None) : | ... | @@ -125,17 +108,45 @@ def load (plugins, base, name=None) : |
125 | inspect.stack()[1][0].f_globals[name] = result | 108 | inspect.stack()[1][0].f_globals[name] = result |
126 | return result | 109 | return result |
127 | 110 | ||
111 | +"""## Creating plugins ### | ||
112 | + | ||
113 | +We show now how to develop a plugin that allows instances of | ||
114 | +`PetriNet` to say hello: a new method `PetriNet.hello` is added and | ||
115 | +the constructor `PetriNet.__init__` is added a keyword argument | ||
116 | +`hello` for the message to print when calling method `hello`. | ||
117 | + | ||
118 | +Defining a plugins required to write a module with a function called | ||
119 | +`extend` that takes as its single argument the module to be extended. | ||
120 | +Inside this function, extensions of the classes in the module are | ||
121 | +defined as normal sub-classes. Function `extend` returns the extended | ||
122 | +classes. A decorator called `plugin` must be used, it also allows to | ||
123 | +resolve plugin dependencies and conflicts. | ||
124 | +""" | ||
125 | + | ||
126 | +# apidoc include "hello.py" lang="python" | ||
127 | + | ||
128 | +"""Note that, when extending an existing method like `__init__` above, | ||
129 | +we have to take care that you may be working on an already extended | ||
130 | +class, consequently, we cannot know how its arguments have been | ||
131 | +changed already. So, we must always use those from the unextended | ||
132 | +method plus `**args`. Then, we remove from the latter what your plugin | ||
133 | +needs and pass the remaining to the method of the base class if we | ||
134 | +need to call it (which is usually the case). """ | ||
135 | + | ||
128 | def plugin (base, depends=[], conflicts=[]) : | 136 | def plugin (base, depends=[], conflicts=[]) : |
129 | """Decorator for extension functions | 137 | """Decorator for extension functions |
130 | 138 | ||
131 | - @param base: name of base module (usually 'snakes.nets') | 139 | + @param base: name of base module (usually 'snakes.nets') that the |
132 | - @type base: str | 140 | + plugin extends |
133 | - @param depends: list of plugins on which this one depends | 141 | + @type base: `str` |
134 | - @type depends: list of str | 142 | + @param depends: list of plugin names (as `str`) this one depends |
135 | - @param conflicts: list of plugins with which this one conflicts | 143 | + on, prefix `snakes.plugins.` may be omitted |
136 | - @type conflicts: list of str | 144 | + @type depends: `list` |
145 | + @param conflicts: list of plugin names with which this one | ||
146 | + conflicts | ||
147 | + @type conflicts: `list` | ||
137 | @return: the appropriate decorator | 148 | @return: the appropriate decorator |
138 | - @rtype: function | 149 | + @rtype: `decorator` |
139 | """ | 150 | """ |
140 | def wrapper (fun) : | 151 | def wrapper (fun) : |
141 | @wraps(fun) | 152 | @wraps(fun) |
... | @@ -164,9 +175,39 @@ def plugin (base, depends=[], conflicts=[]) : | ... | @@ -164,9 +175,39 @@ def plugin (base, depends=[], conflicts=[]) : |
164 | return extend | 175 | return extend |
165 | return wrapper | 176 | return wrapper |
166 | 177 | ||
178 | +# apidoc skip | ||
167 | def new_instance (cls, obj) : | 179 | def new_instance (cls, obj) : |
168 | """Create a copy of `obj` which is an instance of `cls` | 180 | """Create a copy of `obj` which is an instance of `cls` |
169 | """ | 181 | """ |
170 | result = object.__new__(cls) | 182 | result = object.__new__(cls) |
171 | result.__dict__.update(obj.__dict__) | 183 | result.__dict__.update(obj.__dict__) |
172 | return result | 184 | return result |
185 | + | ||
186 | +# apidoc skip | ||
187 | +def build (name, module, *objects) : | ||
188 | + """Builds an extended module. | ||
189 | + | ||
190 | + The parameter `module` is exactly that taken by the function | ||
191 | + `extend` of a plugin. This list argument `objects` holds all the | ||
192 | + objects, constructed in `extend`, that are extensions of objects | ||
193 | + from `module`. The resulting value should be returned by `extend`. | ||
194 | + | ||
195 | + @param name: the name of the constructed module | ||
196 | + @type name: `str` | ||
197 | + @param module: the extended module | ||
198 | + @type module: `module` | ||
199 | + @param objects: the sub-objects | ||
200 | + @type objects: each is a class object | ||
201 | + @return: the new module | ||
202 | + @rtype: `module` | ||
203 | + """ | ||
204 | + result = imp.new_module(name) | ||
205 | + result.__dict__.update(module.__dict__) | ||
206 | + update(result, objects) | ||
207 | + result.__plugins__ = (module.__dict__.get("__plugins__", | ||
208 | + (module.__name__,)) | ||
209 | + + (name,)) | ||
210 | + for obj in objects : | ||
211 | + if inspect.isclass(obj) : | ||
212 | + obj.__plugins__ = result.__plugins__ | ||
213 | + return result | ... | ... |
... | @@ -26,6 +26,8 @@ | ... | @@ -26,6 +26,8 @@ |
26 | >>> n.layout() | 26 | >>> n.layout() |
27 | >>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str)) | 27 | >>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str)) |
28 | False | 28 | False |
29 | + | ||
30 | +@todo: revise documentation | ||
29 | """ | 31 | """ |
30 | 32 | ||
31 | import os, os.path, subprocess, collections | 33 | import os, os.path, subprocess, collections | ... | ... |
1 | -"""An example plugin that allows instances class `PetriNet` to say hello. | 1 | +"""An example plugin that allows instances of `PetriNet` to |
2 | - | 2 | +say hello. The source code can be used as a starting |
3 | -A new method `hello` is added. The constructor is added a keyword | 3 | +example.""" |
4 | -argument `hello` that must be the `str` to print when calling `hello`, | ||
5 | -with one `%s` that will be replaced by the name of the net when | ||
6 | -`hello` is called. | ||
7 | - | ||
8 | -Defining a plugins need writing a module with a single function called | ||
9 | -`extend` that takes a single argument that is the module to be | ||
10 | -extended. | ||
11 | - | ||
12 | -Inside the function, extensions of the classes in the module are | ||
13 | -defined as normal sub-classes. | ||
14 | - | ||
15 | -The function `extend` should return the extended module created by | ||
16 | -`snakes.plugins.build` that takes as arguments: the name of the | ||
17 | -extended module, the module taken as argument and the sub-classes | ||
18 | -defined (expected as a list argument `*args` in no special order). | ||
19 | - | ||
20 | -If the plugin depends on other plugins, for instance `foo` and `bar`, | ||
21 | -the function `extend` should be decorated by `@depends('foo', 'bar')`. | ||
22 | - | ||
23 | -Read the source code of this module to have an example | ||
24 | -""" | ||
25 | 4 | ||
26 | import snakes.plugins | 5 | import snakes.plugins |
27 | 6 | ||
28 | @snakes.plugins.plugin("snakes.nets") | 7 | @snakes.plugins.plugin("snakes.nets") |
29 | def extend (module) : | 8 | def extend (module) : |
30 | - """Extends `module` | 9 | + "Extends `module`" |
31 | - """ | ||
32 | class PetriNet (module.PetriNet) : | 10 | class PetriNet (module.PetriNet) : |
33 | - """Extension of the class `PetriNet` in `module` | 11 | + "Extension of the class `PetriNet` in `module`" |
34 | - """ | ||
35 | def __init__ (self, name, **args) : | 12 | def __init__ (self, name, **args) : |
36 | - """When extending an existing method, take care that you may | 13 | + """Add new keyword argument `hello` |
37 | - be working on an already extended class, so you so not | ||
38 | - know how its arguments have been changed. So, always use | ||
39 | - those from the unextended class plus `**args`, remove from | ||
40 | - it what your plugin needs and pass it to the method of the | ||
41 | - extended class if you need to call it. | ||
42 | 14 | ||
43 | >>> PetriNet('N').hello() | 15 | >>> PetriNet('N').hello() |
44 | Hello from N | 16 | Hello from N |
... | @@ -46,18 +18,13 @@ def extend (module) : | ... | @@ -46,18 +18,13 @@ def extend (module) : |
46 | Hi! This is N... | 18 | Hi! This is N... |
47 | 19 | ||
48 | @param args: plugin options | 20 | @param args: plugin options |
49 | - @keyword hello: the message to print, with `%s` where the | 21 | + @keyword hello: the message to print, with |
50 | - net name should appear. | 22 | + `%s` where the net name should appear. |
51 | @type hello: `str` | 23 | @type hello: `str` |
52 | """ | 24 | """ |
53 | self._hello = args.pop("hello", "Hello from %s") | 25 | self._hello = args.pop("hello", "Hello from %s") |
54 | module.PetriNet.__init__(self, name, **args) | 26 | module.PetriNet.__init__(self, name, **args) |
55 | def hello (self) : | 27 | def hello (self) : |
56 | - """A new method `hello` | 28 | + "Ask the net to say hello" |
57 | - | ||
58 | - >>> n = PetriNet('N') | ||
59 | - >>> n.hello() | ||
60 | - Hello from N | ||
61 | - """ | ||
62 | print(self._hello % self.name) | 29 | print(self._hello % self.name) |
63 | return PetriNet | 30 | return PetriNet | ... | ... |
... | @@ -42,6 +42,8 @@ Position(1, 3) | ... | @@ -42,6 +42,8 @@ Position(1, 3) |
42 | >>> n.transpose() | 42 | >>> n.transpose() |
43 | >>> n.node('t01').pos | 43 | >>> n.node('t01').pos |
44 | Position(-3, 1) | 44 | Position(-3, 1) |
45 | + | ||
46 | +@todo: revise documentation | ||
45 | """ | 47 | """ |
46 | 48 | ||
47 | from snakes import SnakesError | 49 | from snakes import SnakesError | ... | ... |
1 | +""" | ||
2 | +@todo: revise (actually make) documentation | ||
3 | +""" | ||
4 | + | ||
1 | from snakes.plugins import plugin | 5 | from snakes.plugins import plugin |
2 | from snakes.pnml import Tree, loads, dumps | 6 | from snakes.pnml import Tree, loads, dumps |
3 | import imp, sys, socket, traceback, operator | 7 | import imp, sys, socket, traceback, operator | ... | ... |
... | @@ -12,6 +12,8 @@ Several status are defined by default: `entry`, `internal`, `exit`, | ... | @@ -12,6 +12,8 @@ Several status are defined by default: `entry`, `internal`, `exit`, |
12 | >>> n.add_place(Place('p1'), status=status.entry) | 12 | >>> n.add_place(Place('p1'), status=status.entry) |
13 | >>> n.place('p1') | 13 | >>> n.place('p1') |
14 | Place('p1', MultiSet([]), tAll, status=Status('entry')) | 14 | Place('p1', MultiSet([]), tAll, status=Status('entry')) |
15 | + | ||
16 | +@todo: revise documentation | ||
15 | """ | 17 | """ |
16 | 18 | ||
17 | import operator, weakref | 19 | import operator, weakref | ... | ... |
... | @@ -6,8 +6,6 @@ The plugin proposes a generalisation of the M-nets synchronisation in | ... | @@ -6,8 +6,6 @@ The plugin proposes a generalisation of the M-nets synchronisation in |
6 | that it does not impose a fixed correspondence between action names | 6 | that it does not impose a fixed correspondence between action names |
7 | and action arities. | 7 | and action arities. |
8 | 8 | ||
9 | - | ||
10 | - | ||
11 | * class `Action` corresponds to a synchronisable action, it has a | 9 | * class `Action` corresponds to a synchronisable action, it has a |
12 | name, a send/receive flag and a list of parameters. Actions have | 10 | name, a send/receive flag and a list of parameters. Actions have |
13 | no predetermined arities, only conjugated actions with the same | 11 | no predetermined arities, only conjugated actions with the same |
... | @@ -66,6 +64,8 @@ t2 z>0 | ... | @@ -66,6 +64,8 @@ t2 z>0 |
66 | >>> [t.name for t in sorted(n.transition(), key=str)] | 64 | >>> [t.name for t in sorted(n.transition(), key=str)] |
67 | ["((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]", | 65 | ["((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]", |
68 | "((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]"] | 66 | "((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]"] |
67 | + | ||
68 | +@todo: revise documentation | ||
69 | """ | 69 | """ |
70 | 70 | ||
71 | from snakes import ConstraintError | 71 | from snakes import ConstraintError |
... | @@ -91,6 +91,7 @@ class Action (object) : | ... | @@ -91,6 +91,7 @@ class Action (object) : |
91 | self.send = send | 91 | self.send = send |
92 | self.params = list(params) | 92 | self.params = list(params) |
93 | __pnmltag__ = "action" | 93 | __pnmltag__ = "action" |
94 | + # apidoc skip | ||
94 | def __pnmldump__ (self) : | 95 | def __pnmldump__ (self) : |
95 | """ | 96 | """ |
96 | >>> Action('a', True, [Value(1), Variable('x')]).__pnmldump__() | 97 | >>> Action('a', True, [Value(1), Variable('x')]).__pnmldump__() |
... | @@ -114,6 +115,7 @@ class Action (object) : | ... | @@ -114,6 +115,7 @@ class Action (object) : |
114 | for param in self.params : | 115 | for param in self.params : |
115 | result.add_child(Tree.from_obj(param)) | 116 | result.add_child(Tree.from_obj(param)) |
116 | return result | 117 | return result |
118 | + # apidoc skip | ||
117 | @classmethod | 119 | @classmethod |
118 | def __pnmlload__ (cls, tree) : | 120 | def __pnmlload__ (cls, tree) : |
119 | """ | 121 | """ |
... | @@ -136,6 +138,7 @@ class Action (object) : | ... | @@ -136,6 +138,7 @@ class Action (object) : |
136 | return "%s!(%s)" % (self.name, ",".join([str(p) for p in self])) | 138 | return "%s!(%s)" % (self.name, ",".join([str(p) for p in self])) |
137 | else : | 139 | else : |
138 | return "%s?(%s)" % (self.name, ",".join([str(p) for p in self])) | 140 | return "%s?(%s)" % (self.name, ",".join([str(p) for p in self])) |
141 | + # apidoc stop | ||
139 | def __repr__ (self) : | 142 | def __repr__ (self) : |
140 | """ | 143 | """ |
141 | >>> a = Action('a', True, [Value(1), Variable('x')]) | 144 | >>> a = Action('a', True, [Value(1), Variable('x')]) | ... | ... |
... | @@ -3,6 +3,8 @@ | ... | @@ -3,6 +3,8 @@ |
3 | Petri nets objects are saved in PNML, other Python objects are saved | 3 | Petri nets objects are saved in PNML, other Python objects are saved |
4 | in a readable format when possible and pickled as a last solution. | 4 | in a readable format when possible and pickled as a last solution. |
5 | This should result in a complete PNML serialization of any object. | 5 | This should result in a complete PNML serialization of any object. |
6 | + | ||
7 | +@todo: revise documentation | ||
6 | """ | 8 | """ |
7 | 9 | ||
8 | import xml.dom.minidom | 10 | import xml.dom.minidom | ... | ... |
... | @@ -60,6 +60,8 @@ TypeError: ... | ... | @@ -60,6 +60,8 @@ TypeError: ... |
60 | Traceback (most recent call last): | 60 | Traceback (most recent call last): |
61 | ... | 61 | ... |
62 | TypeError: ... | 62 | TypeError: ... |
63 | + | ||
64 | +@todo: revise documentation | ||
63 | """ | 65 | """ |
64 | 66 | ||
65 | import inspect, sys | 67 | import inspect, sys | ... | ... |
1 | +""" | ||
2 | +@todo: revise (actually make) documentation | ||
3 | +""" | ||
4 | + | ||
1 | import sys, os, os.path | 5 | import sys, os, os.path |
2 | -import inspect, fnmatch, collections | 6 | +import inspect, fnmatch, collections, re, shlex |
3 | -import textwrap, doctest, ast | 7 | +import textwrap, doctest |
4 | import snakes | 8 | import snakes |
5 | from snakes.lang import unparse | 9 | from snakes.lang import unparse |
10 | +from snakes.lang.python.parser import parse, ast | ||
11 | + | ||
12 | +try : | ||
13 | + import markdown | ||
14 | +except : | ||
15 | + markdown = None | ||
6 | 16 | ||
7 | ## | 17 | ## |
8 | ## console messages | 18 | ## console messages |
... | @@ -62,6 +72,13 @@ class DocExtract (object) : | ... | @@ -62,6 +72,13 @@ class DocExtract (object) : |
62 | self.out = None | 72 | self.out = None |
63 | self.exclude = exclude | 73 | self.exclude = exclude |
64 | self._last = "\n\n" | 74 | self._last = "\n\n" |
75 | + def md (self, text, inline=True) : | ||
76 | + if markdown is None : | ||
77 | + return text | ||
78 | + elif inline : | ||
79 | + return re.sub("</?p>", "\n", markdown.markdown(text), re.I) | ||
80 | + else : | ||
81 | + return markdown.markdown(text) | ||
65 | def openout (self, path) : | 82 | def openout (self, path) : |
66 | if self.out is not None : | 83 | if self.out is not None : |
67 | self.out.close() | 84 | self.out.close() |
... | @@ -81,6 +98,10 @@ class DocExtract (object) : | ... | @@ -81,6 +98,10 @@ class DocExtract (object) : |
81 | return False | 98 | return False |
82 | outdir = os.path.join(self.outpath, relpath) | 99 | outdir = os.path.join(self.outpath, relpath) |
83 | outpath = os.path.join(outdir, target) | 100 | outpath = os.path.join(outdir, target) |
101 | + if os.path.exists(outpath) : | ||
102 | + if os.stat(path).st_mtime <= os.stat(outpath).st_mtime : | ||
103 | + return False | ||
104 | + self.inpath = path | ||
84 | info("%s -> %r" % (self.module, outpath)) | 105 | info("%s -> %r" % (self.module, outpath)) |
85 | if not os.path.exists(outdir) : | 106 | if not os.path.exists(outdir) : |
86 | os.makedirs(outdir) | 107 | os.makedirs(outdir) |
... | @@ -114,56 +135,89 @@ class DocExtract (object) : | ... | @@ -114,56 +135,89 @@ class DocExtract (object) : |
114 | def process (self) : | 135 | def process (self) : |
115 | for dirpath, dirnames, filenames in os.walk(self.path) : | 136 | for dirpath, dirnames, filenames in os.walk(self.path) : |
116 | for name in sorted(filenames) : | 137 | for name in sorted(filenames) : |
117 | - if not name.endswith(".py") : | 138 | + if not name.endswith(".py") or name.startswith(".") : |
118 | continue | 139 | continue |
119 | path = os.path.join(dirpath, name) | 140 | path = os.path.join(dirpath, name) |
120 | if not self.openout(path) : | 141 | if not self.openout(path) : |
121 | continue | 142 | continue |
122 | - node = ast.parse(open(path).read()) | 143 | + node = parse(open(path).read()) |
123 | if ".plugins." in self.module : | 144 | if ".plugins." in self.module : |
124 | self.visit_plugin(node) | 145 | self.visit_plugin(node) |
125 | else : | 146 | else : |
126 | self.visit_module(node) | 147 | self.visit_module(node) |
127 | def _pass (self, node) : | 148 | def _pass (self, node) : |
128 | pass | 149 | pass |
150 | + def directive (self, node) : | ||
151 | + lines = node.st.text.lexer.lines | ||
152 | + num = node.st.srow - 2 | ||
153 | + while num >= 0 and (not lines[num].strip() | ||
154 | + or lines[num].strip().startswith("@")) : | ||
155 | + num -= 1 | ||
156 | + if num >= 0 and lines[num].strip().startswith("#") : | ||
157 | + dirline = lines[num].lstrip("# \t").rstrip() | ||
158 | + items = shlex.split(dirline) | ||
159 | + if len(items) >= 2 and items[0].lower() == "apidoc" : | ||
160 | + if len(items) == 2 and items[1].lower() in ("skip", "stop") : | ||
161 | + return items[1] | ||
162 | + elif items[1].lower() == "include" : | ||
163 | + path = items[2] | ||
164 | + try : | ||
165 | + args = dict(i.split("=", 1) for i in items[3:]) | ||
166 | + except : | ||
167 | + err("invalid directive %r (line %s)" | ||
168 | + % (dirline, num+1)) | ||
169 | + self.write_include(path, **args) | ||
170 | + else : | ||
171 | + err("unknown directive %r (line %s)" | ||
172 | + % (items[1], num+1)) | ||
173 | + return None | ||
174 | + def children (self, node) : | ||
175 | + for child in ast.iter_child_nodes(node) : | ||
176 | + directive = self.directive(child) | ||
177 | + if directive == "skip" : | ||
178 | + continue | ||
179 | + elif directive == "stop" : | ||
180 | + break | ||
181 | + yield child | ||
129 | def visit (self, node) : | 182 | def visit (self, node) : |
130 | name = getattr(node, "name", "__") | 183 | name = getattr(node, "name", "__") |
131 | if (name.startswith("_") and not (name.startswith("__") | 184 | if (name.startswith("_") and not (name.startswith("__") |
132 | and name.endswith("__"))) : | 185 | and name.endswith("__"))) : |
133 | return | 186 | return |
134 | try : | 187 | try : |
135 | - getattr(self, "visit_" + node.__class__.__name__, self._pass)(node) | 188 | + getattr(self, "visit_" + node.__class__.__name__, |
189 | + self._pass)(node) | ||
136 | except : | 190 | except : |
137 | - src = unparse(node) | 191 | + src = node.st.source() |
138 | if len(src) > 40 : | 192 | if len(src) > 40 : |
139 | src = src[:40] + "..." | 193 | src = src[:40] + "..." |
140 | err("line %s source %r" % (node.lineno, src)) | 194 | err("line %s source %r" % (node.lineno, src)) |
141 | raise | 195 | raise |
142 | def visit_module (self, node) : | 196 | def visit_module (self, node) : |
143 | self.write_module() | 197 | self.write_module() |
144 | - for child in ast.iter_child_nodes(node) : | 198 | + for child in self.children(node) : |
145 | self.visit(child) | 199 | self.visit(child) |
146 | def visit_plugin (self, node) : | 200 | def visit_plugin (self, node) : |
147 | self.write_module() | 201 | self.write_module() |
148 | extend = None | 202 | extend = None |
149 | - for child in ast.iter_child_nodes(node) : | 203 | + for child in self.children(node) : |
150 | if (getattr(child, "name", None) == "extend" | 204 | if (getattr(child, "name", None) == "extend" |
151 | and isinstance(child, ast.FunctionDef)) : | 205 | and isinstance(child, ast.FunctionDef)) : |
152 | extend = child | 206 | extend = child |
153 | else : | 207 | else : |
154 | self.visit(child) | 208 | self.visit(child) |
155 | self.write_plugin() | 209 | self.write_plugin() |
156 | - for child in ast.iter_child_nodes(extend) : | 210 | + for child in self.children(extend) : |
157 | self.visit(child) | 211 | self.visit(child) |
158 | def visit_ClassDef (self, node) : | 212 | def visit_ClassDef (self, node) : |
159 | self.write_class(node) | 213 | self.write_class(node) |
160 | self.classname = node.name | 214 | self.classname = node.name |
161 | - for child in ast.iter_child_nodes(node) : | 215 | + for child in self.children(node) : |
162 | self.visit(child) | 216 | self.visit(child) |
163 | self.classname = None | 217 | self.classname = None |
164 | def visit_FunctionDef (self, node) : | 218 | def visit_FunctionDef (self, node) : |
165 | self.write_function(node) | 219 | self.write_function(node) |
166 | - self.args = [n.id for n in node.args.args] | 220 | + self.args = [n.arg for n in node.args.args] |
167 | if self.args and self.args[0] == "self" : | 221 | if self.args and self.args[0] == "self" : |
168 | del self.args[0] | 222 | del self.args[0] |
169 | if node.args.vararg : | 223 | if node.args.vararg : |
... | @@ -193,26 +247,32 @@ class DocExtract (object) : | ... | @@ -193,26 +247,32 @@ class DocExtract (object) : |
193 | else : | 247 | else : |
194 | self.writeline("### Function `%s` ###" % node.name) | 248 | self.writeline("### Function `%s` ###" % node.name) |
195 | self.newline() | 249 | self.newline() |
196 | - self.writeline(" :::python") | 250 | + self.write_def(node) |
197 | - for line in unparse(node).splitlines() : | ||
198 | - if line.startswith("def") : | ||
199 | - self.writeline(" %s ..." % line) | ||
200 | - break | ||
201 | - else : | ||
202 | - self.writeline(" " + line) | ||
203 | - self.newline | ||
204 | def write_class (self, node) : | 251 | def write_class (self, node) : |
205 | self.newline() | 252 | self.newline() |
206 | self.writeline("### Class `%s` ###" % node.name) | 253 | self.writeline("### Class `%s` ###" % node.name) |
254 | + self.write_def(node) | ||
255 | + def write_def (self, node) : | ||
256 | + indent = node.st.scol | ||
257 | + if node.st[0].symbol == "decorated" : | ||
258 | + srow, scol = node.st[0][0][0][0].srow, node.st[0][0][0][0].scol | ||
259 | + erow, ecol = node.st[0][1][-2].erow, node.st[0][1][-2].ecol | ||
260 | + else : | ||
261 | + srow, scol = node.st[0].srow, node.st[0].scol | ||
262 | + erow, ecol = node.st[0][-2].erow, node.st[0][-2].ecol | ||
263 | + lines = node.st.text.lexer.lines | ||
264 | + if srow == erow : | ||
265 | + source = [lines[srow-1][scol:ecol+1]] | ||
266 | + else : | ||
267 | + source = lines[srow-1:erow] | ||
268 | + source[0] = source[0][scol:] | ||
269 | + source[1:-1] = [s[indent:] for s in source[1:-1]] | ||
270 | + source[-1] = source[-1][indent:ecol+1].rstrip() + " ..." | ||
207 | self.newline() | 271 | self.newline() |
208 | self.writeline(" :::python") | 272 | self.writeline(" :::python") |
209 | - for line in unparse(node).splitlines() : | 273 | + for line in source : |
210 | - if line.startswith("class") : | ||
211 | - self.writeline(" %s ..." % line) | ||
212 | - break | ||
213 | - else : | ||
214 | self.writeline(" " + line) | 274 | self.writeline(" " + line) |
215 | - self.newline | 275 | + self.newline() |
216 | parse = doctest.DocTestParser().parse | 276 | parse = doctest.DocTestParser().parse |
217 | def write_doc (self, doc) : | 277 | def write_doc (self, doc) : |
218 | if doc is None : | 278 | if doc is None : |
... | @@ -240,7 +300,9 @@ class DocExtract (object) : | ... | @@ -240,7 +300,9 @@ class DocExtract (object) : |
240 | if not test : | 300 | if not test : |
241 | test = True | 301 | test = True |
242 | self.newline() | 302 | self.newline() |
243 | - self.writeline(" :::python") | 303 | + self.writetext("<!-- this comment avoids a bug in" |
304 | + " Markdown parsing -->") | ||
305 | + self.writeline(" :::pycon") | ||
244 | for i, line in enumerate(doc.source.splitlines()) : | 306 | for i, line in enumerate(doc.source.splitlines()) : |
245 | if i > 0 : | 307 | if i > 0 : |
246 | self.writeline(" ... %s" % line) | 308 | self.writeline(" ... %s" % line) |
... | @@ -281,65 +343,74 @@ class DocExtract (object) : | ... | @@ -281,65 +343,74 @@ class DocExtract (object) : |
281 | if any(k in info for k in ("author", "organization", "copyright", | 343 | if any(k in info for k in ("author", "organization", "copyright", |
282 | "license", "contact")) : | 344 | "license", "contact")) : |
283 | self.newline() | 345 | self.newline() |
284 | - self.writeline('<div class="api-info">') | 346 | + self.writeline('<ul id="api-info">') |
285 | - for tag in ("author", "organization", "copyright", | 347 | + for tag in ("author", "organization", "contact", |
286 | - "license", "contact") : | 348 | + "copyright", "license", ) : |
287 | if tag in info : | 349 | if tag in info : |
288 | - self.writeline('<div class="api-%s">' % tag) | 350 | + self.writeline('<li id="api-%s">' % tag) |
289 | self.writetext('<span class="api-title">%s:</span> %s' | 351 | self.writetext('<span class="api-title">%s:</span> %s' |
290 | - % (tag.capitalize(), info[tag]), | 352 | + % (tag.capitalize(), |
353 | + self.md(info[tag])), | ||
291 | subsequent_indent=" ") | 354 | subsequent_indent=" ") |
292 | - self.writeline('</div>') | 355 | + self.writeline('</li>') |
293 | - self.writeline('</div>') | 356 | + self.writeline('</ul>') |
294 | if any(info[k] for k in | 357 | if any(info[k] for k in |
295 | ("todo", "note", "attention", "bug", "warning")) : | 358 | ("todo", "note", "attention", "bug", "warning")) : |
296 | self.newline() | 359 | self.newline() |
297 | - self.writeline('<div class="api-remarks">') | 360 | + self.writeline('<div id="api-remarks">') |
298 | - self.writeline("##### Remarks #####") | ||
299 | - self.newline() | ||
300 | for tag in ("note", "todo", "attention", "bug", "warning") : | 361 | for tag in ("note", "todo", "attention", "bug", "warning") : |
301 | for text in info[tag] : | 362 | for text in info[tag] : |
302 | self.writeline('<div class="api-%s">' % tag) | 363 | self.writeline('<div class="api-%s">' % tag) |
303 | self.writetext('<span class="api-title">%s:</span> %s' | 364 | self.writetext('<span class="api-title">%s:</span> %s' |
304 | - % (tag.capitalize(), text), | 365 | + % (tag.capitalize(), self.md(text)), |
305 | subsequent_indent=" ") | 366 | subsequent_indent=" ") |
306 | self.writeline('</div>') | 367 | self.writeline('</div>') |
307 | self.writeline('</div>') | 368 | self.writeline('</div>') |
308 | if (any(info[k] for k in ("param", "type", "keyword")) | 369 | if (any(info[k] for k in ("param", "type", "keyword")) |
309 | or any(k in info for k in ("return", "rtype"))) : | 370 | or any(k in info for k in ("return", "rtype"))) : |
310 | self.newline() | 371 | self.newline() |
311 | - self.writeline('<div class="api-call">') | ||
312 | self.writeline("##### Call API #####") | 372 | self.writeline("##### Call API #####") |
313 | self.newline() | 373 | self.newline() |
314 | for arg in self.args : | 374 | for arg in self.args : |
315 | if arg in info["param"] : | 375 | if arg in info["param"] : |
316 | - self.writelist("`%s` (%s): %s" | 376 | + self.writelist("`%s %s`: %s" |
317 | - % (arg, | 377 | + % (info["type"].get(arg, "object").strip("`"), |
318 | - info["type"].get(arg, "`object`"), | 378 | + arg, |
319 | info["param"][arg])) | 379 | info["param"][arg])) |
320 | else : | 380 | else : |
321 | - self.writelist("`%s` (%s)" | 381 | + self.writelist("`%s %s`" |
322 | - % (arg, | 382 | + % (info["type"].get(arg, "object").strip("`"), |
323 | - info["type"].get(arg, "`object`"))) | 383 | + arg)) |
324 | for kw, text in sorted(info["keyword"].items()) : | 384 | for kw, text in sorted(info["keyword"].items()) : |
325 | self.writelist("`%s`: %s" % (kw, text)) | 385 | self.writelist("`%s`: %s" % (kw, text)) |
326 | if any(k in info for k in ("return", "rtype")) : | 386 | if any(k in info for k in ("return", "rtype")) : |
327 | if "return" in info : | 387 | if "return" in info : |
328 | - self.writelist("return %s: %s" | 388 | + self.writelist("`return %s`: %s" |
329 | - % (info.get("rtype", "`object`"), | 389 | + % (info.get("rtype", "object").strip("`"), |
330 | info["return"])) | 390 | info["return"])) |
331 | else : | 391 | else : |
332 | - self.writelist("return %s" | 392 | + self.writelist("`return %s`" |
333 | - % (info.get("rtype", "`object`"))) | 393 | + % (info.get("rtype", "object").strip("`"))) |
334 | - self.writeline('</div>') | ||
335 | if info["raise"] : | 394 | if info["raise"] : |
336 | self.newline() | 395 | self.newline() |
337 | - self.writeline('<div class="api-errors">') | ||
338 | self.writeline("##### Exceptions #####") | 396 | self.writeline("##### Exceptions #####") |
339 | self.newline() | 397 | self.newline() |
340 | for exc, reason in sorted(info["raise"].items()) : | 398 | for exc, reason in sorted(info["raise"].items()) : |
341 | self.writelist("`%s`: %s" % (exc, reason)) | 399 | self.writelist("`%s`: %s" % (exc, reason)) |
342 | - self.writeline('</div>') | 400 | + self.newline() |
401 | + def write_include (self, name, lang="python") : | ||
402 | + if os.path.exists(name) : | ||
403 | + path = name | ||
404 | + else : | ||
405 | + path = os.path.join(os.path.dirname(self.inpath), name) | ||
406 | + if not os.path.exists(path) : | ||
407 | + err("include file %r not found" % name) | ||
408 | + with open(path) as infile : | ||
409 | + self.newline() | ||
410 | + self.writeline(" :::%s" % lang) | ||
411 | + for line in infile : | ||
412 | + self.writeline(" " + line.rstrip()) | ||
413 | + self.newline() | ||
343 | 414 | ||
344 | def main (finder, args) : | 415 | def main (finder, args) : |
345 | try : | 416 | try : | ... | ... |
-
Please register or login to post a comment