Initial checkin
[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 ErrorReporting(object):
44
45         def push_message(self, message):
46                 raise NotImplementedError
47
48         def push_exception(self, exception):
49                 self.push_message(exception.message)
50                 warnings.warn(exception, stacklevel=3)
51
52         def pop_message(self):
53                 raise NotImplementedError
54
55
56 class ErrorIgnore(ErrorReporting):
57
58         def push_message(self, message):
59                 pass
60
61         def pop_message(self):
62                 pass
63
64
65 class ErrorWarning(ErrorReporting):
66
67         def push_message(self, message):
68                 warnings.warn(message, stacklevel=2)
69
70         def pop_message(self):
71                 pass
72
73
74 class AbstractHistory(object):
75         """
76         Is it just me or is this class name begging for some jokes?
77         """
78
79         def push(self, node):
80                 raise NotImplementedError
81
82         def pop(self):
83                 raise NotImplementedError
84
85         def unpush(self):
86                 node = self.pop()
87                 for child in node.get_children():
88                         self.push(child)
89
90         def peek(self):
91                 raise NotImplementedError
92
93         def clear(self):
94                 raise NotImplementedError
95
96         def __len__(self):
97                 raise NotImplementedError
98
99         def __iter__(self):
100                 raise NotImplementedError
101
102
103 class CalcHistory(AbstractHistory):
104
105         def __init__(self):
106                 super(CalcHistory, self).__init__()
107                 self.__nodeStack = []
108
109         def push(self, node):
110                 assert node is not None
111                 self.__nodeStack.append(node)
112                 return node
113
114         def pop(self):
115                 popped = self.__nodeStack[-1]
116                 del self.__nodeStack[-1]
117                 return popped
118
119         def peek(self):
120                 return self.__nodeStack[-1]
121
122         def clear(self):
123                 self.__nodeStack = []
124
125         def __len__(self):
126                 return len(self.__nodeStack)
127
128         def __iter__(self):
129                 return self.__nodeStack[::-1]
130
131
132 class RpnCalcHistory(object):
133
134         def __init__(self, history, entry, errorReporting, constants, operations):
135                 self.history = history
136                 self.__entry = weakref.ref(entry)
137
138                 self.__errorReporter = errorReporting
139                 self.__constants = constants
140                 self.__operations = operations
141
142                 self.__serialRenderer = operation.render_number()
143
144         @property
145         def errorReporter(self):
146                 return self.__errorReporter
147
148         @property
149         def OPERATIONS(self):
150                 return self.__operations
151
152         @property
153         def CONSTANTS(self):
154                 return self.__constants
155
156         def clear(self):
157                 self.history.clear()
158                 self.__entry().clear()
159
160         def push_entry(self):
161                 """
162                 @todo Add operation duplication.  If value is empty, peek at the top
163                         item.  If it has children, grab the last one, push it and reapply the
164                         operation.  If there are no children then just duplicate the item
165                 """
166
167                 value = self.__entry().get_value()
168
169                 valueNode = None
170                 if 0 < len(value):
171                         valueNode = self._parse_value(value)
172                         self.history.push(valueNode)
173
174                 self.__entry().clear()
175                 return valueNode
176
177         def apply_operation(self, Node):
178                 try:
179                         self.push_entry()
180
181                         node = self._apply_operation(Node)
182                         return node
183                 except StandardError, e:
184                         self.errorReporter.push_exception(e)
185                         return None
186
187         def serialize_stack(self):
188                 serialized = (
189                         stackNode.serialize(self.__serialRenderer)
190                         for stackNode in self.history
191                 )
192                 serialized = list(serialized)
193                 serialized.reverse()
194                 return serialized
195
196         def deserialize_stack(self, data):
197                 for possibleNode in data:
198                         for nodeValue in possibleNode:
199                                 if nodeValue in self.OPERATIONS:
200                                         Node = self.OPERATIONS[nodeValue]
201                                         self._apply_operation(Node)
202                                 else:
203                                         node = self._parse_value(nodeValue)
204                                         self.history.push(node)
205
206         def _parse_value(self, userInput):
207                 try:
208                         value, base = parse_number(userInput)
209                         return operation.Value(value, base)
210                 except ValueError:
211                         pass
212
213                 try:
214                         return self.CONSTANTS[userInput]
215                 except KeyError:
216                         pass
217
218                 return operation.Variable(userInput)
219
220         def _apply_operation(self, Node):
221                 numArgs = Node.argumentCount
222
223                 if len(self.history) < numArgs:
224                         raise ValueError(
225                                 "Not enough arguments.  The stack has %d but %s needs %d" % (
226                                         len(self.history), Node.symbol, numArgs
227                                 )
228                         )
229
230                 args = [arg for arg in algorithms.func_repeat(numArgs, self.history.pop)]
231                 args.reverse()
232
233                 try:
234                         node = Node(*args)
235                 except StandardError:
236                         for arg in args:
237                                 self.history.push(arg)
238                         raise
239                 self.history.push(node)
240                 return node