2 ##########################################################################
4 # Copyright 2008-2012 Jose Fonseca
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 ##########################################################################/
43 def match(self, value, mo):
44 raise NotImplementedError
46 def _matchSequence(self, refValues, srcValues, mo):
47 if not isinstance(srcValues, (list, tuple)):
50 if len(refValues) != len(srcValues):
53 for refValue, srcValue in zip(refValues, srcValues):
54 if not refValue.match(srcValue, mo):
59 raise NotImplementerError
65 class WildcardMatcher(Matcher):
67 def __init__(self, name = ''):
70 def match(self, value, mo):
73 refValue = mo.params[self.name]
75 mo.params[self.name] = value
77 return refValue == value
81 return '<' + self.name + '>'
84 class LiteralMatcher(Matcher):
86 def __init__(self, refValue):
87 self.refValue = refValue
89 def match(self, value, mo):
90 return self.refValue == value
93 return repr(self.refValue)
96 class ApproxMatcher(Matcher):
98 def __init__(self, refValue, tolerance = 2**-23):
99 self.refValue = refValue
100 self.tolerance = tolerance
102 def match(self, value, mo):
103 if not isinstance(value, float):
106 error = abs(self.refValue - value)
108 error = error / self.refValue
109 return error <= self.tolerance
112 return repr(self.refValue)
115 class BitmaskMatcher(Matcher):
117 def __init__(self, refElements):
118 self.refElements = refElements
120 def match(self, value, mo):
121 return self._matchSequence(self.refElements, value, mo)
124 return ' | '.join(map(str, self.refElements))
127 class OffsetMatcher(Matcher):
129 def __init__(self, refValue, offset):
130 self.refValue = refValue
133 def match(self, value, mo):
134 return self.refValue.match(value - self.offset, mo)
137 return '%s + %i' % (self.refValue, self.offset)
140 class ArrayMatcher(Matcher):
142 def __init__(self, refElements):
143 self.refElements = refElements
145 def match(self, value, mo):
146 return self._matchSequence(self.refElements, value, mo)
149 return '{' + ', '.join(map(str, self.refElements)) + '}'
152 class StructMatcher(Matcher):
154 def __init__(self, refMembers):
155 self.refMembers = refMembers
157 def match(self, value, mo):
158 if not isinstance(value, dict):
161 if len(value) != len(self.refMembers):
164 for name, refMember in self.refMembers.iteritems():
170 if not refMember.match(member, mo):
176 return '{' + ', '.join(['%s = %s' % refMember for refMember in self.refMembers.iteritems()]) + '}'
179 class CallMatcher(Matcher):
181 def __init__(self, callNo, functionName, args, ret):
183 self.functionName = functionName
187 def match(self, call, mo):
188 callNo, srcFunctionName, srcArgs, srcRet = call
190 if self.functionName != srcFunctionName:
193 refArgs = [value for name, value in self.args]
194 srcArgs = [value for name, value in srcArgs]
196 if not self._matchSequence(refArgs, srcArgs, mo):
200 if srcRet is not None:
203 if not self.ret.match(srcRet, mo):
206 if self.callNo is not None:
207 if not self.callNo.match(callNo, mo):
213 s = self.functionName
214 s += '(' + ', '.join(['%s = %s' % refArg for refArg in self.args]) + ')'
215 if self.ret is not None:
216 s += ' = ' + str(self.ret)
220 class TraceMismatch(Exception):
227 def __init__(self, calls):
230 def match(self, calls, verbose = False):
232 srcCalls = iter(calls)
233 for refCall in self.calls:
239 srcCall = srcCalls.next()
240 except StopIteration:
242 raise TraceMismatch('missing call `%s` (found `%s`)' % (refCall, skippedSrcCalls[0]))
244 raise TraceMismatch('missing call %s' % refCall)
246 print '\t%s %s%r = %r' % srcCall
247 if refCall.match(srcCall, mo):
250 skippedSrcCalls.append(srcCall)
254 return ''.join(['%s\n' % call for call in self.calls])
257 #######################################################################
263 class ParseError(Exception):
265 def __init__(self, msg=None, filename=None, line=None, col=None):
267 self.filename = filename
272 return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
276 """Stateless scanner."""
278 # should be overriden by derived classes
287 flags |= re.IGNORECASE
288 self.tokens_re = re.compile(
289 '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
293 def next(self, buf, pos):
296 mo = self.tokens_re.match(buf, pos)
299 type, regexp, test_lit = self.tokens[mo.lastindex - 1]
302 type = self.literals.get(text, type)
303 return type, text, pos
306 return self.symbols.get(c, None), c, pos + 1
311 def __init__(self, type, text, line, col):
320 # should be overriden by derived classes
324 newline_re = re.compile(r'\r\n?|\n')
326 def __init__(self, buf = None, pos = 0, filename = None, fp = None):
330 length = os.path.getsize(fp.name)
333 # read whole file into memory
337 # map the whole file into memory
339 # length must not be zero
340 buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
341 pos = os.lseek(fileno, 0, 1)
349 except AttributeError:
356 self.filename = filename
365 type, text, endpos = self.scanner.next(self.buf, pos)
366 assert pos + len(text) == endpos
368 type, text = self.filter(type, text)
374 msg = 'unexpected char '
375 if text >= ' ' and text <= '~':
378 msg += "0x%X" % ord(text)
379 raise ParseError(msg, self.filename, line, col)
382 return Token(type = type, text = text, line = line, col = col)
384 def consume(self, text):
387 for mo in self.newline_re.finditer(text, pos):
392 # update column number
394 tabpos = text.find('\t', pos)
397 self.col += tabpos - pos
398 self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
400 self.col += len(text) - pos
405 def __init__(self, lexer):
407 self.lookahead = self.lexer.next()
409 def match(self, type):
410 return self.lookahead.type == type
412 def skip(self, type):
413 while not self.match(type):
418 msg = 'unexpected token %r' % self.lookahead.text,
419 filename = self.lexer.filename,
420 line = self.lookahead.line,
421 col = self.lookahead.col)
423 def consume(self, type = None):
424 if type is not None and not self.match(type):
426 token = self.lookahead
427 self.lookahead = self.lexer.next()
431 #######################################################################
433 ID, NUMBER, HEXNUM, STRING, WILDCARD, PRAGMA, LPAREN, RPAREN, LCURLY, RCURLY, COMMA, AMP, EQUAL, PLUS, VERT, BLOB = xrange(16)
436 class CallScanner(Scanner):
438 # token regular expression table
441 (SKIP, r'[ \t\f\r\n\v]+', False),
444 (ID, r'[a-zA-Z_][a-zA-Z0-9_]*(?:::[a-zA-Z_][a-zA-Z0-9_]*)?', True),
447 (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
450 (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+][0-9]+)?', False),
453 (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
456 (WILDCARD, r'<[^>]*>', False),
459 (PRAGMA, r'#[^\r\n]*', False),
481 class CallLexer(Lexer):
483 scanner = CallScanner()
485 def filter(self, type, text):
490 text = text.replace('\\\r\n', '')
491 text = text.replace('\\\r', '')
492 text = text.replace('\\\n', '')
495 text = text.replace('\\"', '"')
502 class TraceParser(Parser):
504 def __init__(self, stream):
505 lexer = CallLexer(fp = stream)
506 Parser.__init__(self, lexer)
509 return self.match(EOF)
512 while not self.eof():
514 return TraceMatcher(self.calls)
516 def parse_element(self):
517 if self.lookahead.type == PRAGMA:
518 token = self.consume()
519 self.handlePragma(token.text)
523 def parse_call(self):
524 if self.lookahead.type == NUMBER:
525 token = self.consume()
526 callNo = self.handleInt(int(token.text))
527 elif self.lookahead.type == WILDCARD:
528 token = self.consume()
529 callNo = self.handleWildcard((token.text[1:-1]))
533 functionName = self.consume(ID).text
535 args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
537 if self.match(EQUAL):
539 ret = self.parse_value()
543 self.handleCall(callNo, functionName, args, ret)
545 def parse_pair(self):
546 '''Parse a `name = value` pair.'''
547 name = self.consume(ID).text
549 value = self.parse_value()
552 def parse_opt_pair(self):
553 '''Parse an optional `name = value` pair.'''
555 name = self.consume(ID).text
556 if self.match(EQUAL):
558 value = self.parse_value()
564 value = self.parse_value()
570 def parse_value(self):
571 value = self._parse_value()
574 while self.match(VERT):
576 value = self._parse_value()
578 return self.handleBitmask(flags)
579 elif self.match(PLUS):
581 if self.match(NUMBER):
582 token = self.consume()
583 offset = int(token.text)
584 elif self.match(HEXNUM):
585 token = self.consume()
586 offset = int(token.text, 16)
589 return self.handleOffset(value, offset)
593 def _parse_value(self):
596 value = [self.parse_value()]
597 return self.handleArray(value)
599 token = self.consume()
601 return self.handleID(value)
602 elif self.match(STRING):
603 token = self.consume()
605 return self.handleString(value)
606 elif self.match(NUMBER):
607 token = self.consume()
608 if '.' in token.text:
609 value = float(token.text)
610 return self.handleFloat(value)
612 value = int(token.text)
613 return self.handleInt(value)
614 elif self.match(HEXNUM):
615 token = self.consume()
616 value = int(token.text, 16)
617 return self.handleInt(value)
618 elif self.match(LCURLY):
619 value = self.parse_sequence(LCURLY, RCURLY, self.parse_opt_pair)
620 if len(value) and isinstance(value[0], tuple):
622 return self.handleStruct(value)
624 return self.handleArray(value)
625 elif self.match(BLOB):
626 token = self.consume()
628 token = self.consume()
629 length = int(token.text)
631 return self.handleBlob(length)
632 elif self.match(WILDCARD):
633 token = self.consume()
634 return self.handleWildcard(token.text[1:-1])
638 def parse_sequence(self, ltype, rtype, elementParser):
639 '''Parse a comma separated list'''
645 while not self.match(rtype):
650 element = elementParser()
651 elements.append(element)
656 def handleID(self, value):
657 raise NotImplementedError
659 def handleInt(self, value):
660 raise NotImplementedError
662 def handleFloat(self, value):
663 raise NotImplementedError
665 def handleString(self, value):
666 raise NotImplementedError
668 def handleBitmask(self, value):
669 raise NotImplementedError
671 def handleOffset(self, value, offset):
672 raise NotImplementedError
674 def handleArray(self, value):
675 raise NotImplementedError
677 def handleStruct(self, value):
678 raise NotImplementedError
680 def handleBlob(self, length):
681 return self.handleID('blob(%u)' % length)
683 def handleWildcard(self, name):
684 raise NotImplementedError
686 def handleCall(self, callNo, functionName, args, ret):
687 raise NotImplementedError
689 def handlePragma(self, line):
690 raise NotImplementedError
693 class RefTraceParser(TraceParser):
695 def __init__(self, stream):
696 TraceParser.__init__(self, stream)
700 TraceParser.parse(self)
701 return TraceMatcher(self.calls)
703 def handleID(self, value):
704 return LiteralMatcher(value)
706 def handleInt(self, value):
707 return LiteralMatcher(value)
709 def handleFloat(self, value):
710 return ApproxMatcher(value)
712 def handleString(self, value):
713 return LiteralMatcher(value)
715 def handleBitmask(self, value):
716 return BitmaskMatcher(value)
718 def handleOffset(self, value, offset):
719 return OffsetMatcher(value, offset)
721 def handleArray(self, value):
722 return ArrayMatcher(value)
724 def handleStruct(self, value):
725 return StructMatcher(value)
727 def handleWildcard(self, name):
728 return WildcardMatcher(name)
730 def handleCall(self, callNo, functionName, args, ret):
731 call = CallMatcher(callNo, functionName, args, ret)
732 self.calls.append(call)
734 def handlePragma(self, line):
738 class SrcTraceParser(TraceParser):
740 def __init__(self, stream):
741 TraceParser.__init__(self, stream)
745 TraceParser.parse(self)
748 def handleID(self, value):
751 def handleInt(self, value):
754 def handleFloat(self, value):
757 def handleString(self, value):
760 def handleBitmask(self, value):
763 def handleArray(self, elements):
764 return list(elements)
766 def handleStruct(self, members):
769 def handleCall(self, callNo, functionName, args, ret):
770 call = (callNo, functionName, args, ret)
771 self.calls.append(call)
775 # Parse command line options
776 optparser = optparse.OptionParser(
777 usage='\n\t%prog [OPTIONS] REF_TXT SRC_TRACE',
779 optparser.add_option(
780 '--apitrace', metavar='PROGRAM',
781 type='string', dest='apitrace', default=os.environ.get('APITRACE', 'apitrace'),
782 help='path to apitrace executable')
783 optparser.add_option(
786 dest="verbose", default=True,
787 help="verbose output")
788 (options, args) = optparser.parse_args(sys.argv[1:])
791 optparser.error('wrong number of arguments')
793 refFileName, srcFileName = args
795 refStream = open(refFileName, 'rt')
796 refParser = RefTraceParser(refStream)
797 refTrace = refParser.parse()
799 sys.stdout.write('// Reference\n')
800 sys.stdout.write(str(refTrace))
801 sys.stdout.write('\n')
803 if srcFileName.endswith('.trace'):
804 cmd = [options.apitrace, 'dump', '--color=never', srcFileName]
805 p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
808 srcStream = open(srcFileName, 'rt')
809 srcParser = SrcTraceParser(srcStream)
810 srcTrace = srcParser.parse()
812 sys.stdout.write('// Source\n')
813 sys.stdout.write(''.join(['%s %s%r = %r\n' % call for call in srcTrace]))
814 sys.stdout.write('\n')
817 sys.stdout.write('// Matching\n')
818 mo = refTrace.match(srcTrace, options.verbose)
820 sys.stdout.write('\n')
823 sys.stdout.write('// Parameters\n')
824 paramNames = mo.params.keys()
826 for paramName in paramNames:
827 print '%s = %r' % (paramName, mo.params[paramName])
830 if __name__ == '__main__':