PageRenderTime 59ms CodeModel.GetById 12ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/lib/python/indra/ipc/llmessage.py

https://bitbucket.org/lindenlab/viewer-beta/
Python | 372 lines | 363 code | 0 blank | 9 comment | 10 complexity | 0eaaee39e2b130ac5555df73ac518763 MD5 | raw file
  1"""\
  2@file llmessage.py
  3@brief Message template parsing and compatiblity
  4
  5$LicenseInfo:firstyear=2007&license=mit$
  6
  7Copyright (c) 2007-2009, Linden Research, Inc.
  8
  9Permission is hereby granted, free of charge, to any person obtaining a copy
 10of this software and associated documentation files (the "Software"), to deal
 11in the Software without restriction, including without limitation the rights
 12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 13copies of the Software, and to permit persons to whom the Software is
 14furnished to do so, subject to the following conditions:
 15
 16The above copyright notice and this permission notice shall be included in
 17all copies or substantial portions of the Software.
 18
 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 25THE SOFTWARE.
 26$/LicenseInfo$
 27"""
 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              frozenset(self.messages.keys())
 46            | frozenset(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    UDPBLACKLISTED = "UDPBlackListed"
 88    deprecations = [ NOTDEPRECATED, UDPDEPRECATED, UDPBLACKLISTED, DEPRECATED ]
 89    # in order of increasing deprecation
 90    
 91    def __init__(self, name, number, priority, trust, coding):
 92        self.name = name
 93        self.number = number
 94        self.priority = priority
 95        self.trust = trust
 96        self.coding = coding
 97        self.deprecateLevel = 0
 98        self.blocks = [ ]
 99
100    def deprecated(self):
101        return self.deprecateLevel != 0
102
103    def deprecate(self, deprecation): 
104        self.deprecateLevel = self.deprecations.index(deprecation)
105
106    def addBlock(self, block):
107        self.blocks.append(block)
108        
109    def compatibleWithBase(self, base):
110        if self.name != base.name:
111            # this should never happen in real life because of the
112            # way Template matches up messages by name
113            return Incompatible("has different name: %s vs. %s in base"
114                               % (self.name, base.name)) 
115        if self.priority != base.priority:
116            return Incompatible("has different priority: %s vs. %s in base"
117                                % (self.priority, base.priority)) 
118        if self.trust != base.trust:
119            return Incompatible("has different trust: %s vs. %s in base"
120                                % (self.trust, base.trust)) 
121        if self.coding != base.coding:
122            return Incompatible("has different coding: %s vs. %s in base"
123                                % (self.coding, base.coding)) 
124        if self.number != base.number:
125            return Incompatible("has different number: %s vs. %s in base"
126                                % (self.number, base.number))
127        
128        compatibility = Same()
129
130        if self.deprecateLevel != base.deprecateLevel:
131            if self.deprecateLevel < base.deprecateLevel:
132                c = Older("is less deprecated: %s vs. %s in base" % (
133                    self.deprecations[self.deprecateLevel],
134                    self.deprecations[base.deprecateLevel]))
135            else:
136                c = Newer("is more deprecated: %s vs. %s in base" % (
137                    self.deprecations[self.deprecateLevel],
138                    self.deprecations[base.deprecateLevel]))
139            compatibility = compatibility.combine(c)
140        
141        selflen = len(self.blocks)
142        baselen = len(base.blocks)
143        samelen = min(selflen, baselen)
144            
145        for i in xrange(0, samelen):
146            selfblock = self.blocks[i]
147            baseblock = base.blocks[i]
148            
149            c = selfblock.compatibleWithBase(baseblock)
150            if not c.same():
151                c = Incompatible("block %d isn't identical" % i)
152            compatibility = compatibility.combine(c)
153        
154        if selflen > baselen:
155            c = Newer("has %d extra blocks" % (selflen - baselen))
156        elif selflen < baselen:
157            c = Older("missing %d extra blocks" % (baselen - selflen))
158        else:
159            c = Same()
160        
161        compatibility = compatibility.combine(c)
162        return compatibility
163
164
165
166class Block(object):
167    SINGLE = "Single"
168    MULTIPLE = "Multiple"
169    VARIABLE = "Variable"
170    repeats = [ SINGLE, MULTIPLE, VARIABLE ]
171    repeatswithcount = [ MULTIPLE ]
172    
173    def __init__(self, name, repeat, count=None):
174        self.name = name
175        self.repeat = repeat
176        self.count = count
177        self.variables = [ ]
178
179    def addVariable(self, variable):
180        self.variables.append(variable)
181        
182    def compatibleWithBase(self, base):
183        if self.name != base.name:
184            return Incompatible("has different name: %s vs. %s in base"
185                                % (self.name, base.name))
186        if self.repeat != base.repeat:
187            return Incompatible("has different repeat: %s vs. %s in base"
188                                % (self.repeat, base.repeat))
189        if self.repeat in Block.repeatswithcount:
190            if self.count != base.count:
191                return Incompatible("has different count: %s vs. %s in base"
192                                    % (self.count, base.count)) 
193
194        compatibility = Same()
195        
196        selflen = len(self.variables)
197        baselen = len(base.variables)
198        
199        for i in xrange(0, min(selflen, baselen)):
200            selfvar = self.variables[i]
201            basevar = base.variables[i]
202            
203            c = selfvar.compatibleWithBase(basevar)
204            if not c.same():
205                c = Incompatible("variable %d isn't identical" % i)
206            compatibility = compatibility.combine(c)
207
208        if selflen > baselen:
209            c = Newer("has %d extra variables" % (selflen - baselen))
210        elif selflen < baselen:
211            c = Older("missing %d extra variables" % (baselen - selflen))
212        else:
213            c = Same()
214
215        compatibility = compatibility.combine(c)
216        return compatibility
217
218
219
220class Variable:
221    U8 = "U8"; U16 = "U16"; U32 = "U32"; U64 = "U64"
222    S8 = "S8"; S16 = "S16"; S32 = "S32"; S64 = "S64"
223    F32 = "F32"; F64 = "F64"
224    LLVECTOR3 = "LLVector3"; LLVECTOR3D = "LLVector3d"; LLVECTOR4 = "LLVector4"
225    LLQUATERNION = "LLQuaternion"
226    LLUUID = "LLUUID"
227    BOOL = "BOOL"
228    IPADDR = "IPADDR"; IPPORT = "IPPORT"
229    FIXED = "Fixed"
230    VARIABLE = "Variable"
231    types = [ U8, U16, U32, U64, S8, S16, S32, S64, F32, F64,
232                LLVECTOR3, LLVECTOR3D, LLVECTOR4, LLQUATERNION,
233                LLUUID, BOOL, IPADDR, IPPORT, FIXED, VARIABLE ]
234    typeswithsize = [ FIXED, VARIABLE ]
235    
236    def __init__(self, name, type, size):
237        self.name = name
238        self.type = type
239        self.size = size
240        
241    def compatibleWithBase(self, base):
242        if self.name != base.name:
243            return Incompatible("has different name: %s vs. %s in base"
244                                % (self.name, base.name))
245        if self.type != base.type:
246            return Incompatible("has different type: %s vs. %s in base"
247                                % (self.type, base.type))
248        if self.type in Variable.typeswithsize:
249            if self.size != base.size:
250                return Incompatible("has different size: %s vs. %s in base"
251                                    % (self.size, base.size)) 
252        return Same()
253
254
255
256###
257### Parsing Message Templates
258###
259
260class TemplateParser:
261    def __init__(self, tokens):
262        self._tokens = tokens
263        self._version = 0
264        self._numbers = { }
265        for p in Message.priorities:
266            self._numbers[p] = 0
267
268    def parseTemplate(self):
269        tokens = self._tokens
270        t = Template()
271        while True:
272            if tokens.want("version"):
273                v = float(tokens.require(tokens.wantFloat()))
274                self._version = v
275                t.version = v
276                continue
277    
278            m = self.parseMessage()
279            if m:
280                t.addMessage(m)
281                continue
282            
283            if self._version >= 2.0:
284                tokens.require(tokens.wantEOF())
285                break
286            else:
287                if tokens.wantEOF():
288                    break
289            
290                tokens.consume()
291                    # just assume (gulp) that this is a comment
292                    # line 468: "sim -> dataserver"
293        return t                
294
295
296    def parseMessage(self):
297        tokens = self._tokens
298        if not tokens.want("{"):
299            return None
300        
301        name     = tokens.require(tokens.wantSymbol())
302        priority = tokens.require(tokens.wantOneOf(Message.priorities))
303        
304        if self._version >= 2.0  or  priority in Message.prioritieswithnumber:
305            number = int("+" + tokens.require(tokens.wantInteger()), 0)
306        else:
307            self._numbers[priority] += 1
308            number = self._numbers[priority]
309        
310        trust    = tokens.require(tokens.wantOneOf(Message.trusts))
311        coding   = tokens.require(tokens.wantOneOf(Message.encodings))
312    
313        m = Message(name, number, priority, trust, coding)
314        
315        if self._version >= 2.0:
316            d = tokens.wantOneOf(Message.deprecations)
317            if d:
318                m.deprecate(d)
319                
320        while True:
321            b = self.parseBlock()
322            if not b:
323                break
324            m.addBlock(b)
325            
326        tokens.require(tokens.want("}"))
327        
328        return m
329    
330    
331    def parseBlock(self):
332        tokens = self._tokens
333        if not tokens.want("{"):
334            return None
335        name = tokens.require(tokens.wantSymbol())
336        repeat = tokens.require(tokens.wantOneOf(Block.repeats))
337        if repeat in Block.repeatswithcount:
338            count = int(tokens.require(tokens.wantInteger()))
339        else:
340            count = None
341    
342        b = Block(name, repeat, count)
343    
344        while True:
345            v = self.parseVariable()
346            if not v:
347                break
348            b.addVariable(v)
349            
350        tokens.require(tokens.want("}"))
351        return b
352            
353    
354    def parseVariable(self):
355        tokens = self._tokens
356        if not tokens.want("{"):
357            return None
358        name = tokens.require(tokens.wantSymbol())
359        type = tokens.require(tokens.wantOneOf(Variable.types))
360        if type in Variable.typeswithsize:
361            size = tokens.require(tokens.wantInteger())
362        else:
363            tokens.wantInteger() # in LandStatRequest: "{ ParcelLocalID S32 1 }" 
364            size = None
365        tokens.require(tokens.want("}"))
366        return Variable(name, type, size)
367        
368def parseTemplateString(s):
369    return TemplateParser(TokenStream().fromString(s)).parseTemplate()
370
371def parseTemplateFile(f):
372    return TemplateParser(TokenStream().fromFile(f)).parseTemplate()