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