MAINT-6585: put back indra/ipc files needed for scripts/template_verifier.py
parent
5ab6b73d57
commit
609595e938
|
|
@ -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$
|
||||
"""
|
||||
|
|
@ -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$
|
||||
"""
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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")
|
||||
|
||||
Loading…
Reference in New Issue