Updating from Dialcentral
[ejpi] / src / history.py
1 #!/usr/bin/env python
2
3
4 import weakref
5 import warnings
6
7 from libraries.recipes import algorithms
8 import operation
9
10
11 __BASE_MAPPINGS = {
12         "0x": 16,
13         "0o": 8,
14         "0b": 2,
15 }
16
17
18 def parse_number(userInput):
19         try:
20                 base = __BASE_MAPPINGS.get(userInput[0:2], 10)
21                 if base != 10:
22                         userInput = userInput[2:] # Remove prefix
23                 value = int(userInput, base)
24                 return value, base
25         except ValueError:
26                 pass
27
28         try:
29                 value = float(userInput)
30                 return value, 10
31         except ValueError:
32                 pass
33
34         try:
35                 value = complex(userInput)
36                 return value, 10
37         except ValueError:
38                 pass
39
40         raise ValueError('Cannot parse "%s" as a number' % userInput)
41
42
43 class AbstractHistory(object):
44         """
45         Is it just me or is this class name begging for some jokes?
46         """
47
48         def push(self, node):
49                 raise NotImplementedError
50
51         def pop(self):
52                 raise NotImplementedError
53
54         def unpush(self):
55                 node = self.pop()
56                 for child in node.get_children():
57                         self.push(child)
58
59         def peek(self):
60                 raise NotImplementedError
61
62         def clear(self):
63                 raise NotImplementedError
64
65         def __len__(self):
66                 raise NotImplementedError
67
68         def __iter__(self):
69                 raise NotImplementedError
70
71
72 class CalcHistory(AbstractHistory):
73
74         def __init__(self):
75                 super(CalcHistory, self).__init__()
76                 self.__nodeStack = []
77
78         def push(self, node):
79                 assert node is not None
80                 self.__nodeStack.append(node)
81                 return node
82
83         def pop(self):
84                 popped = self.__nodeStack[-1]
85                 del self.__nodeStack[-1]
86                 return popped
87
88         def peek(self):
89                 return self.__nodeStack[-1]
90
91         def clear(self):
92                 self.__nodeStack = []
93
94         def __len__(self):
95                 return len(self.__nodeStack)
96
97         def __iter__(self):
98                 return self.__nodeStack[::-1]
99
100
101 class RpnCalcHistory(object):
102
103         def __init__(self, history, entry, errorReporting, constants, operations):
104                 self.history = history
105                 self.__entry = weakref.ref(entry)
106
107                 self.__errorReporter = errorReporting
108                 self.__constants = constants
109                 self.__operations = operations
110
111                 self.__serialRenderer = operation.render_number()
112
113         @property
114         def errorReporter(self):
115                 return self.__errorReporter
116
117         @property
118         def OPERATIONS(self):
119                 return self.__operations
120
121         @property
122         def CONSTANTS(self):
123                 return self.__constants
124
125         def clear(self):
126                 self.history.clear()
127                 self.__entry().clear()
128
129         def push_entry(self):
130                 """
131                 @todo Add operation duplication.  If value is empty, peek at the top
132                         item.  If it has children, grab the last one, push it and reapply the
133                         operation.  If there are no children then just duplicate the item
134                 """
135
136                 value = self.__entry().get_value()
137
138                 valueNode = None
139                 if 0 < len(value):
140                         valueNode = self._parse_value(value)
141                         self.history.push(valueNode)
142
143                 self.__entry().clear()
144                 return valueNode
145
146         def apply_operation(self, Node):
147                 try:
148                         self.push_entry()
149
150                         node = self._apply_operation(Node)
151                         return node
152                 except StandardError, e:
153                         self.errorReporter.push_exception()
154                         return None
155
156         def serialize_stack(self):
157                 serialized = (
158                         stackNode.serialize(self.__serialRenderer)
159                         for stackNode in self.history
160                 )
161                 serialized = list(serialized)
162                 serialized.reverse()
163                 return serialized
164
165         def deserialize_stack(self, data):
166                 for possibleNode in data:
167                         for nodeValue in possibleNode:
168                                 if nodeValue in self.OPERATIONS:
169                                         Node = self.OPERATIONS[nodeValue]
170                                         self._apply_operation(Node)
171                                 else:
172                                         node = self._parse_value(nodeValue)
173                                         self.history.push(node)
174
175         def _parse_value(self, userInput):
176                 try:
177                         value, base = parse_number(userInput)
178                         return operation.Value(value, base)
179                 except ValueError:
180                         pass
181
182                 try:
183                         return self.CONSTANTS[userInput]
184                 except KeyError:
185                         pass
186
187                 return operation.Variable(userInput)
188
189         def _apply_operation(self, Node):
190                 numArgs = Node.argumentCount
191
192                 if len(self.history) < numArgs:
193                         raise ValueError(
194                                 "Not enough arguments.  The stack has %d but %s needs %d" % (
195                                         len(self.history), Node.symbol, numArgs
196                                 )
197                         )
198
199                 args = [arg for arg in algorithms.func_repeat(numArgs, self.history.pop)]
200                 args.reverse()
201
202                 try:
203                         node = Node(*args)
204                 except StandardError:
205                         for arg in args:
206                                 self.history.push(arg)
207                         raise
208                 self.history.push(node)
209                 return node