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 #######################################################################
367 class CallScanner(Scanner):
369 # token regular expression table
372 (SKIP, r'[ \t\f\r\n\v]+', False),
375 (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True),
378 (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
381 (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False),
384 (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
404 class CallLexer(Lexer):
406 scanner = CallScanner()
408 def filter(self, type, text):
413 text = text.replace('\\\r\n', '')
414 text = text.replace('\\\r', '')
415 text = text.replace('\\\n', '')
418 text = text.replace('\\"', '"')
425 class CallParser(Parser):
427 def __init__(self, stream):
428 lexer = CallLexer(fp = stream)
429 Parser.__init__(self, lexer)
432 return self.match(EOF)
435 while not self.eof():
438 def parse_call(self):
439 if self.lookahead.type == NUMBER:
440 token = self.consume()
441 callNo = int(token.text)
445 functionName = self.consume(ID).text
447 args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
449 if self.match(EQUAL):
451 ret = self.parse_value()
455 return self.handleCall(callNo, functionName, args, ret)
457 def parse_pair(self):
458 '''Parse a `name = value` pair.'''
459 name = self.consume(ID).text
461 value = self.parse_value()
464 def parse_opt_pair(self):
465 '''Parse an optional `name = value` pair.'''
467 name = self.consume(ID).text
468 if self.match(EQUAL):
470 value = self.parse_value()
476 value = self.parse_value()
482 def parse_value(self):
485 value = [self.parse_value()]
486 return self.handleArray(value)
488 token = self.consume()
490 return self.handleID(value)
491 elif self.match(STRING):
492 token = self.consume()
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):
507 return self.handleStruct(value)
509 return self.handleArray(value)
510 elif self.match(BLOB):
511 token = self.consume()
513 length = self.consume()
515 return self.handleBlob()
519 def parse_sequence(self, ltype, rtype, elementParser):
520 '''Parse a comma separated list'''
526 while not self.match(rtype):
531 element = elementParser()
532 elements.append(element)
537 def handleID(self, value):
538 return LiteralValueMatcher(value)
540 def handleInt(self, value):
541 return LiteralValueMatcher(value)
543 def handleFloat(self, value):
544 return ApproxValueMatcher(value)
546 def handleString(self, value):
547 return LiteralValueMatcher(value)
549 def handleArray(self, value):
550 return ArrayMatcher(value)
552 def handleStruct(self, value):
553 return StructMatcher(value)
555 def handleBlob(self, value):
557 return WildcardMatcher()
559 def handleCall(self, callNo, functionName, args, ret):
560 matcher = CallMatcher(functionName, args, ret)
562 if callNo is not None:
563 sys.stdout.write('%u ' % callNo)
564 sys.stdout.write(str(matcher))
565 sys.stdout.write('\n')
569 parser = CallParser(sys.stdin)
573 if __name__ == '__main__':