bison_.pyx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. """
  2. Pyrex-generated portion of pybison
  3. """
  4. cdef extern from "Python.h":
  5. object PyString_FromStringAndSize(char *, int)
  6. object PyString_FromString(char *)
  7. char *PyString_AsString(object o)
  8. object PyInt_FromLong(long ival)
  9. long PyInt_AsLong(object io)
  10. object PyList_New(int len)
  11. int PyList_SetItem(object list, int index, object item)
  12. void Py_INCREF(object o)
  13. object PyObject_GetAttrString(object o, char *attr_name)
  14. object PyTuple_New(int len)
  15. int PyTuple_SetItem(object p, int pos, object o)
  16. object PyObject_Call(object callable_object, object args, object kw)
  17. object PyObject_CallObject(object callable_object, object args)
  18. int PyObject_SetAttrString(object o, char *attr_name, object v)
  19. # use libdl for now - easy and simple - maybe switch to
  20. # glib or libtool if a keen windows dev sends in a patch
  21. #cdef extern from "dlfcn.h":
  22. # void *dlopen(char *filename, int mode)
  23. # int dlclose(void *handle)
  24. # void *dlsym(void *handle, char *name)
  25. # char *dlerror()
  26. #
  27. # ctypedef enum DL_MODES:
  28. # RTLD_LAZY
  29. # RTLD_NOW
  30. # RTLD_BINDING_MASK
  31. # RTLD_NOLOAD
  32. # RTLD_GLOBAL
  33. cdef extern from "stdio.h":
  34. int printf(char *format,...)
  35. cdef extern from "string.h":
  36. void *memcpy(void *dest, void *src, long n)
  37. cdef extern from "../c/bisondynlib.h":
  38. void *bisondynlib_open(char *filename)
  39. int bisondynlib_close(void *handle)
  40. char *bisondynlib_err()
  41. object (*bisondynlib_lookup_parser(void *handle))(object, char *)
  42. char *bisondynlib_lookup_hash(void *handle)
  43. object bisondynlib_run(void *handle, object parser, void *cb, void *pyin, int debug)
  44. #int bisondynlib_build(char *libName, char *includedir)
  45. # Definitions for variadic functions (e.g. py_callback).
  46. cdef extern from "stdarg.h":
  47. ctypedef struct va_list:
  48. pass
  49. ctypedef struct fake_type:
  50. pass
  51. void va_start(va_list, int arg)
  52. void* va_arg(va_list, fake_type)
  53. void va_end(va_list)
  54. fake_type void_type "void *"
  55. fake_type str_type "char *"
  56. # Callback function which is invoked by target handlers
  57. # within the C yyparse() function.
  58. import signal
  59. cdef public object py_callback(object parser, char *target, int option, \
  60. int nargs, ...):
  61. cdef int i
  62. cdef va_list ap
  63. va_start(ap, <int>nargs)
  64. cdef void *objptr
  65. cdef object obj
  66. cdef object valobj
  67. cdef void *val
  68. cdef char *tokval
  69. cdef char *termname
  70. #if parser.verbose:
  71. # print 'py_callback: called with nargs=%d' % nargs
  72. try:
  73. names = PyList_New(nargs)
  74. values = PyList_New(nargs)
  75. Py_INCREF(names)
  76. Py_INCREF(values)
  77. #for i in range(nargs):
  78. # print 'i=%d' % i , <char*>va_arg(ap, str_type), \
  79. # hex(<int>va_arg(ap, str_type))
  80. for i in range(nargs):
  81. termname = <char*>va_arg(ap, str_type)
  82. PyList_SetItem(names, i, termname)
  83. Py_INCREF(termname)
  84. val = <void *>va_arg(ap, void_type)
  85. valobj = <object>val
  86. PyList_SetItem(values, i, valobj)
  87. Py_INCREF(valobj)
  88. #if parser.verbose:
  89. # print 'py_callback: calling handler:', \
  90. # (target, option, names, values)
  91. # Set the signal handler and a timeout alarm
  92. #signal.signal(signal.SIGALRM, parser.handle_timeout)
  93. #signal.alarm(parser.timeout)
  94. res = parser._handle(target, option, names, values)
  95. #signal.alarm(0)
  96. #if parser.verbose:
  97. # print 'py_callback: handler returned:', res
  98. except:
  99. traceback.print_exc()
  100. res = None
  101. va_end(ap)
  102. return res
  103. # callback routine for reading input
  104. cdef public void py_input(object parser, char *buf, int *result, int max_size):
  105. cdef char *buf1
  106. cdef int buflen
  107. if parser.verbose:
  108. print "\npy_input: want to read up to %s bytes" % max_size
  109. raw = parser.read(max_size)
  110. buflen = PyInt_AsLong(len(raw))
  111. result[0] = buflen
  112. memcpy(buf, PyString_AsString(raw), buflen)
  113. if parser.verbose:
  114. print "\npy_input: got %s bytes" % buflen
  115. import sys, os, sha, re, imp, traceback
  116. import shutil
  117. import distutils.sysconfig
  118. import distutils.ccompiler
  119. reSpaces = re.compile("\\s+")
  120. #unquoted = r"""^|[^'"]%s[^'"]?"""
  121. unquoted = "[^'\"]%s[^'\"]?"
  122. cdef class ParserEngine:
  123. """
  124. Wraps the interface to the binary bison/lex-generated parser engine dynamic
  125. library.
  126. You shouldn't need to deal with this at all.
  127. Takes care of:
  128. - building the library (if the parser rules have changed)
  129. - loading the library and extracting the parser entry point
  130. - calling the entry point
  131. - closing the library
  132. Makes direct calls to the platform-dependent routines in
  133. bisondynlib-[linux|windows].c
  134. """
  135. cdef object parser
  136. cdef object parserHash # hash of current python parser object
  137. cdef object libFilename_py
  138. cdef void *libHandle
  139. # rules hash str embedded in bison parser lib
  140. cdef char *libHash
  141. def __init__(self, parser):
  142. """
  143. Creates a ParserEngine wrapper, and builds/loads the library.
  144. Arguments:
  145. - parser - an instance of a subclass of Parser
  146. In the course of initialisation, we check the library against the
  147. parser object's rules. If the lib doesn't exist, or can't be loaded, or
  148. doesn't match, we build a new library.
  149. Either way, we end up with a binary parser engine which matches the
  150. current rules in the parser object.
  151. """
  152. self.parser = parser
  153. self.libFilename_py = parser.buildDirectory \
  154. + parser.bisonEngineLibName \
  155. + imp.get_suffixes()[0][0]
  156. self.parserHash = hashParserObject(self.parser)
  157. self.openCurrentLib()
  158. def openCurrentLib(self):
  159. """
  160. Tests if library exists and is current. If not, builds a fresh one.
  161. Opens the library and imports the parser entry point.
  162. """
  163. parser = self.parser
  164. verbose = parser.verbose
  165. if not os.path.isfile(self.libFilename_py):
  166. self.buildLib()
  167. self.openLib()
  168. # hash our parser spec, compare to hash val stored in lib
  169. libHash = PyString_FromString(self.libHash)
  170. if self.parserHash != libHash:
  171. if verbose:
  172. print "Hash discrepancy, need to rebuild bison lib"
  173. print " current parser class: %s" % self.parserHash
  174. print " bison library: %s" % libHash
  175. self.closeLib()
  176. self.buildLib()
  177. self.openLib()
  178. else:
  179. if verbose:
  180. print "Hashes match, no need to rebuild bison engine lib"
  181. def openLib(self):
  182. """
  183. Loads the parser engine's dynamic library, and extracts the following
  184. symbols:
  185. - void *do_parse() (runs parser)
  186. - char *parserHash (contains hash of python parser rules)
  187. Returns lib handle, plus pointer to do_parse() function, as long ints
  188. (which later need to be cast to pointers)
  189. Important note -this is totally linux-specific.
  190. If you want windows support, you'll have to modify these funcs to
  191. use glib instead (or create windows equivalents), in which case I'd
  192. greatly appreciate you sending me a patch.
  193. """
  194. cdef char *libFilename
  195. cdef char *err
  196. cdef void *handle
  197. # convert python filename string to c string
  198. libFilename = PyString_AsString(self.libFilename_py)
  199. parser = self.parser
  200. if parser.verbose:
  201. print "Opening library %s" % self.libFilename_py
  202. handle = bisondynlib_open(libFilename)
  203. self.libHandle = handle
  204. err = bisondynlib_err()
  205. if err:
  206. printf("ParserEngine.openLib: error '%s'\n", err)
  207. return
  208. # extract symbols
  209. self.libHash = bisondynlib_lookup_hash(handle)
  210. if parser.verbose:
  211. print "Successfully loaded library"
  212. def buildLib(self):
  213. """
  214. Creates the parser engine lib
  215. This consists of:
  216. 1. Ripping the tokens list, precedences, start target, handler docstrings
  217. and lex script from this Parser instance's attribs and methods
  218. 2. Creating bison and lex files
  219. 3. Compiling bison/lex files to C
  220. 4. Compiling the C files, and link into a dynamic lib
  221. """
  222. cdef char *incdir
  223. # -------------------------------------------------
  224. # rip the pertinent grammar specs from parser class
  225. parser = self.parser
  226. # get target handler methods, in the order of appearance in the source
  227. # file.
  228. attribs = dir(parser)
  229. gHandlers = []
  230. for a in attribs:
  231. if a.startswith("on_"):
  232. method = getattr(parser, a)
  233. gHandlers.append(method)
  234. gHandlers.sort(cmpLines)
  235. # get start symbol, tokens, precedences, lex script
  236. gStart = parser.start
  237. gTokens = parser.tokens
  238. gPrecedences = parser.precedences
  239. gLex = parser.lexscript
  240. buildDirectory = parser.buildDirectory
  241. # ------------------------------------------------
  242. # now, can generate the grammar file
  243. if os.path.isfile(buildDirectory + parser.bisonFile):
  244. os.unlink(buildDirectory + parser.bisonFile)
  245. if parser.verbose:
  246. print 'generating bison file:', buildDirectory + parser.bisonFile
  247. f = open(buildDirectory + parser.bisonFile, "w")
  248. write = f.write
  249. writelines = f.writelines
  250. # grammar file prologue
  251. write("\n".join([
  252. "%{",
  253. '',
  254. '#include "Python.h"',
  255. "#include <stdio.h>",
  256. "extern FILE *yyin;",
  257. "extern int yylineno;"
  258. "extern char *yytext;",
  259. "#define YYSTYPE void*",
  260. #'extern void *py_callback(void *, char *, int, void*, ...);',
  261. 'void *(*py_callback)(void *, char *, int, int, ...);',
  262. 'void (*py_input)(void *, char *, int *, int);',
  263. 'void *py_parser;',
  264. 'char *rules_hash = "%s";' % self.parserHash,
  265. '',
  266. "%}",
  267. '',
  268. ]))
  269. # write out tokens and start target dec
  270. write("%%token %s\n\n" % " ".join(gTokens))
  271. write("%%start %s\n\n" % gStart)
  272. # write out precedences
  273. for p in gPrecedences:
  274. write("%%%s %s\n" % (p[0], " ".join(p[1])))
  275. write("\n\n%%\n\n")
  276. # carve up docstrings
  277. rules = []
  278. for h in gHandlers:
  279. doc = h.__doc__.strip()
  280. # added by Eugene Oden
  281. #target, options = doc.split(":")
  282. doc = re.sub(unquoted % ";", "", doc)
  283. #print "---------------------"
  284. s = re.split(unquoted % ":", doc)
  285. #print "s=%s" % s
  286. target, options = s
  287. target = target.strip()
  288. options = options.strip()
  289. tmp = []
  290. #print "options = %s" % repr(options)
  291. opts = options.split("|")
  292. #print "opts = %s" % repr(opts)
  293. r = unquoted % r"\|"
  294. #print "r = <%s>" % r
  295. opts1 = re.split(r, " " + options)
  296. #print "opts1 = %s" % repr(opts1)
  297. for o in opts1:
  298. o = o.strip()
  299. tmp.append(reSpaces.split(o))
  300. options = tmp
  301. rules.append((target, options))
  302. # and render rules to grammar file
  303. for rule in rules:
  304. try:
  305. write("%s\n : " % rule[0])
  306. options = []
  307. idx = 0
  308. for option in rule[1]:
  309. nterms = len(option)
  310. if nterms == 1 and option[0] == '':
  311. nterms = 0
  312. option = []
  313. action = '\n {\n'
  314. if 'error' in option:
  315. action = action + " yyerrok;\n"
  316. action = action + ' $$ = (*py_callback)(\n py_parser, "%s", %s, %%s' % \
  317. (rule[0], idx) # note we're deferring the substitution of 'nterms' (last arg)
  318. args = []
  319. if nterms == 0:
  320. args.append('NULL')
  321. i = -1
  322. else:
  323. for i in range(nterms):
  324. if option[i] == '%prec':
  325. i = i - 1
  326. break # hack for rules using '%prec'
  327. args.append('"%s", $%d' % (option[i], i+1))
  328. # now, we have the correct terms count
  329. action = action % (i + 1)
  330. # assemble the full rule + action, ad to list
  331. action = action + ",\n "
  332. action = action + ",\n ".join(args) + "\n );\n"
  333. if 'error' in option:
  334. action = action + " PyObject_SetAttrString(py_parser, \"lasterror\", Py_None);\n"
  335. action = action + " Py_INCREF(Py_None);\n"
  336. action = action + " yyclearin;\n"
  337. action = action + " if ($$ && $$ != Py_None && PyObject_HasAttrString($$, \"_pyBisonError\"))\n"
  338. action = action + " {\n"
  339. action = action + " yyerror(PyString_AsString(PyObject_GetAttrString(py_parser, \"lasterror\")));\n"
  340. action = action + " Py_INCREF(Py_None);\n"
  341. action = action + " YYERROR;\n"
  342. action = action + " }\n"
  343. action = action + " }\n"
  344. options.append(" ".join(option) + action)
  345. idx = idx + 1
  346. write(" | ".join(options) + " ;\n\n")
  347. except:
  348. traceback.print_exc()
  349. write("\n\n%%\n\n")
  350. # now generate C code
  351. epilogue = "\n".join([
  352. 'void do_parse(void *parser1,',
  353. ' void *(*cb)(void *, char *, int, int, void *, ...),',
  354. ' void (*in)(void *, char*, int *, int),',
  355. ' int debug',
  356. ' )',
  357. '{',
  358. ' //printf("Not calling yyparse\\n");',
  359. ' //return;',
  360. ' py_callback = cb;',
  361. ' py_input = in;',
  362. " py_parser = parser1;",
  363. " yydebug = debug;",
  364. " //yyin = stdin;",
  365. ' //printf("calling yyparse(), in=0x%lx\\n", py_input);',
  366. " yyparse();",
  367. ' //printf("Back from parser\\n");',
  368. "}",
  369. "int yyerror(char *mesg)",
  370. "{",
  371. ' //printf("yytext=0x%lx\\n", yytext);',
  372. ' PyObject *args = PyTuple_New(3);',
  373. ' int ret;',
  374. '',
  375. ' PyTuple_SetItem(args, 0, PyInt_FromLong(yylineno+1));',
  376. ' PyTuple_SetItem(args, 1, PyString_FromString(mesg));',
  377. ' PyTuple_SetItem(args, 2, PyString_FromString(yytext));',
  378. '',
  379. ' ret = PyObject_SetAttrString((PyObject *)py_parser, "lasterror", args);',
  380. ' //printf("PyObject_SetAttrString: %d\\n", ret);',
  381. '',
  382. ' //printf("line %d: %s before %s\\n", yylineno+1, mesg, yytext);',
  383. " //exit(0);",
  384. "}",
  385. ]) + "\n"
  386. write(epilogue)
  387. # done with grammar file
  388. f.close()
  389. # -----------------------------------------------
  390. # now generate the lex script
  391. if os.path.isfile(buildDirectory + parser.flexFile):
  392. os.unlink(buildDirectory + parser.flexFile)
  393. lexLines = gLex.split("\n")
  394. tmp = []
  395. for line in lexLines:
  396. tmp.append(line.strip())
  397. f = open(buildDirectory + parser.flexFile, "w")
  398. f.write("\n".join(tmp) + "\n")
  399. f.close()
  400. # create and set up a compiler object
  401. ccompiler = distutils.ccompiler.new_compiler(verbose=parser.verbose)
  402. ccompiler.set_include_dirs([distutils.sysconfig.get_python_inc()])
  403. # -----------------------------------------
  404. # Now run bison on the grammar file
  405. #os.system("bison -d tmp.y")
  406. bisonCmd = parser.bisonCmd + [buildDirectory + parser.bisonFile]
  407. if parser.verbose:
  408. print 'bison cmd:', ' '.join(bisonCmd)
  409. ccompiler.spawn(bisonCmd)
  410. if parser.verbose:
  411. print "renaming bison output files"
  412. print '%s => %s%s' % (parser.bisonCFile, buildDirectory,
  413. parser.bisonCFile1)
  414. print '%s => %s%s' % (parser.bisonHFile, buildDirectory,
  415. parser.bisonHFile1)
  416. if os.path.isfile(buildDirectory + parser.bisonCFile1):
  417. os.unlink(buildDirectory + parser.bisonCFile1)
  418. shutil.copy(parser.bisonCFile, buildDirectory + parser.bisonCFile1)
  419. if os.path.isfile(buildDirectory + parser.bisonHFile1):
  420. os.unlink(buildDirectory + parser.bisonHFile1)
  421. shutil.copy(parser.bisonHFile, buildDirectory + parser.bisonHFile1)
  422. # -----------------------------------------
  423. # Now run lex on the lex file
  424. #os.system("lex tmp.l")
  425. flexCmd = parser.flexCmd + [buildDirectory + parser.flexFile]
  426. if parser.verbose:
  427. print 'flex cmd:', ' '.join(flexCmd)
  428. ccompiler.spawn(flexCmd)
  429. if os.path.isfile(buildDirectory + parser.flexCFile1):
  430. os.unlink(buildDirectory + parser.flexCFile1)
  431. if parser.verbose:
  432. print '%s => %s%s' % (parser.flexCFile, buildDirectory,
  433. parser.flexCFile1)
  434. shutil.copy(parser.flexCFile, buildDirectory + parser.flexCFile1)
  435. # -----------------------------------------
  436. # Now compile the files into a shared lib
  437. # compile bison and lex c sources
  438. #bisonObj = ccompiler.compile([parser.bisonCFile1])
  439. #lexObj = ccompiler.compile([parser.flexCFile1])
  440. #cl /DWIN32 /G4 /Gs /Oit /MT /nologo /W3 /WX bisondynlib-win32.c /Id:\python23\include
  441. #cc.compile(['bisondynlib-win32.c'],
  442. # extra_preargs=['/DWIN32', '/G4', '/Gs', '/Oit', '/MT', '/nologo', '/W3', '/WX', '/Id:\python23\include'])
  443. # link 'em into a shared lib
  444. objs = ccompiler.compile([buildDirectory + parser.bisonCFile1,
  445. buildDirectory + parser.flexCFile1],
  446. extra_preargs=parser.cflags_pre,
  447. extra_postargs=parser.cflags_post,
  448. debug=parser.debugSymbols)
  449. libFileName = buildDirectory + parser.bisonEngineLibName \
  450. + imp.get_suffixes()[0][0]
  451. if os.path.isfile(libFileName+".bak"):
  452. os.unlink(libFileName+".bak")
  453. if os.path.isfile(libFileName):
  454. os.rename(libFileName, libFileName+".bak")
  455. if parser.verbose:
  456. print 'linking: %s => %s' % (', '.join(objs), libFileName)
  457. ccompiler.link_shared_object(objs, libFileName)
  458. #incdir = PyString_AsString(get_python_inc())
  459. #bisondynlib_build(self.libFilename_py, incdir)
  460. # --------------------------------------------
  461. # clean up, if we succeeded
  462. hitlist = objs[:]
  463. hitlist.append(buildDirectory + "tmp.output")
  464. if os.path.isfile(libFileName):
  465. for name in ['bisonFile', 'bisonCFile', 'bisonHFile',
  466. 'bisonCFile1', 'bisonHFile1', 'flexFile',
  467. 'flexCFile', 'flexCFile1',
  468. ] + objs:
  469. if hasattr(parser, name):
  470. fname = buildDirectory + getattr(parser, name)
  471. else:
  472. fname = None
  473. #print "want to delete %s" % fname
  474. if fname and os.path.isfile(fname):
  475. hitlist.append(fname)
  476. if not parser.keepfiles:
  477. for f in hitlist:
  478. try:
  479. os.unlink(f)
  480. except:
  481. print "Warning: failed to delete temporary file %s" % f
  482. def closeLib(self):
  483. """
  484. Does the necessary cleanups and closes the parser library
  485. """
  486. bisondynlib_close(self.libHandle)
  487. def runEngine(self, debug=0):
  488. """
  489. Runs the binary parser engine, as loaded from the lib
  490. """
  491. cdef void *handle
  492. cdef void *cbvoid
  493. cdef void *invoid
  494. handle = self.libHandle
  495. parser = self.parser
  496. cbvoid = <void *>py_callback
  497. invoid = <void *>py_input
  498. return bisondynlib_run(handle, parser, cbvoid, invoid, debug)
  499. def __del__(self):
  500. """
  501. Clean up and bail
  502. """
  503. self.closeLib()
  504. def cmpLines(meth1, meth2):
  505. """
  506. Used as a sort() argument for sorting parse target handler methods by
  507. the order of their declaration in their source file.
  508. """
  509. try:
  510. line1 = meth1.func_code.co_firstlineno
  511. line2 = meth2.func_code.co_firstlineno
  512. except:
  513. line1 = meth1.__init__.func_code.co_firstlineno
  514. line2 = meth2.__init__.func_code.co_firstlineno
  515. return cmp(line1, line2)
  516. def hashParserObject(parser):
  517. """
  518. Calculates an sha1 hex 'hash' of the lex script
  519. and grammar rules in a parser class instance.
  520. This is based on the raw text of the lex script attribute,
  521. and the grammar rule docstrings within the handler methods.
  522. Used to detect if someone has changed any grammar rules or
  523. lex script, and therefore, whether a shared parser lib rebuild
  524. is required.
  525. """
  526. hasher = sha.new()
  527. # add the lex script
  528. hasher.update(parser.lexscript)
  529. # add the tokens
  530. # workaround pyrex weirdness
  531. tokens = list(parser.tokens)
  532. hasher.update(",".join(list(parser.tokens)))
  533. # add the precedences
  534. for direction, tokens in parser.precedences:
  535. hasher.update(direction + "".join(tokens))
  536. # extract the parser target handler names
  537. handlerNames = dir(parser)
  538. #handlerNames = filter(lambda m: m.startswith('on_'), dir(parser))
  539. tmp = []
  540. for name in handlerNames:
  541. if name.startswith('on_'):
  542. tmp.append(name)
  543. handlerNames = tmp
  544. handlerNames.sort()
  545. # extract method objects, filter down to callables
  546. #handlers = [getattr(parser, m) for m in handlerNames]
  547. #handlers = filter(lambda h: callable(h), handlers)
  548. tmp = []
  549. for m in handlerNames:
  550. attr = getattr(parser, m)
  551. if callable(attr):
  552. tmp.append(attr)
  553. handlers = tmp
  554. # now add in the methods' docstrings
  555. for h in handlers:
  556. docString = h.__doc__
  557. hasher.update(docString)
  558. # done
  559. return hasher.hexdigest()