let.py
3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""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`. This is useful in some cases when you want to
repeat a computation in several output arcs: you do it once in the
guard and then use the bounded variable on the output arcs.
>>> import snakes.plugins
>>> snakes.plugins.load('let', 'snakes.nets', 'snk')
<module ...>
>>> from snk import *
>>> n = PetriNet('n')
>>> t = Transition('t', Expression('x is not None and let("egg, spam = iter(str(foo))", foo=42, __raise__=True)'))
>>> n.add_transition(t)
>>> n.add_place(Place('p', [dot]))
>>> n.add_input('p', 't', Variable('x'))
>>> t.modes() == [Substitution(x=dot, foo=42, egg='4', spam='2')]
True
@param args: a list of `name=value` to assign
@return: `True` if assignment could be made, `False` otherwise
@rtype: `bool`
@warning: use with care, sides effects are nasty tricks, you may
get unexpected results while playing with `let`
"""
import inspect
import snakes.plugins
from snakes.lang.python.parser import ast, parse
from snakes.lang import unparse
class DropLet (ast.NodeTransformer) :
def __init__ (self, names) :
ast.NodeTransformer.__init__(self)
self.names = set(names)
self.calls = []
def visit_Call (self, node) :
func = node.func
if func.__class__.__name__ == "Name" and func.id in self.names :
self.calls.append((func.id,
node.args and node.args[0].s or None,
[(k.arg, unparse(k.value))
for k in node.keywords]))
return ast.Name(id="True")
return node
class DropTrue (ast.NodeTransformer) :
def visit_BoolOp (self, node) :
if node.op.__class__.__name__ == "And" :
values = [self.visit(v) for v in node.values
if v.__class__.__name__ != "Name" or v.id != "True"]
if values :
node.values[:] = values
return node
else :
return ast.Name(id="True")
else :
return self.visit(node)
def unlet (expr, *names) :
if not names :
names = ["let"]
drop = DropLet(names)
new = DropTrue().visit(drop.visit(parse(expr)))
return unparse(new), drop.calls
class MakeLet (object) :
def __init__ (self, globals) :
self.globals = globals
def match (self, match, binding) :
env = dict(binding)
env.update(iter(self.globals))
exec("", env)
old = set(env)
exec(match, env)
for name in set(env) - old :
binding[name] = env[name]
def __call__ (self, __match__=None, __raise__=False, **args) :
try :
__binding__ = inspect.stack()[1][0].f_locals["__binding__"]
for name, value in args.items() :
__binding__[name] = value
if __match__ :
self.match(__match__, __binding__)
except :
if __raise__ :
raise
return False
return True
@snakes.plugins.plugin("snakes.nets")
def extend (module) :
class PetriNet (module.PetriNet) :
def __init__ (self, name, **args) :
module.PetriNet.__init__(self, name, **args)
self.globals["let"] = MakeLet(self.globals)
return PetriNet, unlet