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 ##########################################################################/
34 def match(self, value):
35 raise NotImplementedError
38 raise NotImplementerError
41 class WildcardMatcher(ValueMatcher):
43 def match(self, value):
50 class LiteralValueMatcher(ValueMatcher):
52 def __init__(self, refValue):
53 self.refValue = refValue
55 def match(self, value):
56 return self.refValue == value
59 return repr(self.refValue)
62 class ApproxValueMatcher(ValueMatcher):
65 def __init__(self, refValue, tolerance = 2**-23):
66 self.refValue = refValue
67 self.tolerance = tolerance
69 def match(self, value):
70 error = abs(self.refValue - value)
72 error = error / self.refValue
73 return error <= self.tolerance
76 return repr(self.refValue)
79 class ArrayMatcher(ValueMatcher):
81 def __init__(self, refElements):
82 self.refElements = refElements
84 def match(self, value):
85 if not isinstance(value, list):
88 if len(value) != len(self.refElements):
91 for refElement, element in zip(self.refElements, value):
92 if not refElement.match(element):
98 return '{' + ', '.join(map(str, self.refElements)) + '}'
101 class StructMatcher(ValueMatcher):
103 def __init__(self, refMembers):
104 self.refMembers = refMembers
106 def match(self, value):
107 if not isinstance(value, dict):
110 if len(value) != len(self.refMembers):
113 for name, refMember in self.refMembers.iteritems():
119 if not refMember.match(member):
125 print self.refMembers
126 return '{' + ', '.join(['%s = %s' % refMember for refMember in self.refMembers.iteritems()]) + '}'
131 def __init__(self, refFunctionName, refArgs, refRet = None):
132 self.refFunctionName = refFunctionName
133 self.refArgs = refArgs
136 def match(self, functionName, args, ret = None):
137 if refFunctionName != functionName:
140 if len(self.refArgs) != len(args):
143 for (refArgName, refArg), (argName, arg) in zip(self.refArgs, args):
144 if not refArg.match(arg):
147 if self.refRet is None:
151 if not self.refRet.match(ret):
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)
166 def __init__(self, refCalls):
167 self.refCalls = refCalls
170 return ''.join(['%s\n' % refCall for refCall in self.refCalls])
177 class ParseError(Exception):
179 def __init__(self, msg=None, filename=None, line=None, col=None):
181 self.filename = filename
186 return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
190 """Stateless scanner."""
192 # should be overriden by derived classes
201 flags |= re.IGNORECASE
202 self.tokens_re = re.compile(
203 '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
207 def next(self, buf, pos):
210 mo = self.tokens_re.match(buf, pos)
213 type, regexp, test_lit = self.tokens[mo.lastindex - 1]
216 type = self.literals.get(text, type)
217 return type, text, pos
220 return self.symbols.get(c, None), c, pos + 1
225 def __init__(self, type, text, line, col):
234 # should be overriden by derived classes
238 newline_re = re.compile(r'\r\n?|\n')
240 def __init__(self, buf = None, pos = 0, filename = None, fp = None):
244 length = os.path.getsize(fp.name)
247 # read whole file into memory
251 # map the whole file into memory
253 # length must not be zero
254 buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
255 pos = os.lseek(fileno, 0, 1)
263 except AttributeError:
270 self.filename = filename
279 type, text, endpos = self.scanner.next(self.buf, pos)
280 assert pos + len(text) == endpos
282 type, text = self.filter(type, text)
288 msg = 'unexpected char '
289 if text >= ' ' and text <= '~':
292 msg += "0x%X" % ord(text)
293 raise ParseError(msg, self.filename, line, col)
296 return Token(type = type, text = text, line = line, col = col)
298 def consume(self, text):
301 for mo in self.newline_re.finditer(text, pos):
306 # update column number
308 tabpos = text.find('\t', pos)
311 self.col += tabpos - pos
312 self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
314 self.col += len(text) - pos
319 def __init__(self, lexer):
321 self.lookahead = self.lexer.next()
323 def match(self, type):
324 return self.lookahead.type == type
326 def skip(self, type):
327 while not self.match(type):
332 msg = 'unexpected token %r' % self.lookahead.text,
333 filename = self.lexer.filename,
334 line = self.lookahead.line,
335 col = self.lookahead.col)
337 def consume(self, type = None):
338 if type is not None and not self.match(type):
340 token = self.lookahead
341 self.lookahead = self.lexer.next()
361 class CallScanner(Scanner):
363 # token regular expression table
366 (SKIP, r'[ \t\f\r\n\v]+', False),
369 (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True),
372 (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
375 (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False),
378 (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
398 class CallLexer(Lexer):
400 scanner = CallScanner()
402 def filter(self, type, text):
407 text = text.replace('\\\r\n', '')
408 text = text.replace('\\\r', '')
409 text = text.replace('\\\n', '')
412 text = text.replace('\\"', '"')
419 class CallParser(Parser):
421 def __init__(self, stream):
422 lexer = CallLexer(fp = stream)
423 Parser.__init__(self, lexer)
426 while not self.match(EOF):
429 def parse_call(self):
430 if self.lookahead.type == NUMBER:
431 token = self.consume()
432 callNo = int(token.text)
436 functionName = self.consume(ID).text
438 args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
440 if self.match(EQUAL):
442 ret = self.parse_value()
446 self.handle_call(callNo, functionName, args, ret)
448 def parse_pair(self):
449 '''Parse a `name = value` pair.'''
450 name = self.consume(ID).text
452 value = self.parse_value()
455 def parse_opt_pair(self):
456 '''Parse an optional `name = value` pair.'''
458 name = self.consume(ID).text
459 if self.match(EQUAL):
461 value = self.parse_value()
467 value = self.parse_value()
473 def parse_value(self):
476 value = [self.parse_value()]
477 return ArrayMatcher(value)
479 token = self.consume()
481 return LiteralValueMatcher(value)
482 elif self.match(STRING):
483 token = self.consume()
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))
499 return ArrayMatcher(value)
500 elif self.match(BLOB):
501 token = self.consume()
503 length = self.consume()
506 return WildcardMatcher()
510 def parse_sequence(self, ltype, rtype, elementParser):
511 '''Parse a comma separated list'''
517 while not self.match(rtype):
522 element = elementParser()
523 elements.append(element)
528 def handle_call(self, callNo, functionName, args, ret):
529 matcher = CallMatcher(functionName, args, ret)
531 if callNo is not None:
532 sys.stdout.write('%u ' % callNo)
533 sys.stdout.write(str(matcher))
534 sys.stdout.write('\n')
538 parser = CallParser(sys.stdin)
542 if __name__ == '__main__':