NumberDef: Store 'expr' field, a string for things that will get evaled but a static...
[python-bulletml.git] / bulletml / expr.py
1 """BulletML expression evaluator.
2
3 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html
4 """
5
6 # BulletML assumes 1/2 = 0.5.
7 from __future__ import division
8
9 import random
10 import re
11
12 from bulletml.errors import Error
13
14 class ExprError(Error):
15 """Raised when an invalid expression is evaluated/compiled."""
16 pass
17
18 class NumberDef(object):
19 """BulletML numeric expression.
20
21 This translates BulletML numeric expressions into Python expressions.
22 The
23
24 Examples:
25 35
26 360/16
27 0.7 + 0.9*$rand
28 180-$rank*20
29 (2+$1)*0.3
30
31 """
32
33 GLOBALS = dict(random=random.random, __builtins__={})
34
35 def __init__(self, expr):
36 try:
37 expr = expr.string
38 except AttributeError:
39 pass
40 self.string = str(expr)
41 repl = lambda match: "params[%d]" % (int(match.group()[1:]) - 1)
42 expr = re.sub(r"\$\d+", repl, expr.lower())
43 self.__expr = expr.replace("$rand", "random()").replace("$rank", "rank")
44 try:
45 try:
46 self._value = eval(self.__expr, dict(__builtins__={}))
47 except NameError:
48 variables = dict(rank=1, params=[0] * 99)
49 value = eval(self.__expr, self.GLOBALS, variables)
50 if not isinstance(value, (int, float)):
51 raise TypeError(expr)
52 self._value = None
53 self.expr = self.string
54 else:
55 self.expr = self._value
56 except Exception:
57 raise ExprError(expr)
58 self.__expr = compile(self.__expr, __file__, "eval")
59
60 def __call__(self, params, rank):
61 """Evaluate the expression and return its value."""
62 if self._value is not None:
63 return self._value
64 variables = { 'rank': rank, 'params': params }
65 return eval(self.__expr, self.GLOBALS, variables)
66
67 def __repr__(self):
68 return "%s(%r)" % (type(self).__name__, self.expr)
69
70 class INumberDef(NumberDef):
71 """A NumberDef, but returns rounded integer results."""
72 def __init__(self, expr):
73 super(INumberDef, self).__init__(expr)
74 if self._value is not None:
75 self._value = int(round(self._value))
76
77 def __call__(self, params, rank):
78 if self._value is not None:
79 return self._value
80 return int(round(super(INumberDef, self).__call__(params, rank)))