00001 """\
00002 @file llmessage.py
00003 @brief Message template parsing and compatiblity
00004
00005 $LicenseInfo:firstyear=2007&license=mit$
00006
00007 Copyright (c) 2007-2008, Linden Research, Inc.
00008
00009 Permission is hereby granted, free of charge, to any person obtaining a copy
00010 of this software and associated documentation files (the "Software"), to deal
00011 in the Software without restriction, including without limitation the rights
00012 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00013 copies of the Software, and to permit persons to whom the Software is
00014 furnished to do so, subject to the following conditions:
00015
00016 The above copyright notice and this permission notice shall be included in
00017 all copies or substantial portions of the Software.
00018
00019 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00020 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00021 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00022 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00023 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00024 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
00025 THE SOFTWARE.
00026 $/LicenseInfo$
00027 """
00028
00029 from sets import Set, ImmutableSet
00030
00031 from compatibility import Incompatible, Older, Newer, Same
00032 from tokenstream import TokenStream
00033
00034
00035
00036
00037
00038 class Template:
00039 def __init__(self):
00040 self.messages = { }
00041
00042 def addMessage(self, m):
00043 self.messages[m.name] = m
00044
00045 def compatibleWithBase(self, base):
00046 messagenames = (
00047 ImmutableSet(self.messages.keys())
00048 | ImmutableSet(base.messages.keys())
00049 )
00050
00051 compatibility = Same()
00052 for name in messagenames:
00053 selfmessage = self.messages.get(name, None)
00054 basemessage = base.messages.get(name, None)
00055
00056 if not selfmessage:
00057 c = Older("missing message %s, did you mean to deprecate?" % name)
00058 elif not basemessage:
00059 c = Newer("added message %s" % name)
00060 else:
00061 c = selfmessage.compatibleWithBase(basemessage)
00062 c.prefix("in message %s: " % name)
00063
00064 compatibility = compatibility.combine(c)
00065
00066 return compatibility
00067
00068
00069
00070 class Message:
00071 HIGH = "High"
00072 MEDIUM = "Medium"
00073 LOW = "Low"
00074 FIXED = "Fixed"
00075 priorities = [ HIGH, MEDIUM, LOW, FIXED ]
00076 prioritieswithnumber = [ FIXED ]
00077
00078 TRUSTED = "Trusted"
00079 NOTTRUSTED = "NotTrusted"
00080 trusts = [ TRUSTED, NOTTRUSTED ]
00081
00082 UNENCODED = "Unencoded"
00083 ZEROCODED = "Zerocoded"
00084 encodings = [ UNENCODED, ZEROCODED ]
00085
00086 NOTDEPRECATED = "NotDeprecated"
00087 DEPRECATED = "Deprecated"
00088 UDPDEPRECATED = "UDPDeprecated"
00089 deprecations = [ NOTDEPRECATED, UDPDEPRECATED, DEPRECATED ]
00090
00091
00092 def __init__(self, name, number, priority, trust, coding):
00093 self.name = name
00094 self.number = number
00095 self.priority = priority
00096 self.trust = trust
00097 self.coding = coding
00098 self.deprecateLevel = 0
00099 self.blocks = [ ]
00100
00101 def deprecated(self):
00102 return self.deprecateLevel != 0
00103
00104 def deprecate(self, deprecation):
00105 self.deprecateLevel = self.deprecations.index(deprecation)
00106
00107 def addBlock(self, block):
00108 self.blocks.append(block)
00109
00110 def compatibleWithBase(self, base):
00111 if self.name != base.name:
00112
00113
00114 return Incompatible("has different name: %s vs. %s in base"
00115 % (self.name, base.name))
00116 if self.priority != base.priority:
00117 return Incompatible("has different priority: %s vs. %s in base"
00118 % (self.priority, base.priority))
00119 if self.trust != base.trust:
00120 return Incompatible("has different trust: %s vs. %s in base"
00121 % (self.trust, base.trust))
00122 if self.coding != base.coding:
00123 return Incompatible("has different coding: %s vs. %s in base"
00124 % (self.coding, base.coding))
00125 if self.number != base.number:
00126 return Incompatible("has different number: %s vs. %s in base"
00127 % (self.number, base.number))
00128
00129 compatibility = Same()
00130
00131 if self.deprecateLevel != base.deprecateLevel:
00132 if self.deprecateLevel < base.deprecateLevel:
00133 c = Older("is less deprecated: %s vs. %s in base" % (
00134 self.deprecations[self.deprecateLevel],
00135 self.deprecations[base.deprecateLevel]))
00136 else:
00137 c = Newer("is more deprecated: %s vs. %s in base" % (
00138 self.deprecations[self.deprecateLevel],
00139 self.deprecations[base.deprecateLevel]))
00140 compatibility = compatibility.combine(c)
00141
00142 selflen = len(self.blocks)
00143 baselen = len(base.blocks)
00144 samelen = min(selflen, baselen)
00145
00146 for i in xrange(0, samelen):
00147 selfblock = self.blocks[i]
00148 baseblock = base.blocks[i]
00149
00150 c = selfblock.compatibleWithBase(baseblock)
00151 if not c.same():
00152 c = Incompatible("block %d isn't identical" % i)
00153 compatibility = compatibility.combine(c)
00154
00155 if selflen > baselen:
00156 c = Newer("has %d extra blocks" % (selflen - baselen))
00157 elif selflen < baselen:
00158 c = Older("missing %d extra blocks" % (baselen - selflen))
00159 else:
00160 c = Same()
00161
00162 compatibility = compatibility.combine(c)
00163 return compatibility
00164
00165
00166
00167 class Block(object):
00168 SINGLE = "Single"
00169 MULTIPLE = "Multiple"
00170 VARIABLE = "Variable"
00171 repeats = [ SINGLE, MULTIPLE, VARIABLE ]
00172 repeatswithcount = [ MULTIPLE ]
00173
00174 def __init__(self, name, repeat, count=None):
00175 self.name = name
00176 self.repeat = repeat
00177 self.count = count
00178 self.variables = [ ]
00179
00180 def addVariable(self, variable):
00181 self.variables.append(variable)
00182
00183 def compatibleWithBase(self, base):
00184 if self.name != base.name:
00185 return Incompatible("has different name: %s vs. %s in base"
00186 % (self.name, base.name))
00187 if self.repeat != base.repeat:
00188 return Incompatible("has different repeat: %s vs. %s in base"
00189 % (self.repeat, base.repeat))
00190 if self.repeat in Block.repeatswithcount:
00191 if self.count != base.count:
00192 return Incompatible("has different count: %s vs. %s in base"
00193 % (self.count, base.count))
00194
00195 compatibility = Same()
00196
00197 selflen = len(self.variables)
00198 baselen = len(base.variables)
00199
00200 for i in xrange(0, min(selflen, baselen)):
00201 selfvar = self.variables[i]
00202 basevar = base.variables[i]
00203
00204 c = selfvar.compatibleWithBase(basevar)
00205 if not c.same():
00206 c = Incompatible("variable %d isn't identical" % i)
00207 compatibility = compatibility.combine(c)
00208
00209 if selflen > baselen:
00210 c = Newer("has %d extra variables" % (selflen - baselen))
00211 elif selflen < baselen:
00212 c = Older("missing %d extra variables" % (baselen - selflen))
00213 else:
00214 c = Same()
00215
00216 compatibility = compatibility.combine(c)
00217 return compatibility
00218
00219
00220
00221 class Variable:
00222 U8 = "U8"; U16 = "U16"; U32 = "U32"; U64 = "U64"
00223 S8 = "S8"; S16 = "S16"; S32 = "S32"; S64 = "S64"
00224 F32 = "F32"; F64 = "F64"
00225 LLVECTOR3 = "LLVector3"; LLVECTOR3D = "LLVector3d"; LLVECTOR4 = "LLVector4"
00226 LLQUATERNION = "LLQuaternion"
00227 LLUUID = "LLUUID"
00228 BOOL = "BOOL"
00229 IPADDR = "IPADDR"; IPPORT = "IPPORT"
00230 FIXED = "Fixed"
00231 VARIABLE = "Variable"
00232 types = [ U8, U16, U32, U64, S8, S16, S32, S64, F32, F64,
00233 LLVECTOR3, LLVECTOR3D, LLVECTOR4, LLQUATERNION,
00234 LLUUID, BOOL, IPADDR, IPPORT, FIXED, VARIABLE ]
00235 typeswithsize = [ FIXED, VARIABLE ]
00236
00237 def __init__(self, name, type, size):
00238 self.name = name
00239 self.type = type
00240 self.size = size
00241
00242 def compatibleWithBase(self, base):
00243 if self.name != base.name:
00244 return Incompatible("has different name: %s vs. %s in base"
00245 % (self.name, base.name))
00246 if self.type != base.type:
00247 return Incompatible("has different type: %s vs. %s in base"
00248 % (self.type, base.type))
00249 if self.type in Variable.typeswithsize:
00250 if self.size != base.size:
00251 return Incompatible("has different size: %s vs. %s in base"
00252 % (self.size, base.size))
00253 return Same()
00254
00255
00256
00257
00258
00259
00260
00261 class TemplateParser:
00262 def __init__(self, tokens):
00263 self._tokens = tokens
00264 self._version = 0
00265 self._numbers = { }
00266 for p in Message.priorities:
00267 self._numbers[p] = 0
00268
00269 def parseTemplate(self):
00270 tokens = self._tokens
00271 t = Template()
00272 while True:
00273 if tokens.want("version"):
00274 v = float(tokens.require(tokens.wantFloat()))
00275 self._version = v
00276 t.version = v
00277 continue
00278
00279 m = self.parseMessage()
00280 if m:
00281 t.addMessage(m)
00282 continue
00283
00284 if self._version >= 2.0:
00285 tokens.require(tokens.wantEOF())
00286 break
00287 else:
00288 if tokens.wantEOF():
00289 break
00290
00291 tokens.consume()
00292
00293
00294 return t
00295
00296
00297 def parseMessage(self):
00298 tokens = self._tokens
00299 if not tokens.want("{"):
00300 return None
00301
00302 name = tokens.require(tokens.wantSymbol())
00303 priority = tokens.require(tokens.wantOneOf(Message.priorities))
00304
00305 if self._version >= 2.0 or priority in Message.prioritieswithnumber:
00306 number = int("+" + tokens.require(tokens.wantInteger()), 0)
00307 else:
00308 self._numbers[priority] += 1
00309 number = self._numbers[priority]
00310
00311 trust = tokens.require(tokens.wantOneOf(Message.trusts))
00312 coding = tokens.require(tokens.wantOneOf(Message.encodings))
00313
00314 m = Message(name, number, priority, trust, coding)
00315
00316 if self._version >= 2.0:
00317 d = tokens.wantOneOf(Message.deprecations)
00318 if d:
00319 m.deprecate(d)
00320
00321 while True:
00322 b = self.parseBlock()
00323 if not b:
00324 break
00325 m.addBlock(b)
00326
00327 tokens.require(tokens.want("}"))
00328
00329 return m
00330
00331
00332 def parseBlock(self):
00333 tokens = self._tokens
00334 if not tokens.want("{"):
00335 return None
00336 name = tokens.require(tokens.wantSymbol())
00337 repeat = tokens.require(tokens.wantOneOf(Block.repeats))
00338 if repeat in Block.repeatswithcount:
00339 count = int(tokens.require(tokens.wantInteger()))
00340 else:
00341 count = None
00342
00343 b = Block(name, repeat, count)
00344
00345 while True:
00346 v = self.parseVariable()
00347 if not v:
00348 break
00349 b.addVariable(v)
00350
00351 tokens.require(tokens.want("}"))
00352 return b
00353
00354
00355 def parseVariable(self):
00356 tokens = self._tokens
00357 if not tokens.want("{"):
00358 return None
00359 name = tokens.require(tokens.wantSymbol())
00360 type = tokens.require(tokens.wantOneOf(Variable.types))
00361 if type in Variable.typeswithsize:
00362 size = tokens.require(tokens.wantInteger())
00363 else:
00364 tokens.wantInteger()
00365 size = None
00366 tokens.require(tokens.want("}"))
00367 return Variable(name, type, size)
00368
00369 def parseTemplateString(s):
00370 return TemplateParser(TokenStream().fromString(s)).parseTemplate()
00371
00372 def parseTemplateFile(f):
00373 return TemplateParser(TokenStream().fromFile(f)).parseTemplate()