aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/lib
diff options
context:
space:
mode:
authorJacek Antonelli2008-08-15 23:45:02 -0500
committerJacek Antonelli2008-08-15 23:45:02 -0500
commitd644fc64407dcd14ffcee6a0e9fbe28ee3a4e9bd (patch)
tree7ed0c2c27d717801238a2e6b5749cd5bf88c3059 /linden/indra/lib
parentSecond Life viewer sources 1.17.3.0 (diff)
downloadmeta-impy-d644fc64407dcd14ffcee6a0e9fbe28ee3a4e9bd.zip
meta-impy-d644fc64407dcd14ffcee6a0e9fbe28ee3a4e9bd.tar.gz
meta-impy-d644fc64407dcd14ffcee6a0e9fbe28ee3a4e9bd.tar.bz2
meta-impy-d644fc64407dcd14ffcee6a0e9fbe28ee3a4e9bd.tar.xz
Second Life viewer sources 1.18.0.6
Diffstat (limited to 'linden/indra/lib')
-rw-r--r--linden/indra/lib/python/indra/__init__.py25
-rw-r--r--linden/indra/lib/python/indra/compatibility.py121
-rw-r--r--linden/indra/lib/python/indra/llmanifest.py2
-rw-r--r--linden/indra/lib/python/indra/llmessage.py375
-rw-r--r--linden/indra/lib/python/indra/tokenstream.py152
5 files changed, 674 insertions, 1 deletions
diff --git a/linden/indra/lib/python/indra/__init__.py b/linden/indra/lib/python/indra/__init__.py
new file mode 100644
index 0000000..7548558
--- /dev/null
+++ b/linden/indra/lib/python/indra/__init__.py
@@ -0,0 +1,25 @@
1# @file __init__.py
2# @brief Initialization file for the indra module.
3#
4# Copyright (c) 2006-2007, Linden Research, Inc.
5#
6# Second Life Viewer Source Code
7# The source code in this file ("Source Code") is provided by Linden Lab
8# to you under the terms of the GNU General Public License, version 2.0
9# ("GPL"), unless you have obtained a separate licensing agreement
10# ("Other License"), formally executed by you and Linden Lab. Terms of
11# the GPL can be found in doc/GPL-license.txt in this distribution, or
12# online at http://secondlife.com/developers/opensource/gplv2
13#
14# There are special exceptions to the terms and conditions of the GPL as
15# it is applied to this Source Code. View the full text of the exception
16# in the file doc/FLOSS-exception.txt in this software distribution, or
17# online at http://secondlife.com/developers/opensource/flossexception
18#
19# By copying, modifying or distributing this software, you acknowledge
20# that you have read and understood your obligations described above,
21# and agree to abide by those obligations.
22#
23# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25# COMPLETENESS OR PERFORMANCE.
diff --git a/linden/indra/lib/python/indra/compatibility.py b/linden/indra/lib/python/indra/compatibility.py
new file mode 100644
index 0000000..abc1c6a
--- /dev/null
+++ b/linden/indra/lib/python/indra/compatibility.py
@@ -0,0 +1,121 @@
1# @file compatibility.py
2# @brief Classes that manage compatibility states.
3#
4# Copyright (c) 2007-2007, Linden Research, Inc.
5#
6# Second Life Viewer Source Code
7# The source code in this file ("Source Code") is provided by Linden Lab
8# to you under the terms of the GNU General Public License, version 2.0
9# ("GPL"), unless you have obtained a separate licensing agreement
10# ("Other License"), formally executed by you and Linden Lab. Terms of
11# the GPL can be found in doc/GPL-license.txt in this distribution, or
12# online at http://secondlife.com/developers/opensource/gplv2
13#
14# There are special exceptions to the terms and conditions of the GPL as
15# it is applied to this Source Code. View the full text of the exception
16# in the file doc/FLOSS-exception.txt in this software distribution, or
17# online at http://secondlife.com/developers/opensource/flossexception
18#
19# By copying, modifying or distributing this software, you acknowledge
20# that you have read and understood your obligations described above,
21# and agree to abide by those obligations.
22#
23# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25# COMPLETENESS OR PERFORMANCE.
26
27
28"""Compatibility combination table:
29
30 I M O N S
31 -- -- -- -- --
32I: I I I I I
33M: I M M M M
34O: I M O M O
35N: I M M N N
36S: I M O N S
37
38"""
39
40class _Compatibility(object):
41 def __init__(self, reason):
42 self.reasons = [ ]
43 if reason:
44 self.reasons.append(reason)
45
46 def combine(self, other):
47 if self._level() <= other._level():
48 return self._buildclone(other)
49 else:
50 return other._buildclone(self)
51
52 def prefix(self, leadin):
53 self.reasons = [ leadin + r for r in self.reasons ]
54
55 def same(self): return self._level() >= 1
56 def deployable(self): return self._level() > 0
57 def resolved(self): return self._level() > -1
58 def compatible(self): return self._level() > -2
59
60 def explain(self):
61 return self.__class__.__name__ + "\n" + "\n".join(self.reasons) + "\n"
62
63 def _buildclone(self, other=None):
64 c = self._buildinstance()
65 c.reasons = self.reasons
66 if other:
67 c.reasons = c.reasons + other.reasons
68 return c
69
70 def _buildinstance(self):
71 return self.__class__(None)
72
73# def _level(self):
74# raise RuntimeError('implement in subclass')
75
76
77class Incompatible(_Compatibility):
78 def _level(self):
79 return -2
80
81class Mixed(_Compatibility):
82 def __init__(self, *inputs):
83 _Compatibility.__init__(self, None)
84 for i in inputs:
85 self.reasons += i.reasons
86
87 def _buildinstance(self):
88 return self.__class__()
89
90 def _level(self):
91 return -1
92
93class _Aged(_Compatibility):
94 def combine(self, other):
95 if self._level() == other._level():
96 return self._buildclone(other)
97 if int(self._level()) == int(other._level()):
98 return Mixed(self, other)
99 return _Compatibility.combine(self, other)
100
101class Older(_Aged):
102 def _level(self):
103 return -0.25
104
105class Newer(_Aged):
106 def _level(self):
107 return 0.25
108
109class Same(_Compatibility):
110 def __init__(self):
111 _Compatibility.__init__(self, None)
112
113 def _buildinstance(self):
114 return self.__class__()
115
116 def _level(self):
117 return 1
118
119
120
121
diff --git a/linden/indra/lib/python/indra/llmanifest.py b/linden/indra/lib/python/indra/llmanifest.py
index e295cd7..22483b4 100644
--- a/linden/indra/lib/python/indra/llmanifest.py
+++ b/linden/indra/lib/python/indra/llmanifest.py
@@ -3,7 +3,7 @@
3# @author Ryan Williams 3# @author Ryan Williams
4# @brief Library for specifying operations on a set of files. 4# @brief Library for specifying operations on a set of files.
5# 5#
6# Copyright (c) 2006-2007, Linden Research, Inc. 6# Copyright (c) 2007-2007, Linden Research, Inc.
7# 7#
8# Second Life Viewer Source Code 8# Second Life Viewer Source Code
9# The source code in this file ("Source Code") is provided by Linden Lab 9# The source code in this file ("Source Code") is provided by Linden Lab
diff --git a/linden/indra/lib/python/indra/llmessage.py b/linden/indra/lib/python/indra/llmessage.py
new file mode 100644
index 0000000..1d4995d
--- /dev/null
+++ b/linden/indra/lib/python/indra/llmessage.py
@@ -0,0 +1,375 @@
1# @file llmessage.py
2# @brief Message template parsing and compatiblity
3#
4# Copyright (c) 2007-2007, Linden Research, Inc.
5#
6# Second Life Viewer Source Code
7# The source code in this file ("Source Code") is provided by Linden Lab
8# to you under the terms of the GNU General Public License, version 2.0
9# ("GPL"), unless you have obtained a separate licensing agreement
10# ("Other License"), formally executed by you and Linden Lab. Terms of
11# the GPL can be found in doc/GPL-license.txt in this distribution, or
12# online at http://secondlife.com/developers/opensource/gplv2
13#
14# There are special exceptions to the terms and conditions of the GPL as
15# it is applied to this Source Code. View the full text of the exception
16# in the file doc/FLOSS-exception.txt in this software distribution, or
17# online at http://secondlife.com/developers/opensource/flossexception
18#
19# By copying, modifying or distributing this software, you acknowledge
20# that you have read and understood your obligations described above,
21# and agree to abide by those obligations.
22#
23# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25# COMPLETENESS OR PERFORMANCE.
26
27from sets import Set, ImmutableSet
28
29from compatibility import Incompatible, Older, Newer, Same
30from tokenstream import TokenStream
31
32###
33### Message Template
34###
35
36class Template:
37 def __init__(self):
38 self.messages = { }
39
40 def addMessage(self, m):
41 self.messages[m.name] = m
42
43 def compatibleWithBase(self, base):
44 messagenames = (
45 ImmutableSet(self.messages.keys())
46 | ImmutableSet(base.messages.keys())
47 )
48
49 compatibility = Same()
50 for name in messagenames:
51 selfmessage = self.messages.get(name, None)
52 basemessage = base.messages.get(name, None)
53
54 if not selfmessage:
55 c = Older("missing message %s, did you mean to deprecate?" % name)
56 elif not basemessage:
57 c = Newer("added message %s" % name)
58 else:
59 c = selfmessage.compatibleWithBase(basemessage)
60 c.prefix("in message %s: " % name)
61
62 compatibility = compatibility.combine(c)
63
64 return compatibility
65
66
67
68class Message:
69 HIGH = "High"
70 MEDIUM = "Medium"
71 LOW = "Low"
72 FIXED = "Fixed"
73 priorities = [ HIGH, MEDIUM, LOW, FIXED ]
74 prioritieswithnumber = [ FIXED ]
75
76 TRUSTED = "Trusted"
77 NOTTRUSTED = "NotTrusted"
78 trusts = [ TRUSTED, NOTTRUSTED ]
79
80 UNENCODED = "Unencoded"
81 ZEROCODED = "Zerocoded"
82 encodings = [ UNENCODED, ZEROCODED ]
83
84 NOTDEPRECATED = "NotDeprecated"
85 DEPRECATED = "Deprecated"
86 UDPDEPRECATED = "UDPDeprecated"
87 deprecations = [ NOTDEPRECATED, UDPDEPRECATED, DEPRECATED ]
88 # in order of increasing deprecation
89
90 def __init__(self, name, number, priority, trust, coding):
91 self.name = name
92 self.number = number
93 self.priority = priority
94 self.trust = trust
95 self.coding = coding
96 self.deprecateLevel = 0
97 self.blocks = [ ]
98
99 def deprecated(self):
100 return self.deprecateLevel != 0
101
102 def deprecate(self, deprecation):
103 self.deprecateLevel = self.deprecations.index(deprecation)
104
105 def addBlock(self, block):
106 self.blocks.append(block)
107
108 def compatibleWithBase(self, base):
109 if self.name != base.name:
110 # this should never happen in real life because of the
111 # way Template matches up messages by name
112 return Incompatible("has different name: %s vs. %s in base"
113 % (self.name, base.name))
114 if self.priority != base.priority:
115 return Incompatible("has different priority: %s vs. %s in base"
116 % (self.priority, base.priority))
117 if self.trust != base.trust:
118 return Incompatible("has different trust: %s vs. %s in base"
119 % (self.trust, base.trust))
120 if self.coding != base.coding:
121 return Incompatible("has different coding: %s vs. %s in base"
122 % (self.coding, base.coding))
123 if self.number != base.number:
124 return Incompatible("has different number: %s vs. %s in base"
125 % (self.number, base.number))
126
127 compatibility = Same()
128
129 if self.deprecateLevel != base.deprecateLevel:
130 if self.deprecateLevel < base.deprecateLevel:
131 c = Older("is less deprecated: %s vs. %s in base" % (
132 self.deprecations[self.deprecateLevel],
133 self.deprecations[base.deprecateLevel]))
134 else:
135 c = Newer("is more deprecated: %s vs. %s in base" % (
136 self.deprecations[self.deprecateLevel],
137 self.deprecations[base.deprecateLevel]))
138 compatibility = compatibility.combine(c)
139
140 selflen = len(self.blocks)
141 baselen = len(base.blocks)
142 samelen = min(selflen, baselen)
143
144 for i in xrange(0, samelen):
145 selfblock = self.blocks[i]
146 baseblock = base.blocks[i]
147
148 c = selfblock.compatibleWithBase(baseblock)
149 if not c.same():
150 c = Incompatible("block %d isn't identical" % i)
151 compatibility = compatibility.combine(c)
152
153 if selflen > baselen:
154 c = Newer("has %d extra blocks" % (selflen - baselen))
155 elif selflen < baselen:
156 c = Older("missing %d extra blocks" % (baselen - selflen))
157 else:
158 c = Same()
159
160 compatibility = compatibility.combine(c)
161 return compatibility
162
163
164
165class Block(object):
166 SINGLE = "Single"
167 MULTIPLE = "Multiple"
168 VARIABLE = "Variable"
169 repeats = [ SINGLE, MULTIPLE, VARIABLE ]
170 repeatswithcount = [ MULTIPLE ]
171
172 def __init__(self, name, repeat, count=None):
173 self.name = name
174 self.repeat = repeat
175 self.count = count
176 self.variables = [ ]
177
178 def addVariable(self, variable):
179 self.variables.append(variable)
180
181 def compatibleWithBase(self, base):
182 if self.name != base.name:
183 return Incompatible("has different name: %s vs. %s in base"
184 % (self.name, base.name))
185 if self.repeat != base.repeat:
186 return Incompatible("has different repeat: %s vs. %s in base"
187 % (self.repeat, base.repeat))
188 if self.repeat in Block.repeatswithcount:
189 if self.count != base.count:
190 return Incompatible("has different count: %s vs. %s in base"
191 % (self.count, base.count))
192
193 compatibility = Same()
194
195 selflen = len(self.variables)
196 baselen = len(base.variables)
197
198 for i in xrange(0, min(selflen, baselen)):
199 selfvar = self.variables[i]
200 basevar = base.variables[i]
201
202 c = selfvar.compatibleWithBase(basevar)
203 if not c.same():
204 c = Incompatible("variable %d isn't identical" % i)
205 compatibility = compatibility.combine(c)
206
207 if selflen > baselen:
208 c = Newer("has %d extra variables" % (selflen - baselen))
209 elif selflen < baselen:
210 c = Older("missing %d extra variables" % (baselen - selflen))
211 else:
212 c = Same()
213
214 compatibility = compatibility.combine(c)
215 return compatibility
216
217
218
219class Variable:
220 U8 = "U8"; U16 = "U16"; U32 = "U32"; U64 = "U64"
221 S8 = "S8"; S16 = "S16"; S32 = "S32"; S64 = "S64"
222 F32 = "F32"; F64 = "F64"
223 LLVECTOR3 = "LLVector3"; LLVECTOR3D = "LLVector3d"; LLVECTOR4 = "LLVector4"
224 LLQUATERNION = "LLQuaternion"
225 LLUUID = "LLUUID"
226 BOOL = "BOOL"
227 IPADDR = "IPADDR"; IPPORT = "IPPORT"
228 FIXED = "Fixed"
229 VARIABLE = "Variable"
230 types = [ U8, U16, U32, U64, S8, S16, S32, S64, F32, F64,
231 LLVECTOR3, LLVECTOR3D, LLVECTOR4, LLQUATERNION,
232 LLUUID, BOOL, IPADDR, IPPORT, FIXED, VARIABLE ]
233 typeswithsize = [ FIXED, VARIABLE ]
234
235 def __init__(self, name, type, size):
236 self.name = name
237 self.type = type
238 self.size = size
239
240 def compatibleWithBase(self, base):
241 if self.name != base.name:
242 return Incompatible("has different name: %s vs. %s in base"
243 % (self.name, base.name))
244 if self.type != base.type:
245 return Incompatible("has different type: %s vs. %s in base"
246 % (self.type, base.type))
247 if self.type in Variable.typeswithsize:
248 if self.size != base.size:
249 return Incompatible("has different size: %s vs. %s in base"
250 % (self.size, base.size))
251 return Same()
252
253
254
255###
256### Parsing Message Templates
257###
258
259class TemplateParser:
260 def __init__(self, tokens):
261 self._tokens = tokens
262 self._version = 0
263 self._numbers = { }
264 for p in Message.priorities:
265 self._numbers[p] = 0
266
267 def parseTemplate(self):
268 tokens = self._tokens
269 t = Template()
270 while True:
271 if tokens.want("version"):
272 v = float(tokens.require(tokens.wantFloat()))
273 self._version = v
274 t.version = v
275 continue
276
277 m = self.parseMessage()
278 if m:
279 t.addMessage(m)
280 continue
281
282 if self._version >= 2.0:
283 tokens.require(tokens.wantEOF())
284 break
285 else:
286 if tokens.wantEOF():
287 break
288
289 tokens.consume()
290 # just assume (gulp) that this is a comment
291 # line 468: "sim -> dataserver"
292 return t
293
294
295 def parseMessage(self):
296 tokens = self._tokens
297 if not tokens.want("{"):
298 return None
299
300 name = tokens.require(tokens.wantSymbol())
301 priority = tokens.require(tokens.wantOneOf(Message.priorities))
302
303 if self._version >= 2.0 or priority in Message.prioritieswithnumber:
304 number = int("+" + tokens.require(tokens.wantInteger()), 0)
305 else:
306 self._numbers[priority] += 1
307 number = self._numbers[priority]
308
309 trust = tokens.require(tokens.wantOneOf(Message.trusts))
310 coding = tokens.require(tokens.wantOneOf(Message.encodings))
311
312 m = Message(name, number, priority, trust, coding)
313
314 if self._version >= 2.0:
315 d = tokens.wantOneOf(Message.deprecations)
316 if d:
317 m.deprecate(d)
318
319 while True:
320 b = self.parseBlock()
321 if not b:
322 break
323 m.addBlock(b)
324
325 tokens.require(tokens.want("}"))
326
327 return m
328
329
330 def parseBlock(self):
331 tokens = self._tokens
332 if not tokens.want("{"):
333 return None
334 name = tokens.require(tokens.wantSymbol())
335 repeat = tokens.require(tokens.wantOneOf(Block.repeats))
336 if repeat in Block.repeatswithcount:
337 count = int(tokens.require(tokens.wantInteger()))
338 else:
339 count = None
340
341 b = Block(name, repeat, count)
342
343 while True:
344 v = self.parseVariable()
345 if not v:
346 break
347 b.addVariable(v)
348
349 tokens.require(tokens.want("}"))
350 return b
351
352
353 def parseVariable(self):
354 tokens = self._tokens
355 if not tokens.want("{"):
356 return None
357 name = tokens.require(tokens.wantSymbol())
358 type = tokens.require(tokens.wantOneOf(Variable.types))
359 if type in Variable.typeswithsize:
360 size = tokens.require(tokens.wantInteger())
361 else:
362 tokens.wantInteger() # in LandStatRequest: "{ ParcelLocalID S32 1 }"
363 size = None
364 tokens.require(tokens.want("}"))
365 return Variable(name, type, size)
366
367def parseTemplateString(s):
368 return TemplateParser(TokenStream().fromString(s)).parseTemplate()
369
370def parseTemplateFile(f):
371 return TemplateParser(TokenStream().fromFile(f)).parseTemplate()
372
373
374
375
diff --git a/linden/indra/lib/python/indra/tokenstream.py b/linden/indra/lib/python/indra/tokenstream.py
new file mode 100644
index 0000000..7dab11f
--- /dev/null
+++ b/linden/indra/lib/python/indra/tokenstream.py
@@ -0,0 +1,152 @@
1# @file tokenstream.py
2# @brief Message template parsing utility class
3#
4# Copyright (c) 2007-2007, Linden Research, Inc.
5#
6# Second Life Viewer Source Code
7# The source code in this file ("Source Code") is provided by Linden Lab
8# to you under the terms of the GNU General Public License, version 2.0
9# ("GPL"), unless you have obtained a separate licensing agreement
10# ("Other License"), formally executed by you and Linden Lab. Terms of
11# the GPL can be found in doc/GPL-license.txt in this distribution, or
12# online at http://secondlife.com/developers/opensource/gplv2
13#
14# There are special exceptions to the terms and conditions of the GPL as
15# it is applied to this Source Code. View the full text of the exception
16# in the file doc/FLOSS-exception.txt in this software distribution, or
17# online at http://secondlife.com/developers/opensource/flossexception
18#
19# By copying, modifying or distributing this software, you acknowledge
20# that you have read and understood your obligations described above,
21# and agree to abide by those obligations.
22#
23# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25# COMPLETENESS OR PERFORMANCE.
26
27import re
28
29class _EOF(object):
30 pass
31
32EOF = _EOF()
33
34class _LineMarker(int):
35 pass
36
37_commentRE = re.compile(r'//.*')
38_symbolRE = re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
39_integerRE = re.compile(r'(0x[0-9A-Fa-f]+|0\d*|[1-9]\d*)')
40_floatRE = re.compile(r'\d+(\.\d*)?')
41
42
43class ParseError(Exception):
44 def __init__(self, stream, reason):
45 self.line = stream.line
46 self.context = stream._context()
47 self.reason = reason
48
49 def _contextString(self):
50 c = [ ]
51 for t in self.context:
52 if isinstance(t, _LineMarker):
53 break
54 c.append(t)
55 return " ".join(c)
56
57 def __str__(self):
58 return "line %d: %s @ ... %s" % (
59 self.line, self.reason, self._contextString())
60
61 def __nonzero__(self):
62 return False
63
64
65def _optionText(options):
66 n = len(options)
67 if n == 1:
68 return '"%s"' % options[0]
69 return '"' + '", "'.join(options[0:(n-1)]) + '" or "' + options[-1] + '"'
70
71
72class TokenStream(object):
73 def __init__(self):
74 self.line = 0
75 self.tokens = [ ]
76
77 def fromString(self, string):
78 return self.fromLines(string.split('\n'))
79
80 def fromFile(self, file):
81 return self.fromLines(file)
82
83 def fromLines(self, lines):
84 i = 0
85 for line in lines:
86 i += 1
87 self.tokens.append(_LineMarker(i))
88 self.tokens.extend(_commentRE.sub(" ", line).split())
89 self._consumeLines()
90 return self
91
92 def consume(self):
93 if not self.tokens:
94 return EOF
95 t = self.tokens.pop(0)
96 self._consumeLines()
97 return t
98
99 def _consumeLines(self):
100 while self.tokens and isinstance(self.tokens[0], _LineMarker):
101 self.line = self.tokens.pop(0)
102
103 def peek(self):
104 if not self.tokens:
105 return EOF
106 return self.tokens[0]
107
108 def want(self, t):
109 if t == self.peek():
110 return self.consume()
111 return ParseError(self, 'expected "%s"' % t)
112
113 def wantOneOf(self, options):
114 assert len(options)
115 if self.peek() in options:
116 return self.consume()
117 return ParseError(self, 'expected one of %s' % _optionText(options))
118
119 def wantEOF(self):
120 return self.want(EOF)
121
122 def wantRE(self, re, message=None):
123 t = self.peek()
124 if t != EOF:
125 m = re.match(t)
126 if m and m.end() == len(t):
127 return self.consume()
128 if not message:
129 message = "expected match for r'%s'" % re.pattern
130 return ParseError(self, message)
131
132 def wantSymbol(self):
133 return self.wantRE(_symbolRE, "expected symbol")
134
135 def wantInteger(self):
136 return self.wantRE(_integerRE, "expected integer")
137
138 def wantFloat(self):
139 return self.wantRE(_floatRE, "expected float")
140
141 def _context(self):
142 n = min(5, len(self.tokens))
143 return self.tokens[0:n]
144
145 def require(self, t):
146 if t:
147 return t
148 if isinstance(t, ParseError):
149 raise t
150 else:
151 raise ParseError(self, "unmet requirement")
152