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
30import sys
31import xml.parsers.expat
32import binascii
33import optparse
34
35from model import *
36
37
38ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
39
40
41class 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
63class 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
134class 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
145class 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
191class 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
345class 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
357class 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
391if __name__ == '__main__':
392    Main().main()
393