Home | History | Annotate | Download | only in trace
      1 #!/usr/bin/env python
      2 ##########################################################################
      3 # 
      4 # Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
      5 # All Rights Reserved.
      6 # 
      7 # Permission is hereby granted, free of charge, to any person obtaining a
      8 # copy of this software and associated documentation files (the
      9 # "Software"), to deal in the Software without restriction, including
     10 # without limitation the rights to use, copy, modify, merge, publish,
     11 # distribute, sub license, and/or sell copies of the Software, and to
     12 # permit persons to whom the Software is furnished to do so, subject to
     13 # the following conditions:
     14 # 
     15 # The above copyright notice and this permission notice (including the
     16 # next paragraph) shall be included in all copies or substantial portions
     17 # of the Software.
     18 # 
     19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     20 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     21 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
     22 # IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
     23 # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     24 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     25 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     26 # 
     27 ##########################################################################
     28 
     29 
     30 import sys
     31 import xml.parsers.expat
     32 import binascii
     33 import optparse
     34 
     35 from model import *
     36 
     37 
     38 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
     39 
     40 
     41 class XmlToken:
     42 
     43     def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
     44         assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
     45         self.type = type
     46         self.name_or_data = name_or_data
     47         self.attrs = attrs
     48         self.line = line
     49         self.column = column
     50 
     51     def __str__(self):
     52         if self.type == ELEMENT_START:
     53             return '<' + self.name_or_data + ' ...>'
     54         if self.type == ELEMENT_END:
     55             return '</' + self.name_or_data + '>'
     56         if self.type == CHARACTER_DATA:
     57             return self.name_or_data
     58         if self.type == EOF:
     59             return 'end of file'
     60         assert 0
     61 
     62 
     63 class XmlTokenizer:
     64     """Expat based XML tokenizer."""
     65 
     66     def __init__(self, fp, skip_ws = True):
     67         self.fp = fp
     68         self.tokens = []
     69         self.index = 0
     70         self.final = False
     71         self.skip_ws = skip_ws
     72         
     73         self.character_pos = 0, 0
     74         self.character_data = ''
     75         
     76         self.parser = xml.parsers.expat.ParserCreate()
     77         self.parser.StartElementHandler  = self.handle_element_start
     78         self.parser.EndElementHandler    = self.handle_element_end
     79         self.parser.CharacterDataHandler = self.handle_character_data
     80     
     81     def handle_element_start(self, name, attributes):
     82         self.finish_character_data()
     83         line, column = self.pos()
     84         token = XmlToken(ELEMENT_START, name, attributes, line, column)
     85         self.tokens.append(token)
     86     
     87     def handle_element_end(self, name):
     88         self.finish_character_data()
     89         line, column = self.pos()
     90         token = XmlToken(ELEMENT_END, name, None, line, column)
     91         self.tokens.append(token)
     92 
     93     def handle_character_data(self, data):
     94         if not self.character_data:
     95             self.character_pos = self.pos()
     96         self.character_data += data
     97     
     98     def finish_character_data(self):
     99         if self.character_data:
    100             if not self.skip_ws or not self.character_data.isspace(): 
    101                 line, column = self.character_pos
    102                 token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column)
    103                 self.tokens.append(token)
    104             self.character_data = ''
    105     
    106     def next(self):
    107         size = 16*1024
    108         while self.index >= len(self.tokens) and not self.final:
    109             self.tokens = []
    110             self.index = 0
    111             data = self.fp.read(size)
    112             self.final = len(data) < size
    113             data = data.rstrip('\0')
    114             try:
    115                 self.parser.Parse(data, self.final)
    116             except xml.parsers.expat.ExpatError, e:
    117                 #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
    118                 if e.code == 3:
    119                     pass
    120                 else:
    121                     raise e
    122         if self.index >= len(self.tokens):
    123             line, column = self.pos()
    124             token = XmlToken(EOF, None, None, line, column)
    125         else:
    126             token = self.tokens[self.index]
    127             self.index += 1
    128         return token
    129 
    130     def pos(self):
    131         return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
    132 
    133 
    134 class TokenMismatch(Exception):
    135 
    136     def __init__(self, expected, found):
    137         self.expected = expected
    138         self.found = found
    139 
    140     def __str__(self):
    141         return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
    142 
    143 
    144 
    145 class XmlParser:
    146     """Base XML document parser."""
    147 
    148     def __init__(self, fp):
    149         self.tokenizer = XmlTokenizer(fp)
    150         self.consume()
    151     
    152     def consume(self):
    153         self.token = self.tokenizer.next()
    154 
    155     def match_element_start(self, name):
    156         return self.token.type == ELEMENT_START and self.token.name_or_data == name
    157     
    158     def match_element_end(self, name):
    159         return self.token.type == ELEMENT_END and self.token.name_or_data == name
    160 
    161     def element_start(self, name):
    162         while self.token.type == CHARACTER_DATA:
    163             self.consume()
    164         if self.token.type != ELEMENT_START:
    165             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
    166         if self.token.name_or_data != name:
    167             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
    168         attrs = self.token.attrs
    169         self.consume()
    170         return attrs
    171     
    172     def element_end(self, name):
    173         while self.token.type == CHARACTER_DATA:
    174             self.consume()
    175         if self.token.type != ELEMENT_END:
    176             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
    177         if self.token.name_or_data != name:
    178             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
    179         self.consume()
    180 
    181     def character_data(self, strip = True):
    182         data = ''
    183         while self.token.type == CHARACTER_DATA:
    184             data += self.token.name_or_data
    185             self.consume()
    186         if strip:
    187             data = data.strip()
    188         return data
    189 
    190 
    191 class TraceParser(XmlParser):
    192 
    193     def __init__(self, fp):
    194         XmlParser.__init__(self, fp)
    195         self.last_call_no = 0
    196     
    197     def parse(self):
    198         self.element_start('trace')
    199         while self.token.type not in (ELEMENT_END, EOF):
    200             call = self.parse_call()
    201             self.handle_call(call)
    202         if self.token.type != EOF:
    203             self.element_end('trace')
    204 
    205     def parse_call(self):
    206         attrs = self.element_start('call')
    207         try:
    208             no = int(attrs['no'])
    209         except KeyError:
    210             self.last_call_no += 1
    211             no = self.last_call_no
    212         else:
    213             self.last_call_no = no
    214         klass = attrs['class']
    215         method = attrs['method']
    216         args = []
    217         ret = None
    218         while self.token.type == ELEMENT_START:
    219             if self.token.name_or_data == 'arg':
    220                 arg = self.parse_arg()
    221                 args.append(arg)
    222             elif self.token.name_or_data == 'ret':
    223                 ret = self.parse_ret()
    224             elif self.token.name_or_data == 'call':
    225                 # ignore nested function calls
    226                 self.parse_call()
    227             else:
    228                 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
    229         self.element_end('call')
    230         
    231         return Call(no, klass, method, args, ret)
    232 
    233     def parse_arg(self):
    234         attrs = self.element_start('arg')
    235         name = attrs['name']
    236         value = self.parse_value()
    237         self.element_end('arg')
    238 
    239         return name, value
    240 
    241     def parse_ret(self):
    242         attrs = self.element_start('ret')
    243         value = self.parse_value()
    244         self.element_end('ret')
    245 
    246         return value
    247 
    248     def parse_value(self):
    249         expected_tokens = ('null', 'bool', 'int', 'uint', 'float', 'string', 'enum', 'array', 'struct', 'ptr', 'bytes')
    250         if self.token.type == ELEMENT_START:
    251             if self.token.name_or_data in expected_tokens:
    252                 method = getattr(self, 'parse_' +  self.token.name_or_data)
    253                 return method()
    254         raise TokenMismatch(" or " .join(expected_tokens), self.token)
    255 
    256     def parse_null(self):
    257         self.element_start('null')
    258         self.element_end('null')
    259         return Literal(None)
    260         
    261     def parse_bool(self):
    262         self.element_start('bool')
    263         value = int(self.character_data())
    264         self.element_end('bool')
    265         return Literal(value)
    266         
    267     def parse_int(self):
    268         self.element_start('int')
    269         value = int(self.character_data())
    270         self.element_end('int')
    271         return Literal(value)
    272         
    273     def parse_uint(self):
    274         self.element_start('uint')
    275         value = int(self.character_data())
    276         self.element_end('uint')
    277         return Literal(value)
    278         
    279     def parse_float(self):
    280         self.element_start('float')
    281         value = float(self.character_data())
    282         self.element_end('float')
    283         return Literal(value)
    284         
    285     def parse_enum(self):
    286         self.element_start('enum')
    287         name = self.character_data()
    288         self.element_end('enum')
    289         return NamedConstant(name)
    290         
    291     def parse_string(self):
    292         self.element_start('string')
    293         value = self.character_data()
    294         self.element_end('string')
    295         return Literal(value)
    296         
    297     def parse_bytes(self):
    298         self.element_start('bytes')
    299         value = binascii.a2b_hex(self.character_data())
    300         self.element_end('bytes')
    301         return Literal(value)
    302         
    303     def parse_array(self):
    304         self.element_start('array')
    305         elems = []
    306         while self.token.type != ELEMENT_END:
    307             elems.append(self.parse_elem())
    308         self.element_end('array')
    309         return Array(elems)
    310 
    311     def parse_elem(self):
    312         self.element_start('elem')
    313         value = self.parse_value()
    314         self.element_end('elem')
    315         return value
    316 
    317     def parse_struct(self):
    318         attrs = self.element_start('struct')
    319         name = attrs['name']
    320         members = []
    321         while self.token.type != ELEMENT_END:
    322             members.append(self.parse_member())
    323         self.element_end('struct')
    324         return Struct(name, members)
    325 
    326     def parse_member(self):
    327         attrs = self.element_start('member')
    328         name = attrs['name']
    329         value = self.parse_value()
    330         self.element_end('member')
    331 
    332         return name, value
    333 
    334     def parse_ptr(self):
    335         self.element_start('ptr')
    336         address = self.character_data()
    337         self.element_end('ptr')
    338 
    339         return Pointer(address)
    340 
    341     def handle_call(self, call):
    342         pass
    343     
    344     
    345 class TraceDumper(TraceParser):
    346     
    347     def __init__(self, fp):
    348         TraceParser.__init__(self, fp)
    349         self.formatter = format.DefaultFormatter(sys.stdout)
    350         self.pretty_printer = PrettyPrinter(self.formatter)
    351 
    352     def handle_call(self, call):
    353         call.visit(self.pretty_printer)
    354         self.formatter.newline()
    355         
    356 
    357 class Main:
    358     '''Common main class for all retrace command line utilities.''' 
    359 
    360     def __init__(self):
    361         pass
    362 
    363     def main(self):
    364         optparser = self.get_optparser()
    365         (options, args) = optparser.parse_args(sys.argv[1:])
    366     
    367         if args:
    368             for arg in args:
    369                 if arg.endswith('.gz'):
    370                     from gzip import GzipFile
    371                     stream = GzipFile(arg, 'rt')
    372                 elif arg.endswith('.bz2'):
    373                     from bz2 import BZ2File
    374                     stream = BZ2File(arg, 'rU')
    375                 else:
    376                     stream = open(arg, 'rt')
    377                 self.process_arg(stream, options)
    378         else:
    379             self.process_arg(stream, options)
    380 
    381     def get_optparser(self):
    382         optparser = optparse.OptionParser(
    383             usage="\n\t%prog [options] [traces] ...")
    384         return optparser
    385 
    386     def process_arg(self, stream, options):
    387         parser = TraceDumper(stream)
    388         parser.parse()
    389 
    390 
    391 if __name__ == '__main__':
    392     Main().main()
    393