]> git.cworth.org Git - apitrace-tests/blob - checker.py
Bit more work on the checker.
[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 = 0
352 NUMBER = 1
353 HEXNUM = 2
354 STRING = 3
355
356 LPAREN = 4
357 RPAREN = 5
358 LCURLY = 6
359 RCURLY = 7
360 COMMA = 8
361 AMP = 9
362 EQUAL = 11
363
364 BLOB = 12
365
366
367 class CallScanner(Scanner):
368
369     # token regular expression table
370     tokens = [
371         # whitespace
372         (SKIP, r'[ \t\f\r\n\v]+', False),
373
374         # Alphanumeric IDs
375         (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True),
376
377         # Numeric IDs
378         (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
379         
380         # Numeric IDs
381         (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False),
382
383         # String IDs
384         (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
385     ]
386
387     # symbol table
388     symbols = {
389         '(': LPAREN,
390         ')': RPAREN,
391         '{': LCURLY,
392         '}': RCURLY,
393         ',': COMMA,
394         '&': AMP,
395         '=': EQUAL,
396     }
397
398     # literal table
399     literals = {
400         'blob': BLOB
401     }
402
403
404 class CallLexer(Lexer):
405
406     scanner = CallScanner()
407
408     def filter(self, type, text):
409         if type == STRING:
410             text = text[1:-1]
411
412             # line continuations
413             text = text.replace('\\\r\n', '')
414             text = text.replace('\\\r', '')
415             text = text.replace('\\\n', '')
416             
417             # quotes
418             text = text.replace('\\"', '"')
419
420             type = ID
421
422         return type, text
423
424
425 class CallParser(Parser):
426
427     def __init__(self, stream):
428         lexer = CallLexer(fp = stream)
429         Parser.__init__(self, lexer)
430
431     def eof(self):
432         return self.match(EOF)
433
434     def parse(self):
435         while not self.eof():
436             self.parse_call()
437
438     def parse_call(self):
439         if self.lookahead.type == NUMBER:
440             token = self.consume()
441             callNo = int(token.text)
442         else:
443             callNo = None
444         
445         functionName = self.consume(ID).text
446
447         args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
448
449         if self.match(EQUAL):
450             self.consume(EQUAL)
451             ret = self.parse_value()
452         else:
453             ret = None
454
455         return self.handleCall(callNo, functionName, args, ret)
456
457     def parse_pair(self):
458         '''Parse a `name = value` pair.'''
459         name = self.consume(ID).text
460         self.consume(EQUAL)
461         value = self.parse_value()
462         return name, value
463
464     def parse_opt_pair(self):
465         '''Parse an optional `name = value` pair.'''
466         if self.match(ID):
467             name = self.consume(ID).text
468             if self.match(EQUAL):
469                 self.consume(EQUAL)
470                 value = self.parse_value()
471             else:
472                 value = name
473                 name = None
474         else:
475             name = None
476             value = self.parse_value()
477         if name is None:
478             return value
479         else:
480             return name, value
481
482     def parse_value(self):
483         if self.match(AMP):
484             self.consume()
485             value = [self.parse_value()]
486             return self.handleArray(value)
487         elif self.match(ID):
488             token = self.consume()
489             value = token.text
490             return self.handleID(value)
491         elif self.match(STRING):
492             token = self.consume()
493             value = token.text
494             return self.handleString(value)
495         elif self.match(NUMBER):
496             token = self.consume()
497             value = float(token.text)
498             return self.handleFloat(value)
499         elif self.match(HEXNUM):
500             token = self.consume()
501             value = int(token.text, 16)
502             return self.handleInt(value)
503         elif self.match(LCURLY):
504             value = self.parse_sequence(LCURLY, RCURLY, self.parse_opt_pair)
505             if len(value) and isinstance(value[0], tuple):
506                 value = dict(value)
507                 return self.handleStruct(value)
508             else:
509                 return self.handleArray(value)
510         elif self.match(BLOB):
511             token = self.consume()
512             self.consume(LPAREN)
513             length = self.consume()
514             self.consume(RPAREN)
515             return self.handleBlob()
516         else:
517             self.error()
518
519     def parse_sequence(self, ltype, rtype, elementParser):
520         '''Parse a comma separated list'''
521
522         elements = []
523
524         self.consume(ltype)
525         sep = None
526         while not self.match(rtype):
527             if sep is None:
528                 sep = COMMA
529             else:
530                 self.consume(sep)
531             element = elementParser()
532             elements.append(element)
533         self.consume(rtype)
534
535         return elements
536
537     def handleID(self, value):
538         return LiteralValueMatcher(value)
539
540     def handleInt(self, value):
541         return LiteralValueMatcher(value)
542
543     def handleFloat(self, value):
544         return ApproxValueMatcher(value)
545
546     def handleString(self, value):
547         return LiteralValueMatcher(value)
548
549     def handleArray(self, value):
550         return ArrayMatcher(value)
551
552     def handleStruct(self, value):
553         return StructMatcher(value)
554
555     def handleBlob(self, value):
556         # TODO
557         return WildcardMatcher()
558
559     def handleCall(self, callNo, functionName, args, ret):
560         matcher = CallMatcher(functionName, args, ret)
561
562         if callNo is not None:
563             sys.stdout.write('%u ' % callNo)
564         sys.stdout.write(str(matcher))
565         sys.stdout.write('\n')
566
567
568 def main():
569     parser = CallParser(sys.stdin)
570     parser.parse()
571
572
573 if __name__ == '__main__':
574     main()