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