Minor tweak
[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                 value = self.__entry().get_value()
131
132                 valueNode = None
133                 if 0 < len(value):
134                         valueNode = self._parse_value(value)
135                         self.history.push(valueNode)
136
137                 self.__entry().clear()
138                 return valueNode
139
140         def apply_operation(self, Node):
141                 try:
142                         self.push_entry()
143
144                         node = self._apply_operation(Node)
145                         return node
146                 except StandardError, e:
147                         self.errorReporter.push_exception()
148                         return None
149
150         def serialize_stack(self):
151                 serialized = (
152                         stackNode.serialize(self.__serialRenderer)
153                         for stackNode in self.history
154                 )
155                 serialized = list(serialized)
156                 return serialized
157
158         def deserialize_stack(self, data):
159                 for possibleNode in data:
160                         for nodeValue in possibleNode:
161                                 if nodeValue in self.OPERATIONS:
162                                         Node = self.OPERATIONS[nodeValue]
163                                         self._apply_operation(Node)
164                                 else:
165                                         node = self._parse_value(nodeValue)
166                                         self.history.push(node)
167
168         def _parse_value(self, userInput):
169                 try:
170                         value, base = parse_number(userInput)
171                         return operation.Value(value, base)
172                 except ValueError:
173                         pass
174
175                 try:
176                         return self.CONSTANTS[userInput]
177                 except KeyError:
178                         pass
179
180                 return operation.Variable(userInput)
181
182         def _apply_operation(self, Node):
183                 numArgs = Node.argumentCount
184
185                 if len(self.history) < numArgs:
186                         raise ValueError(
187                                 "Not enough arguments.  The stack has %d but %s needs %d" % (
188                                         len(self.history), Node.symbol, numArgs
189                                 )
190                         )
191
192                 args = [arg for arg in algorithms.func_repeat(numArgs, self.history.pop)]
193                 args.reverse()
194
195                 try:
196                         node = Node(*args)
197                 except StandardError:
198                         for arg in args:
199                                 self.history.push(arg)
200                         raise
201                 self.history.push(node)
202                 return node