summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorGlenn Glazer <coyot@lindenlab.com>2016-07-21 16:51:47 -0700
committerGlenn Glazer <coyot@lindenlab.com>2016-07-21 16:51:47 -0700
commit609595e9384c86f78f5eb5471c0b7cac4b7fef9e (patch)
treef87de87a8cf6e9cc44cf2461350d88771128788a /indra
parent5ab6b73d57818c7aaf9a4bcd0b302854166fd93a (diff)
MAINT-6585: put back indra/ipc files needed for scripts/template_verifier.py
Diffstat (limited to 'indra')
-rwxr-xr-xindra/lib/python/indra/__init__.py25
-rwxr-xr-xindra/lib/python/indra/ipc/__init__.py27
-rwxr-xr-xindra/lib/python/indra/ipc/compatibility.py123
-rwxr-xr-xindra/lib/python/indra/ipc/llmessage.py372
-rwxr-xr-xindra/lib/python/indra/ipc/tokenstream.py154
5 files changed, 701 insertions, 0 deletions
diff --git a/indra/lib/python/indra/__init__.py b/indra/lib/python/indra/__init__.py
new file mode 100755
index 0000000000..0c5053cf49
--- /dev/null
+++ b/indra/lib/python/indra/__init__.py
@@ -0,0 +1,25 @@
+"""\
+@file __init__.py
+@brief Initialization file for the indra module.
+
+$LicenseInfo:firstyear=2006&license=viewerlgpl$
+Second Life Viewer Source Code
+Copyright (C) 2006-2010, Linden Research, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation;
+version 2.1 of the License only.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+$/LicenseInfo$
+"""
diff --git a/indra/lib/python/indra/ipc/__init__.py b/indra/lib/python/indra/ipc/__init__.py
new file mode 100755
index 0000000000..302bbf4a03
--- /dev/null
+++ b/indra/lib/python/indra/ipc/__init__.py
@@ -0,0 +1,27 @@
+"""\
+@file __init__.py
+@brief Initialization file for the indra ipc module.
+
+$LicenseInfo:firstyear=2006&license=mit$
+
+Copyright (c) 2006-2009, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
diff --git a/indra/lib/python/indra/ipc/compatibility.py b/indra/lib/python/indra/ipc/compatibility.py
new file mode 100755
index 0000000000..b9045c22f3
--- /dev/null
+++ b/indra/lib/python/indra/ipc/compatibility.py
@@ -0,0 +1,123 @@
+"""\
+@file compatibility.py
+@brief Classes that manage compatibility states.
+
+$LicenseInfo:firstyear=2007&license=mit$
+
+Copyright (c) 2007-2009, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+
+"""Compatibility combination table:
+
+ I M O N S
+ -- -- -- -- --
+I: I I I I I
+M: I M M M M
+O: I M O M O
+N: I M M N N
+S: I M O N S
+
+"""
+
+class _Compatibility(object):
+ def __init__(self, reason):
+ self.reasons = [ ]
+ if reason:
+ self.reasons.append(reason)
+
+ def combine(self, other):
+ if self._level() <= other._level():
+ return self._buildclone(other)
+ else:
+ return other._buildclone(self)
+
+ def prefix(self, leadin):
+ self.reasons = [ leadin + r for r in self.reasons ]
+
+ def same(self): return self._level() >= 1
+ def deployable(self): return self._level() > 0
+ def resolved(self): return self._level() > -1
+ def compatible(self): return self._level() > -2
+
+ def explain(self):
+ return self.__class__.__name__ + "\n" + "\n".join(self.reasons) + "\n"
+
+ def _buildclone(self, other=None):
+ c = self._buildinstance()
+ c.reasons = self.reasons
+ if other:
+ c.reasons = c.reasons + other.reasons
+ return c
+
+ def _buildinstance(self):
+ return self.__class__(None)
+
+# def _level(self):
+# raise RuntimeError('implement in subclass')
+
+
+class Incompatible(_Compatibility):
+ def _level(self):
+ return -2
+
+class Mixed(_Compatibility):
+ def __init__(self, *inputs):
+ _Compatibility.__init__(self, None)
+ for i in inputs:
+ self.reasons += i.reasons
+
+ def _buildinstance(self):
+ return self.__class__()
+
+ def _level(self):
+ return -1
+
+class _Aged(_Compatibility):
+ def combine(self, other):
+ if self._level() == other._level():
+ return self._buildclone(other)
+ if int(self._level()) == int(other._level()):
+ return Mixed(self, other)
+ return _Compatibility.combine(self, other)
+
+class Older(_Aged):
+ def _level(self):
+ return -0.25
+
+class Newer(_Aged):
+ def _level(self):
+ return 0.25
+
+class Same(_Compatibility):
+ def __init__(self):
+ _Compatibility.__init__(self, None)
+
+ def _buildinstance(self):
+ return self.__class__()
+
+ def _level(self):
+ return 1
+
+
+
+
diff --git a/indra/lib/python/indra/ipc/llmessage.py b/indra/lib/python/indra/ipc/llmessage.py
new file mode 100755
index 0000000000..91fb36b72c
--- /dev/null
+++ b/indra/lib/python/indra/ipc/llmessage.py
@@ -0,0 +1,372 @@
+"""\
+@file llmessage.py
+@brief Message template parsing and compatiblity
+
+$LicenseInfo:firstyear=2007&license=mit$
+
+Copyright (c) 2007-2009, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+from compatibility import Incompatible, Older, Newer, Same
+from tokenstream import TokenStream
+
+###
+### Message Template
+###
+
+class Template:
+ def __init__(self):
+ self.messages = { }
+
+ def addMessage(self, m):
+ self.messages[m.name] = m
+
+ def compatibleWithBase(self, base):
+ messagenames = (
+ frozenset(self.messages.keys())
+ | frozenset(base.messages.keys())
+ )
+
+ compatibility = Same()
+ for name in messagenames:
+ selfmessage = self.messages.get(name, None)
+ basemessage = base.messages.get(name, None)
+
+ if not selfmessage:
+ c = Older("missing message %s, did you mean to deprecate?" % name)
+ elif not basemessage:
+ c = Newer("added message %s" % name)
+ else:
+ c = selfmessage.compatibleWithBase(basemessage)
+ c.prefix("in message %s: " % name)
+
+ compatibility = compatibility.combine(c)
+
+ return compatibility
+
+
+
+class Message:
+ HIGH = "High"
+ MEDIUM = "Medium"
+ LOW = "Low"
+ FIXED = "Fixed"
+ priorities = [ HIGH, MEDIUM, LOW, FIXED ]
+ prioritieswithnumber = [ FIXED ]
+
+ TRUSTED = "Trusted"
+ NOTTRUSTED = "NotTrusted"
+ trusts = [ TRUSTED, NOTTRUSTED ]
+
+ UNENCODED = "Unencoded"
+ ZEROCODED = "Zerocoded"
+ encodings = [ UNENCODED, ZEROCODED ]
+
+ NOTDEPRECATED = "NotDeprecated"
+ DEPRECATED = "Deprecated"
+ UDPDEPRECATED = "UDPDeprecated"
+ UDPBLACKLISTED = "UDPBlackListed"
+ deprecations = [ NOTDEPRECATED, UDPDEPRECATED, UDPBLACKLISTED, DEPRECATED ]
+ # in order of increasing deprecation
+
+ def __init__(self, name, number, priority, trust, coding):
+ self.name = name
+ self.number = number
+ self.priority = priority
+ self.trust = trust
+ self.coding = coding
+ self.deprecateLevel = 0
+ self.blocks = [ ]
+
+ def deprecated(self):
+ return self.deprecateLevel != 0
+
+ def deprecate(self, deprecation):
+ self.deprecateLevel = self.deprecations.index(deprecation)
+
+ def addBlock(self, block):
+ self.blocks.append(block)
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ # this should never happen in real life because of the
+ # way Template matches up messages by name
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.priority != base.priority:
+ return Incompatible("has different priority: %s vs. %s in base"
+ % (self.priority, base.priority))
+ if self.trust != base.trust:
+ return Incompatible("has different trust: %s vs. %s in base"
+ % (self.trust, base.trust))
+ if self.coding != base.coding:
+ return Incompatible("has different coding: %s vs. %s in base"
+ % (self.coding, base.coding))
+ if self.number != base.number:
+ return Incompatible("has different number: %s vs. %s in base"
+ % (self.number, base.number))
+
+ compatibility = Same()
+
+ if self.deprecateLevel != base.deprecateLevel:
+ if self.deprecateLevel < base.deprecateLevel:
+ c = Older("is less deprecated: %s vs. %s in base" % (
+ self.deprecations[self.deprecateLevel],
+ self.deprecations[base.deprecateLevel]))
+ else:
+ c = Newer("is more deprecated: %s vs. %s in base" % (
+ self.deprecations[self.deprecateLevel],
+ self.deprecations[base.deprecateLevel]))
+ compatibility = compatibility.combine(c)
+
+ selflen = len(self.blocks)
+ baselen = len(base.blocks)
+ samelen = min(selflen, baselen)
+
+ for i in xrange(0, samelen):
+ selfblock = self.blocks[i]
+ baseblock = base.blocks[i]
+
+ c = selfblock.compatibleWithBase(baseblock)
+ if not c.same():
+ c = Incompatible("block %d isn't identical" % i)
+ compatibility = compatibility.combine(c)
+
+ if selflen > baselen:
+ c = Newer("has %d extra blocks" % (selflen - baselen))
+ elif selflen < baselen:
+ c = Older("missing %d extra blocks" % (baselen - selflen))
+ else:
+ c = Same()
+
+ compatibility = compatibility.combine(c)
+ return compatibility
+
+
+
+class Block(object):
+ SINGLE = "Single"
+ MULTIPLE = "Multiple"
+ VARIABLE = "Variable"
+ repeats = [ SINGLE, MULTIPLE, VARIABLE ]
+ repeatswithcount = [ MULTIPLE ]
+
+ def __init__(self, name, repeat, count=None):
+ self.name = name
+ self.repeat = repeat
+ self.count = count
+ self.variables = [ ]
+
+ def addVariable(self, variable):
+ self.variables.append(variable)
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.repeat != base.repeat:
+ return Incompatible("has different repeat: %s vs. %s in base"
+ % (self.repeat, base.repeat))
+ if self.repeat in Block.repeatswithcount:
+ if self.count != base.count:
+ return Incompatible("has different count: %s vs. %s in base"
+ % (self.count, base.count))
+
+ compatibility = Same()
+
+ selflen = len(self.variables)
+ baselen = len(base.variables)
+
+ for i in xrange(0, min(selflen, baselen)):
+ selfvar = self.variables[i]
+ basevar = base.variables[i]
+
+ c = selfvar.compatibleWithBase(basevar)
+ if not c.same():
+ c = Incompatible("variable %d isn't identical" % i)
+ compatibility = compatibility.combine(c)
+
+ if selflen > baselen:
+ c = Newer("has %d extra variables" % (selflen - baselen))
+ elif selflen < baselen:
+ c = Older("missing %d extra variables" % (baselen - selflen))
+ else:
+ c = Same()
+
+ compatibility = compatibility.combine(c)
+ return compatibility
+
+
+
+class Variable:
+ U8 = "U8"; U16 = "U16"; U32 = "U32"; U64 = "U64"
+ S8 = "S8"; S16 = "S16"; S32 = "S32"; S64 = "S64"
+ F32 = "F32"; F64 = "F64"
+ LLVECTOR3 = "LLVector3"; LLVECTOR3D = "LLVector3d"; LLVECTOR4 = "LLVector4"
+ LLQUATERNION = "LLQuaternion"
+ LLUUID = "LLUUID"
+ BOOL = "BOOL"
+ IPADDR = "IPADDR"; IPPORT = "IPPORT"
+ FIXED = "Fixed"
+ VARIABLE = "Variable"
+ types = [ U8, U16, U32, U64, S8, S16, S32, S64, F32, F64,
+ LLVECTOR3, LLVECTOR3D, LLVECTOR4, LLQUATERNION,
+ LLUUID, BOOL, IPADDR, IPPORT, FIXED, VARIABLE ]
+ typeswithsize = [ FIXED, VARIABLE ]
+
+ def __init__(self, name, type, size):
+ self.name = name
+ self.type = type
+ self.size = size
+
+ def compatibleWithBase(self, base):
+ if self.name != base.name:
+ return Incompatible("has different name: %s vs. %s in base"
+ % (self.name, base.name))
+ if self.type != base.type:
+ return Incompatible("has different type: %s vs. %s in base"
+ % (self.type, base.type))
+ if self.type in Variable.typeswithsize:
+ if self.size != base.size:
+ return Incompatible("has different size: %s vs. %s in base"
+ % (self.size, base.size))
+ return Same()
+
+
+
+###
+### Parsing Message Templates
+###
+
+class TemplateParser:
+ def __init__(self, tokens):
+ self._tokens = tokens
+ self._version = 0
+ self._numbers = { }
+ for p in Message.priorities:
+ self._numbers[p] = 0
+
+ def parseTemplate(self):
+ tokens = self._tokens
+ t = Template()
+ while True:
+ if tokens.want("version"):
+ v = float(tokens.require(tokens.wantFloat()))
+ self._version = v
+ t.version = v
+ continue
+
+ m = self.parseMessage()
+ if m:
+ t.addMessage(m)
+ continue
+
+ if self._version >= 2.0:
+ tokens.require(tokens.wantEOF())
+ break
+ else:
+ if tokens.wantEOF():
+ break
+
+ tokens.consume()
+ # just assume (gulp) that this is a comment
+ # line 468: "sim -> dataserver"
+ return t
+
+
+ def parseMessage(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+
+ name = tokens.require(tokens.wantSymbol())
+ priority = tokens.require(tokens.wantOneOf(Message.priorities))
+
+ if self._version >= 2.0 or priority in Message.prioritieswithnumber:
+ number = int("+" + tokens.require(tokens.wantInteger()), 0)
+ else:
+ self._numbers[priority] += 1
+ number = self._numbers[priority]
+
+ trust = tokens.require(tokens.wantOneOf(Message.trusts))
+ coding = tokens.require(tokens.wantOneOf(Message.encodings))
+
+ m = Message(name, number, priority, trust, coding)
+
+ if self._version >= 2.0:
+ d = tokens.wantOneOf(Message.deprecations)
+ if d:
+ m.deprecate(d)
+
+ while True:
+ b = self.parseBlock()
+ if not b:
+ break
+ m.addBlock(b)
+
+ tokens.require(tokens.want("}"))
+
+ return m
+
+
+ def parseBlock(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+ name = tokens.require(tokens.wantSymbol())
+ repeat = tokens.require(tokens.wantOneOf(Block.repeats))
+ if repeat in Block.repeatswithcount:
+ count = int(tokens.require(tokens.wantInteger()))
+ else:
+ count = None
+
+ b = Block(name, repeat, count)
+
+ while True:
+ v = self.parseVariable()
+ if not v:
+ break
+ b.addVariable(v)
+
+ tokens.require(tokens.want("}"))
+ return b
+
+
+ def parseVariable(self):
+ tokens = self._tokens
+ if not tokens.want("{"):
+ return None
+ name = tokens.require(tokens.wantSymbol())
+ type = tokens.require(tokens.wantOneOf(Variable.types))
+ if type in Variable.typeswithsize:
+ size = tokens.require(tokens.wantInteger())
+ else:
+ tokens.wantInteger() # in LandStatRequest: "{ ParcelLocalID S32 1 }"
+ size = None
+ tokens.require(tokens.want("}"))
+ return Variable(name, type, size)
+
+def parseTemplateString(s):
+ return TemplateParser(TokenStream().fromString(s)).parseTemplate()
+
+def parseTemplateFile(f):
+ return TemplateParser(TokenStream().fromFile(f)).parseTemplate()
diff --git a/indra/lib/python/indra/ipc/tokenstream.py b/indra/lib/python/indra/ipc/tokenstream.py
new file mode 100755
index 0000000000..b96f26d3ff
--- /dev/null
+++ b/indra/lib/python/indra/ipc/tokenstream.py
@@ -0,0 +1,154 @@
+"""\
+@file tokenstream.py
+@brief Message template parsing utility class
+
+$LicenseInfo:firstyear=2007&license=mit$
+
+Copyright (c) 2007-2009, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+import re
+
+class _EOF(object):
+ pass
+
+EOF = _EOF()
+
+class _LineMarker(int):
+ pass
+
+_commentRE = re.compile(r'//.*')
+_symbolRE = re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
+_integerRE = re.compile(r'(0x[0-9A-Fa-f]+|0\d*|[1-9]\d*)')
+_floatRE = re.compile(r'\d+(\.\d*)?')
+
+
+class ParseError(Exception):
+ def __init__(self, stream, reason):
+ self.line = stream.line
+ self.context = stream._context()
+ self.reason = reason
+
+ def _contextString(self):
+ c = [ ]
+ for t in self.context:
+ if isinstance(t, _LineMarker):
+ break
+ c.append(t)
+ return " ".join(c)
+
+ def __str__(self):
+ return "line %d: %s @ ... %s" % (
+ self.line, self.reason, self._contextString())
+
+ def __nonzero__(self):
+ return False
+
+
+def _optionText(options):
+ n = len(options)
+ if n == 1:
+ return '"%s"' % options[0]
+ return '"' + '", "'.join(options[0:(n-1)]) + '" or "' + options[-1] + '"'
+
+
+class TokenStream(object):
+ def __init__(self):
+ self.line = 0
+ self.tokens = [ ]
+
+ def fromString(self, string):
+ return self.fromLines(string.split('\n'))
+
+ def fromFile(self, file):
+ return self.fromLines(file)
+
+ def fromLines(self, lines):
+ i = 0
+ for line in lines:
+ i += 1
+ self.tokens.append(_LineMarker(i))
+ self.tokens.extend(_commentRE.sub(" ", line).split())
+ self._consumeLines()
+ return self
+
+ def consume(self):
+ if not self.tokens:
+ return EOF
+ t = self.tokens.pop(0)
+ self._consumeLines()
+ return t
+
+ def _consumeLines(self):
+ while self.tokens and isinstance(self.tokens[0], _LineMarker):
+ self.line = self.tokens.pop(0)
+
+ def peek(self):
+ if not self.tokens:
+ return EOF
+ return self.tokens[0]
+
+ def want(self, t):
+ if t == self.peek():
+ return self.consume()
+ return ParseError(self, 'expected "%s"' % t)
+
+ def wantOneOf(self, options):
+ assert len(options)
+ if self.peek() in options:
+ return self.consume()
+ return ParseError(self, 'expected one of %s' % _optionText(options))
+
+ def wantEOF(self):
+ return self.want(EOF)
+
+ def wantRE(self, re, message=None):
+ t = self.peek()
+ if t != EOF:
+ m = re.match(t)
+ if m and m.end() == len(t):
+ return self.consume()
+ if not message:
+ message = "expected match for r'%s'" % re.pattern
+ return ParseError(self, message)
+
+ def wantSymbol(self):
+ return self.wantRE(_symbolRE, "expected symbol")
+
+ def wantInteger(self):
+ return self.wantRE(_integerRE, "expected integer")
+
+ def wantFloat(self):
+ return self.wantRE(_floatRE, "expected float")
+
+ def _context(self):
+ n = min(5, len(self.tokens))
+ return self.tokens[0:n]
+
+ def require(self, t):
+ if t:
+ return t
+ if isinstance(t, ParseError):
+ raise t
+ else:
+ raise ParseError(self, "unmet requirement")
+