c525027d707f8bcd4004eb16e62ce9070c691dfd
[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 OPERATIONS(self):
125                 return self.__operations
126
127         @property
128         def CONSTANTS(self):
129                 return self.__constants
130
131         def clear(self):
132                 self.history.clear()
133                 self.__entry().clear()
134
135         def push_entry(self):
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                 return serialized
163
164         def deserialize_stack(self, data):
165                 for possibleNode in data:
166                         for nodeValue in possibleNode:
167                                 if nodeValue in self.OPERATIONS:
168                                         Node = self.OPERATIONS[nodeValue]
169                                         self._apply_operation(Node)
170                                 else:
171                                         node = self._parse_value(nodeValue)
172                                         self.history.push(node)
173
174         def _parse_value(self, userInput):
175                 try:
176                         value, base = parse_number(userInput)
177                         return operation.Value(value, base)
178                 except ValueError:
179                         pass
180
181                 try:
182                         return self.CONSTANTS[userInput]
183                 except KeyError:
184                         pass
185
186                 validate_variable_name(userInput)
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