Bug fix with bases and improved error display for evaluating variables
[ejpi] / src / operation.py
1 #!/usr/bin/env python
2
3
4 import itertools
5 import functools
6 import decimal
7
8 from libraries.recipes import overloading
9 from libraries.recipes import algorithms
10
11
12 @overloading.overloaded
13 def serialize_value(value, base, renderer):
14         yield renderer(value, base)
15
16
17 @serialize_value.register(complex, overloading.AnyType, overloading.AnyType)
18 def serialize_complex(value, base, renderer):
19         if value.real == 0.0:
20                 yield renderer(value.imag*1j, base)
21         elif value.imag == 0.0:
22                 yield renderer(value.real, base)
23         else:
24                 yield renderer(value.real, base)
25                 yield renderer(value.imag*1j, base)
26                 yield "+"
27
28
29 def render_float(value):
30         return str(value)
31
32
33 def render_float_dec(value):
34         floatText = str(value)
35         dec = decimal.Decimal(floatText)
36         return str(dec)
37
38
39 def render_float_eng(value):
40         floatText = str(value)
41         dec = decimal.Decimal(floatText)
42         return dec.to_eng_string()
43
44
45 def render_float_sci(value):
46         floatText = str(value)
47         dec = decimal.Decimal(floatText)
48         return dec.to_sci_string()
49
50
51 def render_complex(floatRender):
52
53         def render_complex_real(value):
54                 realRendered = floatRender(value.real)
55                 imagRendered = floatRender(value.imag)
56                 rendered = "%s+%sj" % (realRendered, imagRendered)
57                 return rendered
58
59         return render_complex_real
60
61
62 def _seperate_num(rendered, sep, count):
63         """
64         >>> _seperate_num("123", ",", 3)
65         '123'
66         >>> _seperate_num("123456", ",", 3)
67         '123,456'
68         >>> _seperate_num("1234567", ",", 3)
69         '1,234,567'
70         """
71         leadCount = len(rendered) % count
72         choppyRest = algorithms.itergroup(rendered[leadCount:], count)
73         rest = (
74                 "".join(group)
75                 for group in choppyRest
76         )
77         if 0 < leadCount:
78                 lead = rendered[0:leadCount]
79                 parts = itertools.chain((lead, ), rest)
80         else:
81                 parts = rest
82         return sep.join(parts)
83
84
85 def render_integer_oct(value, sep=""):
86         rendered = oct(int(value))
87         if 0 < len(sep):
88                 assert rendered.startswith("0")
89                 rendered = "0o%s" % _seperate_num(rendered[1:], sep, 3)
90         return rendered
91
92
93 def render_integer_dec(value, sep=""):
94         rendered = str(int(value))
95         if 0 < len(sep):
96                 rendered = "%s" % _seperate_num(rendered, sep, 3)
97         return rendered
98
99
100 def render_integer_hex(value, sep=""):
101         rendered = hex(int(value))
102         if 0 < len(sep):
103                 assert rendered.startswith("0x")
104                 rendered = "0x%s" % _seperate_num(rendered[2:], sep, 3)
105         return rendered
106
107
108 def set_render_int_seperator(renderer, sep):
109
110         @functools.wrap(renderer)
111         def render_with_sep(value):
112                 return renderer(value, sep)
113
114         return render_with_sep
115
116
117 class render_number(object):
118
119         def __init__(self,
120                 ints = None,
121                 f = None,
122                 c = None,
123         ):
124                 if ints is not None:
125                         self.render_int = ints
126                 else:
127                         self.render_int = {
128                                 2: render_integer_hex,
129                                 8: render_integer_oct,
130                                 10: render_integer_dec,
131                                 16: render_integer_hex,
132                         }
133                 self.render_float = f if c is not None else render_float
134                 self.render_complex = c if c is not None else self
135
136         def __call__(self, value, base):
137                 return self.render(value, base)
138
139         @overloading.overloaded
140         def render(self, value, base):
141                 return str(value)
142
143         @render.register(overloading.AnyType, int, overloading.AnyType)
144         def _render_int(self, value, base):
145                 renderer = self.render_int.get(base, render_integer_dec)
146                 return renderer(value)
147
148         @render.register(overloading.AnyType, float, overloading.AnyType)
149         def _render_float(self, value, base):
150                 return self.render_float(value)
151
152         @render.register(overloading.AnyType, complex, overloading.AnyType)
153         def _render_complex(self, value, base):
154                 return self.render_float(value)
155
156
157 class Operation(object):
158
159         def __init__(self):
160                 self._base = 10
161
162         def __str__(self):
163                 raise NotImplementedError
164
165         @property
166         def base(self):
167                 base = self._base
168                 return base
169
170         def get_children(self):
171                 return []
172
173         def serialize(self, renderer):
174                 for child in self.get_children():
175                         for childItem in child.serialize(renderer):
176                                 yield childItem
177
178         def simplify(self):
179                 """
180                 @returns an operation tree with all constant calculations performed and only variables left
181                 """
182                 raise NotImplementedError
183
184         def evaluate(self):
185                 """
186                 @returns a value that the tree represents, if it can't be evaluated,
187                         then an exception is throwd
188                 """
189                 raise NotImplementedError
190
191         def __call__(self):
192                 return self.evaluate()
193
194
195 class Value(Operation):
196
197         def __init__(self, value, base):
198                 super(Value, self).__init__()
199                 self.value = value
200                 self._base = base
201
202         def serialize(self, renderer):
203                 for item in super(Value, self).serialize(renderer):
204                         yield item
205                 for component in serialize_value(self.value, self.base, renderer):
206                         yield component
207
208         def __str__(self):
209                 return str(self.value)
210
211         def simplify(self):
212                 return self
213
214         def evaluate(self):
215                 return self.value
216
217
218 class Constant(Operation):
219
220         def __init__(self, name, valueNode):
221                 super(Constant, self).__init__()
222                 self.name = name
223                 self.__valueNode = valueNode
224
225         def serialize(self, renderer):
226                 for item in super(Constant, self).serialize(renderer):
227                         yield item
228                 yield self.name
229
230         def __str__(self):
231                 return self.name
232
233         def simplify(self):
234                 return self.__valueNode.simplify()
235
236         def evaluate(self):
237                 return self.__valueNode.evaluate()
238
239
240 class Variable(Operation):
241
242         def __init__(self, name):
243                 super(Variable, self).__init__()
244                 self.name = name
245
246         def serialize(self, renderer):
247                 for item in super(Variable, self).serialize(renderer):
248                         yield item
249                 yield self.name
250
251         def __str__(self):
252                 return self.name
253
254         def simplify(self):
255                 return self
256
257         def evaluate(self):
258                 raise KeyError('Variable "%s" unable to evaluate to specific value' % self.name)
259
260
261 class Function(Operation):
262
263         REP_FUNCTION = 0
264         REP_PREFIX = 1
265         REP_INFIX = 2
266         REP_POSTFIX = 3
267
268         _op = None
269         _rep = REP_FUNCTION
270         symbol = None
271         argumentCount = 0
272
273         def __init__(self, *args, **kwd):
274                 super(Function, self).__init__()
275                 self._base = None
276                 self._args = args
277                 self._kwd = kwd
278                 self._simple = self._simplify()
279                 self._str = self.pretty_print(args, kwd)
280
281         def serialize(self, renderer):
282                 for item in super(Function, self).serialize(renderer):
283                         yield item
284                 yield self.symbol
285
286         def get_children(self):
287                 return (
288                         arg
289                         for arg in self._args
290                 )
291
292         @property
293         def base(self):
294                 base = self._base
295                 if base is None:
296                         bases = [arg.base for arg in self._args]
297                         base = bases[0]
298                 assert base is not None
299                 return base
300
301         def __str__(self):
302                 return self._str
303
304         def simplify(self):
305                 return self._simple
306
307         def evaluate(self):
308                 selfArgs = [arg.evaluate() for arg in self._args]
309                 return Value(self._op(*selfArgs), self.base)
310
311         def _simplify(self):
312                 selfArgs = [arg.simplify() for arg in self._args]
313                 selfKwd = dict(
314                         (name, arg.simplify())
315                         for (name, arg) in self._kwd
316                 )
317
318                 try:
319                         args = [arg.evaluate() for arg in selfArgs]
320                         base = self.base
321                         result = self._op(*args)
322
323                         node = Value(result, base)
324                 except KeyError:
325                         node = self
326
327                 return node
328
329         @classmethod
330         def pretty_print(cls, args = None, kwds = None):
331                 if args is None:
332                         args = []
333                 if kwds is None:
334                         kwds = {}
335
336                 if cls._rep == cls.REP_FUNCTION:
337                         positional = (str(arg) for arg in args)
338                         named = (
339                                 "%s=%s" % (str(key), str(value))
340                                 for (key, value) in kwds.iteritems()
341                         )
342                         return "%s(%s)" % (
343                                 cls.symbol,
344                                 ", ".join(itertools.chain(named, positional)),
345                         )
346                 elif cls._rep == cls.REP_PREFIX:
347                         assert len(args) == 1
348                         return "%s %s" % (cls.symbol, args[0])
349                 elif cls._rep == cls.REP_POSTFIX:
350                         assert len(args) == 1
351                         return "%s %s" % (args[0], cls.symbol)
352                 elif cls._rep == cls.REP_INFIX:
353                         assert len(args) == 2
354                         return "(%s %s %s)" % (
355                                 str(args[0]),
356                                 str(cls.symbol),
357                                 str(args[1]),
358                         )
359                 else:
360                         raise AssertionError("Unsupported rep style")
361
362
363 def generate_function(op, rep, style, numArgs):
364
365         class GenFunc(Function):
366
367                 def __init__(self, *args, **kwd):
368                         super(GenFunc, self).__init__(*args, **kwd)
369
370                 _op = op
371                 _rep = style
372                 symbol = rep
373                 argumentCount = numArgs
374
375         GenFunc.__name__ = op.__name__
376         return GenFunc
377
378
379 def change_base(base, rep):
380
381         class GenFunc(Function):
382
383                 def __init__(self, *args, **kwd):
384                         super(GenFunc, self).__init__(*args, **kwd)
385                         self._base = base
386                         self._simple = self._simplify()
387                         self._str = self.pretty_print(args, kwd)
388
389                 _op = lambda self, n: n
390                 _rep = Function.REP_FUNCTION
391                 symbol = rep
392                 argumentCount = 1
393
394         GenFunc.__name__ = rep
395         return GenFunc
396
397
398 @overloading.overloaded
399 def render_operation(render_func, operation):
400         return str(operation)
401
402
403 @render_operation.register(overloading.AnyType, Value)
404 def render_value(render_func, operation):
405         return render_func(operation.value, operation.base)
406
407
408 @render_operation.register(overloading.AnyType, Variable)
409 @render_operation.register(overloading.AnyType, Constant)
410 def render_variable(render_func, operation):
411         return operation.name
412
413
414 @render_operation.register(overloading.AnyType, Function)
415 def render_function(render_func, operation):
416         args = [
417                 render_operation(render_func, arg)
418                 for arg in operation.get_children()
419         ]
420         return operation.pretty_print(args)