Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Franck Pommereau
/
snakes
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Franck Pommereau
2013-03-12 16:52:05 +0100
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
1157b819e1c08c099f15aa1ba6904ab12a462ee3
1157b819
1 parent
8a3e10b5
doc update
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1040 additions
and
823 deletions
snakes/__init__.py
snakes/data.py
snakes/hashables.py
snakes/lang/__init__.py
snakes/nets.py
snakes/plugins/__init__.py
snakes/plugins/clusters.py
snakes/plugins/gv.py
snakes/plugins/hello.py
snakes/plugins/labels.py
snakes/plugins/ops.py
snakes/plugins/pos.py
snakes/plugins/query.py
snakes/plugins/status.py
snakes/plugins/synchro.py
snakes/pnml.py
snakes/typing.py
snakes/utils/abcd/__init__.py
snakes/utils/apidoc.py
snakes/utils/ctlstar/__init__.py
snakes/__init__.py
View file @
1157b81
"""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
)
:
...
...
snakes/data.py
View file @
1157b81
"""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: `
it
erator`
@rtype: `
gen
erator`
"""
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
se
t of values associated to the names.
"""Return the
lis
t of values associated to the names.
>>> list(sorted(Substitution(x=1, y=2).image()))
[1, 2]
@return: the
se
t of values associated to names
@rtype: `
se
t`
@return: the
lis
t of values associated to names
@rtype: `
lis
t`
"""
return
se
t
(
self
.
_dict
.
values
())
return
lis
t
(
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')
...
...
snakes/hashables.py
View file @
1157b81
"""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
)
:
...
...
snakes/lang/__init__.py
View file @
1157b81
"""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
)
...
...
snakes/nets.py
View file @
1157b81
...
...
@@ -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`
"""
...
...
snakes/plugins/__init__.py
View file @
1157b81
"""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
...
...
snakes/plugins/clusters.py
View file @
1157b81
"""
@todo: revise (actually make) documentation
"""
import
snakes.plugins
from
snakes.plugins
import
new_instance
from
snakes.pnml
import
Tree
...
...
snakes/plugins/gv.py
View file @
1157b81
...
...
@@ -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
...
...
snakes/plugins/hello.py
View file @
1157b81
"""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
...
...
snakes/plugins/labels.py
View file @
1157b81
"""A plugin to add labels to nodes and nets.
@todo: revise (actually make) documentation
"""
from
snakes.plugins
import
plugin
,
new_instance
...
...
snakes/plugins/ops.py
View file @
1157b81
...
...
@@ -65,6 +65,8 @@ Buffer('buffer')
(1, 2)
>>> n._declare
['global x; x=1']
@todo: revise documentation
"""
import
snakes.plugins
...
...
snakes/plugins/pos.py
View file @
1157b81
...
...
@@ -42,6 +42,8 @@ Position(1, 3)
>>> n.transpose()
>>> n.node('t01').pos
Position(-3, 1)
@todo: revise documentation
"""
from
snakes
import
SnakesError
...
...
snakes/plugins/query.py
View file @
1157b81
"""
@todo: revise (actually make) documentation
"""
from
snakes.plugins
import
plugin
from
snakes.pnml
import
Tree
,
loads
,
dumps
import
imp
,
sys
,
socket
,
traceback
,
operator
...
...
snakes/plugins/status.py
View file @
1157b81
...
...
@@ -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
...
...
snakes/plugins/synchro.py
View file @
1157b81
...
...
@@ -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
...
...
snakes/pnml.py
View file @
1157b81
...
...
@@ -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
...
...
snakes/typing.py
View file @
1157b81
...
...
@@ -60,6 +60,8 @@ TypeError: ...
Traceback (most recent call last):
...
TypeError: ...
@todo: revise documentation
"""
import
inspect
,
sys
...
...
snakes/utils/abcd/__init__.py
View file @
1157b81
"""
@todo: revise (actually make) documentation
"""
from
snakes
import
SnakesError
class
CompilationError
(
SnakesError
)
:
...
...
snakes/utils/apidoc.py
View file @
1157b81
"""
@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"
,
"co
pyrigh
t"
,
"
license"
,
"contact"
)
:
self
.
writeline
(
'<
ul id
="api-info">'
)
for
tag
in
(
"author"
,
"organization"
,
"co
ntac
t"
,
"
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
:
...
...
snakes/utils/ctlstar/__init__.py
View file @
1157b81
"""
@todo: revise (actually make) documentation
"""
...
...
Please
register
or
login
to post a comment