llmessage.py

Go to the documentation of this file.
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 ### Message Template
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         # in order of increasing deprecation
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             # this should never happen in real life because of the
00113             # way Template matches up messages by name
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 ### Parsing Message Templates
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                     # just assume (gulp) that this is a comment
00293                     # line 468: "sim -> dataserver"
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() # in LandStatRequest: "{ ParcelLocalID S32 1 }" 
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()

Generated on Fri May 16 08:31:53 2008 for SecondLife by  doxygen 1.5.5