1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 '''
31 Allow global per-thread values to be defined within a certain scope in a
32 way that supports multiple values, temporary changes inside with contexts,
33 etc.
34
35 This is implemented in two layers. The base layer is a map from keys
36 to values which isolates different, broad, functionalities. Despite the name,
37 a NamespaceMap can map from any key to any value - it's just a thread-local
38 map. However, typically is it used with Namespaces because they have support
39 for some useful idioms.
40
41 A Namespace is, as described above, associated with a name in the thread's
42 NamespaceMap. It manages state for some functionality, so is another map,
43 forming the second layer. The motivating example of a Namespace is the
44 OperatorNamespace, which maps from operators to matchers. This uses the
45 support in Namespace that allows values to be over-ridden within a certain
46 scope to support overriding matchers for matching spaces.
47 '''
48
49 from collections import deque
50
51 from threading import local
52
53 from lepl.support.lib import singleton, fmt
54
55
56 -class ContextError(Exception):
57 '''
58 Exception raised on problems with context.
59 '''
60 pass
61
62
63
65 '''
66 A store for namespaces.
67
68 This subclasses threading.local so each thread effectively has its own
69 instance (see test).
70 '''
71
75
76 - def get(self, name, default=None):
87
88
90 '''
91 A store for global definitions.
92 '''
93
95 self.__stack = deque([{} if base is None else base])
96
97 - def push(self, extra=None):
98 '''
99 Copy the current state to the stack and modify it. Values in extra
100 that map to None are ignored.
101 '''
102 self.__stack.append(dict(self.current()))
103 extra = {} if extra is None else extra
104 for name in extra:
105 self.set_if_not_none(name, extra[name])
106
108 '''
109 Return the previous state from the stack.
110 '''
111 self.__stack.pop()
112
114 '''
115 Allow use within a with context by duplicating the current state
116 and saving to the stack. Returns self to allow manipulation via set.
117 '''
118 self.push()
119 return self
120
122 '''
123 Restore the previous state from the stack on leaving the context.
124 '''
125 self.pop()
126
128 '''
129 The current state (a map from names to operator implementations).
130 '''
131 return self.__stack[-1]
132
133 - def set(self, name, value):
134 '''
135 Set a value.
136 '''
137 self.current()[name] = value
138
140 '''
141 Set a value if it is not None.
142 '''
143 if value != None:
144 self.set(name, value)
145
146 - def get(self, name, default):
147 '''
148 Get a value if defined, else the default.
149 '''
150 return self.current().get(name, default)
151
152
154 '''
155 Allow some values to be set only once.
156 '''
157
158 - def __init__(self, base=None, once_only=None):
161
163 '''
164 The given name can be set only once.
165 '''
166 self.__once_only.add(name)
167
168 - def set(self, name, value):
176
177
178
179 -def Global(name, default=None):
180 '''
181 Global (per-thread) binding from operator name to implementation, by
182 namespace.
183 '''
184
185 assert name
186 namespace_map = singleton(NamespaceMap)
187 return namespace_map.get(name, default)
188
189
191 '''
192 Allow access to global (per-thread) values.
193 '''
194
199
200 - def _lookup(self, name, default=None):
201 '''
202 Retrieve the named namespace from the global (per thread) store.
203 '''
204 return Global(self.__name, self.__namespace).get(name, default)
205
206
208 '''
209 Base class supporting dedicated syntax for particular options.
210 '''
211
212 - def __init__(self, name, namespace, frame):
213 self.__name = name
214 self.__namespace = namespace
215 self.__frame = frame
216
218 '''
219 On entering the context, add the new definitions.
220 '''
221 Global(self.__name, self.__namespace).push(self.__frame)
222
224 '''
225 On leaving the context, return to previous definition.
226 '''
227 Global(self.__name, self.__namespace).pop()
228