Franck Pommereau

doc update

"""SNAKES is the Net Algebra Kit for Editors and Simulators
SNAKES is a Python library allowing to model all sorts of Petri nets
and to execute them. It is very general as most Petri nets annotations
can be arbitrary Python expressions while most values can be arbitrary
Python objects.
SNAKES can be further extended with plugins, several ones being
already provided, in particular two plugins implement the Petri nets
compositions defined for the Petri Box Calculus and its successors.
"""SNAKES library is organised into three parts:
* the core library is package `snakes` and its modules, among which
`snakes.nets` is the one to work with Petri nets while the others
can be seen as auxiliary modules
* the plugin system and the plugins themselves reside into package
`snakes.plugins`
* auxiliary tools are kept into other sub-packages: `snakes.lang`
for all the material related to parsing Python and other
languages, `snakes.utils` for various utilities like the ABCD
compiler
@author: Franck Pommereau
@organization: University of Evry/Paris-Saclay
@copyright: (C) 2005-2013 Franck Pommereau
@license: GNU Lesser General Public Licence (aka. GNU LGPL), see the
file `doc/COPYING` in the distribution or visit [the GNU web
file `doc/COPYING` in the distribution or visit the [GNU web
site](http://www.gnu.org/licenses/licenses.html#LGPL)
@contact: franck.pommereau@ibisc.univ-evry.fr
"""
......@@ -21,8 +22,13 @@ compositions defined for the Petri Box Calculus and its successors.
version = "0.9.16"
defaultencoding = "utf-8"
"""## Module `snakes`
This module only provides the exceptions used throughout SNAKES.
"""
class SnakesError (Exception) :
"An error in SNAKES"
"Generic error in SNAKES"
pass
class ConstraintError (SnakesError) :
......
"""Basic data types and functions used in SNAKES
"""Basic data types and related functions used in SNAKES
"""
import operator, inspect
......@@ -8,8 +8,9 @@ from snakes.hashables import hdict
from snakes.pnml import Tree
def cross (sets) :
"""Cross-product.
"""Cross-product of some iterable collections (typically, `list`
or `set`).
>>> list(cross([[1, 2], [3, 4, 5]]))
[(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)]
>>> list(cross([[1, 2], [3, 4, 5], [6, 7, 8, 9]]))
......@@ -19,12 +20,11 @@ def cross (sets) :
(2, 4, 8), (2, 4, 9), (2, 5, 6), (2, 5, 7), (2, 5, 8), (2, 5, 9)]
>>> list(cross([[], [1]]))
[]
@param sets: the sets of values to use
@type sets: `iterable(iterable(object))`
@return: the `list` of obtained tuples (lists are used to allow
unhashable objects)
@rtype: `generator(tuple(object))`
@type sets: `iterable`
@return: an iterator over the tuples in the cross-product
@rtype: `generator`
"""
if len(sets) == 0 :
pass
......@@ -38,7 +38,7 @@ def cross (sets) :
def iterate (value) :
"""Like Python's builtin `iter` but consider strings as atomic.
>>> list(iter([1, 2, 3]))
[1, 2, 3]
>>> list(iterate([1, 2, 3]))
......@@ -47,7 +47,7 @@ def iterate (value) :
['f', 'o', 'o']
>>> list(iterate('foo'))
['foo']
@param value: any object
@type value: `object`
@return: an iterator on the elements of `value` if is is iterable
......@@ -65,10 +65,10 @@ def iterate (value) :
class WordSet (set) :
"""A set of words being able to generate fresh words.
"""
def fresh (self, add=False, min=1, allowed="abcdefghijklmnopqrstuvwxyz",
base="") :
def fresh (self, add=False, min=1, base="",
allowed="abcdefghijklmnopqrstuvwxyz") :
"""Create a fresh word (ie, which is not in the set).
>>> w = WordSet(['foo', 'bar'])
>>> list(sorted(w))
['bar', 'foo']
......@@ -80,13 +80,15 @@ class WordSet (set) :
'baa'
>>> list(sorted(w))
['aaa', 'baa', 'bar', 'foo']
@param add: add the created word to the set if `add=True`
@type add: `bool`
@param min: minimal length of the new word
@type min: `int`
@param allowed: characters allowed in the new word
@type allowed: `str`
@param base: prefix of generated words
@type base: `str`
"""
if base :
result = [base] + [allowed[0]] * max(0, min - len(base))
......@@ -115,31 +117,32 @@ class WordSet (set) :
class MultiSet (hdict) :
"""Set with repetitions, ie, function from values to integers.
MultiSets support various operations, in particular: addition
(`+`), substraction (`-`), multiplication by a non negative
integer (`*k`), comparisons (`<`, `>`, etc.), length (`len`)
integer (`*k`), comparisons (`<`, `>`, etc.), length (`len`).
"""
def __init__ (self, values=[]) :
"""Initialise the multiset, adding values to it.
>>> MultiSet([1, 2, 3, 1, 2])
MultiSet([...])
>>> MultiSet()
MultiSet([])
@param values: a single value or an iterable object holding
@param values: a single value or an iterable collection of
values (strings are not iterated)
@type values: any atomic object (`str` included) or an
iterable object
@type values: `object`
"""
self.add(values)
def copy (self) :
"""Copy a `MultiSet`
>>> MultiSet([1, 2, 3, 1, 2]).copy()
MultiSet([...])
>>> m1 = MultiSet([1, 2, 3, 1, 2])
>>> m2 = m1.copy()
>>> m1 == m2 and m1 is not m2
True
@return: a copy of the multiset
@rtype: `MultiSet`
"""
......@@ -147,6 +150,7 @@ class MultiSet (hdict) :
result.update(self)
return result
__pnmltag__ = "multiset"
# apidoc skip
def __pnmldump__ (self) :
"""
>>> MultiSet([1, 2, 3, 4, 1, 2]).__pnmldump__()
......@@ -202,10 +206,11 @@ class MultiSet (hdict) :
Tree("value", None, Tree.from_obj(value)),
Tree("multiplicity", str(self[value]))))
return Tree(self.__pnmltag__, None, *nodes)
# apidoc skip
@classmethod
def __pnmlload__ (cls, tree) :
"""Load a multiset from its PNML representation
>>> t = MultiSet([1, 2, 3, 4, 1, 2]).__pnmldump__()
>>> MultiSet.__pnmlload__(t)
MultiSet([...])
......@@ -218,11 +223,12 @@ class MultiSet (hdict) :
return result
def _add (self, value, times=1) :
"""Add a single value `times` times.
@param value: the value to add
@type value: any object
@type value: `object`
@param times: the number of times that `value` has to be added
@type times: non negative `int`
@raise ValueError: when `times` is negative
"""
if times < 0 :
raise ValueError("negative values are forbidden")
......@@ -232,7 +238,7 @@ class MultiSet (hdict) :
self[value] = times
def add (self, values, times=1) :
"""Add values to the multiset.
>>> m = MultiSet()
>>> m.add([1, 2, 2, 3], 2)
>>> list(sorted(m.items()))
......@@ -240,24 +246,26 @@ class MultiSet (hdict) :
>>> m.add(5, 3)
>>> list(sorted(m.items()))
[1, 1, 2, 2, 2, 2, 3, 3, 5, 5, 5]
@param values: the values to add or a single value to add
@type values: any atomic object (`str` included) or an
iterable object
@type values: `object`
@param times: the number of times each value should be added
@type times: non negative `int`
(must be non-negative)
@type times: `int`
@raise ValueError: when `times` is negative
"""
self.__mutable__()
for value in iterate(values) :
self._add(value, times)
def _remove (self, value, times=1) :
"""Remove a single value `times` times.
@param value: the value to remove
@type value: any object
@param times: the number of times that `value` has to be
removed
@type times: non negative `int`
@raise ValueError: when `times` is negative
"""
if times < 0 :
raise ValueError("negative values are forbidden")
......@@ -268,9 +276,8 @@ class MultiSet (hdict) :
del self[value]
def remove (self, values, times=1) :
"""Remove values to the multiset.
>>> m = MultiSet()
>>> m.add([1, 2, 2, 3], 2)
>>> m = MultiSet([1, 2, 2, 3] * 2)
>>> list(sorted(m.items()))
[1, 1, 2, 2, 2, 2, 3, 3]
>>> m.remove(2, 3)
......@@ -279,63 +286,63 @@ class MultiSet (hdict) :
>>> m.remove([1, 3], 2)
>>> list(sorted(m.items()))
[2]
@param values: the values to remove or a single value to
remove
@type values: any atomic object (`str` included) or an
iterable object
@type values: `object`
@param times: the number of times each value should be removed
@type times: non negative `int`
(must be non negative)
@type times: `int`
@raise ValueError: when `times` is negative
"""
self.__mutable__()
for value in iterate(values) :
self._remove(value, times)
def __call__ (self, value) :
"""Number of occurrences of `value`.
>>> m = MultiSet([1, 1, 2, 3, 3, 3])
>>> m(1), m(2), m(3), m(4)
(2, 1, 3, 0)
@param value: the value the count
@type value: `object`
@rtype: `int`
"""
return self.get(value, 0)
def __iter__ (self) :
"""Iterate over the values (with repetitions).
Use `MultiSet.keys` to ignore repetitions.
"""Iterate over the values, _including repetitions_. Use
`MultiSet.keys` to ignore repetitions.
>>> list(sorted(iter(MultiSet([1, 2, 3, 1, 2]))))
[1, 1, 2, 2, 3]
@return: an iterator on the elements
@rtype: `iterator`
@rtype: `generator`
"""
for value in dict.__iter__(self) :
for count in range(self[value]) :
yield value
def items (self) :
"""Return the list of items with repetitions. The list without
repetitions can be retrieved with the `key` method.
repetitions can be retrieved with `MultiSet.key`.
>>> m = MultiSet([1, 2, 2, 3])
>>> list(sorted(m.items()))
[1, 2, 2, 3]
>>> list(sorted(m.keys()))
[1, 2, 3]
@return: list of items with repetitions
@return: list of items including repetitions
@rtype: `list`
"""
return list(iter(self))
def __str__ (self) :
"""Return a simple string representation of the multiset
>>> str(MultiSet([1, 2, 2, 3]))
'{...}'
@return: simple string representation of the multiset
@rtype: `str`
"""
......@@ -343,20 +350,20 @@ class MultiSet (hdict) :
def __repr__ (self) :
"""Return a string representation of the multiset that is
suitable for `eval`
>>> repr(MultiSet([1, 2, 2, 3]))
'MultiSet([...])'
@return: precise string representation of the multiset
@rtype: `str`
"""
return "MultiSet([%s])" % ", ".join(repr(x) for x in self)
def __len__ (self) :
"""Return the number of elements, including repetitions.
"""Return the number of elements, _including repetitions_.
>>> len(MultiSet([1, 2] * 3))
6
@rtype: `int`
"""
if self.size() == 0 :
......@@ -364,20 +371,20 @@ class MultiSet (hdict) :
else :
return reduce(operator.add, self.values())
def size (self) :
"""Return the number of elements, excluding repetitions.
"""Return the number of elements, _excluding repetitions_.
>>> MultiSet([1, 2] * 3).size()
2
@rtype: `int`
"""
return dict.__len__(self)
def __add__ (self, other) :
"""Adds two multisets.
>>> MultiSet([1, 2, 3]) + MultiSet([2, 3, 4])
MultiSet([...])
@param other: the multiset to add
@type other: `MultiSet`
@rtype: `MultiSet`
......@@ -387,18 +394,20 @@ class MultiSet (hdict) :
result._add(value, times)
return result
def __sub__ (self, other) :
"""Substract two multisets.
"""Substract two multisets. The second multiset must be
smaller than the first one.
>>> MultiSet([1, 2, 3]) - MultiSet([2, 3])
MultiSet([1])
>>> MultiSet([1, 2, 3]) - MultiSet([2, 3, 4])
Traceback (most recent call last):
...
ValueError: not enough occurrences
@param other: the multiset to substract
@type other: `MultiSet`
@rtype: `MultiSet`
@raise ValueError: when the second multiset is not smaller
"""
result = self.copy()
for value, times in dict.items(other) :
......@@ -406,13 +415,14 @@ class MultiSet (hdict) :
return result
def __mul__ (self, other) :
"""Multiplication by a non-negative integer.
>>> MultiSet([1, 2]) * 3
MultiSet([...])
>>> MultiSet([1, 2]) * 3 == MultiSet([1, 2] * 3)
True
@param other: the integer to multiply
@type other: non-negative `int`
@rtype: `MultiSet`
@raise ValueError: when `other` is negative
"""
if other < 0 :
raise ValueError("negative values are forbidden")
......@@ -426,12 +436,12 @@ class MultiSet (hdict) :
__hash__ = hdict.__hash__
def __eq__ (self, other) :
"""Test for equality.
>>> MultiSet([1, 2, 3]*2) == MultiSet([1, 2, 3]*2)
True
>>> MultiSet([1, 2, 3]) == MultiSet([1, 2, 3, 3])
False
@param other: the multiset to compare with
@type other: `MultiSet`
@rtype: `bool`
......@@ -448,20 +458,22 @@ class MultiSet (hdict) :
return True
def __ne__ (self, other) :
"""Test for difference.
>>> MultiSet([1, 2, 3]*2) != MultiSet([1, 2, 3]*2)
False
>>> MultiSet([1, 2, 3]) != MultiSet([1, 2, 3, 3])
True
@param other: the multiset to compare with
@type other: `MultiSet`
@rtype: `bool`
"""
return not(self == other)
def __lt__ (self, other) :
"""Test for strict inclusion.
"""Test for strict inclusion. A multiset `A` is strictly
included in a multiset `B` iff every element in `A` is also in
`B` but less repetitions `A` than in `B`.
>>> MultiSet([1, 2, 3]) < MultiSet([1, 2, 3, 4])
True
>>> MultiSet([1, 2, 3]) < MultiSet([1, 2, 3, 3])
......@@ -472,7 +484,7 @@ class MultiSet (hdict) :
False
>>> MultiSet([1, 2, 2]) < MultiSet([1, 2, 3, 4])
False
@param other: the multiset to compare with
@type other: `MultiSet`
@rtype: `bool`
......@@ -488,8 +500,8 @@ class MultiSet (hdict) :
result = True
return result or (dict.__len__(self) < dict.__len__(other))
def __le__ (self, other) :
"""Test for inclusion inclusion.
"""Test for inclusion.
>>> MultiSet([1, 2, 3]) <= MultiSet([1, 2, 3, 4])
True
>>> MultiSet([1, 2, 3]) <= MultiSet([1, 2, 3, 3])
......@@ -500,7 +512,7 @@ class MultiSet (hdict) :
False
>>> MultiSet([1, 2, 2]) <= MultiSet([1, 2, 3, 4])
False
@param other: the multiset to compare with
@type other: `MultiSet`
@rtype: `bool`
......@@ -514,7 +526,7 @@ class MultiSet (hdict) :
return True
def __gt__ (self, other) :
"""Test for strict inclusion.
>>> MultiSet([1, 2, 3, 4]) > MultiSet([1, 2, 3])
True
>>> MultiSet([1, 2, 3, 3]) > MultiSet([1, 2, 3])
......@@ -525,7 +537,7 @@ class MultiSet (hdict) :
False
>>> MultiSet([1, 2, 3, 4]) > MultiSet([1, 2, 2])
False
@param other: the multiset to compare with
@type other: `MultiSet`
@rtype: `bool`
......@@ -533,7 +545,7 @@ class MultiSet (hdict) :
return other.__lt__(self)
def __ge__ (self, other) :
"""Test for inclusion.
>>> MultiSet([1, 2, 3, 4]) >= MultiSet([1, 2, 3])
True
>>> MultiSet([1, 2, 3, 3]) >= MultiSet([1, 2, 3])
......@@ -544,18 +556,19 @@ class MultiSet (hdict) :
False
>>> MultiSet([1, 2, 3, 4]) >= MultiSet([1, 2, 2])
False
@param other: the multiset to compare with
@type other: `MultiSet`
@rtype: `bool`
"""
return other.__le__(self)
def domain (self) :
"""Return the domain of the multiset
"""Return the domain of the multiset, that is, the set of
elements that occurr at least once in the multiset.
>>> list(sorted((MultiSet([1, 2, 3, 4]) + MultiSet([1, 2, 3])).domain()))
[1, 2, 3, 4]
@return: the set of values in the domain
@rtype: `set`
"""
......@@ -564,21 +577,21 @@ class MultiSet (hdict) :
class Substitution (object) :
"""Map names to values or names, equals the identity where not
defined.
Substitutions support the `+` operation (union with consistency
check between the two operands) and the `*` operation which is the
composition of functions (`(f*g)(x)` is `f(g(x))`).
Several methods (eg, `image`) return lists instead of sets, this
avoids the restriction of having only hashable values in a
substitution image.
"""
def __init__ (self, *largs, **dargs) :
"""Initialise using a dictionnary as a mapping.
The expected arguments are any ones acceptables for
initializing a dictionnary.
>>> Substitution()
Substitution()
>>> Substitution(x=1, y=2)
......@@ -589,6 +602,7 @@ class Substitution (object) :
Substitution(...)
"""
self._dict = dict(*largs, **dargs)
# apidoc skip
def __hash__ (self) :
"""
>>> hash(Substitution(x=1, y=2)) == hash(Substitution(y=2, x=1))
......@@ -599,7 +613,8 @@ class Substitution (object) :
(hash(i) for i in self._dict.items()),
153913524)
def __eq__ (self, other) :
"""
"""Test for equality.
>>> Substitution(x=1, y=2) == Substitution(y=2, x=1)
True
>>> Substitution(x=1, y=2) == Substitution(y=1, x=1)
......@@ -610,7 +625,8 @@ class Substitution (object) :
except :
return False
def __ne__ (self, other) :
"""
"""Test for inequality.
>>> Substitution(x=1, y=2) != Substitution(y=2, x=1)
False
>>> Substitution(x=1, y=2) != Substitution(y=1, x=1)
......@@ -618,9 +634,10 @@ class Substitution (object) :
"""
return not self.__eq__(other)
__pnmltag__ = "substitution"
# apidoc skip
def __pnmldump__ (self) :
"""Dumps a substitution to a PNML tree
>>> Substitution(x=1, y=2).__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -647,7 +664,7 @@ class Substitution (object) :
</item>
</substitution>
</pnml>
@return: PNML representation
@rtype: `snakes.pnml.Tree`
"""
......@@ -658,14 +675,15 @@ class Substitution (object) :
Tree("value", None,
Tree.from_obj(value))))
return Tree(self.__pnmltag__, None, *nodes)
# apidoc skip
@classmethod
def __pnmlload__ (cls, tree) :
"""Load a substitution from its PNML representation
>>> t = Substitution(x=1, y=2).__pnmldump__()
>>> Substitution.__pnmlload__(t)
Substitution(...)
@param tree: the PNML tree to load
@type tree: `snakes.pnml.Tree`
@return: the substitution loaded
......@@ -678,43 +696,44 @@ class Substitution (object) :
result._dict[name] = value
return result
def items (self) :
"""Return the list of pairs (name, value).
"""Return the list of pairs `(name, value)` such that the
substitution maps each `name` to the correspondign `value`.
>>> Substitution(x=1, y=2).items()
[('...', ...), ('...', ...)]
@return: a list of pairs (name, value) for each mapped name
@rtype: `list`
"""
return list(self._dict.items())
def domain (self) :
"""Return the set of mapped names.
>>> list(sorted(Substitution(x=1, y=2).domain()))
['x', 'y']
@return: the set of mapped names
@rtype: `set`
"""
return set(self._dict.keys())
def image (self) :
"""Return the set of values associated to the names.
"""Return the list of values associated to the names.
>>> list(sorted(Substitution(x=1, y=2).image()))
[1, 2]
@return: the set of values associated to names
@rtype: `set`
@return: the list of values associated to names
@rtype: `list`
"""
return set(self._dict.values())
return list(self._dict.values())
def __contains__ (self, name) :
"""Test if a name is mapped.
"""Test if a name is mapped by the substitution.
>>> 'x' in Substitution(x=1, y=2)
True
>>> 'z' in Substitution(x=1, y=2)
False
@param name: the name to test
@type name: `str` (usually)
@return: a Boolean indicating whether this name is in the
......@@ -724,20 +743,20 @@ class Substitution (object) :
return name in self._dict
def __iter__ (self) :
"""Iterate over the mapped names.
>>> list(sorted(iter(Substitution(x=1, y=2))))
['x', 'y']
@return: an iterator over the domain of the substitution
@rtype: `generator`
"""
return iter(self._dict)
def __str__ (self) :
"""Return a compact string representation.
>>> str(Substitution(x=1, y=2))
'{... -> ..., ... -> ...}'
@return: a simple string representation
@rtype: `str`
"""
......@@ -745,10 +764,10 @@ class Substitution (object) :
for var, val in self.items()])
def __repr__ (self) :
"""Return a string representation suitable for `eval`.
>>> repr(Substitution(x=1, y=2))
'Substitution(...)'
@return: a precise string representation
@rtype: `str`
"""
......@@ -757,33 +776,35 @@ class Substitution (object) :
for var, val in self.items())))
def dict (self) :
"""Return the mapping as a dictionnary.
>>> Substitution(x=1, y=2).dict()
{'...': ..., '...': ...}
>>> Substitution(x=1, y=2).dict() == {'x': 1, 'y': 2}
True
@return: a dictionnary that does the same mapping as the
substitution
@rtype: `dict`
"""
return self._dict.copy()
def copy (self) :
"""Copy the mapping.
>>> Substitution(x=1, y=2).copy()
Substitution(...)
"""Return a distinct copy of the mapping.
>>> s1 = Substitution(x=1, y=2)
>>> s2 = s1.copy()
>>> s1 == s2 and s1 is not s2
True
@return: a copy of the substitution
@rtype: `Substitution`
"""
return Substitution(self.dict())
def __setitem__ (self, var, value) :
"""Assign an entry to the substitution
>>> s = Substitution()
>>> s['x'] = 42
>>> s
Substitution(x=42)
@param var: the name of the variable
@type var: `str`
@param value: the value to which `var` is bound
......@@ -792,16 +813,14 @@ class Substitution (object) :
self._dict[var] = value
def __getitem__ (self, var) :
"""Return the mapped value.
Fails with `DomainError` if `var` is not mapped.
>>> s = Substitution(x=1, y=2)
>>> s['x']
1
>>> try : s['z']
... except DomainError : print(sys.exc_info()[1])
unbound variable 'z'
@param var: the name of the variable
@type var: `str` (usually)
@return: the value that `var` maps to
......@@ -813,16 +832,15 @@ class Substitution (object) :
except KeyError :
raise DomainError("unbound variable '%s'" % var)
def __call__ (self, var) :
"""Return the mapped value.
Never fails but return `var` if it is not mapped.
"""Return the mapped value or `var` itself if it is not
mapped.
>>> s = Substitution(x=1, y=2)
>>> s('x')
1
>>> s('z')
'z'
@param var: the name of the variable
@type var: `str` (usually)
@return: the value that `var` maps to or `var` itself if it
......@@ -835,22 +853,21 @@ class Substitution (object) :
return var
def __add__ (self, other) :
"""Add two substitution.
Fails with `DomainError` if the two substitutions map a same
name to different values.
>>> s = Substitution(x=1, y=2) + Substitution(y=2, z=3)
>>> s('x'), s('y'), s('z')
(1, 2, 3)
>>> try : Substitution(x=1, y=2) + Substitution(y=4, z=3)
... except DomainError : print(sys.exc_info()[1])
conflict on 'y'
@param other: another substitution
@type other: `Substitution`
@return: the union of the substitutions
@rtype: `Substitution`
@raise DomainError: when one name is mapped to distinct values
@raise DomainError: when a name is inconsistently mapped
"""
for var in self :
if var in other and (self[var] != other[var]) :
......@@ -860,15 +877,15 @@ class Substitution (object) :
return s
def __mul__ (self, other) :
"""Compose two substitutions.
The composition of f and g is such that (f*g)(x) = f(g(x)).
The composition of `f` and `g` is such that `(f*g)(x)` is
`f(g(x))`.
>>> f = Substitution(a=1, d=3, y=5)
>>> g = Substitution(b='d', c=2, e=4, y=6)
>>> h = f*g
>>> h('a'), h('b'), h('c'), h('d'), h('e'), h('y'), h('x')
(1, 3, 2, 3, 4, 6, 'x')
@param other: another substitution
@type other: `Substitution`
@return: the composition of the substitutions
......@@ -886,13 +903,14 @@ class Symbol (object) :
"""If `export` is `True`, the created symbol is exported under
its name. If `export` is `False`, no export is made. Finally,
if `export` is a string, it specifies the name of the exported
symbol.
symbol. Exporting the name is made by adding it to the
caller's global dict.
@param name: the name (or value of the symbol)
@type name: `str`
@param export: the name under which the symbol is exported
@type export: `str` or `bool` or `None`
>>> Symbol('foo')
Symbol('foo')
>>> foo
......@@ -915,6 +933,7 @@ class Symbol (object) :
if export :
inspect.stack()[1][0].f_globals[export] = self
__pnmltag__ = "symbol"
# apidoc skip
def __pnmldump__ (self) :
"""
>>> Symbol('egg', 'spam').__pnmldump__()
......@@ -946,6 +965,7 @@ class Symbol (object) :
else :
children = [Tree.from_obj(self._export)]
return Tree(self.__pnmltag__, None, *children, **dict(name=self.name))
# apidoc skip
@classmethod
def __pnmlload__ (cls, tree) :
"""
......@@ -963,7 +983,9 @@ class Symbol (object) :
export = name
return cls(name, export)
def __eq__ (self, other) :
"""
"""Test for equality of two symbols, which is the equality of
their names.
>>> Symbol('foo', 'bar') == Symbol('foo')
True
>>> Symbol('egg') == Symbol('spam')
......@@ -975,13 +997,15 @@ class Symbol (object) :
except AttributeError :
return False
def __ne__ (self, other) :
"""
"""Test for inequality.
>>> Symbol('foo', 'bar') != Symbol('foo')
False
>>> Symbol('egg') != Symbol('spam')
True
"""
return not (self == other)
# apidoc skip
def __hash__ (self) :
"""
>>> hash(Symbol('foo', 'bar')) == hash(Symbol('foo'))
......@@ -989,13 +1013,15 @@ class Symbol (object) :
"""
return hash((self.__class__.__name__, self.name))
def __str__ (self) :
"""
"""Short string representation
>>> str(Symbol('foo'))
'foo'
"""
return self.name
def __repr__ (self) :
"""
"""String representation suitable for `eval`
>>> Symbol('foo')
Symbol('foo')
>>> Symbol('egg', 'spam')
......
"""Hashable mutable objets.
This module proposes hashable version of the mutable containers
"""This module proposes hashable version of the mutable containers
`list`, `dict` and `set`, called respectively `hlist`, `hdict` and
`hset`. After one object has been hashed, it becomes not mutable and
raises a ValueError if a method which changes the object (let call a
_mutation_ such a method) is invoqued. The object can be then un-
hashed by calling `unhash` on it so that it becomes mutable again.
`hset`.
After one object has been hashed, it becomes not mutable and raises a
`ValueError` if a method which changes the object (let call a
_mutation_ such a method) is invoqued. The object can be then
un-hashed by calling `unhash` on it so that it becomes mutable again.
Notice that this may cause troubles if the object is stored in a set
or dict which uses its hashcode to locate it. Notice also that
hashable containers cannot be hashed if they contain non hashable
......@@ -53,8 +53,9 @@ from operator import xor
from snakes.compat import *
def unhash (obj) :
"""Make the object hashable again.
"""Make the object mutable again. This should be used with
caution, especially if the object is stored in a dict or a set.
>>> l = hlist(range(3))
>>> _ = hash(l)
>>> l.append(3)
......@@ -63,7 +64,7 @@ def unhash (obj) :
ValueError: hashed 'hlist' object is not mutable
>>> unhash(l)
>>> l.append(3)
@param obj: any object
@type obj: `object`
"""
......@@ -72,6 +73,7 @@ def unhash (obj) :
except :
pass
# apidoc skip
def hashable (cls) :
"""Wrap methods in a class in order to make it hashable.
"""
......@@ -122,8 +124,8 @@ def hashable (cls) :
return cls
class hlist (list) :
"""Hashable lists.
"""Hashable lists. They support all standard methods from `list`.
>>> l = hlist(range(5))
>>> l
hlist([0, 1, 2, 3, 4])
......@@ -138,6 +140,7 @@ class hlist (list) :
>>> unhash(l)
>>> l.append(6)
"""
# apidoc stop
def __add__ (self, other) :
return self.__class__(list.__add__(self, other))
def __delitem__ (self, item) :
......@@ -200,8 +203,9 @@ class hlist (list) :
hlist = hashable(hlist)
class hdict (dict) :
"""Hashable dictionnaries.
"""Hashable dictionnaries. They support all standard methods from
`dict`.
>>> l = hlist(range(5))
>>> d = hdict([(l, 0)])
>>> d
......@@ -229,6 +233,7 @@ class hdict (dict) :
>>> d.pop(l)
0
"""
# apidoc stop
def __delitem__ (self, key) :
self.__mutable__()
dict.__delitem__(self, key)
......@@ -271,8 +276,8 @@ class hdict (dict) :
hdict = hashable(hdict)
class hset (set) :
"""Hashable sets.
"""Hashable sets. They support all standard methods from `set`.
>>> s = hset()
>>> l = hlist(range(5))
>>> s.add(l)
......@@ -298,6 +303,7 @@ class hset (set) :
>>> unhash(s)
>>> s.discard(l)
"""
# apidoc stop
def __and__ (self, other) :
return self.__class__(set.__and__(self, other))
def __hash__ (self) :
......
"""This package is dedicated to parse and work with various languages,
in particular Python itself and ABCD. These are mainly utilities for
internal use in SNAKES, however, they may be of general interest
independently of SNAKES.
@todo: add documentation about how to use parsing and similar services
"""
import sys
if sys.version_info[:2] in ((2, 6), (2, 7)) :
import ast
......@@ -14,9 +22,34 @@ else :
sys.modules["snkast"] = ast
"""### Module `ast` ###
The module `ast` exported by `snakes.lang` is similar to Python's
standard `ast` module (starting from version 2.6) but is available and
uniform on every implementation of Python supported by SNAKES: CPython
from 2.5, Jython and PyPy. (In general, these modules are available in
these implementation - except CPython 2.5 - but with slight
differences, so using `snakes.lang.ast` can be seen as a portable
implementation.)
Notice that this module is _not_ available for direct import but is
exposed as a member of `snakes.lang`. Moreover, when `snakes.lang` is
loaded, this module `ast` is also loaded as `snkast` in `sys.modules`,
this allows to have both versions from Python and SNAKES
simultaneously.
>>> import snakes.lang.ast as ast
ImportError ...
...
ImportError: No module named ast
>>> from snakes.lang import ast
>>> import snkast
"""
from . import unparse as _unparse
from snakes.compat import *
# apidoc skip
class Names (ast.NodeVisitor) :
def __init__ (self) :
ast.NodeVisitor.__init__(self)
......@@ -25,16 +58,24 @@ class Names (ast.NodeVisitor) :
self.names.add(node.id)
def getvars (expr) :
"""
"""Return the set of variables (or names in general) involved in a
Python expression.
>>> list(sorted(getvars('x+y<z')))
['x', 'y', 'z']
>>> list(sorted(getvars('x+y<z+f(3,t)')))
['f', 't', 'x', 'y', 'z']
@param expr: a Python expression
@type expr: `str`
@return: the set of variable names as strings
@rtype: `set`
"""
names = Names()
names.visit(ast.parse(expr))
return names.names - set(['None', 'True', 'False'])
# apidoc skip
class Unparser(_unparse.Unparser) :
boolops = {"And": 'and', "Or": 'or'}
def _Interactive (self, tree) :
......@@ -58,11 +99,13 @@ class Unparser(_unparse.Unparser) :
self.dispatch(tree.body)
self.leave()
# apidoc skip
def unparse (st) :
output = io.StringIO()
Unparser(st, output)
return output.getvalue().strip()
# apidoc skip
class Renamer (ast.NodeTransformer) :
def __init__ (self, map_names) :
ast.NodeTransformer.__init__(self)
......@@ -96,7 +139,8 @@ class Renamer (ast.NodeTransformer) :
ctx=ast.Load()), node)
def rename (expr, map={}, **ren) :
"""
"""Rename variables (ie, names) in a Python expression
>>> rename('x+y<z', x='t')
'((t + y) < z)'
>>> rename('x+y<z+f(3,t)', f='g', t='z', z='t')
......@@ -105,12 +149,22 @@ def rename (expr, map={}, **ren) :
'[(x + y) for x in range(3)]'
>>> rename('[x+y for x in range(3)]', y='z')
'[(x + z) for x in range(3)]'
@param expr: a Python expression
@type expr: `str`
@param map: a mapping from old to new names (`str` to `str`)
@type map: `dict`
@param ren: additional mapping of old to new names
@type ren: `str`
@return: the new expression
@rtype: `str`
"""
map_names = dict(map)
map_names.update(ren)
transf = Renamer(map_names)
return unparse(transf.visit(ast.parse(expr)))
# apidoc skip
class Binder (Renamer) :
def visit_Name (self, node) :
if node.id in self.map[-1] :
......@@ -119,7 +173,10 @@ class Binder (Renamer) :
return node
def bind (expr, map={}, **ren) :
"""
"""Replace variables (ie, names) in an expression with other
expressions. The replacements should be provided as `ast` nodes,
and so could be arbitrary expression.
>>> bind('x+y<z', x=ast.Num(n=2))
'((2 + y) < z)'
>>> bind('x+y<z', y=ast.Num(n=2))
......@@ -128,6 +185,15 @@ def bind (expr, map={}, **ren) :
'[(x + y) for x in range(3)]'
>>> bind('[x+y for x in range(3)]', y=ast.Num(n=2))
'[(x + 2) for x in range(3)]'
@param expr: a Python expression
@type expr: `str`
@param map: a mapping from old to new names (`str` to `ast.AST`)
@type map: `dict`
@param ren: additional mapping of old to new names
@type ren: `ast.AST`
@return: the new expression
@rtype: `str`
"""
map_names = dict(map)
map_names.update(ren)
......
......@@ -2,6 +2,8 @@
This module holds the various Petri net elements: arcs, places,
transitions, markings, nets themselves and marking graphs.
@todo: revise documentation
"""
import re, operator, inspect
......@@ -35,7 +37,7 @@ class Evaluator (object) :
exec(stmt, self._env, locals)
def attach (self, other) :
"""Make this instance use the namespaces of another
All the instances attached to this will also share the same
namespace.
"""
......@@ -88,20 +90,20 @@ class Evaluator (object) :
class NetElement (object) :
"""The base class for Petri net elements.
Attributes can be locked, forbidding their assignment or deletion.
For instance, a PetriNet object may lock the 'name' field of its
nodes in order to force the renaming through the method
'PetriNet.rename'.
FIXME: attribute locking is an unnecessarry complication, this
will be progressively removed.
This class is abstract and should not be instanciated.
"""
def __init__ (self) :
"""Abstract method
>>> try : NetElement()
... except NotImplementedError : print(sys.exc_info()[1])
abstract class
......@@ -110,7 +112,7 @@ class NetElement (object) :
def lock (self, name, owner, value=None) :
"""Lock the attribute `name` that becomes writeable only by
`owner`, set its value if `value` is not `None`.
@param name: the name of the attribute
@type name: `str`
@param owner: any value, usually the object instance that
......@@ -131,7 +133,7 @@ class NetElement (object) :
def unlock (self, name, owner, remove=False, value=None) :
"""Release a locked attribute, making it assignable or deletable
from everywhere.
@param name: the name of the attribute
@type name: `str`
@param owner: the value that was used to lock the attribute
......@@ -158,11 +160,11 @@ class NetElement (object) :
# raise ConstraintError, "'%s' not locked" % name
def __setattr__ (self, name, value) :
"""Assign attribute, taking care of locks
Check whether the assignment is allowed or not, the owner may
re-assign the attribute by using `lock` with its parameter
`value` instead of a direct assignment.
@param name: name of the attribute
@type name: `str`
@param value: value of the attribute
......@@ -174,11 +176,11 @@ class NetElement (object) :
self.__dict__[name] = value
def __delattr__ (self, name) :
"""Delete attribute, taking care of locks
Check whether the deletion is allowed or not, the owner may
delete the attribute by using `unlock` with its parameter
`remove` set to `True` instead of a direct deletion.
@param name: name of the attribute
@type name: `str`
"""
......@@ -193,49 +195,49 @@ class NetElement (object) :
class Token (NetElement) :
"""A container for one token value
This class is intended for internal use only in order to avoid
some confusion when inserting arbitrary values in a place. End
users will probably never need to use it.
"""
def __init__ (self, value) :
"""Initialise the token
>>> Token(42)
Token(42)
@param value: value of the token
@type value: `object`
"""
self.value = value
def __str__ (self) :
"""Simple string representation (that of the value)
>>> str(Token(42))
'42'
@return: simple string representation
@rtype: `str`
"""
return str(self.value)
def __repr__ (self) :
"""String representation suitable for `eval`
>>> repr(Token(42))
'Token(42)'
@return: precise string representation
@rtype: `str`
"""
return "%s(%s)" % (self.__class__.__name__, repr(self.value))
def __eq__ (self, other) :
"""Test token equality
>>> Token(42) == Token(10)
False
>>> Token(42) == Token(42)
True
@param other: second argument of equality
@type other: `Token`
@return: `True` if token values are equal, `False` otherwise
......@@ -247,12 +249,12 @@ class Token (NetElement) :
return False
def __ne__ (self, other) :
"""Test token inequality
>>> Token(42) != Token(10)
True
>>> Token(42) != Token(42)
False
@param other: second argument of inequality
@type other: `Token`
@return: `True` if token values differ, `False` otherwise
......@@ -261,12 +263,12 @@ class Token (NetElement) :
return not self.__eq__(other)
def __hash__ (self) :
"""Hash a token
This is done by hashing the value stored in the token
>>> hash(Token(42)) == hash(42)
True
@return: hash value for the token
@rtype: `int`
"""
......@@ -274,39 +276,39 @@ class Token (NetElement) :
class BlackToken (Token) :
"""The usual black token
>>> BlackToken()
dot
"""
def __init__ (self) :
"""Initialise the token
>>> BlackToken()
dot
"""
self.value = None
def __repr__ (self) :
"""String representation suitable for `eval`
Since `snakes.nets` sets `dot = BlackToken()`, the string
representation is `'dot'`.
>>> repr(BlackToken())
'dot'
@return: precise string representation
@rtype: `str`
"""
return "dot"
def __str__ (self) :
"""Simple string representation (that of the value)
Since `snakes.nets` sets `dot = BlackToken()`, the string
representation is `'dot'`.
>>> str(BlackToken())
'dot'
@return: simple string representation
@rtype: `str`
"""
......@@ -314,7 +316,7 @@ class BlackToken (Token) :
__pnmltag__ = "token"
def __pnmldump__ (self) :
"""Dumps value to PNML tree
>>> BlackToken().__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -325,7 +327,7 @@ class BlackToken (Token) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Load from a PNML tree
>>> t = BlackToken().__pnmldump__()
>>> BlackToken.__pnmlload__(t)
dot
......@@ -341,12 +343,12 @@ tBlackToken = Instance(BlackToken)
class ArcAnnotation (NetElement) :
"""An annotation on an arc.
On input arcs (from a place to a transition) annotations may be
values or variables, potentially nested in tuples. On output arcs
(from a transition to a place), expressions are allowed, which
corresponds to a computation.
A class attribute `input_allowed` is set to `True` or `False`
according to whether an annotation is allowed on input arcs.
"""
......@@ -356,7 +358,7 @@ class ArcAnnotation (NetElement) :
raise NotImplementedError("abstract method")
def substitute (self, binding) :
"""Substitutes the variables in the annotation.
@param binding: the substitution to apply
@type binding: `snakes.data.Substitution`
"""
......@@ -364,11 +366,11 @@ class ArcAnnotation (NetElement) :
def replace (self, old, new) :
"""Returns a copy of the annotation in which the `old` annotation
has been replaced by the `new` one.
With non composite annotation, this will return a copy of the
annotation if it is not the same as `old` otherwise it returns
`new`. Composite annotations will replace there components.
>>> Value(2).replace(Variable('x'), Value(5))
Value(2)
>>> Variable('x').replace(Variable('x'), Value(5))
......@@ -379,7 +381,7 @@ class ArcAnnotation (NetElement) :
Test(Value(2))
>>> Test(Variable('x')).replace(Variable('x'), Value(5))
Test(Value(5))
@param old: the annotation to be replaced
@type old: `ArcAnnotation`
@param new: the annotation to use instead of `old`
......@@ -394,21 +396,21 @@ class ArcAnnotation (NetElement) :
raise NotImplementedError("abstract method")
def __eq__ (self, other) :
"""Check for equality.
@param other: the other object to compare with
@type other: `ArcAnnotation`
"""
raise NotImplementedError("abstract method")
def __hash__ (self) :
"""Computes a hash of the annotation
Warning: must be consistent with equality, ie, two equal
values must have the same hash.
"""
raise NotImplementedError("abstract method")
def __ne__ (self, other) :
"""Check for difference.
@param other: the other object to compare with
@type other: `ArcAnnotation`
"""
......@@ -416,10 +418,10 @@ class ArcAnnotation (NetElement) :
def bind (self, binding) :
"""Return the value of the annotation evaluated through
`binding`.
>>> Expression('x+1').bind(Substitution(x=2))
Token(3)
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a value
......@@ -428,10 +430,10 @@ class ArcAnnotation (NetElement) :
def flow (self, binding) :
"""Return the flow of tokens implied by the annotation evaluated
through `binding`.
>>> Value(1).flow(Substitution(x=2))
MultiSet([1])
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a multiset of values
......@@ -441,10 +443,10 @@ class ArcAnnotation (NetElement) :
"""Return the list of modes under which an arc with the
annotation may flow the tokens in `values`. Each mode is a
substitution indicating how to bind the annotation.
>>> Variable('x').modes([1, 2, 3])
[Substitution(x=1), Substitution(x=2), Substitution(x=3)]
@param values: a collection of values
@type values: any collection like `set`, `list`, `tuple`, ...
@return: a list of substitutions
......@@ -457,17 +459,17 @@ class Value (ArcAnnotation) :
input_allowed = True
def __init__ (self, value) :
"""Initialise with the encapsulated value.
@param value: the value of the token.
@type value: any object
"""
self.value = value
def copy (self) :
"""Return a copy of the value.
>>> Value(5).copy()
Value(5)
@return: a copy of the value
@rtype: `Value`
"""
......@@ -475,7 +477,7 @@ class Value (ArcAnnotation) :
__pnmltag__ = "value"
def __pnmldump__ (self) :
"""Dump a `Value` as a PNML tree
>>> Value(3).__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -485,7 +487,7 @@ class Value (ArcAnnotation) :
</object>
</value>
</pnml>
@return: PNML tree
@rtype: `pnml.Tree`
"""
......@@ -493,11 +495,11 @@ class Value (ArcAnnotation) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Create a `Value` from a PNML tree
>>> t = Value(3).__pnmldump__()
>>> Value.__pnmlload__(t)
Value(3)
@param tree: the tree to convert
@type tree: `pnml.Tree`
@return: the value built
......@@ -506,14 +508,14 @@ class Value (ArcAnnotation) :
return cls(tree.child().to_obj())
def __str__ (self) :
"""Concise string representation
This is the string representation of the encapsulated value.
>>> print(str(Value(42)))
42
>>> print(str(Value('hello')))
'hello'
@return: concise string representation
@rtype: `str`
"""
......@@ -523,36 +525,36 @@ class Value (ArcAnnotation) :
return str(self.value)
def __repr__ (self) :
"""String representation suitable for `eval`
>>> print(repr(Value(42)))
Value(42)
>>> print(repr(Value('hello')))
Value('hello')
@return: precise string representation
@rtype: `str`
"""
return "%s(%s)" % (self.__class__.__name__, repr(self.value))
def vars (self) :
"""Return the list of variables involved in the value (empty).
>>> Value(42).vars()
[]
@return: empty list
@rtype: `list`
"""
return []
def __eq__ (self, other) :
"""Test for equality
This tests the equality of encapsulated values
>>> Value(42) == Value(42)
True
>>> Value(42) == Value(4)
False
@param other: second operand of the equality
@type other: `Value`
@return: `True` if encapsulated values are equal, `False`
......@@ -568,10 +570,10 @@ class Value (ArcAnnotation) :
def bind (self, binding) :
"""Return the value evaluated through `binding` (which is the
value itself).
>>> Value(1).bind(Substitution(x=2))
Token(1)
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a value
......@@ -580,13 +582,13 @@ class Value (ArcAnnotation) :
def modes (self, values) :
"""Return an empty binding (no binding needed for values) iff the
value is in `values`, raise ModeError otherwise.
>>> Value(1).modes([1, 2, 3])
[Substitution()]
>>> try : Value(1).modes([2, 3, 4])
... except ModeError : print(sys.exc_info()[1])
no match for value
@param values: a collection of values
@type values: any collection like `set`, `list`, `tuple`, ...
@return: a list of substitutions
......@@ -597,12 +599,12 @@ class Value (ArcAnnotation) :
raise ModeError("no match for value")
def substitute (self, binding) :
"""Bind the value (nothing to do).
>>> v = Value(5)
>>> v.substitute(Substitution(x=5))
>>> v
Value(5)
@param binding: a substitution
@type binding: `snakes.data.Substitution`
"""
......@@ -615,13 +617,13 @@ class Variable (ArcAnnotation) :
def __init__ (self, name) :
"""Variable names must start with a letter and may continue with
any alphanumeric character.
>>> Variable('x')
Variable('x')
>>> try : Variable('_test')
... except ValueError: print(sys.exc_info()[1])
not a variable name '_test'
@param name: the name of the variable
@type name: `str`
"""
......@@ -630,10 +632,10 @@ class Variable (ArcAnnotation) :
self.name = name
def copy (self) :
"""Return a copy of the variable.
>>> Variable('x').copy()
Variable('x')
@return: a copy of the variable
@rtype: `Variable`
"""
......@@ -641,7 +643,7 @@ class Variable (ArcAnnotation) :
__pnmltag__ = "variable"
def __pnmldump__ (self) :
"""Dump a `Variable` as a PNML tree
>>> Variable('x').__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -649,7 +651,7 @@ class Variable (ArcAnnotation) :
x
</variable>
</pnml>
@return: PNML tree
@rtype: `pnml.Tree`
"""
......@@ -657,11 +659,11 @@ class Variable (ArcAnnotation) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Create a `Variable` from a PNML tree
>>> t = Variable('x').__pnmldump__()
>>> Variable.__pnmlload__(t)
Variable('x')
@param tree: the tree to convert
@type tree: `pnml.Tree`
@return: the variable built
......@@ -670,34 +672,34 @@ class Variable (ArcAnnotation) :
return cls(tree.data)
def rename (self, name) :
"""Change the name of the variable.
>>> v = Variable('x')
>>> v.rename('y')
>>> v
Variable('y')
@param name: the new name of the variable
@type name: `str`
"""
self.__init__(name)
def __str__ (self) :
"""Concise string representation
This is the name of the variable
>>> str(Variable('x'))
'x'
@return: concise string representation
@rtype: `str`
"""
return self.name
def __repr__ (self) :
"""String representation suitable for `eval`
>>> Variable('x')
Variable('x')
@return: precise string representation
@rtype: `str`
"""
......@@ -705,10 +707,10 @@ class Variable (ArcAnnotation) :
def modes (self, values) :
"""Return the the list of substitutions mapping the name of the
variable to each value in `values`.
>>> Variable('x').modes(range(3))
[Substitution(x=0), Substitution(x=1), Substitution(x=2)]
@param values: a collection of values
@type values: any collection like `set`, `list`, `tuple`, ...
@return: a list of substitutions
......@@ -719,13 +721,13 @@ class Variable (ArcAnnotation) :
return result
def bind (self, binding) :
"""Return the value of the variable evaluated through `binding`.
>>> Variable('x').bind(Substitution(x=3))
Token(3)
>>> try : Variable('x').bind(Substitution(z=3))
... except DomainError : print(sys.exc_info()[1])
unbound variable 'x'
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a value
......@@ -733,7 +735,7 @@ class Variable (ArcAnnotation) :
return Token(binding[self.name])
def substitute (self, binding) :
"""Change the name according to `binding`.
>>> v = Variable('x')
>>> v.substitute(Substitution(x='y'))
>>> v
......@@ -744,29 +746,29 @@ class Variable (ArcAnnotation) :
>>> v.rename('z')
>>> v
Variable('z')
@param binding: a substitution
@type binding: `snakes.data.Substitution`
"""
self.rename(binding(self.name))
def vars (self) :
"""Return variable name in a list.
>>> Variable('x').vars()
['x']
@return: a list holding the name of the variable
@rtype: `list` holding a single `str`
"""
return [self.name]
def __eq__ (self, other) :
"""Test for equality
>>> Variable('x') == Variable('x')
True
>>> Variable('x') == Variable('y')
False
@param other: right operand of equality
@type other: `Variable`
@return: `True` if variables have the same name, `False`
......@@ -782,14 +784,14 @@ class Variable (ArcAnnotation) :
class Expression (ArcAnnotation) :
"""An arbitrary Python expression which may be evaluated.
Each expression has its private namespace which can be extended or
changed.
"""
input_allowed = False
def __init__ (self, expr) :
"""The expression is compiled so its syntax is checked.
@param expr: a Python expression suitable for `eval`
@type expr: `str`
"""
......@@ -803,10 +805,10 @@ class Expression (ArcAnnotation) :
__pnmltag__ = "expression"
def __pnmldump__ (self) :
"""Dump an `Expression` as a PNML tree
@return: PNML tree
@rtype: `pnml.Tree`
>>> Expression('x+1').__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -819,12 +821,12 @@ class Expression (ArcAnnotation) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Create an `Expression` from a PNML tree
@param tree: the tree to convert
@type tree: `pnml.Tree`
@return: the expression built
@rtype: `Expression`
>>> t = Expression('x+1').__pnmldump__()
>>> Expression.__pnmlload__(t)
Expression('x+1')
......@@ -836,7 +838,7 @@ class Expression (ArcAnnotation) :
return "%s(%s)" % (self.__class__.__name__, repr(self._str))
def bind (self, binding) :
"""Evaluate the expression through `binding`.
>>> e = Expression('x*(y-1)')
>>> e.bind(Substitution(x=2, y=3))
Token(4)
......@@ -845,7 +847,7 @@ class Expression (ArcAnnotation) :
... raise Exception()
... except NameError:
... pass
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a value
......@@ -861,15 +863,15 @@ class Expression (ArcAnnotation) :
return self.bind(binding).value
def substitute (self, binding) :
"""Substitute the variables according to 'binding'.
>>> e = Expression('x+1*xy')
>>> e.substitute(Substitution(x='y'))
>>> e
Expression('(y + (1 * xy))')
As one can see, the substitution adds a lot of parentheses and
spaces to the expression.
@param binding: a substitution
@type binding: `snakes.data.Substitution`
"""
......@@ -879,30 +881,30 @@ class Expression (ArcAnnotation) :
self._str = expr.strip()
def vars (self) :
"""Return the list of variable names involved in the expression.
>>> list(sorted(Expression('x+y').vars()))
['x', 'y']
@return: a list holding the names of the variables
@rtype: `list` of `str`
"""
return list(n for n in getvars(self._str) if n not in self.globals)
def __and__ (self, other) :
"""Implement `&` to perform `and` between two expressions.
It is not checked whether we combine boolean expressions or
not.
>>> Expression('x==1') & Expression('y==2')
Expression('(x==1) and (y==2)')
Minor optimisation are implemented:
>>> Expression('True') & Expression('x==y')
Expression('x==y')
>>> Expression('x==y') & Expression('True')
Expression('x==y')
@param other: an expression
@type other: `snakes.nets.Expression`
@return: an expression
......@@ -919,20 +921,20 @@ class Expression (ArcAnnotation) :
return result
def __or__ (self, other) :
"""Implement `|` to perform `or` between two expressions.
It is not checked whether we combine boolean expressions or
not.
>>> Expression('x==1') | Expression('y==2')
Expression('(x==1) or (y==2)')
Minor optimisation are implemented:
>>> Expression('True') | Expression('x==y')
Expression('True')
>>> Expression('x==y') | Expression('True')
Expression('True')
@param other: an expression
@type other: `snakes.nets.Expression`
@return: an expression
......@@ -947,20 +949,20 @@ class Expression (ArcAnnotation) :
return result
def __invert__ (self) :
"""Implement `~` to perform `not`.
It is not checked whether we negate a boolean expression or
not.
>>> ~Expression('x==1')
Expression('not (x==1)')
Minor optimisation are implemented:
>>> ~Expression('True')
Expression('False')
>>> ~Expression('False')
Expression('True')
@return: an expression
@rtype: `snakes.nets.Expression`
"""
......@@ -980,14 +982,14 @@ class Expression (ArcAnnotation) :
def let (**args) :
"""A dirty trick to allow side effects in expressions
Assignement takes place when method `bind` of the expression is
called. Assigned variables are then stored in the `Substitution`
passed to `bind`.
Use with care: sides effects are nasty tricks, you may get
unexpected results while playing with `let`.
>>> n = PetriNet('n')
>>> n.globals['let'] = let
>>> t = Transition('t', Expression('x is not None and let(foo=42)'))
......@@ -996,7 +998,7 @@ def let (**args) :
>>> n.add_input('p', 't', Variable('x'))
>>> t.modes() == [Substitution(x=dot, foo=42)]
True
@param args: a list of `name=value` to assign
@return: `True` if assignment could be made, `False` otherwise
@rtype: `bool`
......@@ -1012,21 +1014,21 @@ def let (**args) :
class MultiArc (ArcAnnotation) :
"""A collection of other annotations, allowing to consume or produce
several tokens at once.
Such a collection is allowed on input arcs only if so are all its
components.
"""
input_allowed = False
def __init__ (self, components) :
"""Initialise with the components of the tuple.
>>> MultiArc((Value(1), Expression('x+1')))
MultiArc((Value(1), Expression('x+1')))
>>> MultiArc((Value(1), Expression('x+1'))).input_allowed
False
>>> MultiArc((Value(1), Variable('x'))).input_allowed
True
@param components: a list of components
@type components: an iterable collection of `ArcAnnotation`
"""
......@@ -1046,10 +1048,10 @@ class MultiArc (ArcAnnotation) :
__pnmltag__ = "multiarc"
def __pnmldump__ (self) :
"""Dump a `MultiArc` as a PNML tree
@return: PNML tree
@rtype: `pnml.Tree`
>>> MultiArc([Value(3), Expression('x+1')]).__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -1070,12 +1072,12 @@ class MultiArc (ArcAnnotation) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Create a `MultiArc` from a PNML tree
@param tree: the tree to convert
@type tree: `pnml.Tree`
@return: the multiarc built
@rtype: `MultiArc`
>>> t = MultiArc((Value(3), Expression('x+1'))).__pnmldump__()
>>> MultiArc.__pnmlload__(t)
MultiArc((Value(3), Expression('x+1')))
......@@ -1133,11 +1135,11 @@ class MultiArc (ArcAnnotation) :
def bind (self, binding) :
"""Return the value of the annotation evaluated through
`binding`.
>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> t.bind(Substitution(x=2, y=3))
(Token(1), Token(2), Token(3))
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a tuple of value
......@@ -1147,7 +1149,7 @@ class MultiArc (ArcAnnotation) :
"""Return the list of modes under which an arc with the
annotation may flow the tokens in `values`. Each mode is a
substitution indicating how to bind the annotation.
>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> m = t.modes([1, 2, 3])
>>> len(m)
......@@ -1156,7 +1158,7 @@ class MultiArc (ArcAnnotation) :
True
>>> Substitution(y=2, x=3) in m
True
@param values: a collection of values
@type values: any collection like `set`, `list`, `tuple`, ...
@return: a list of substitutions
......@@ -1179,12 +1181,12 @@ class MultiArc (ArcAnnotation) :
return result
def substitute (self, binding) :
"""Substitute each component according to 'binding'.
>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> t.substitute(Substitution(x='z'))
>>> t
MultiArc((Value(1), Variable('z'), Variable('y')))
@param binding: a substitution
@type binding: `snakes.data.Substitution`
"""
......@@ -1192,11 +1194,11 @@ class MultiArc (ArcAnnotation) :
x.substitute(binding)
def vars (self) :
"""Return the list of variables involved in the components.
>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> list(sorted(t.vars()))
['x', 'y']
@return: the set of variables
@rtype: `set`
"""
......@@ -1207,9 +1209,9 @@ class MultiArc (ArcAnnotation) :
def replace (self, old, new) :
"""Returns a copy of the annotation in which the `old` annotation
has been replaced by the `new` one.
With `MultiArc`, replaces each occurrence of `old` by `new`
@param old: the annotation to be replaced
@type old: `ArcAnnotation`
@param new: the annotation to use instead of `old`
......@@ -1276,11 +1278,11 @@ class Tuple (MultiArc) :
def bind (self, binding) :
"""Return the value of the annotation evaluated through
`binding`.
>>> t = Tuple((Value(1), Variable('x'), Variable('y')))
>>> t.bind(Substitution(x=2, y=3))
Token((1, 2, 3))
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a tuple of value
......@@ -1312,10 +1314,10 @@ class Test (ArcAnnotation) :
__pnmltag__ = "test"
def __pnmldump__ (self) :
"""Dump a `Test` as a PNML tree
@return: PNML tree
@rtype: `pnml.Tree`
>>> Test(Value(3)).__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -1332,12 +1334,12 @@ class Test (ArcAnnotation) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Create a `Test` from a PNML tree
@param tree: the tree to convert
@type tree: `pnml.Tree`
@return: the test arc built
@rtype: `Test`
>>> t = Test(Value(3)).__pnmldump__()
>>> Test.__pnmlload__(t)
Test(Value(3))
......@@ -1369,7 +1371,7 @@ class Test (ArcAnnotation) :
return "%s(%s)" % (self.__class__.__name__, repr(self._annotation))
def substitute (self, binding) :
"""Substitutes the variables in the annotation.
@param binding: the substitution to apply
@type binding: `snakes.data.Substitution`
"""
......@@ -1388,10 +1390,10 @@ class Test (ArcAnnotation) :
def bind (self, binding) :
"""Return the value of the annotation evaluated through
`binding`.
>>> Test(Expression('x+1')).bind(Substitution(x=2))
Token(3)
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a value
......@@ -1400,10 +1402,10 @@ class Test (ArcAnnotation) :
def flow (self, binding) :
"""Return the flow of tokens implied by the annotation evaluated
through `binding`.
>>> Test(Value(1)).flow(Substitution())
MultiSet([])
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: an empty multiset
......@@ -1413,10 +1415,10 @@ class Test (ArcAnnotation) :
"""Return the list of modes under which an arc with the
annotation may flow the tokens in `values`. Each mode is a
substitution indicating how to bind the annotation.
>>> Test(Variable('x')).modes([1, 2, 3])
[Substitution(x=1), Substitution(x=2), Substitution(x=3)]
@param values: a collection of values
@type values: any collection like `set`, `list`, `tuple`, ...
@return: a list of substitutions
......@@ -1425,9 +1427,9 @@ class Test (ArcAnnotation) :
def replace (self, old, new) :
"""Returns a copy of the annotation in which the `old` annotation
has been replaced by the `new` one.
With `Test`, always returns `Test(new)`
@param old: the annotation to be replaced
@type old: `ArcAnnotation`
@param new: the annotation to use instead of `old`
......@@ -1451,10 +1453,10 @@ class Inhibitor (Test) :
__pnmltag__ = "inhibitor"
def __pnmldump__ (self) :
"""Dump a `Inhibitor` as a PNML tree
@return: PNML tree
@rtype: `pnml.Tree`
>>> Inhibitor(Value(3)).__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -1480,12 +1482,12 @@ class Inhibitor (Test) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Create a `Inhibitor` from a PNML tree
@param tree: the tree to convert
@type tree: `pnml.Tree`
@return: the inhibitor arc built
@rtype: `Inhibitor`
>>> t = Inhibitor(Value(3)).__pnmldump__()
>>> Inhibitor.__pnmlload__(t)
Inhibitor(Value(3))
......@@ -1520,7 +1522,7 @@ class Inhibitor (Test) :
self._annotation, self._condition)
def substitute (self, binding) :
"""Substitutes the variables in the annotation.
@param binding: the substitution to apply
@type binding: `snakes.data.Substitution`
"""
......@@ -1542,13 +1544,13 @@ class Inhibitor (Test) :
"""Return the value of the annotation evaluated through
`binding`, or raise `ValueError` of this binding does not
validate the condition.
>>> Inhibitor(Expression('x+1'), Expression('x>0')).bind(Substitution(x=2))
Token(3)
>>> try : Inhibitor(Expression('x+1'), Expression('x<0')).bind(Substitution(x=2))
... except ValueError : print(sys.exc_info()[1])
condition not True for {x -> 2}
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a value
......@@ -1561,7 +1563,7 @@ class Inhibitor (Test) :
"""Return the list of modes under which an arc with the
annotation may flow the tokens in `values`. Each mode is a
substitution indicating how to bind the annotation.
>>> try : Inhibitor(Value(1)).modes([1, 2, 3])
... except ModeError : print(sys.exc_info()[1])
inhibited by {}
......@@ -1579,7 +1581,7 @@ class Inhibitor (Test) :
>>> Inhibitor(MultiArc([Variable('x'), Variable('y')]),
... Expression('x==y')).modes([1, 2, 3])
[Substitution()]
@param values: a collection of values
@type values: any collection like `set`, `list`, `tuple`, ...
@return: a list of substitutions
......@@ -1595,7 +1597,7 @@ class Inhibitor (Test) :
def replace (self, old, new) :
"""Returns a copy of the annotation in which the `old` annotation
has been replaced by the `new` one.
@param old: the annotation to be replaced
@type old: `ArcAnnotation`
@param new: the annotation to use instead of `old`
......@@ -1666,13 +1668,13 @@ class Flush (ArcAnnotation) :
"""Return the list of modes under which an arc with the
annotation may flow the tokens in `values`. Each mode is a
substitution indicating how to bind the annotation.
>>> m = Flush('x').modes([1, 2, 3])
>>> m
[Substitution(x=MultiSet([...]))]
>>> m[0]['x'] == MultiSet([1, 2, 3])
True
@param values: a collection of values
@type values: any collection like `set`, `list`, `tuple`, ...
@return: a list of substitutions
......@@ -1681,10 +1683,10 @@ class Flush (ArcAnnotation) :
def flow (self, binding) :
"""Return the flow of tokens implied by the annotation evaluated
through `binding`.
>>> Flush('x').flow(Substitution(x=MultiSet([1, 2, 3]))) == MultiSet([1, 2, 3])
True
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a multiset
......@@ -1693,10 +1695,10 @@ class Flush (ArcAnnotation) :
def bind (self, binding) :
"""Return the value of the annotation evaluated through
`binding`.
>>> set(Flush('x').bind(Substitution(x=MultiSet([1, 2, 3])))) == set([Token(1), Token(2), Token(3)])
True
@param binding: a substitution
@type binding: `snakes.data.Substitution`
@return: a value
......@@ -1704,10 +1706,10 @@ class Flush (ArcAnnotation) :
return tuple(Token(v) for v in self._annotation.bind(binding).value)
def vars (self) :
"""Return the list of variable names involved in the arc.
>>> Flush('x').vars()
['x']
@return: list of variable names
@rtype: `list` of `str`
"""
......@@ -1719,13 +1721,13 @@ class Flush (ArcAnnotation) :
class Node (NetElement) :
"""A node in a Petri net.
This class is abstract and should not be instanciated, use `Place`
or `Transition` as concrete nodes.
"""
def rename (self, name) :
"""Change the name of the node.
>>> p = Place('p', range(3))
>>> p.rename('egg')
>>> p
......@@ -1734,7 +1736,7 @@ class Node (NetElement) :
>>> t.rename('spam')
>>> t
Transition('spam', Expression('x==1'))
@param name: the new name of the node
@type name: `str`
"""
......@@ -1744,14 +1746,14 @@ class Place (Node) :
"A place of a Petri net."
def __init__ (self, name, tokens=[], check=None) :
"""Initialise with name, tokens and typecheck.
`tokens` may be a single value or an iterable object. `check`
may be `None` (any token allowed) or a type from the module
typing.
>>> Place('p', range(3), tInteger)
Place('p', MultiSet([...]), Instance(int))
@param name: the name of the place
@type name: `str`
@param tokens: a collection of tokens that mark the place
......@@ -1770,7 +1772,7 @@ class Place (Node) :
self.add(tokens)
def copy (self, name=None) :
"""Return a copy of the place, with no arc attached.
>>> p = Place('p', range(3), tInteger)
>>> n = p.copy()
>>> n.name == 'p'
......@@ -1786,7 +1788,7 @@ class Place (Node) :
True
>>> n.checker()
Instance(int)
@param name: if not `None`, the name of the copy
@type name: `str`
@return: a copy of the place.
......@@ -1798,10 +1800,10 @@ class Place (Node) :
__pnmltag__ = "place"
def __pnmldump__ (self) :
"""Dump a `Place` as a PNML tree
@return: PNML tree
@rtype: `pnml.Tree`
>>> Place('p', [dot, dot], tBlackToken).__pnmldump__()
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -1873,14 +1875,14 @@ class Place (Node) :
return cls(tree["id"], [dot] * toks, tBlackToken)
def checker (self, check=None) :
"""Change or return the type of the place.
>>> p = Place('p', range(3), tInteger)
>>> p.checker()
Instance(int)
>>> p.checker(tAll)
>>> p.checker()
tAll
@param check: the new constraint for the place or `None` to
retreive the current constraint
@type check: `None` or a type from the module `typing`
......@@ -1894,13 +1896,13 @@ class Place (Node) :
self._check = check
def __contains__ (self, token) :
"""Check if a token is in the place.
>>> p = Place('p', range(3))
>>> 1 in p
True
>>> 5 in p
False
@param token: a token value
@type token: any type
@return: `True` if `token` is held by the place, `False`
......@@ -1910,17 +1912,17 @@ class Place (Node) :
return token in self.tokens
def __iter__ (self) :
"""Iterate over the tokens in the place.
>>> p = Place('p', range(3))
>>> list(sorted([tok for tok in p]))
[0, 1, 2]
@return: an iterator object
"""
return iter(self.tokens)
def is_empty (self) :
"""Check is the place is empty.
>>> p = Place('p', range(3))
>>> p.tokens == MultiSet([0, 1, 2])
True
......@@ -1929,7 +1931,7 @@ class Place (Node) :
>>> p.tokens = MultiSet()
>>> p.is_empty()
True
@return: `True` if the place is empty, `False` otherwise
@rtype: `bool`
"""
......@@ -1938,13 +1940,13 @@ class Place (Node) :
"""Check if the 'tokens' are allowed in the place. An exception
`ValueError` is raised whenever a forbidden token is
encountered.
>>> p = Place('p', [], tInteger)
>>> p.check([1, 2, 3])
>>> try : p.check(['forbidden!'])
... except ValueError : print(sys.exc_info()[1])
forbidden token 'forbidden!'
@param tokens: an iterable collection of tokens or a single
value
@type tokens: any type, iterable types (except `str`) will be
......@@ -1955,21 +1957,21 @@ class Place (Node) :
raise ValueError("forbidden token '%s'" % (t,))
def __str__ (self) :
"""Return the name of the place.
>>> p = Place('Great place!')
>>> str(p)
'Great place!'
@return: the name of the place
@rtype: `str`
"""
return self.name
def __repr__ (self) :
"""Return a textual representation of the place.
>>> repr(Place('p', range(3), tInteger))
"Place('p', MultiSet([...]), Instance(int))"
@return: a string suitable to `eval` representing the place
@rtype: `str`
"""
......@@ -1979,14 +1981,14 @@ class Place (Node) :
repr(self._check))
def add (self, tokens) :
"""Add tokens to the place.
>>> p = Place('p')
>>> p.tokens == MultiSet([])
True
>>> p.add(range(3))
>>> p.tokens == MultiSet([0, 1, 2])
True
@param tokens: a collection of tokens to be added to the place
@type tokens: any type, iterable types (except `str`) will be
considered as collections of values
......@@ -1995,14 +1997,14 @@ class Place (Node) :
self.tokens.add(iterate(tokens))
def remove (self, tokens) :
"""Remove tokens from the place.
>>> p = Place('p', list(range(3)) * 2)
>>> p.tokens == MultiSet([0, 0, 1, 1, 2, 2])
True
>>> p.remove(range(3))
>>> p.tokens == MultiSet([0, 1, 2])
True
@param tokens: a collection of tokens to be removed from the
place
@type tokens: any type, iterable types (except `str`) will be
......@@ -2011,7 +2013,7 @@ class Place (Node) :
self.tokens.remove(tokens)
def empty (self) :
"""Remove all the tokens.
>>> p = Place('p', list(range(3)) * 2)
>>> p.tokens == MultiSet([0, 0, 1, 1, 2, 2])
True
......@@ -2022,7 +2024,7 @@ class Place (Node) :
self.tokens = MultiSet()
def reset (self, tokens) :
"""Replace the marking with 'tokens'.
>>> p = Place('p', list(range(3)) * 2)
>>> p.tokens == MultiSet([0, 0, 1, 1, 2, 2])
True
......@@ -2037,12 +2039,12 @@ class Transition (Node) :
"A transition in a Petri net."
def __init__ (self, name, guard=None) :
"""Initialise with the name and the guard.
If `guard` is `None`, `True` is assumed instead.
>>> Transition('t')
Transition('t', Expression('True'))
@param name: the name of the transition
@type name: `str`
@param guard: the guard of the transition
......@@ -2057,13 +2059,13 @@ class Transition (Node) :
self.name = name
def copy (self, name=None) :
"""Return a copy of the transition, with no arc attached.
>>> t = Transition('t', Expression('x==1'))
>>> t.copy()
Transition('t', Expression('x==1'))
>>> t.copy('x')
Transition('x', Expression('x==1'))
@param name: if not `None`, the name if of the copy
@type name: `str`
@return: a copy of the transition.
......@@ -2114,21 +2116,21 @@ class Transition (Node) :
return cls(tree["id"])
def __str__ (self) :
"""Return the name of the transition.
>>> t = Transition('What a transition!')
>>> str(t)
'What a transition!'
@return: the name of the transition
@rtype: `str`
"""
return self.name
def __repr__ (self) :
"""Return a textual representation of the transition.
>>> repr(Transition('t', Expression('x==1')))
"Transition('t', Expression('x==1'))"
@return: a string suitable to `eval` representing the
transition
@rtype: `str`
......@@ -2138,7 +2140,7 @@ class Transition (Node) :
repr(self.guard))
def add_input (self, place, label) :
"""Add an input arc from `place` labelled by `label`.
>>> t = Transition('t')
>>> t.add_input(Place('p'), Variable('x'))
>>> t.input()
......@@ -2146,7 +2148,7 @@ class Transition (Node) :
>>> try : t.add_input(Place('x'), Expression('x+y'))
... except ConstraintError : print(sys.exc_info()[1])
'Expression' objects not allowed on input arcs
@param place: the input place
@type place: `Place`
@param label: the arc label
......@@ -2161,7 +2163,7 @@ class Transition (Node) :
self._input[place] = label
def remove_input (self, place) :
"""Remove the input arc from `place`.
>>> t = Transition('t')
>>> p = Place('p')
>>> t.add_input(p, Variable('x'))
......@@ -2170,7 +2172,7 @@ class Transition (Node) :
>>> t.remove_input(p)
>>> t.input()
[]
@param place: the input place
@type place: `Place`
"""
......@@ -2180,24 +2182,24 @@ class Transition (Node) :
raise ConstraintError("not connected to '%s'" % place.name)
def input (self) :
"""Return the list of input arcs.
>>> t = Transition('t')
>>> t.add_input(Place('p'), Variable('x'))
>>> t.input()
[(Place('p', MultiSet([]), tAll), Variable('x'))]
@return: a list of pairs (place, label).
@rtype: `[(Place, ArcAnnotation)]`
"""
return list(self._input.items())
def add_output (self, place, label) :
"""Add an output arc from `place` labelled by `label`.
>>> t = Transition('t')
>>> t.add_output(Place('p'), Expression('x+1'))
>>> t.output()
[(Place('p', MultiSet([]), tAll), Expression('x+1'))]
@param place: the output place
@type place: `Place`
@param label: the arc label
......@@ -2209,7 +2211,7 @@ class Transition (Node) :
self._output[place] = label
def remove_output (self, place) :
"""Remove the output arc to `place`.
>>> t = Transition('t')
>>> p = Place('p')
>>> t.add_output(p, Variable('x'))
......@@ -2218,7 +2220,7 @@ class Transition (Node) :
>>> t.remove_output(p)
>>> t.output()
[]
@param place: the output place
@type place: `Place`
"""
......@@ -2228,25 +2230,25 @@ class Transition (Node) :
raise ConstraintError("not connected to '%s'" % place.name)
def output (self) :
"""Return the list of output arcs.
>>> t = Transition('t')
>>> t.add_output(Place('p'), Expression('x+1'))
>>> t.output()
[(Place('p', MultiSet([]), tAll), Expression('x+1'))]
@return: a list of pairs (place, label).
@rtype: `[(Place, ArcAnnotation)]`
"""
return list(self._output.items())
def _check (self, binding, tokens, input) :
"""Check `binding` against the guard and tokens flow.
If `tokens` is `True`, check whether the flow of tokens can be
provided by the input places. If `input` is `True`, check if
the evaluation of the inputs arcs respect the types of the
input places. In any case, this is checked for the output
places.
>>> t = Transition('t', Expression('x>0'))
>>> p = Place('p', [], tInteger)
>>> t.add_input(p, Variable('x'))
......@@ -2268,7 +2270,7 @@ class Transition (Node) :
False
>>> t._check(s, True, True)
False
@param binding: a valuation of the variables on the transition
@type binding: `data.Substitution`
@param tokens: whether it should be checked that input places
......@@ -2299,11 +2301,11 @@ class Transition (Node) :
return True
def activated (self, binding) :
"""Check if `binding` activates the transition.
This is the case if the guard evaluates to `True` and if the
types of the places are respected. Note that the presence of
enough tokens is not required.
>>> t = Transition('t', Expression('x>0'))
>>> p = Place('p', [], tInteger)
>>> t.add_input(p, Variable('x'))
......@@ -2313,17 +2315,17 @@ class Transition (Node) :
False
>>> t.activated(Substitution(x=3.14))
False
@param binding: a valuation of the variables on the transition
@type binding: `Substitution`
"""
return self._check(binding, False, True)
def enabled (self, binding) :
"""Check if `binding` enables the transition.
This is the case if the transition is activated and if the
input places hold enough tokens to allow the firing.
>>> t = Transition('t', Expression('x>0'))
>>> p = Place('p', [0], tInteger)
>>> t.add_input(p, Variable('x'))
......@@ -2332,7 +2334,7 @@ class Transition (Node) :
>>> p.add(1)
>>> t.enabled(Substitution(x=1))
True
@param binding: a valuation of the variables on the transition
@type binding: `Substitution`
"""
......@@ -2340,7 +2342,7 @@ class Transition (Node) :
def vars (self) :
"""Return the set of variables involved in the guard, input and
output arcs of the transition.
>>> t = Transition('t', Expression('z is not None'))
>>> px = Place('px')
>>> t.add_input(px, Variable('x'))
......@@ -2348,7 +2350,7 @@ class Transition (Node) :
>>> t.add_output(py, Variable('y'))
>>> list(sorted(t.vars()))
['x', 'y', 'z']
@return: the set of variables names
@rtype: a `set` of `str`
"""
......@@ -2360,10 +2362,10 @@ class Transition (Node) :
return v
def substitute (self, binding) :
"""Substite all the annotations arround the transition.
`binding` is used to substitute the guard and all the labels
on the arcs attached to the transition.
>>> t = Transition('t', Expression('z is not None'))
>>> px = Place('px')
>>> t.add_input(px, Variable('x'))
......@@ -2376,7 +2378,7 @@ class Transition (Node) :
[(Place('px', MultiSet([]), tAll), Variable('a'))]
>>> t.output()
[(Place('py', MultiSet([]), tAll), Variable('b'))]
@param binding: a substitution from variables to variables
(not values)
@type binding: `Substitution`
......@@ -2388,12 +2390,12 @@ class Transition (Node) :
label.substitute(binding)
def modes (self) :
"""Return the list of bindings which enable the transition.
Note that the modes are usually considered to be the list of
bindings that _activate_ a transitions. However, this list is
generally infinite so we retricted ourselves to _actual
modes_, taking into account the present tokens.
>>> t = Transition('t', Expression('x!=y'))
>>> px = Place('px', range(2))
>>> t.add_input(px, Variable('x'))
......@@ -2406,12 +2408,12 @@ class Transition (Node) :
True
>>> Substitution(y=1, x=0) in m
True
Note also that modes cannot be computed with respect to output
arcs: indeed, only input arcs allow for concretely associate
values to variables; on the other hand, binding an output arc
would require to solve the equation provided by the guard.
>>> t = Transition('t', Expression('x!=y'))
>>> px = Place('px', range(2))
>>> t.add_input(px, Variable('x'))
......@@ -2422,7 +2424,7 @@ class Transition (Node) :
... raise Exception()
... except NameError :
... pass
@return: a list of substitutions usable to fire the transition
@rtype: a `list` of `Substitution`
"""
......@@ -2447,10 +2449,10 @@ class Transition (Node) :
return result
def flow (self, binding) :
"""Return the token flow for a firing with `binding`.
The flow is represented by a pair `(in, out)`, both being
instances of the class `Marking`.
>>> t = Transition('t', Expression('x!=1'))
>>> px = Place('px', range(3))
>>> t.add_input(px, Variable('x'))
......@@ -2463,7 +2465,7 @@ class Transition (Node) :
transition not enabled for {x -> 1}
>>> t.flow(Substitution(x=2))
(Marking({'px': MultiSet([2])}), Marking({'py': MultiSet([3])}))
@param binding: a substitution from variables to values (not
variables)
@type binding: `Substitution`
......@@ -2480,7 +2482,7 @@ class Transition (Node) :
raise ValueError("transition not enabled for %s" % binding)
def fire (self, binding) :
"""Fire the transition with `binding`.
>>> t = Transition('t', Expression('x!=1'))
>>> px = Place('px', range(3))
>>> t.add_input(px, Variable('x'))
......@@ -2499,7 +2501,7 @@ class Transition (Node) :
True
>>> py.tokens == MultiSet([1, 3])
True
@param binding: a substitution from variables to values (not
variables)
@type binding: `Substitution`
......@@ -2518,10 +2520,10 @@ class Transition (Node) :
class Marking (hdict) :
"""A marking of a Petri net.
This is basically a `snakes.hashables.hdict` mapping place names
to multisets of tokens.
The parameters for the constructor must be given in a form
suitable for initialising a `hdict` with place names as keys and
multisets as values. Places not given in the marking are assumed
......@@ -2587,16 +2589,16 @@ class Marking (hdict) :
for child in tree.get_children("place")))
def __call__ (self, place) :
"""Return the marking of `place`.
The empty multiset is returned if `place` is not explicitely
given in the marking.
>>> m = Marking(p1=MultiSet([1]), p2=MultiSet([2]))
>>> m('p1')
MultiSet([1])
>>> m('p')
MultiSet([])
@param place: a place name
@type place: `str`
@return: the marking of `place`
......@@ -2608,7 +2610,7 @@ class Marking (hdict) :
return MultiSet()
def copy (self) :
"""Copy a marking
>>> m = Marking(p1=MultiSet([1]), p2=MultiSet([2]))
>>> m.copy() == Marking({'p2': MultiSet([2]), 'p1': MultiSet([1])})
True
......@@ -2619,10 +2621,10 @@ class Marking (hdict) :
return result
def __add__ (self, other) :
"""Addition of markings.
>>> Marking(p1=MultiSet([1]), p2=MultiSet([2])) + Marking(p2=MultiSet([2]), p3=MultiSet([3])) == Marking({'p2': MultiSet([2, 2]), 'p3': MultiSet([3]), 'p1': MultiSet([1])})
True
@param other: another marking
@type other: `Marking`
@return: the addition of the two markings
......@@ -2637,7 +2639,7 @@ class Marking (hdict) :
return result
def __sub__ (self, other) :
"""Substraction of markings.
>>> Marking(p1=MultiSet([1]), p2=MultiSet([2, 2])) - Marking(p2=MultiSet([2]))
Marking({'p2': MultiSet([2]), 'p1': MultiSet([1])})
>>> try : Marking(p1=MultiSet([1]), p2=MultiSet([2])) - Marking(p2=MultiSet([2, 2]))
......@@ -2646,7 +2648,7 @@ class Marking (hdict) :
>>> try : Marking(p1=MultiSet([1]), p2=MultiSet([2])) - Marking(p3=MultiSet([3]))
... except DomainError : print(sys.exc_info()[1])
'p3' absent from the marking
@param other: another marking
@type other: `Marking`
@return: the difference of the two markings
......@@ -2664,14 +2666,14 @@ class Marking (hdict) :
__hash__ = hdict.__hash__
def __eq__ (self, other) :
"""Test for equality (same places with the same tokens).
>>> Marking(p=MultiSet([1])) == Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1])) == Marking(p=MultiSet([1, 1]))
False
>>> Marking(p1=MultiSet([1])) == Marking(p2=MultiSet([1]))
False
@param other: another marking
@type other: `Marking`
@return: `True` if the markings are equal, `False` otherwise
......@@ -2688,14 +2690,14 @@ class Marking (hdict) :
return True
def __ne__ (self, other) :
"""Test if two markings differ.
>>> Marking(p=MultiSet([1])) != Marking(p=MultiSet([1]))
False
>>> Marking(p=MultiSet([1])) != Marking(p=MultiSet([1, 1]))
True
>>> Marking(p1=MultiSet([1])) != Marking(p2=MultiSet([1]))
True
@param other: another marking
@type other: `Marking`
@return: `True` if the markings differ, `False` otherwise
......@@ -2705,10 +2707,10 @@ class Marking (hdict) :
def __ge__ (self, other) :
"""Test if the marking `self` is greater than or equal to
`other`.
This is the case when any place in `other` is also in `self`
and is marked with a smaller or equal multiset of tokens.
>>> Marking(p=MultiSet([1])) >= Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1, 1])) >= Marking(p=MultiSet([1]))
......@@ -2719,7 +2721,7 @@ class Marking (hdict) :
False
>>> Marking(p=MultiSet([1]), r=MultiSet([2])) >= Marking(p=MultiSet([1, 1]))
False
@param other: another marking
@type other: `Marking`
@return: `True` if `self >= other`, `False` otherwise
......@@ -2733,11 +2735,11 @@ class Marking (hdict) :
return True
def __gt__ (self, other) :
"""Test if the marking `self` is strictly greater than `other`.
This is the case when any place in `other` is also in `self`
and either one place in `other` is marked with a smaller
multiset of tokens or `slef` has more places than `other`.
>>> Marking(p=MultiSet([1])) > Marking(p=MultiSet([1]))
False
>>> Marking(p=MultiSet([1, 1])) > Marking(p=MultiSet([1]))
......@@ -2748,7 +2750,7 @@ class Marking (hdict) :
False
>>> Marking(p=MultiSet([1]), r=MultiSet([2])) > Marking(p=MultiSet([1, 1]))
False
@param other: another marking
@type other: `Marking`
@return: `True` if `self > other`, `False` otherwise
......@@ -2766,10 +2768,10 @@ class Marking (hdict) :
def __le__ (self, other) :
"""Test if the marking `self` is smaller than or equal to
`other`.
This is the case when any place in `self` is also in `other`
and is marked with a smaller or equal multiset of tokens.
>>> Marking(p=MultiSet([1])) <= Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1])) <= Marking(p=MultiSet([1, 1]))
......@@ -2780,7 +2782,7 @@ class Marking (hdict) :
False
>>> Marking(p=MultiSet([1, 1])) <= Marking(p=MultiSet([1]), r=MultiSet([2]))
False
@param other: another marking
@type other: `Marking`
@return: `True` if `self <= other`, `False` otherwise
......@@ -2794,12 +2796,12 @@ class Marking (hdict) :
return True
def __lt__ (self, other) :
"""Test if the marking `self` is strictly smaller than `other`.
This is the case when any place in `self` is also in `other`
and either one place in `self` marked in self with a strictly
smaller multiset of tokens or `other` has more places than
`self`.
>>> Marking(p=MultiSet([1])) < Marking(p=MultiSet([1]))
False
>>> Marking(p=MultiSet([1])) < Marking(p=MultiSet([1, 1]))
......@@ -2810,7 +2812,7 @@ class Marking (hdict) :
False
>>> Marking(p=MultiSet([1, 1])) < Marking(p=MultiSet([1]), r=MultiSet([2]))
False
@param other: another marking
@type other: `Marking`
@return: `True` if `self < other`, `False` otherwise
......@@ -2832,20 +2834,20 @@ class Marking (hdict) :
class PetriNet (object) :
"""A Petri net.
As soon as nodes are added to a `PetriNet`, they are handled by
name instead of by the `Place` or `Transition` instance. For
instance:
>>> n = PetriNet('N')
>>> t = Transition('t')
>>> n.add_transition(t)
>>> n.has_transition('t') # use 't' and not t
True
Because nodes can be retreived through their name, this allows to
rewrite the above code as:
>>> n = PetriNet('N')
>>> n.add_transition(Transition('t'))
>>> n.has_transition('t') # use 't' and not t
......@@ -2855,10 +2857,10 @@ class PetriNet (object) :
"""
def __init__ (self, name) :
"""Initialise with the name.
>>> PetriNet('N')
PetriNet('N')
@param name: the name of the net
@type name: `str`
"""
......@@ -2876,12 +2878,12 @@ class PetriNet (object) :
def copy (self, name=None) :
"""Return a complete copy of the net, including places,
transitions, arcs and declarations.
>>> PetriNet('N').copy()
PetriNet('N')
>>> PetriNet('N').copy('x')
PetriNet('x')
@param name: if not `None`, the name of the copy
@type name: `str`
@return: a copy of the net
......@@ -2905,12 +2907,12 @@ class PetriNet (object) :
@classmethod
def _pnml_dump_arc (cls, label) :
"""Dump an arc to PNML
@param label: the arc label to dump
@type label: `ArcAnnotation`
@return: a tuple of PNML trees
@rtype: `tuple` of `pnml.Tree`
>>> PetriNet._pnml_dump_arc(Value(dot))
(<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -2969,10 +2971,10 @@ class PetriNet (object) :
return (Tree("inscription", None, Tree.from_obj(label)),)
def __pnmldump__ (self) :
"""Dump a `PetriNet` as a PNML tree
@return: PNML tree
@rtype: `pnml.Tree`
>>> n = PetriNet('N')
>>> n.declare('x = "foo" + "bar"')
>>> n.globals['y'] = 'egg'
......@@ -3057,12 +3059,12 @@ class PetriNet (object) :
@classmethod
def __pnmlload__ (cls, tree) :
"""Create a `PetriNet` from a PNML tree
@param tree: the tree to convert
@type tree: `pnml.Tree`
@return: the net built
@rtype: `PetriNet`
>>> old = PetriNet('N')
>>> old.declare('x = "foo" + "bar"')
>>> old.globals['y'] = 'egg'
......@@ -3124,46 +3126,46 @@ class PetriNet (object) :
return result
def __repr__ (self) :
"""Return a string suitable for `eval` to represent the net.
>>> repr(PetriNet('N'))
"PetriNet('N')"
@return: the textual representation of the net
@rtype: `str`
"""
return "%s(%s)" % (self.__class__.__name__, repr(self.name))
def __str__ (self) :
"""Return the name of the net.
>>> str(PetriNet('N'))
'N'
@return: the name if the net
@rtype: `str`
"""
return self.name
def rename (self, name) :
"""Change the name of the net.
>>> n = PetriNet('N')
>>> n.rename('Long name!')
>>> n
PetriNet('Long name!')
@param name: the new name
@type name: `str`
"""
self.name = name
def has_place (self, name) :
"""Check if there is a place called `name` in the net.
>>> n = PetriNet('N')
>>> n.has_place('p')
False
>>> n.add_place(Place('p'))
>>> n.has_place('p')
True
@param name: the name of the searched place
@type name: `str`
@return: `True` if a place called `name` is present in the
......@@ -3173,14 +3175,14 @@ class PetriNet (object) :
return name in self._place
def has_transition (self, name) :
"""Check if there is a transition called `name` in the net.
>>> n = PetriNet('N')
>>> n.has_transition('t')
False
>>> n.add_transition(Transition('t'))
>>> n.has_transition('t')
True
@param name: the name of the searched transition
@type name: `str`
@return: `True` if a transition called `name` is present in
......@@ -3190,7 +3192,7 @@ class PetriNet (object) :
return name in self._trans
def has_node (self, name) :
"""Check if there is a transition called `name` in the net.
>>> n = PetriNet('N')
>>> n.has_node('t')
False
......@@ -3202,7 +3204,7 @@ class PetriNet (object) :
True
>>> n.has_node('p')
True
@param name: the name of the searched node
@type name: `str`
@return: `True` if a node called `name` is present in the net,
......@@ -3212,7 +3214,7 @@ class PetriNet (object) :
return name in self._node
def __contains__ (self, name) :
"""`name in net` is a shortcut for `net.has_node(name)`
>>> n = PetriNet('N')
>>> 't' in n
False
......@@ -3224,7 +3226,7 @@ class PetriNet (object) :
True
>>> 'p' in n
True
@param name: the name of the searched node
@type name: `str`
@return: `True` if a node called `name` is present in the net,
......@@ -3234,7 +3236,7 @@ class PetriNet (object) :
return name in self._node
def declare (self, statements, locals=None) :
"""Execute `statements` in the global dictionnary of the net.
This has also on the dictionnarie of the instances of
`Expression` in the net (guards of the transitions and labels
on the arcs) so the declarations have an influence over the
......@@ -3242,7 +3244,7 @@ class PetriNet (object) :
the declared objects will be placed in it instead of the
global dictionnary, see the documentation of Python for more
details about local and global environments.
>>> n = PetriNet('N')
>>> t = Transition('t', Expression('x==0'))
>>> n.add_transition(t)
......@@ -3265,7 +3267,7 @@ class PetriNet (object) :
>>> t.fire(Substitution())
>>> n.place('p')
Place('p', MultiSet([0...]), tAll)
@param statements: a Python instruction suitable to `exec`
@type statements: `str` or `code`
@param locals: a `dict` used as locals when `statements` is
......@@ -3276,10 +3278,10 @@ class PetriNet (object) :
self._declare.append(statements)
def add_place (self, place) :
"""Add a place to the net.
Each node in a net must have a name unique to this net, which
is checked when it is added.
>>> n = PetriNet('N')
>>> try : n.place('p')
... except ConstraintError : print(sys.exc_info()[1])
......@@ -3292,7 +3294,7 @@ class PetriNet (object) :
>>> try : n.add_place(Place('p'))
... except ConstraintError : print(sys.exc_info()[1])
place 'p' exists
@param place: the place to add
@type place: `Place`
"""
......@@ -3308,7 +3310,7 @@ class PetriNet (object) :
place.lock("post", self, {})
def remove_place (self, name) :
"""Remove a place (given by its name) from the net.
>>> n = PetriNet('N')
>>> try : n.remove_place('p')
... except ConstraintError : print(sys.exc_info()[1])
......@@ -3320,7 +3322,7 @@ class PetriNet (object) :
>>> try : n.place('p')
... except ConstraintError : print(sys.exc_info()[1])
place 'p' not found
@param name: the name of the place to remove
@type name: `str`
"""
......@@ -3340,10 +3342,10 @@ class PetriNet (object) :
place.unlock("net", self, remove=True)
def add_transition (self, trans) :
"""Add a transition to the net.
Each node in a net must have a name unique to this net, which
is checked when it is added.
>>> n = PetriNet('N')
>>> try : n.transition('t')
... except ConstraintError : print(sys.exc_info()[1])
......@@ -3354,7 +3356,7 @@ class PetriNet (object) :
>>> try : n.add_transition(Transition('t'))
... except ConstraintError : print(sys.exc_info()[1])
transition 't' exists
@param trans: the transition to add
@type trans: `Transition`
"""
......@@ -3371,7 +3373,7 @@ class PetriNet (object) :
trans.guard.globals.attach(self.globals)
def remove_transition (self, name) :
"""Remove a transition (given by its name) from the net.
>>> n = PetriNet('N')
>>> try : n.remove_transition('t')
... except ConstraintError : print(sys.exc_info()[1])
......@@ -3383,7 +3385,7 @@ class PetriNet (object) :
>>> try : n.transition('t')
... except ConstraintError : print(sys.exc_info()[1])
transition 't' not found
@param name: the name of the transition to remove
@type name: `str`
"""
......@@ -3404,7 +3406,7 @@ class PetriNet (object) :
trans.guard.globals.detach(self.globals)
def place (self, name=None) :
"""Return one (if `name` is not `None`) or all the places.
>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
......@@ -3415,7 +3417,7 @@ class PetriNet (object) :
place 'p' not found
>>> n.place()
[Place('p2', MultiSet([]), tAll), Place('p1', MultiSet([]), tAll)]
@param name: the name of the place to retrieve or `None` to
get the list of all the places in the net
@type name: `str` or `None`
......@@ -3431,7 +3433,7 @@ class PetriNet (object) :
raise ConstraintError("place '%s' not found" % name)
def transition (self, name=None) :
"""Return one (if `name` is not `None`) or all the transitions.
>>> n = PetriNet('N')
>>> n.add_transition(Transition('t1'))
>>> n.add_transition(Transition('t2'))
......@@ -3442,7 +3444,7 @@ class PetriNet (object) :
transition 't' not found
>>> n.transition()
[Transition('t2', Expression('True')), Transition('t1', Expression('True'))]
@param name: the name of the transition to retrieve or `None`
to get the list of all the transitions in the net
@type name: `str` or `None`
......@@ -3459,7 +3461,7 @@ class PetriNet (object) :
raise ConstraintError("transition '%s' not found" % name)
def node (self, name=None) :
"""Return one (if `name` is not `None`) or all the nodes.
>>> n = PetriNet('N')
>>> n.add_transition(Transition('t'))
>>> n.add_place(Place('p'))
......@@ -3470,7 +3472,7 @@ class PetriNet (object) :
node 'x' not found
>>> list(sorted(n.node(), key=str))
[Place('p', MultiSet([]), tAll), Transition('t', Expression('True'))]
@param name: the name of the node to retrieve or `None` to get
the list of all the nodes in the net
@type name: `str` or `None`
......@@ -3487,9 +3489,9 @@ class PetriNet (object) :
raise ConstraintError("node '%s' not found" % name)
def add_input (self, place, trans, label) :
"""Add an input arc between `place` and `trans` (nodes names).
An input arc is directed from a place toward a transition.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', range(3)))
>>> n.add_transition(Transition('t', Expression('x!=1')))
......@@ -3509,7 +3511,7 @@ class PetriNet (object) :
>>> try : n.add_input('p', 't', Value(42))
... except ConstraintError: print(sys.exc_info()[1])
already connected to 'p'
@param place: the name of the place to connect
@type place: `str`
@param trans: the name of the transition to connect
......@@ -3534,7 +3536,7 @@ class PetriNet (object) :
label.globals.attach(self.globals)
def remove_input (self, place, trans) :
"""Remove an input arc between `place` and `trans` (nodes names).
>>> n = PetriNet('N')
>>> n.add_place(Place('p', range(3)))
>>> n.add_transition(Transition('t', Expression('x!=1')))
......@@ -3547,7 +3549,7 @@ class PetriNet (object) :
>>> try : n.remove_input('p', 't')
... except ConstraintError : print(sys.exc_info()[1])
not connected to 'p'
@param place: the name of the place to disconnect
@type place: `str`
@param trans: the name of the transition to disconnect
......@@ -3569,9 +3571,9 @@ class PetriNet (object) :
l.globals.detach(self.globals)
def add_output (self, place, trans, label) :
"""Add an output arc between `place` and `trans` (nodes names).
An output arc is directed from a transition toward a place.
>>> n = PetriNet('N')
>>> n.add_place(Place('p'))
>>> n.add_transition(Transition('t'))
......@@ -3586,7 +3588,7 @@ class PetriNet (object) :
>>> try : n.add_output('p', 't', Value(42))
... except ConstraintError : print(sys.exc_info()[1])
already connected to 'p'
@param place: the name of the place to connect
@type place: `str`
@param trans: the name of the transition to connect
......@@ -3610,7 +3612,7 @@ class PetriNet (object) :
def remove_output (self, place, trans) :
"""Remove an output arc between `place` and `trans` (nodes
names).
>>> n = PetriNet('N')
>>> n.add_place(Place('p'))
>>> n.add_transition(Transition('t'))
......@@ -3623,7 +3625,7 @@ class PetriNet (object) :
>>> try : n.remove_output('p', 't')
... except ConstraintError : print(sys.exc_info()[1])
not connected to 'p'
@param place: the name of the place to disconnect
@type place: `str`
@param trans: the name of the transition to disconnect
......@@ -3645,9 +3647,9 @@ class PetriNet (object) :
l.globals.detach(self.globals)
def pre (self, nodes) :
"""Return the set of nodes names preceeding `nodes`.
`nodes` can be a single node name ot a list of nodes names.
>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
......@@ -3659,7 +3661,7 @@ class PetriNet (object) :
True
>>> n.pre(['p1', 'p2']) == set(['t2', 't1'])
True
@param nodes: a single node name or a list of node names
@type nodes: `str` or a `list` of `str`
@return: a set of node names
......@@ -3674,9 +3676,9 @@ class PetriNet (object) :
return result
def post (self, nodes) :
"""Return the set of nodes names succeeding `nodes`.
`nodes` can be a single node name ot a list of nodes names.
>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
......@@ -3688,7 +3690,7 @@ class PetriNet (object) :
True
>>> n.post(['t1', 't2']) == set(['p2', 'p1'])
True
@param nodes: a single node name or a list of node names
@type nodes: `str` or a `list` of `str`
@return: a set of node names
......@@ -3703,7 +3705,7 @@ class PetriNet (object) :
return result
def get_marking (self) :
"""Return the current marking of the net, omitting empty places.
>>> n = PetriNet('N')
>>> n.add_place(Place('p0', range(0)))
>>> n.add_place(Place('p1', range(1)))
......@@ -3711,7 +3713,7 @@ class PetriNet (object) :
>>> n.add_place(Place('p3', range(3)))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1]), 'p3': MultiSet([0, 1, 2]), 'p1': MultiSet([0])})
True
@return: the current marking
@rtype: `Marking`
"""
......@@ -3720,14 +3722,14 @@ class PetriNet (object) :
if not place.is_empty())
def _set_marking (self, marking) :
"""Assign a marking to the net.
Places not listed in the marking are considered empty, the
corresponding place in the net is thus emptied. If the marking
has places that do not belong to the net, these are ignored
(as in the last instruction below). If an error occurs during
the assignment, the marking is left inconsistent. You should
thus use `set_marking` unless you will have no error.
>>> n = PetriNet('N')
>>> n.add_place(Place('p0', range(5)))
>>> n.add_place(Place('p1'))
......@@ -3746,7 +3748,7 @@ class PetriNet (object) :
forbidden token '3.14'
>>> n.get_marking() # inconsistent
Marking({'p2': MultiSet([1])})
@param marking: the new marking
@type marking: `Marking`
"""
......@@ -3757,13 +3759,13 @@ class PetriNet (object) :
place.empty()
def set_marking (self, marking) :
"""Assign a marking to the net.
Places not listed in the marking are considered empty, the
corresponding place in the net is thus emptied. If the marking
has places that do not belong to the net, these are ignored
(as in the last instruction below). If an error occurs during
the assignment, the marking is left unchanged.
>>> n = PetriNet('N')
>>> n.add_place(Place('p0', range(5), tInteger))
>>> n.add_place(Place('p1'))
......@@ -3782,7 +3784,7 @@ class PetriNet (object) :
forbidden token '3.14'
>>> n.get_marking() # unchanged
Marking({})
@param marking: the new marking
@type marking: `Marking`
"""
......@@ -3794,11 +3796,11 @@ class PetriNet (object) :
raise
def add_marking (self, marking) :
"""Add a marking to the current one.
If an error occurs during the process, the marking is left
unchanged. Places in the marking that do not belong to the net
are ignored.
>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2', range(3)))
......@@ -3807,7 +3809,7 @@ class PetriNet (object) :
>>> n.add_marking(Marking(p1=MultiSet(range(2)), p2=MultiSet([1])))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1, 1, 2]), 'p1': MultiSet([0, 1])})
True
@param marking: the new marking
@type marking: `Marking`
"""
......@@ -3821,11 +3823,11 @@ class PetriNet (object) :
raise
def remove_marking (self, marking) :
"""Substract a marking from the current one.
If an error occurs during the process, the marking is left
unchanged. Places in the marking that do not belong to the net
are ignored.
>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2', range(3)))
......@@ -3839,7 +3841,7 @@ class PetriNet (object) :
>>> n.remove_marking(Marking(p2=MultiSet([1])))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 2])})
True
@param marking: the new marking
@type marking: `Marking`
"""
......@@ -3853,7 +3855,7 @@ class PetriNet (object) :
raise
def rename_node (self, old, new) :
"""Change the name of a node.
>>> n = PetriNet('N')
>>> n.add_place(Place('p'))
>>> n.add_transition(Transition('t'))
......@@ -3873,7 +3875,7 @@ class PetriNet (object) :
>>> try : n.rename_node('old_t', 'new_t')
... except ConstraintError : print(sys.exc_info()[1])
node 'old_t' not found
@param old: the current name of the node
@type old: `str`
@param new: the new name for the node
......@@ -3903,7 +3905,7 @@ class PetriNet (object) :
del other.pre[old]
def copy_place (self, source, targets) :
"""Make copies of the `source` place (use place names).
>>> n = PetriNet('N')
>>> n.add_place(Place('p', range(3)))
>>> n.add_transition(Transition('t'))
......@@ -3917,7 +3919,7 @@ class PetriNet (object) :
Place('ter', MultiSet([...]), tAll)]
>>> list(sorted(n.pre('t'), key=str))
['bis', 'more', 'p', 'ter']
@param source: the name of the place to copy
@type source: `str`
@param targets: a name or a list of names for the copie(s)
......@@ -3932,7 +3934,7 @@ class PetriNet (object) :
self.add_output(target, trans, label.copy())
def copy_transition (self, source, targets) :
"""Make copies of the `source` transition (use transition names).
>>> n = PetriNet('N')
>>> n.add_transition(Transition('t', Expression('x==1')))
>>> n.add_place(Place('p'))
......@@ -3946,7 +3948,7 @@ class PetriNet (object) :
Transition('ter', Expression('x==1'))]
>>> list(sorted(n.post('p')))
['bis', 'more', 't', 'ter']
@param source: the name of the transition to copy
@type source: `str`
@param targets: a name or a list of names for the copie(s)
......@@ -3961,11 +3963,11 @@ class PetriNet (object) :
self.add_output(place, target, label.copy())
def merge_places (self, target, sources) :
"""Create a new place by merging those in `sources`.
Markings are added, place types are 'or'ed and arcs labels are
joinded into multi-arcs, the sources places are not removed.
Use places names.
>>> n = PetriNet('n')
>>> n.add_place(Place('p1', [1], tInteger))
>>> n.add_place(Place('p2', [2.0], tFloat))
......@@ -3984,7 +3986,7 @@ class PetriNet (object) :
True
>>> n.node('p').checker()
(Instance(int) | Instance(float))
@param target: the name of the created place
@type target: `str`
@param sources: the list of places names to be merged (or a
......@@ -4027,11 +4029,11 @@ class PetriNet (object) :
self.add_output(target, trans, MultiArc(labels))
def merge_transitions (self, target, sources) :
"""Create a new transition by merging those in `sources`.
Guards are 'and'ed and arcs labels are joinded into multi-
arcs, the sources transitions are not removed. Use transitions
names.
>>> n = PetriNet('n')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
......@@ -4049,7 +4051,7 @@ class PetriNet (object) :
Transition('t', Expression('(x==1) and (y==2)'))
>>> n.node('t').post
{'p2': MultiArc((Value(2.0), Value(2.0))), 'p1': Value(1)}
@param target: the name of the created transition
@type target: `str`
@param sources: the list of transitions names to be merged (or
......@@ -4100,10 +4102,10 @@ class StateGraph (object) :
"The graph of reachable markings of a net."
def __init__ (self, net) :
"""Initialise with the net.
>>> StateGraph(PetriNet('N')).net
PetriNet('N')
@param net: the Petri net whose graph has to be computed
@type net: `PetriNet`
"""
......@@ -4128,10 +4130,10 @@ class StateGraph (object) :
return self._last
def goto (self, state) :
"""Change the current state to another (given by its number).
This also changes the marking of the net consistently. Notice
that the state may not exist yet.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4145,7 +4147,7 @@ class StateGraph (object) :
>>> g.goto(2)
>>> g.net.get_marking()
Marking({'p': MultiSet([2])})
@param state: the number of the state to go to
@type state: non-negative `int`
"""
......@@ -4159,7 +4161,7 @@ class StateGraph (object) :
raise ValueError("unknown state")
def current (self) :
"""Return the number of the current state.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4168,7 +4170,7 @@ class StateGraph (object) :
>>> g = StateGraph(n)
>>> g.current()
0
@return: the number of the current state
@rtype: non-negative `int`
"""
......@@ -4224,12 +4226,12 @@ class StateGraph (object) :
return marking in self._state
def successors (self, state=None) :
"""Return the successors of the current state.
The value returned is a dictionnary mapping the numbers of
successor states to pairs (trans, mode) representing the name
of the transition and the binding needed to reach the new
state.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4240,7 +4242,7 @@ class StateGraph (object) :
>>> g.goto(2)
>>> g.successors()
{3: (Transition('t', Expression('x<5')), Substitution(x=2))}
@return: the dictionnary of successors and transitions to them
@rtype: `dict` mapping non-negative `int` to `tuple` holding a
`str` and a `Substitution`
......@@ -4252,12 +4254,12 @@ class StateGraph (object) :
for label in self._succ[state][succ])
def predecessors (self, state=None) :
"""Return the predecessors states.
The returned value is as in `successors`. Notice that if the
graph is not complete, this value may be wrong: states
computed in the future may lead to the current one thus
becoming one of its predecessors.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4268,7 +4270,7 @@ class StateGraph (object) :
>>> g.goto(2)
>>> g.predecessors()
{1: (Transition('t', Expression('x<5')), Substitution(x=1))}
@return: the dictionnary of predecessors and transitions to
them
@rtype: `dict` mapping non-negative `int` to `tuple` holding a
......@@ -4306,7 +4308,7 @@ class StateGraph (object) :
return
def __len__ (self) :
"""Return the number of states currently reached.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4323,21 +4325,21 @@ class StateGraph (object) :
5 states known
6 states known
6 states known
@return: the number of states generated at call time
@rtype: non-negative `int`
"""
return len(self._done) + len(self._todo)
def __iter__ (self) :
"""Iterate over the reachable states (numbers).
If needed, the successors of each state are computed just
before it is yield. So, if the graph is not complete, getting
the predecessors may be wrong during the iteration.
**Warning:** the net may have an infinite state graph, which
is not checked. So you may enter an infinite iteration.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4376,13 +4378,13 @@ class StateGraph (object) :
self.goto(current)
def _build (self, stop=None) :
"""Build the complete reachability graph.
The graph is build using a breadth first exploration as the
newly computed states are put in a queue.
**Warning:** this may be infinite! No check of this is
performed.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4404,7 +4406,7 @@ class StateGraph (object) :
pass
def completed (self) :
"""Check if all the reachable markings have been explored.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4419,7 +4421,7 @@ class StateGraph (object) :
3 False
4 False
5 True
@return: `True` if the graph has been completely computed,
`False` otherwise
@rtype: `bool`
......@@ -4428,7 +4430,7 @@ class StateGraph (object) :
def todo (self) :
"""Return the number of states whose successors are not yet
computed.
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
......@@ -4443,7 +4445,7 @@ class StateGraph (object) :
3 1
4 1
5 0
@return: the number of pending states
@rtype: non-negative `int`
"""
......
"""A plugins system.
"""This package implements SNAKES plugin system. SNAKES plugins
themselves are available as modules within the package.
Examples below are based on plugin `hello` that is distributed with
SNAKES to be used as an exemple of how to build a plugin. It extends
class `PetriNet` adding a method `hello` that says hello displaying
the name of the net.
## Loading plugins ##
The first example shows how to load a plugin: we load
`snakes.plugins.hello` and plug it into `snakes.nets`, which results
in a new module that actually `snakes.nets` extended by
in a new module that is actually `snakes.nets` extended by
`snakes.plugins.hello`.
>>> import snakes.plugins as plugins
......@@ -18,11 +26,6 @@ The next example shows how to simulate the effect of `import module`:
we give to `load` a thrid argument that is the name of the created
module, from which it becomes possible to import names or `*`.
**Warning:** this feature will not work `load` is not called from the
module where we then do the `from ... import ...`. This is exactly the
same when, from a module `foo` that you load a module `bar`: if `bar`
loads other modules they will not be imported in `foo`.
>>> plugins.load('hello', 'snakes.nets', 'another_version')
<module ...>
>>> from another_version import PetriNet
......@@ -33,12 +36,23 @@ Hello from another net
>>> n.hello()
Hi, this is yet another net!
How to define a plugin is explained in the example `hello`.
The last example shows how to load several plugins at once, instead of
giving one plugin name, we just need to give a list of plugin names.
>>> plugins.load(['hello', 'pos'], 'snakes.nets', 'mynets')
<module ...>
>>> from mynets import PetriNet
>>> n = PetriNet('a net')
>>> n.hello() # works thanks to plugin `hello`
Hello from a net
>>> n.bbox() # works thanks to plugin `pos`
((0, 0), (0, 0))
"""
import imp, sys, inspect
from functools import wraps
# apidoc skip
def update (module, objects) :
"""Update a module content
"""
......@@ -54,48 +68,17 @@ def update (module, objects) :
else :
raise ValueError("cannot plug '%r'" % obj)
def build (name, module, *objects) :
"""Builds an extended module.
The parameter `module` is exactly that taken by the function
`extend` of a plugin. This list argument `objects` holds all the
objects, constructed in `extend`, that are extensions of objects
from `module`. The resulting value should be returned by `extend`.
@param name: the name of the constructed module
@type name: `str`
@param module: the extended module
@type module: `module`
@param objects: the sub-objects
@type objects: each is a class object
@return: the new module
@rtype: `module`
"""
result = imp.new_module(name)
result.__dict__.update(module.__dict__)
update(result, objects)
result.__plugins__ = (module.__dict__.get("__plugins__",
(module.__name__,))
+ (name,))
for obj in objects :
if inspect.isclass(obj) :
obj.__plugins__ = result.__plugins__
return result
def load (plugins, base, name=None) :
"""Load plugins.
`plugins` can be a single plugin name or module or a list of such
values. If `name` is not `None`, the extended module is loaded ad
`name` in `sys.modules` as well as in the global environment from
which `load` was called.
"""Load plugins, `plugins` can be a single plugin name, a module
or a list of such values. If `name` is not `None`, the extended
module is loaded as `name` in `sys.modules` as well as in the
global environment from which `load` was called.
@param plugins: the module that implements the plugin, or its
name, or a collection of such values
@type plugins: `str` or `module`, or a `list`/`tuple`/... of such
values
name, or a collection (eg, list, tuple) of such values
@type plugins: `object`
@param base: the module being extended or its name
@type base: `str` or `module`
@type base: `object`
@param name: the name of the created module
@type name: `str`
@return: the extended module
......@@ -125,17 +108,45 @@ def load (plugins, base, name=None) :
inspect.stack()[1][0].f_globals[name] = result
return result
"""## Creating plugins ###
We show now how to develop a plugin that allows instances of
`PetriNet` to say hello: a new method `PetriNet.hello` is added and
the constructor `PetriNet.__init__` is added a keyword argument
`hello` for the message to print when calling method `hello`.
Defining a plugins required to write a module with a function called
`extend` that takes as its single argument the module to be extended.
Inside this function, extensions of the classes in the module are
defined as normal sub-classes. Function `extend` returns the extended
classes. A decorator called `plugin` must be used, it also allows to
resolve plugin dependencies and conflicts.
"""
# apidoc include "hello.py" lang="python"
"""Note that, when extending an existing method like `__init__` above,
we have to take care that you may be working on an already extended
class, consequently, we cannot know how its arguments have been
changed already. So, we must always use those from the unextended
method plus `**args`. Then, we remove from the latter what your plugin
needs and pass the remaining to the method of the base class if we
need to call it (which is usually the case). """
def plugin (base, depends=[], conflicts=[]) :
"""Decorator for extension functions
@param base: name of base module (usually 'snakes.nets')
@type base: str
@param depends: list of plugins on which this one depends
@type depends: list of str
@param conflicts: list of plugins with which this one conflicts
@type conflicts: list of str
@param base: name of base module (usually 'snakes.nets') that the
plugin extends
@type base: `str`
@param depends: list of plugin names (as `str`) this one depends
on, prefix `snakes.plugins.` may be omitted
@type depends: `list`
@param conflicts: list of plugin names with which this one
conflicts
@type conflicts: `list`
@return: the appropriate decorator
@rtype: function
@rtype: `decorator`
"""
def wrapper (fun) :
@wraps(fun)
......@@ -164,9 +175,39 @@ def plugin (base, depends=[], conflicts=[]) :
return extend
return wrapper
# apidoc skip
def new_instance (cls, obj) :
"""Create a copy of `obj` which is an instance of `cls`
"""
result = object.__new__(cls)
result.__dict__.update(obj.__dict__)
return result
# apidoc skip
def build (name, module, *objects) :
"""Builds an extended module.
The parameter `module` is exactly that taken by the function
`extend` of a plugin. This list argument `objects` holds all the
objects, constructed in `extend`, that are extensions of objects
from `module`. The resulting value should be returned by `extend`.
@param name: the name of the constructed module
@type name: `str`
@param module: the extended module
@type module: `module`
@param objects: the sub-objects
@type objects: each is a class object
@return: the new module
@rtype: `module`
"""
result = imp.new_module(name)
result.__dict__.update(module.__dict__)
update(result, objects)
result.__plugins__ = (module.__dict__.get("__plugins__",
(module.__name__,))
+ (name,))
for obj in objects :
if inspect.isclass(obj) :
obj.__plugins__ = result.__plugins__
return result
......
"""
@todo: revise (actually make) documentation
"""
import snakes.plugins
from snakes.plugins import new_instance
from snakes.pnml import Tree
......
......@@ -26,6 +26,8 @@
>>> n.layout()
>>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str))
False
@todo: revise documentation
"""
import os, os.path, subprocess, collections
......
"""An example plugin that allows instances class `PetriNet` to say hello.
A new method `hello` is added. The constructor is added a keyword
argument `hello` that must be the `str` to print when calling `hello`,
with one `%s` that will be replaced by the name of the net when
`hello` is called.
Defining a plugins need writing a module with a single function called
`extend` that takes a single argument that is the module to be
extended.
Inside the function, extensions of the classes in the module are
defined as normal sub-classes.
The function `extend` should return the extended module created by
`snakes.plugins.build` that takes as arguments: the name of the
extended module, the module taken as argument and the sub-classes
defined (expected as a list argument `*args` in no special order).
If the plugin depends on other plugins, for instance `foo` and `bar`,
the function `extend` should be decorated by `@depends('foo', 'bar')`.
Read the source code of this module to have an example
"""
"""An example plugin that allows instances of `PetriNet` to
say hello. The source code can be used as a starting
example."""
import snakes.plugins
@snakes.plugins.plugin("snakes.nets")
def extend (module) :
"""Extends `module`
"""
"Extends `module`"
class PetriNet (module.PetriNet) :
"""Extension of the class `PetriNet` in `module`
"""
"Extension of the class `PetriNet` in `module`"
def __init__ (self, name, **args) :
"""When extending an existing method, take care that you may
be working on an already extended class, so you so not
know how its arguments have been changed. So, always use
those from the unextended class plus `**args`, remove from
it what your plugin needs and pass it to the method of the
extended class if you need to call it.
"""Add new keyword argument `hello`
>>> PetriNet('N').hello()
Hello from N
>>> PetriNet('N', hello='Hi! This is %s...').hello()
Hi! This is N...
@param args: plugin options
@keyword hello: the message to print, with `%s` where the
net name should appear.
@keyword hello: the message to print, with
`%s` where the net name should appear.
@type hello: `str`
"""
self._hello = args.pop("hello", "Hello from %s")
module.PetriNet.__init__(self, name, **args)
def hello (self) :
"""A new method `hello`
>>> n = PetriNet('N')
>>> n.hello()
Hello from N
"""
"Ask the net to say hello"
print(self._hello % self.name)
return PetriNet
......
"""A plugin to add labels to nodes and nets.
@todo: revise (actually make) documentation
"""
from snakes.plugins import plugin, new_instance
......
......@@ -65,6 +65,8 @@ Buffer('buffer')
(1, 2)
>>> n._declare
['global x; x=1']
@todo: revise documentation
"""
import snakes.plugins
......
......@@ -42,6 +42,8 @@ Position(1, 3)
>>> n.transpose()
>>> n.node('t01').pos
Position(-3, 1)
@todo: revise documentation
"""
from snakes import SnakesError
......
"""
@todo: revise (actually make) documentation
"""
from snakes.plugins import plugin
from snakes.pnml import Tree, loads, dumps
import imp, sys, socket, traceback, operator
......
......@@ -12,6 +12,8 @@ Several status are defined by default: `entry`, `internal`, `exit`,
>>> n.add_place(Place('p1'), status=status.entry)
>>> n.place('p1')
Place('p1', MultiSet([]), tAll, status=Status('entry'))
@todo: revise documentation
"""
import operator, weakref
......
......@@ -6,8 +6,6 @@ The plugin proposes a generalisation of the M-nets synchronisation in
that it does not impose a fixed correspondence between action names
and action arities.
* class `Action` corresponds to a synchronisable action, it has a
name, a send/receive flag and a list of parameters. Actions have
no predetermined arities, only conjugated actions with the same
......@@ -66,6 +64,8 @@ t2 z>0
>>> [t.name for t in sorted(n.transition(), key=str)]
["((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]",
"((t1{...}+t2{...})[a(...)]{...}+t2{...})[a(...)]"]
@todo: revise documentation
"""
from snakes import ConstraintError
......@@ -91,6 +91,7 @@ class Action (object) :
self.send = send
self.params = list(params)
__pnmltag__ = "action"
# apidoc skip
def __pnmldump__ (self) :
"""
>>> Action('a', True, [Value(1), Variable('x')]).__pnmldump__()
......@@ -114,6 +115,7 @@ class Action (object) :
for param in self.params :
result.add_child(Tree.from_obj(param))
return result
# apidoc skip
@classmethod
def __pnmlload__ (cls, tree) :
"""
......@@ -136,6 +138,7 @@ class Action (object) :
return "%s!(%s)" % (self.name, ",".join([str(p) for p in self]))
else :
return "%s?(%s)" % (self.name, ",".join([str(p) for p in self]))
# apidoc stop
def __repr__ (self) :
"""
>>> a = Action('a', True, [Value(1), Variable('x')])
......@@ -150,17 +153,17 @@ class Action (object) :
", ".join([repr(p) for p in self]))
def __len__ (self) :
"""Return the number of parameters, aka the arity of the action.
>>> len(Action('a', True, [Value(1), Variable('x')]))
2
@return: the arity of the action
@rtype: non negative `int`
"""
return len(self.params)
def __iter__ (self) :
"""Iterate on the parameters
>>> list(Action('a', True, [Value(1), Variable('x')]))
[Value(1), Variable('x')]
"""
......@@ -169,7 +172,7 @@ class Action (object) :
def __eq__ (self, other) :
"""Two actions are equal if they have the same name, same send
flags and same parameters.
>>> Action('a', True, [Value(1), Variable('x')]) == Action('a', True, [Value(1), Variable('x')])
True
>>> Action('a', True, [Value(1), Variable('x')]) == Action('b', True, [Value(1), Variable('x')])
......@@ -180,7 +183,7 @@ class Action (object) :
False
>>> Action('a', True, [Value(1), Variable('x')]) == Action('a', True, [Value(1)])
False
@param other: the action to compare
@type other: `Action`
@return: `True` if the two actions are equal, `False`
......@@ -201,14 +204,14 @@ class Action (object) :
return not (self == other)
def copy (self, subst=None) :
"""Copy the action, optionally substituting its parameters.
>>> a = Action('a', True, [Variable('x'), Value(2)])
>>> a.copy()
Action('a', True, [Variable('x'), Value(2)])
>>> a = Action('a', True, [Variable('x'), Value(2)])
>>> a.copy(Substitution(x=Value(3)))
Action('a', True, [Value(3), Value(2)])
@param subst: if not `None`, a substitution to apply to the
parameters of the copy
@type subst: `None` or `Substitution` mapping variables names
......@@ -224,12 +227,12 @@ class Action (object) :
return result
def substitute (self, subst) :
"""Substitute the parameters according to `subst`
>>> a = Action('a', True, [Variable('x'), Value(2)])
>>> a.substitute(Substitution(x=Value(3)))
>>> a
Action('a', True, [Value(3), Value(2)])
@param subst: a substitution to apply to the parameters
@type subst: `Substitution` mapping variables names to `Value`
or `Variable`
......@@ -241,7 +244,7 @@ class Action (object) :
"""
>>> Action('a', True, [Value(3), Variable('x'), Variable('y'), Variable('x')]).vars() == set(['x', 'y'])
True
@return: the set of variable names appearing in the parameters
of the action
@rtype: `set` of `str`
......@@ -249,13 +252,13 @@ class Action (object) :
return set(p.name for p in self.params if isinstance(p, Variable))
def __and__ (self, other) :
"""Compute an unification of two conjugated actions.
An unification is a `Substitution` that maps variable names to
`Variable` or `Values`. If both actions are substituted by
this unification, their parameters lists become equal. If no
unification can be found, `ConstraintError` is raised (or,
rarely, `DomainError` depending on the cause of the failure).
>>> s = Action('a', True, [Value(3), Variable('x'), Variable('y'), Variable('x')])
>>> r = Action('a', False, [Value(3), Value(2), Variable('t'), Variable('z')])
>>> u = s & r
......@@ -284,7 +287,7 @@ class Action (object) :
>>> try : s & r
... except ConstraintError : print(sys.exc_info()[1])
actions not conjugated
@param other: the other action to unify with
@type other: `Action`
@return: a substitution that unify both actions
......@@ -323,7 +326,7 @@ class MultiAction (object) :
... Action('a', False, [Value(2)])])
... except ConstraintError : print(sys.exc_info()[1])
conjugated actions in the same multiaction
@param actions: a collection of actions with no conjugated
actions in it
@type actions: `list` of `Action`
......@@ -392,10 +395,10 @@ class MultiAction (object) :
def send (self, name) :
"""Returns the send flag of the action `name` in this
multiaction.
This value is unique as conjugated actions are forbidden in
the same multiaction.
>>> m = MultiAction([Action('a', True, [Variable('x')]),
... Action('b', False, [Variable('y'), Value(2)])])
>>> m.send('a'), m.send('b')
......@@ -404,10 +407,10 @@ class MultiAction (object) :
return self._sndrcv[name]
def add (self, action) :
"""Add an action to the multiaction.
This may raise `ConstraintError` if the added action is
conjugated to one that already belongs to the multiaction.
@param action: the action to add
@type action: `Action`
"""
......@@ -418,10 +421,10 @@ class MultiAction (object) :
self._actions.append(action)
def remove (self, action) :
"""Remove an action from the multiaction.
This may raise `ValueError` if the removed action does belongs
to the multiaction.
@param action: the action to remove
@type action: `Action`
"""
......@@ -432,7 +435,7 @@ class MultiAction (object) :
del self._sndrcv[action.name]
def __iter__ (self) :
"""Iterate over the actions in the multiaction.
>>> list(MultiAction([Action('a', True, [Variable('x')]),
... Action('b', False, [Variable('y'), Value(2)])]))
[Action('a', True, [Variable('x')]),
......@@ -442,18 +445,18 @@ class MultiAction (object) :
yield action
def __len__ (self) :
"""Return the number of actions in a multiaction.
>>> len(MultiAction([Action('a', True, [Variable('x')]),
... Action('b', False, [Variable('y'), Value(2)])]))
2
@return: the number of contained actions
@rtype: non negative `int`
"""
return len(self._actions)
def substitute (self, subst) :
"""Substitute bu `subt` all the actions in the multiaction.
>>> m = MultiAction([Action('a', True, [Variable('x')]),
... Action('b', False, [Variable('y'), Variable('x')])])
>>> m.substitute(Substitution(x=Value(4)))
......@@ -466,7 +469,7 @@ class MultiAction (object) :
def copy (self, subst=None) :
"""Copy the multiaction (and the actions is contains) optionally
substituting it.
@param subst: if not `None`, the substitution to apply to the
copy.
@type subst: `None` or `Substitution`
......@@ -479,10 +482,10 @@ class MultiAction (object) :
return result
def __contains__ (self, action) :
"""Search an action in the multiaction.
The searched action may be a complete `Action`, just an action
name, or a pair `(name, send_flag)`.
>>> m = MultiAction([Action('a', True, [Variable('x'), Value(2)]),
... Action('a', True, [Value(3), Variable('y')]),
... Action('b', False, [Variable('x'), Variable('y')])])
......@@ -498,7 +501,7 @@ class MultiAction (object) :
False
>>> Action('c', True, [Variable('x'), Value(2)]) in m
False
@param action: an complete action, or its name or its name and
send flag
@type action: `Action` or `str` or `tuple(str, bool)`
......@@ -517,7 +520,7 @@ class MultiAction (object) :
raise ValueError("invalid action specification")
def __add__ (self, other) :
"""Create a multiaction by adding the actions of two others.
>>> m = MultiAction([Action('a', True, [Variable('x'), Value(2)]),
... Action('a', True, [Value(3), Variable('y')]),
... Action('b', False, [Variable('x'), Variable('y')])])
......@@ -533,7 +536,7 @@ class MultiAction (object) :
Action('a', True, [Value(3), Variable('y')]),
Action('b', False, [Variable('x'), Variable('y')]),
Action('c', True, [])])
@param other: the other multiaction to combine or a single
action
@type other: `MultiAction` or `Action`
......@@ -549,7 +552,7 @@ class MultiAction (object) :
def __sub__ (self, other) :
"""Create a multiaction by substracting the actions of two
others.
>>> m = MultiAction([Action('a', True, [Variable('x'), Value(2)]),
... Action('a', True, [Value(3), Variable('y')]),
... Action('b', False, [Variable('x'), Variable('y')])])
......@@ -558,7 +561,7 @@ class MultiAction (object) :
>>> m - Action('b', False, [Variable('x'), Variable('y')])
MultiAction([Action('a', True, [Variable('x'), Value(2)]),
Action('a', True, [Value(3), Variable('y')])])
@param other: the other multiaction to combine or a single
action
@type other: `MultiAction` or `Action`
......@@ -574,12 +577,12 @@ class MultiAction (object) :
def vars (self) :
"""Return the set of variable names used in all the actions of
the multiaction.
>>> MultiAction([Action('a', True, [Variable('x'), Value(2)]),
... Action('a', True, [Value(3), Variable('y')]),
... Action('b', False, [Variable('x'), Variable('z')])]).vars() == set(['x', 'y', 'z'])
True
@return: the set of variable names
@rtype: `set` of `str`
"""
......@@ -590,12 +593,12 @@ class MultiAction (object) :
def synchronise (self, other, name) :
"""Search all the possible synchronisation on an action name with
another multiaction.
This method returns an iterator that yields for each possible
synchronisation a 4-tuple whose components are:
* the sending action that did synchronise, it is already
unified, so the corresponding receiving action is just
the same with the reversed send flag
......@@ -605,7 +608,7 @@ class MultiAction (object) :
that provided the sending action
* the substitution that must be applied to the transition
that provided the receiving action
>>> m = MultiAction([Action('a', True, [Variable('x'), Value(2)]),
... Action('a', True, [Value(3), Variable('y')]),
... Action('b', False, [Variable('x'), Variable('y')])])
......@@ -615,7 +618,7 @@ class MultiAction (object) :
... print('%s %s %s %s' % (str(a), str(x), list(sorted(u.items())), list(sorted(v.items()))))
a!(w,2) [a!(3,y), b?(w,y), c?(a)] [('a', Value(2)), ('x', Variable('w'))] [('a', Value(2)), ('x', Variable('w')), ('y', Variable('a'))]
a!(3,a) [a!(x,2), b?(x,a), c?(a)] [('w', Value(3)), ('y', Variable('a'))] [('w', Value(3)), ('y', Variable('a'))]
@param other: the other multiaction to synchronise with
@type other: `MultiAction`
@param name: the name of the action to synchronise on
......
......@@ -3,6 +3,8 @@
Petri nets objects are saved in PNML, other Python objects are saved
in a readable format when possible and pickled as a last solution.
This should result in a complete PNML serialization of any object.
@todo: revise documentation
"""
import xml.dom.minidom
......@@ -160,7 +162,7 @@ class _set (object) :
class Tree (object) :
"""Abstraction of a PNML tree
>>> Tree('tag', 'data', Tree('child', None), attr='attribute value')
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -172,7 +174,7 @@ class Tree (object) :
"""
def __init__ (self, _name, _data, *_children, **_attributes) :
"""Initialize a PNML tree
>>> Tree('tag', 'data',
... Tree('first_child', None),
... Tree('second_child', None),
......@@ -186,10 +188,10 @@ class Tree (object) :
data
</tag>
</pnml>
Note: parameters names start with a '_' in order to allow for
using them as attributes.
@param _name: the name of the tag
@type _name: `str`
@param _data: the text held by the tag or `None`
......@@ -225,7 +227,7 @@ class Tree (object) :
return result
def to_pnml (self) :
"""Dumps a PNML tree to an XML string
>>> print(Tree('tag', 'data', Tree('child', None), attr='value').to_pnml())
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -234,7 +236,7 @@ class Tree (object) :
data
</tag>
</pnml>
@return: the XML string that represents the PNML tree
@rtype: `str`
"""
......@@ -269,7 +271,7 @@ class Tree (object) :
@classmethod
def from_dom (cls, node) :
"""Load a PNML tree from an XML DOM representation
>>> src = Tree('object', '42', type='int').to_pnml()
>>> dom = xml.dom.minidom.parseString(src)
>>> Tree.from_dom(dom.documentElement)
......@@ -279,7 +281,7 @@ class Tree (object) :
42
</object>
</pnml>
@param node: the DOM node to load
@type node: `xml.dom.minidom.Element`
@return: the loaded PNML tree
......@@ -299,7 +301,7 @@ class Tree (object) :
@classmethod
def from_pnml (cls, source, plugins=[]) :
"""Load a PNML tree from an XML string representation
>>> src = Tree('object', '42', type='int').to_pnml()
>>> Tree.from_pnml(src)
<?xml version="1.0" encoding="utf-8"?>
......@@ -308,7 +310,7 @@ class Tree (object) :
42
</object>
</pnml>
@param source: the XML string to load or an opened file that
contains it
@type source: `str` or `file`
......@@ -360,7 +362,7 @@ class Tree (object) :
return result
def nodes (self) :
"""Iterate over all the nodes (top-down) in a tree
>>> t = Tree('foo', None,
... Tree('bar', None),
... Tree('egg', None,
......@@ -371,7 +373,7 @@ class Tree (object) :
<PNML tree 'bar'>
<PNML tree 'egg'>
<PNML tree 'spam'>
@return: an iterator on all the nodes in the tree, including
this one
@rtype: `generator`
......@@ -383,7 +385,7 @@ class Tree (object) :
def update (self, other) :
"""Incorporates children, attributes and data from another PNML
tree
>>> t = Tree('foo', 'hello',
... Tree('bar', None),
... Tree('egg', None,
......@@ -411,7 +413,7 @@ class Tree (object) :
>>> try : t.update(o)
... except SnakesError : print(sys.exc_info()[1])
tag mismatch 'foo', 'oops'
@param other: the other tree to get data from
@type other: `Tree`
@raise SnakesError: when `other` has not the same tag as
......@@ -424,7 +426,7 @@ class Tree (object) :
self.add_data(other.data)
def add_child (self, child) :
"""Add a child to a PNML tree
>>> t = Tree('foo', None)
>>> t.add_child(Tree('bar', None))
>>> t
......@@ -434,14 +436,14 @@ class Tree (object) :
<bar/>
</foo>
</pnml>
@param child: the PNML tree to append
@type child: `Tree`
"""
self.children.append(child)
def add_data (self, data, sep='\n') :
"""Appends data to the current node
>>> t = Tree('foo', None)
>>> t.add_data('hello')
>>> t
......@@ -469,7 +471,7 @@ class Tree (object) :
world!
</foo>
</pnml>
@param data: the data to add
@type data: `str`
@param sep: separator to insert between pieces of data
......@@ -486,14 +488,14 @@ class Tree (object) :
pass
def __getitem__ (self, name) :
"""Returns one attribute
>>> Tree('foo', None, x='egg', y='spam')['x']
'egg'
>>> Tree('foo', None, x='egg', y='spam')['z']
Traceback (most recent call last):
...
KeyError: 'z'
@param name: the name of the attribute
@type name: `str`
@return: the value of the attribute
......@@ -503,7 +505,7 @@ class Tree (object) :
return self.attributes[name]
def __setitem__ (self, name, value) :
"""Sets an attribute
>>> t = Tree('foo', None)
>>> t['egg'] = 'spam'
>>> t
......@@ -511,7 +513,7 @@ class Tree (object) :
<pnml>
<foo egg="spam"/>
</pnml>
@param name: the name of the attribute
@type name: `str`
@param value: the value of the attribute
......@@ -520,20 +522,20 @@ class Tree (object) :
self.attributes[name] = value
def __iter__ (self) :
"""Iterate over children nodes
>>> [str(node) for node in Tree('foo', None,
... Tree('egg', None),
... Tree('spam', None,
... Tree('bar', None)))]
["<PNML tree 'egg'>", "<PNML tree 'spam'>"]
@return: an iterator over direct children of the node
@rtype: `generator`
"""
return iter(self.children)
def has_child (self, name) :
"""Test if the tree has the given tag as a direct child
>>> t = Tree('foo', None,
... Tree('egg', None),
... Tree('spam', None,
......@@ -544,7 +546,7 @@ class Tree (object) :
False
>>> t.has_child('python')
False
@param name: tag name to search for
@type name: `str`
@return: a Boolean value indicating wether such a child was
......@@ -557,7 +559,7 @@ class Tree (object) :
return False
def child (self, name=None) :
"""Return the direct child that as the given tag
>>> t = Tree('foo', None,
... Tree('egg', 'first'),
... Tree('egg', 'second'),
......@@ -582,7 +584,7 @@ class Tree (object) :
>>> try : t.child()
... except SnakesError : print(sys.exc_info()[1])
multiple children
@param name: name of the tag to search for, if `None`, the
fisrt child is returned if it is the only child
@type name: `str` or `None`
......@@ -606,7 +608,7 @@ class Tree (object) :
return result
def get_children (self, name=None) :
"""Iterates over direct children having the given tag
>>> t = Tree('foo', None,
... Tree('egg', 'first'),
... Tree('egg', 'second'),
......@@ -620,7 +622,7 @@ class Tree (object) :
[]
>>> [str(n) for n in t.get_children('bar')]
[]
@param name: tag to search for or `None`
@type name: `str` or `None`
@return: iterator over all the children if `name` is `None`,
......@@ -632,20 +634,20 @@ class Tree (object) :
yield child
def __str__ (self) :
"""Return a simple string representation of the node
>>> str(Tree('foo', None, Tree('child', None)))
"<PNML tree 'foo'>"
@return: simple string representation of the node
@rtype: `str`
"""
return "<PNML tree %r>" % self.name
def __repr__ (self) :
"""Return a detailed representation of the node.
This is actually the XML text that corresponds to the `Tree`,
as returned by `Tree.to_pnml`.
>>> print(repr(Tree('foo', None, Tree('child', None))))
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -653,7 +655,7 @@ class Tree (object) :
<child/>
</foo>
</pnml>
@return: XML string representation of the node
@rtype: `str`
"""
......@@ -663,10 +665,10 @@ class Tree (object) :
@classmethod
def from_obj (cls, obj) :
"""Builds a PNML tree from an object.
Objects defined in SNAKES usually have a method `__pnmldump__`
that handles the conversion, for instance:
>>> import snakes.nets
>>> Tree.from_obj(snakes.nets.Place('p'))
<?xml version="1.0" encoding="utf-8"?>
......@@ -678,9 +680,9 @@ class Tree (object) :
</initialMarking>
</place>
</pnml>
Most basic Python classes are handled has readable XML:
>>> Tree.from_obj(42)
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -703,10 +705,10 @@ class Tree (object) :
</object>
</object>
</pnml>
Otherwise, the object is serialised using module `pickle`,
which allows to embed almost anything into PNML.
>>> import re
>>> Tree.from_obj(re.compile('foo|bar')) # serialized data replaced with '...'
<?xml version="1.0" encoding="utf-8"?>
......@@ -715,7 +717,7 @@ class Tree (object) :
...
</object>
</pnml>
@param obj: the object to convert to PNML
@type obj: `object`
@return: the corresponding PNML tree
......@@ -854,7 +856,7 @@ class Tree (object) :
return pickle.loads(self.data)
def to_obj (self) :
"""Build an object from its PNML representation
This is just the reverse as `Tree.from_obj`, objects that have
a `__pnmldump__` method should also have a `__pnmlload__`
class method to perform the reverse operation, together with
......@@ -863,10 +865,10 @@ class Tree (object) :
that `C.__pnmltag__ == 'foo'` is searched in module
`snakes.nets` and `C.__pnmlload__(tree)` is called to rebuild
the object.
Standard Python objects and pickled ones are also recognised
and correctly rebuilt.
>>> import snakes.nets
>>> Tree.from_obj(snakes.nets.Place('p')).to_obj()
Place('p', MultiSet([]), tAll)
......@@ -877,7 +879,7 @@ class Tree (object) :
>>> import re
>>> Tree.from_obj(re.compile('foo|bar')).to_obj()
<... object at ...>
@return: the Python object encoded by the PNML tree
@rtype: `object`
"""
......@@ -910,7 +912,7 @@ class Tree (object) :
def dumps (obj) :
"""Dump an object to a PNML string
>>> print(dumps(42))
<?xml version="1.0" encoding="utf-8"?>
<pnml>
......@@ -918,7 +920,7 @@ def dumps (obj) :
42
</object>
</pnml>
@param obj: the object to dump
@type obj: `object`
@return: the PNML that represents the object
......@@ -928,10 +930,10 @@ def dumps (obj) :
def loads (source, plugins=[]) :
"""Load an object from a PNML string
>>> loads(dumps(42))
42
@param source: the data to parse
@type source: `str`
@return: the object represented by the source
......
......@@ -60,6 +60,8 @@ TypeError: ...
Traceback (most recent call last):
...
TypeError: ...
@todo: revise documentation
"""
import inspect, sys
......
"""
@todo: revise (actually make) documentation
"""
from snakes import SnakesError
class CompilationError (SnakesError) :
......
"""
@todo: revise (actually make) documentation
"""
import sys, os, os.path
import inspect, fnmatch, collections
import textwrap, doctest, ast
import inspect, fnmatch, collections, re, shlex
import textwrap, doctest
import snakes
from snakes.lang import unparse
from snakes.lang.python.parser import parse, ast
try :
import markdown
except :
markdown = None
##
## console messages
......@@ -62,6 +72,13 @@ class DocExtract (object) :
self.out = None
self.exclude = exclude
self._last = "\n\n"
def md (self, text, inline=True) :
if markdown is None :
return text
elif inline :
return re.sub("</?p>", "\n", markdown.markdown(text), re.I)
else :
return markdown.markdown(text)
def openout (self, path) :
if self.out is not None :
self.out.close()
......@@ -81,6 +98,10 @@ class DocExtract (object) :
return False
outdir = os.path.join(self.outpath, relpath)
outpath = os.path.join(outdir, target)
if os.path.exists(outpath) :
if os.stat(path).st_mtime <= os.stat(outpath).st_mtime :
return False
self.inpath = path
info("%s -> %r" % (self.module, outpath))
if not os.path.exists(outdir) :
os.makedirs(outdir)
......@@ -114,56 +135,89 @@ class DocExtract (object) :
def process (self) :
for dirpath, dirnames, filenames in os.walk(self.path) :
for name in sorted(filenames) :
if not name.endswith(".py") :
if not name.endswith(".py") or name.startswith(".") :
continue
path = os.path.join(dirpath, name)
if not self.openout(path) :
continue
node = ast.parse(open(path).read())
node = parse(open(path).read())
if ".plugins." in self.module :
self.visit_plugin(node)
else :
self.visit_module(node)
def _pass (self, node) :
pass
def directive (self, node) :
lines = node.st.text.lexer.lines
num = node.st.srow - 2
while num >= 0 and (not lines[num].strip()
or lines[num].strip().startswith("@")) :
num -= 1
if num >= 0 and lines[num].strip().startswith("#") :
dirline = lines[num].lstrip("# \t").rstrip()
items = shlex.split(dirline)
if len(items) >= 2 and items[0].lower() == "apidoc" :
if len(items) == 2 and items[1].lower() in ("skip", "stop") :
return items[1]
elif items[1].lower() == "include" :
path = items[2]
try :
args = dict(i.split("=", 1) for i in items[3:])
except :
err("invalid directive %r (line %s)"
% (dirline, num+1))
self.write_include(path, **args)
else :
err("unknown directive %r (line %s)"
% (items[1], num+1))
return None
def children (self, node) :
for child in ast.iter_child_nodes(node) :
directive = self.directive(child)
if directive == "skip" :
continue
elif directive == "stop" :
break
yield child
def visit (self, node) :
name = getattr(node, "name", "__")
if (name.startswith("_") and not (name.startswith("__")
and name.endswith("__"))) :
return
try :
getattr(self, "visit_" + node.__class__.__name__, self._pass)(node)
getattr(self, "visit_" + node.__class__.__name__,
self._pass)(node)
except :
src = unparse(node)
src = node.st.source()
if len(src) > 40 :
src = src[:40] + "..."
err("line %s source %r" % (node.lineno, src))
raise
def visit_module (self, node) :
self.write_module()
for child in ast.iter_child_nodes(node) :
for child in self.children(node) :
self.visit(child)
def visit_plugin (self, node) :
self.write_module()
extend = None
for child in ast.iter_child_nodes(node) :
for child in self.children(node) :
if (getattr(child, "name", None) == "extend"
and isinstance(child, ast.FunctionDef)) :
extend = child
else :
self.visit(child)
self.write_plugin()
for child in ast.iter_child_nodes(extend) :
for child in self.children(extend) :
self.visit(child)
def visit_ClassDef (self, node) :
self.write_class(node)
self.classname = node.name
for child in ast.iter_child_nodes(node) :
for child in self.children(node) :
self.visit(child)
self.classname = None
def visit_FunctionDef (self, node) :
self.write_function(node)
self.args = [n.id for n in node.args.args]
self.args = [n.arg for n in node.args.args]
if self.args and self.args[0] == "self" :
del self.args[0]
if node.args.vararg :
......@@ -193,26 +247,32 @@ class DocExtract (object) :
else :
self.writeline("### Function `%s` ###" % node.name)
self.newline()
self.writeline(" :::python")
for line in unparse(node).splitlines() :
if line.startswith("def") :
self.writeline(" %s ..." % line)
break
else :
self.writeline(" " + line)
self.newline
self.write_def(node)
def write_class (self, node) :
self.newline()
self.writeline("### Class `%s` ###" % node.name)
self.write_def(node)
def write_def (self, node) :
indent = node.st.scol
if node.st[0].symbol == "decorated" :
srow, scol = node.st[0][0][0][0].srow, node.st[0][0][0][0].scol
erow, ecol = node.st[0][1][-2].erow, node.st[0][1][-2].ecol
else :
srow, scol = node.st[0].srow, node.st[0].scol
erow, ecol = node.st[0][-2].erow, node.st[0][-2].ecol
lines = node.st.text.lexer.lines
if srow == erow :
source = [lines[srow-1][scol:ecol+1]]
else :
source = lines[srow-1:erow]
source[0] = source[0][scol:]
source[1:-1] = [s[indent:] for s in source[1:-1]]
source[-1] = source[-1][indent:ecol+1].rstrip() + " ..."
self.newline()
self.writeline(" :::python")
for line in unparse(node).splitlines() :
if line.startswith("class") :
self.writeline(" %s ..." % line)
break
else :
self.writeline(" " + line)
self.newline
for line in source :
self.writeline(" " + line)
self.newline()
parse = doctest.DocTestParser().parse
def write_doc (self, doc) :
if doc is None :
......@@ -240,7 +300,9 @@ class DocExtract (object) :
if not test :
test = True
self.newline()
self.writeline(" :::python")
self.writetext("<!-- this comment avoids a bug in"
" Markdown parsing -->")
self.writeline(" :::pycon")
for i, line in enumerate(doc.source.splitlines()) :
if i > 0 :
self.writeline(" ... %s" % line)
......@@ -281,65 +343,74 @@ class DocExtract (object) :
if any(k in info for k in ("author", "organization", "copyright",
"license", "contact")) :
self.newline()
self.writeline('<div class="api-info">')
for tag in ("author", "organization", "copyright",
"license", "contact") :
self.writeline('<ul id="api-info">')
for tag in ("author", "organization", "contact",
"copyright", "license", ) :
if tag in info :
self.writeline('<div class="api-%s">' % tag)
self.writeline('<li id="api-%s">' % tag)
self.writetext('<span class="api-title">%s:</span> %s'
% (tag.capitalize(), info[tag]),
% (tag.capitalize(),
self.md(info[tag])),
subsequent_indent=" ")
self.writeline('</div>')
self.writeline('</div>')
self.writeline('</li>')
self.writeline('</ul>')
if any(info[k] for k in
("todo", "note", "attention", "bug", "warning")) :
self.newline()
self.writeline('<div class="api-remarks">')
self.writeline("##### Remarks #####")
self.newline()
self.writeline('<div id="api-remarks">')
for tag in ("note", "todo", "attention", "bug", "warning") :
for text in info[tag] :
self.writeline('<div class="api-%s">' % tag)
self.writetext('<span class="api-title">%s:</span> %s'
% (tag.capitalize(), text),
% (tag.capitalize(), self.md(text)),
subsequent_indent=" ")
self.writeline('</div>')
self.writeline('</div>')
if (any(info[k] for k in ("param", "type", "keyword"))
or any(k in info for k in ("return", "rtype"))) :
self.newline()
self.writeline('<div class="api-call">')
self.writeline("##### Call API #####")
self.newline()
for arg in self.args :
if arg in info["param"] :
self.writelist("`%s` (%s): %s"
% (arg,
info["type"].get(arg, "`object`"),
self.writelist("`%s %s`: %s"
% (info["type"].get(arg, "object").strip("`"),
arg,
info["param"][arg]))
else :
self.writelist("`%s` (%s)"
% (arg,
info["type"].get(arg, "`object`")))
self.writelist("`%s %s`"
% (info["type"].get(arg, "object").strip("`"),
arg))
for kw, text in sorted(info["keyword"].items()) :
self.writelist("`%s`: %s" % (kw, text))
if any(k in info for k in ("return", "rtype")) :
if "return" in info :
self.writelist("return %s: %s"
% (info.get("rtype", "`object`"),
self.writelist("`return %s`: %s"
% (info.get("rtype", "object").strip("`"),
info["return"]))
else :
self.writelist("return %s"
% (info.get("rtype", "`object`")))
self.writeline('</div>')
self.writelist("`return %s`"
% (info.get("rtype", "object").strip("`")))
if info["raise"] :
self.newline()
self.writeline('<div class="api-errors">')
self.writeline("##### Exceptions #####")
self.newline()
for exc, reason in sorted(info["raise"].items()) :
self.writelist("`%s`: %s" % (exc, reason))
self.writeline('</div>')
self.newline()
def write_include (self, name, lang="python") :
if os.path.exists(name) :
path = name
else :
path = os.path.join(os.path.dirname(self.inpath), name)
if not os.path.exists(path) :
err("include file %r not found" % name)
with open(path) as infile :
self.newline()
self.writeline(" :::%s" % lang)
for line in infile :
self.writeline(" " + line.rstrip())
self.newline()
def main (finder, args) :
try :
......
"""
@todo: revise (actually make) documentation
"""
......