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
44 class WildcardMatcher(ValueMatcher):
46 def match(self, value):
53 class LiteralValueMatcher(ValueMatcher):
55 def __init__(self, refValue):
56 self.refValue = refValue
58 def match(self, value):
59 return self.refValue == value
62 return repr(self.refValue)
65 class ApproxValueMatcher(ValueMatcher):
67 def __init__(self, refValue, tolerance = 2**-23):
68 self.refValue = refValue
69 self.tolerance = tolerance
71 def match(self, value):
72 error = abs(self.refValue - value)
74 error = error / self.refValue
75 return error <= self.tolerance
78 return repr(self.refValue)
81 class ArrayMatcher(ValueMatcher):
83 def __init__(self, refElements):
84 self.refElements = refElements
86 def match(self, value):
87 if not isinstance(value, list):
90 if len(value) != len(self.refElements):
93 for refElement, element in zip(self.refElements, value):
94 if not refElement.match(element):
100 return '{' + ', '.join(map(str, self.refElements)) + '}'
103 class StructMatcher(ValueMatcher):
105 def __init__(self, refMembers):
106 self.refMembers = refMembers
108 def match(self, value):
109 if not isinstance(value, dict):
112 if len(value) != len(self.refMembers):
115 for name, refMember in self.refMembers.iteritems():
121 if not refMember.match(member):
127 print self.refMembers
128 return '{' + ', '.join(['%s = %s' % refMember for refMember in self.refMembers.iteritems()]) + '}'
133 def __init__(self, refFunctionName, refArgs, refRet = None):
134 self.refFunctionName = refFunctionName
135 self.refArgs = refArgs
138 def match(self, functionName, args, ret = None):
139 if refFunctionName != functionName:
142 if len(self.refArgs) != len(args):
145 for (refArgName, refArg), (argName, arg) in zip(self.refArgs, args):
146 if not refArg.match(arg):
149 if self.refRet is None:
153 if not self.refRet.match(ret):
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)
168 def __init__(self, refCalls):
169 self.refCalls = refCalls
172 return ''.join(['%s\n' % refCall for refCall in self.refCalls])
175 #######################################################################
181 class ParseError(Exception):
183 def __init__(self, msg=None, filename=None, line=None, col=None):
185 self.filename = filename
190 return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
194 """Stateless scanner."""
196 # should be overriden by derived classes
205 flags |= re.IGNORECASE
206 self.tokens_re = re.compile(
207 '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
211 def next(self, buf, pos):
214 mo = self.tokens_re.match(buf, pos)
217 type, regexp, test_lit = self.tokens[mo.lastindex - 1]
220 type = self.literals.get(text, type)
221 return type, text, pos
224 return self.symbols.get(c, None), c, pos + 1
229 def __init__(self, type, text, line, col):
238 # should be overriden by derived classes
242 newline_re = re.compile(r'\r\n?|\n')
244 def __init__(self, buf = None, pos = 0, filename = None, fp = None):
248 length = os.path.getsize(fp.name)
251 # read whole file into memory
255 # map the whole file into memory
257 # length must not be zero
258 buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
259 pos = os.lseek(fileno, 0, 1)
267 except AttributeError:
274 self.filename = filename
283 type, text, endpos = self.scanner.next(self.buf, pos)
284 assert pos + len(text) == endpos
286 type, text = self.filter(type, text)
292 msg = 'unexpected char '
293 if text >= ' ' and text <= '~':
296 msg += "0x%X" % ord(text)
297 raise ParseError(msg, self.filename, line, col)
300 return Token(type = type, text = text, line = line, col = col)
302 def consume(self, text):
305 for mo in self.newline_re.finditer(text, pos):
310 # update column number
312 tabpos = text.find('\t', pos)
315 self.col += tabpos - pos
316 self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
318 self.col += len(text) - pos
323 def __init__(self, lexer):
325 self.lookahead = self.lexer.next()
327 def match(self, type):
328 return self.lookahead.type == type
330 def skip(self, type):
331 while not self.match(type):
336 msg = 'unexpected token %r' % self.lookahead.text,
337 filename = self.lexer.filename,
338 line = self.lookahead.line,
339 col = self.lookahead.col)
341 def consume(self, type = None):
342 if type is not None and not self.match(type):
344 token = self.lookahead
345 self.lookahead = self.lexer.next()
349 #######################################################################
351 ID, NUMBER, HEXNUM, STRING, PRAGMA, LPAREN, RPAREN, LCURLY, RCURLY, COMMA, AMP, EQUAL, BLOB = xrange(13)
354 class CallScanner(Scanner):
356 # token regular expression table
359 (SKIP, r'[ \t\f\r\n\v]+', False),
362 (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True),
365 (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
368 (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False),
371 (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
374 (PRAGMA, r'#[^\r\n]*', False),
394 class CallLexer(Lexer):
396 scanner = CallScanner()
398 def filter(self, type, text):
403 text = text.replace('\\\r\n', '')
404 text = text.replace('\\\r', '')
405 text = text.replace('\\\n', '')
408 text = text.replace('\\"', '"')
415 class CallParser(Parser):
417 def __init__(self, stream):
418 lexer = CallLexer(fp = stream)
419 Parser.__init__(self, lexer)
422 return self.match(EOF)
425 while not self.eof():
428 def parse_element(self):
429 if self.lookahead.type == PRAGMA:
431 token = self.consume()
432 self.handlePragma(token.text)
436 def parse_call(self):
437 while self.lookahead.type == PRAGMA:
439 token = self.consume()
442 if self.lookahead.type == NUMBER:
443 token = self.consume()
444 callNo = int(token.text)
448 functionName = self.consume(ID).text
450 args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
452 if self.match(EQUAL):
454 ret = self.parse_value()
458 return self.handleCall(callNo, functionName, args, ret)
460 def parse_pair(self):
461 '''Parse a `name = value` pair.'''
462 name = self.consume(ID).text
464 value = self.parse_value()
467 def parse_opt_pair(self):
468 '''Parse an optional `name = value` pair.'''
470 name = self.consume(ID).text
471 if self.match(EQUAL):
473 value = self.parse_value()
479 value = self.parse_value()
485 def parse_value(self):
488 value = [self.parse_value()]
489 return self.handleArray(value)
491 token = self.consume()
493 return self.handleID(value)
494 elif self.match(STRING):
495 token = self.consume()
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):
510 return self.handleStruct(value)
512 return self.handleArray(value)
513 elif self.match(BLOB):
514 token = self.consume()
516 length = self.consume()
518 return self.handleBlob()
522 def parse_sequence(self, ltype, rtype, elementParser):
523 '''Parse a comma separated list'''
529 while not self.match(rtype):
534 element = elementParser()
535 elements.append(element)
540 def handleID(self, value):
541 return LiteralValueMatcher(value)
543 def handleInt(self, value):
544 return LiteralValueMatcher(value)
546 def handleFloat(self, value):
547 return ApproxValueMatcher(value)
549 def handleString(self, value):
550 return LiteralValueMatcher(value)
552 def handleArray(self, value):
553 return ArrayMatcher(value)
555 def handleStruct(self, value):
556 return StructMatcher(value)
558 def handleBlob(self, value):
560 return WildcardMatcher()
562 def handleCall(self, callNo, functionName, args, ret):
563 matcher = CallMatcher(functionName, args, ret)
565 if callNo is not None:
566 sys.stdout.write('%u ' % callNo)
567 sys.stdout.write(str(matcher))
568 sys.stdout.write('\n')
570 def handlePragma(self, line):
571 sys.stdout.write(line)
572 sys.stdout.write('\n')
576 parser = CallParser(sys.stdin)
580 if __name__ == '__main__':