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