]> git.cworth.org Git - apitrace-tests/blob - checker.py
Test trimming.
[apitrace-tests] / checker.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2008-2012 Jose Fonseca
5 # All Rights Reserved.
6 #
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:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
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
23 # THE SOFTWARE.
24 #
25 ##########################################################################/
26
27
28 import sys
29 import re
30
31
32 class ValueMatcher:
33
34     def match(self, value):
35         raise NotImplementedError
36
37     def __str__(self):
38         raise NotImplementerError
39
40     def __repr__(self):
41         return str(self)
42
43
44 class WildcardMatcher(ValueMatcher):
45
46     def match(self, value):
47         return true
48
49     def __str__(self):
50         return '*'
51
52
53 class LiteralValueMatcher(ValueMatcher):
54
55     def __init__(self, refValue):
56         self.refValue = refValue
57
58     def match(self, value):
59         return self.refValue == value
60
61     def __str__(self):
62         return repr(self.refValue)
63
64
65 class ApproxValueMatcher(ValueMatcher):
66
67     def __init__(self, refValue, tolerance = 2**-23):
68         self.refValue = refValue
69         self.tolerance = tolerance
70
71     def match(self, value):
72         error = abs(self.refValue - value)
73         if self.refValue:
74             error = error / self.refValue
75         return error <= self.tolerance
76
77     def __str__(self):
78         return repr(self.refValue)
79
80
81 class ArrayMatcher(ValueMatcher):
82
83     def __init__(self, refElements):
84         self.refElements = refElements
85
86     def match(self, value):
87         if not isinstance(value, list):
88             return False
89
90         if len(value) != len(self.refElements):
91             return False
92
93         for refElement, element in zip(self.refElements, value):
94             if not refElement.match(element):
95                 return False
96
97         return True
98
99     def __str__(self):
100         return '{' + ', '.join(map(str, self.refElements)) + '}'
101
102
103 class StructMatcher(ValueMatcher):
104
105     def __init__(self, refMembers):
106         self.refMembers = refMembers
107
108     def match(self, value):
109         if not isinstance(value, dict):
110             return False
111
112         if len(value) != len(self.refMembers):
113             return False
114
115         for name, refMember in self.refMembers.iteritems():
116             try:
117                 member = value[name]
118             except KeyError:
119                 return False
120             else:
121                 if not refMember.match(member):
122                     return False
123
124         return True
125
126     def __str__(self):
127         print self.refMembers
128         return '{' + ', '.join(['%s = %s' % refMember for refMember in self.refMembers.iteritems()]) + '}'
129
130
131 class CallMatcher:
132
133     def __init__(self, refFunctionName, refArgs, refRet = None):
134         self.refFunctionName = refFunctionName
135         self.refArgs = refArgs
136         self.refRet = refRet
137
138     def match(self, functionName, args, ret = None):
139         if refFunctionName != functionName:
140             return False
141
142         if len(self.refArgs) != len(args):
143             return False
144
145         for (refArgName, refArg), (argName, arg) in zip(self.refArgs, args):
146             if not refArg.match(arg):
147                 return False
148
149         if self.refRet is None:
150             if ret is not None:
151                 return False
152         else:
153             if not self.refRet.match(ret):
154                 return False
155
156         return True
157
158     def __str__(self):
159         s = self.refFunctionName
160         s += '(' + ', '.join(['%s = %s' % refArg for refArg in self.refArgs]) + ')'
161         if self.refRet is not None:
162             s += ' = ' + str(self.refRet)
163         return s
164
165
166 class TraceMatcher:
167
168     def __init__(self, refCalls):
169         self.refCalls = refCalls
170
171     def __str__(self):
172         return ''.join(['%s\n' % refCall for refCall in self.refCalls])
173
174
175 #######################################################################
176
177 EOF = -1
178 SKIP = -2
179
180
181 class ParseError(Exception):
182
183     def __init__(self, msg=None, filename=None, line=None, col=None):
184         self.msg = msg
185         self.filename = filename
186         self.line = line
187         self.col = col
188
189     def __str__(self):
190         return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
191         
192
193 class Scanner:
194     """Stateless scanner."""
195
196     # should be overriden by derived classes
197     tokens = []
198     symbols = {}
199     literals = {}
200     ignorecase = False
201
202     def __init__(self):
203         flags = re.DOTALL
204         if self.ignorecase:
205             flags |= re.IGNORECASE
206         self.tokens_re = re.compile(
207             '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
208              flags
209         )
210
211     def next(self, buf, pos):
212         if pos >= len(buf):
213             return EOF, '', pos
214         mo = self.tokens_re.match(buf, pos)
215         if mo:
216             text = mo.group()
217             type, regexp, test_lit = self.tokens[mo.lastindex - 1]
218             pos = mo.end()
219             if test_lit:
220                 type = self.literals.get(text, type)
221             return type, text, pos
222         else:
223             c = buf[pos]
224             return self.symbols.get(c, None), c, pos + 1
225
226
227 class Token:
228
229     def __init__(self, type, text, line, col):
230         self.type = type
231         self.text = text
232         self.line = line
233         self.col = col
234
235
236 class Lexer:
237
238     # should be overriden by derived classes
239     scanner = None
240     tabsize = 8
241
242     newline_re = re.compile(r'\r\n?|\n')
243
244     def __init__(self, buf = None, pos = 0, filename = None, fp = None):
245         if fp is not None:
246             try:
247                 fileno = fp.fileno()
248                 length = os.path.getsize(fp.name)
249                 import mmap
250             except:
251                 # read whole file into memory
252                 buf = fp.read()
253                 pos = 0
254             else:
255                 # map the whole file into memory
256                 if length:
257                     # length must not be zero
258                     buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
259                     pos = os.lseek(fileno, 0, 1)
260                 else:
261                     buf = ''
262                     pos = 0
263
264             if filename is None:
265                 try:
266                     filename = fp.name
267                 except AttributeError:
268                     filename = None
269
270         self.buf = buf
271         self.pos = pos
272         self.line = 1
273         self.col = 1
274         self.filename = filename
275
276     def next(self):
277         while True:
278             # save state
279             pos = self.pos
280             line = self.line
281             col = self.col
282
283             type, text, endpos = self.scanner.next(self.buf, pos)
284             assert pos + len(text) == endpos
285             self.consume(text)
286             type, text = self.filter(type, text)
287             self.pos = endpos
288
289             if type == SKIP:
290                 continue
291             elif type is None:
292                 msg = 'unexpected char '
293                 if text >= ' ' and text <= '~':
294                     msg += "'%s'" % text
295                 else:
296                     msg += "0x%X" % ord(text)
297                 raise ParseError(msg, self.filename, line, col)
298             else:
299                 break
300         return Token(type = type, text = text, line = line, col = col)
301
302     def consume(self, text):
303         # update line number
304         pos = 0
305         for mo in self.newline_re.finditer(text, pos):
306             self.line += 1
307             self.col = 1
308             pos = mo.end()
309
310         # update column number
311         while True:
312             tabpos = text.find('\t', pos)
313             if tabpos == -1:
314                 break
315             self.col += tabpos - pos
316             self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
317             pos = tabpos + 1
318         self.col += len(text) - pos
319
320
321 class Parser:
322
323     def __init__(self, lexer):
324         self.lexer = lexer
325         self.lookahead = self.lexer.next()
326
327     def match(self, type):
328         return self.lookahead.type == type
329
330     def skip(self, type):
331         while not self.match(type):
332             self.consume()
333
334     def error(self):
335         raise ParseError(
336             msg = 'unexpected token %r' % self.lookahead.text, 
337             filename = self.lexer.filename, 
338             line = self.lookahead.line, 
339             col = self.lookahead.col)
340
341     def consume(self, type = None):
342         if type is not None and not self.match(type):
343             self.error()
344         token = self.lookahead
345         self.lookahead = self.lexer.next()
346         return token
347
348
349 #######################################################################
350
351 ID, NUMBER, HEXNUM, STRING, PRAGMA, LPAREN, RPAREN, LCURLY, RCURLY, COMMA, AMP, EQUAL, BLOB = xrange(13)
352
353
354 class CallScanner(Scanner):
355
356     # token regular expression table
357     tokens = [
358         # whitespace
359         (SKIP, r'[ \t\f\r\n\v]+', False),
360
361         # Alphanumeric IDs
362         (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True),
363
364         # Numeric IDs
365         (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
366         
367         # Numeric IDs
368         (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False),
369
370         # String IDs
371         (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
372         
373         # Pragma
374         (PRAGMA, r'#[^\r\n]*', False),
375     ]
376
377     # symbol table
378     symbols = {
379         '(': LPAREN,
380         ')': RPAREN,
381         '{': LCURLY,
382         '}': RCURLY,
383         ',': COMMA,
384         '&': AMP,
385         '=': EQUAL,
386     }
387
388     # literal table
389     literals = {
390         'blob': BLOB
391     }
392
393
394 class CallLexer(Lexer):
395
396     scanner = CallScanner()
397
398     def filter(self, type, text):
399         if type == STRING:
400             text = text[1:-1]
401
402             # line continuations
403             text = text.replace('\\\r\n', '')
404             text = text.replace('\\\r', '')
405             text = text.replace('\\\n', '')
406             
407             # quotes
408             text = text.replace('\\"', '"')
409
410             type = ID
411
412         return type, text
413
414
415 class CallParser(Parser):
416
417     def __init__(self, stream):
418         lexer = CallLexer(fp = stream)
419         Parser.__init__(self, lexer)
420
421     def eof(self):
422         return self.match(EOF)
423
424     def parse(self):
425         while not self.eof():
426             self.parse_element()
427
428     def parse_element(self):
429         if self.lookahead.type == PRAGMA:
430             # TODO
431             token = self.consume()
432             self.handlePragma(token.text)
433         else:
434             self.parse_call()
435
436     def parse_call(self):
437         while self.lookahead.type == PRAGMA:
438             # TODO
439             token = self.consume()
440             print token.text
441
442         if self.lookahead.type == NUMBER:
443             token = self.consume()
444             callNo = int(token.text)
445         else:
446             callNo = None
447         
448         functionName = self.consume(ID).text
449
450         args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
451
452         if self.match(EQUAL):
453             self.consume(EQUAL)
454             ret = self.parse_value()
455         else:
456             ret = None
457
458         return self.handleCall(callNo, functionName, args, ret)
459
460     def parse_pair(self):
461         '''Parse a `name = value` pair.'''
462         name = self.consume(ID).text
463         self.consume(EQUAL)
464         value = self.parse_value()
465         return name, value
466
467     def parse_opt_pair(self):
468         '''Parse an optional `name = value` pair.'''
469         if self.match(ID):
470             name = self.consume(ID).text
471             if self.match(EQUAL):
472                 self.consume(EQUAL)
473                 value = self.parse_value()
474             else:
475                 value = name
476                 name = None
477         else:
478             name = None
479             value = self.parse_value()
480         if name is None:
481             return value
482         else:
483             return name, value
484
485     def parse_value(self):
486         if self.match(AMP):
487             self.consume()
488             value = [self.parse_value()]
489             return self.handleArray(value)
490         elif self.match(ID):
491             token = self.consume()
492             value = token.text
493             return self.handleID(value)
494         elif self.match(STRING):
495             token = self.consume()
496             value = token.text
497             return self.handleString(value)
498         elif self.match(NUMBER):
499             token = self.consume()
500             value = float(token.text)
501             return self.handleFloat(value)
502         elif self.match(HEXNUM):
503             token = self.consume()
504             value = int(token.text, 16)
505             return self.handleInt(value)
506         elif self.match(LCURLY):
507             value = self.parse_sequence(LCURLY, RCURLY, self.parse_opt_pair)
508             if len(value) and isinstance(value[0], tuple):
509                 value = dict(value)
510                 return self.handleStruct(value)
511             else:
512                 return self.handleArray(value)
513         elif self.match(BLOB):
514             token = self.consume()
515             self.consume(LPAREN)
516             length = self.consume()
517             self.consume(RPAREN)
518             return self.handleBlob()
519         else:
520             self.error()
521
522     def parse_sequence(self, ltype, rtype, elementParser):
523         '''Parse a comma separated list'''
524
525         elements = []
526
527         self.consume(ltype)
528         sep = None
529         while not self.match(rtype):
530             if sep is None:
531                 sep = COMMA
532             else:
533                 self.consume(sep)
534             element = elementParser()
535             elements.append(element)
536         self.consume(rtype)
537
538         return elements
539     
540     def handleID(self, value):
541         return LiteralValueMatcher(value)
542
543     def handleInt(self, value):
544         return LiteralValueMatcher(value)
545
546     def handleFloat(self, value):
547         return ApproxValueMatcher(value)
548
549     def handleString(self, value):
550         return LiteralValueMatcher(value)
551
552     def handleArray(self, value):
553         return ArrayMatcher(value)
554
555     def handleStruct(self, value):
556         return StructMatcher(value)
557
558     def handleBlob(self, value):
559         # TODO
560         return WildcardMatcher()
561
562     def handleCall(self, callNo, functionName, args, ret):
563         matcher = CallMatcher(functionName, args, ret)
564
565         if callNo is not None:
566             sys.stdout.write('%u ' % callNo)
567         sys.stdout.write(str(matcher))
568         sys.stdout.write('\n')
569
570     def handlePragma(self, line):
571         sys.stdout.write(line)
572         sys.stdout.write('\n')
573
574
575 def main():
576     parser = CallParser(sys.stdin)
577     parser.parse()
578
579
580 if __name__ == '__main__':
581     main()