bison.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. """
  2. Wrapper module for interfacing with Bison (yacc)
  3. Written April 2004 by David McNab <david@freenet.org.nz>
  4. Copyright (c) 2004 by David McNab, all rights reserved.
  5. Released under the GNU General Public License, a copy of which should appear in
  6. this distribution in the file called 'COPYING'. If this file is missing, then
  7. you can obtain a copy of the GPL license document from the GNU website at
  8. http://www.gnu.org.
  9. This software is released with no warranty whatsoever. Use it at your own
  10. risk.
  11. If you wish to use this software in a commercial application, and wish to
  12. depart from the GPL licensing requirements, please contact the author and apply
  13. for a commercial license.
  14. """
  15. import sys
  16. import traceback
  17. from bison_ import ParserEngine
  18. from .node import BisonNode
  19. class BisonSyntaxError(Exception):
  20. def __init__(self, msg, args):
  21. super(BisonSyntaxError, self).__init__(msg)
  22. self.first_line, self.first_col, self.last_line, self.last_col, \
  23. self.message, self.token_value = args
  24. class TimeoutError(Exception):
  25. pass
  26. class BisonParser(object):
  27. """
  28. Base parser class
  29. You should subclass this, and provide a bunch of methods called
  30. 'on_TargetName', where 'TargetName' is the name of each target in
  31. your grammar (.y) file.
  32. """
  33. # ---------------------------------------
  34. # override these if you need to
  35. # Command and options for running yacc/bison, except for filename arg
  36. bisonCmd = ['bison', '-d', '-v', '-t']
  37. bisonFile = 'tmp.y'
  38. bisonCFile = 'tmp.tab.c'
  39. # Name of header file generated by bison cmd.
  40. bisonHFile = 'tmp.tab.h'
  41. # C output file from bison gets renamed to this.
  42. bisonCFile1 = 'tmp.bison.c'
  43. # Bison-generated header file gets renamed to this.
  44. bisonHFile1 = 'tokens.h'
  45. # command and options for running [f]lex, except for filename arg.
  46. flexCmd = ['flex', ]
  47. flexFile = 'tmp.l'
  48. flexCFile = 'lex.yy.c'
  49. # C output file from flex gets renamed to this.
  50. flexCFile1 = 'tmp.lex.c'
  51. # CFLAGS added before all command line arguments.
  52. cflags_pre = ['-fPIC']
  53. # CFLAGS added after all command line arguments.
  54. cflags_post = ['-O3', '-g']
  55. # Directory used to store the generated / compiled files.
  56. buildDirectory = './'
  57. # Add debugging symbols to the binary files.
  58. debugSymbols = 1
  59. # Enable verbose debug message sent to stdout.
  60. verbose = 0
  61. # Timeout in seconds after which the parser is terminated.
  62. # TODO: this is currently not implemented.
  63. timeout = 1
  64. # Default to sys.stdin.
  65. file = None
  66. # Last parsed target, top of parse tree.
  67. last = None
  68. # Enable this to keep all temporary engine build files.
  69. keepfiles = 0
  70. # Prefix of the shared object / dll file. Defaults to 'modulename-engine'.
  71. # If the module is executed directly, "__main__" will be used (since that
  72. # that is the "module name", in that case).
  73. bisonEngineLibName = None
  74. # Class to use by default for creating new parse nodes. If set to None,
  75. # BisonNode will be used.
  76. default_node_class = BisonNode
  77. def __init__(self, **kw):
  78. """
  79. Abstract representation of parser
  80. Keyword arguments:
  81. - read - a callable accepting an int arg (nbytes) and returning a string,
  82. default is this class' read() method
  83. - file - a file object, or string of a pathname to open as a file, defaults
  84. to sys.stdin. Note that you can leave this blank, and pass a file keyword
  85. argument to the .run() method.
  86. - verbose - set to 1 to enable verbose output messages, default 0
  87. - keepfiles - if non-zero, keeps any files generated in the
  88. course of building the parser engine; by default, all these
  89. files get deleted upon a successful engine build
  90. - defaultNodeClass - the class to use for creating parse nodes, default
  91. is self.defaultNodeClass (in this base class, BisonNode)
  92. """
  93. # setup
  94. read = kw.get('read', None)
  95. if read:
  96. self.read = read
  97. fileobj = kw.get('file', None)
  98. if fileobj:
  99. if isinstance(fileobj, str):
  100. try:
  101. fileobj = open(fileobj, 'rb')
  102. except:
  103. raise Exception('Cannot open input file %s' % fileobj)
  104. self.file = fileobj
  105. else:
  106. self.file = sys.stdin
  107. nodeClass = kw.get('defaultNodeClass', None)
  108. if nodeClass:
  109. self.defaultNodeClass = nodeClass
  110. self.verbose = kw.get('verbose', 0)
  111. if kw.has_key('keepfiles'):
  112. self.keepfiles = kw['keepfiles']
  113. # if engine lib name not declared, invent ont
  114. if not self.bisonEngineLibName:
  115. self.bisonEngineLibName = self.__class__.__module__ + '-parser'
  116. # get an engine
  117. self.engine = ParserEngine(self)
  118. def __getitem__(self, idx):
  119. return self.last[idx]
  120. def _handle(self, targetname, option, names, values):
  121. """
  122. Callback which receives a target from parser, as a targetname
  123. and list of term names and values.
  124. Tries to dispatch to on_TargetName() methods if they exist,
  125. otherwise wraps the target in a BisonNode object
  126. """
  127. handler = getattr(self, 'on_' + targetname, None)
  128. if handler:
  129. if self.verbose:
  130. try:
  131. hdlrline = handler.func_code.co_firstlineno
  132. except:
  133. hdlrline = handler.__init__.func_code.co_firstlineno
  134. print 'BisonParser._handle: call handler at line %s with: %s' \
  135. % (hdlrline, str((targetname, option, names, values)))
  136. self.last = handler(target=targetname, option=option, names=names,
  137. values=values)
  138. #if self.verbose:
  139. # print 'handler for %s returned %s' \
  140. # % (targetname, repr(self.last))
  141. else:
  142. if self.verbose:
  143. print 'no handler for %s, using default' % targetname
  144. cls = self.default_node_class
  145. self.last = cls(target=targetname, option=option, names=names,
  146. values=values)
  147. # assumedly the last thing parsed is at the top of the tree
  148. return self.last
  149. def handle_timeout(self, signum, frame):
  150. raise TimeoutError('Computation exceeded timeout limit.')
  151. def run(self, **kw):
  152. """
  153. Runs the parser, and returns the top-most parse target.
  154. Keywords:
  155. - file - either a string, comprising a file to open and read input from, or
  156. a Python file object
  157. - debug - enables garrulous parser debugging output, default 0
  158. """
  159. if self.verbose:
  160. print 'Parser.run: calling engine'
  161. # grab keywords
  162. fileobj = kw.get('file', self.file)
  163. if isinstance(fileobj, str):
  164. filename = fileobj
  165. try:
  166. fileobj = open(fileobj, 'rb')
  167. except:
  168. raise Exception('Cannot open input file "%s"' % fileobj)
  169. else:
  170. filename = None
  171. fileobj = None
  172. read = kw.get('read', self.read)
  173. debug = kw.get('debug', 0)
  174. # back up existing attribs
  175. oldfile = self.file
  176. oldread = self.read
  177. # plug in new ones, if given
  178. if fileobj:
  179. self.file = fileobj
  180. if read:
  181. self.read = read
  182. if self.verbose and self.file.closed:
  183. print 'Parser.run(): self.file', self.file, 'is closed'
  184. # TODO: add option to fail on first error.
  185. while not self.file.closed:
  186. # do the parsing job, spew if error
  187. self.last = None
  188. try:
  189. self.engine.runEngine(debug)
  190. except Exception as e:
  191. self.report_last_error(filename, e)
  192. if self.verbose:
  193. print 'Parser.run: back from engine'
  194. if hasattr(self, 'hook_run'):
  195. self.last = self.hook_run(filename, self.last)
  196. if self.verbose and not self.file.closed:
  197. print 'last:', self.last
  198. if self.verbose:
  199. print 'last:', self.last
  200. # restore old values
  201. self.file = oldfile
  202. self.read = oldread
  203. if self.verbose:
  204. print '------------------ result=', self.last
  205. # TODO: return last result (see while loop):
  206. # return self.last[:-1]
  207. return self.last
  208. def read(self, nbytes):
  209. """
  210. Override this in your subclass, if you desire.
  211. Arguments:
  212. - nbytes - the maximum length of the string which you may return.
  213. DO NOT return a string longer than this, or else Bad Things will
  214. happen.
  215. """
  216. # default to stdin
  217. if self.verbose:
  218. print 'Parser.read: want %s bytes' % nbytes
  219. bytes = self.file.readline(nbytes)
  220. if self.verbose:
  221. print 'Parser.read: got %s bytes' % len(bytes)
  222. return bytes
  223. def report_last_error(self, filename, error):
  224. """
  225. Report a raised exception. Depending on the mode in which the parser is
  226. running, it will:
  227. - write a verbose message to stderr (verbose=True; interactive=True).
  228. The written error message will include the type, value and traceback
  229. of the raised exception.
  230. - write a minimal message to stderr (verbose=False; interactive=True).
  231. The written error message will only include the type and value of
  232. the raised exception.
  233. """
  234. #if filename != None:
  235. # msg = '%s:%d: "%s" near "%s"' \
  236. # % ((filename,) + error)
  237. # if not self.interactive:
  238. # raise BisonSyntaxError(msg)
  239. # print >>sys.stderr, msg
  240. #elif hasattr(error, '__getitem__') and isinstance(error[0], int):
  241. # msg = 'Line %d: "%s" near "%s"' % error
  242. # if not self.interactive:
  243. # raise BisonSyntaxError(msg)
  244. # print >>sys.stderr, msg
  245. #else:
  246. if not self.interactive:
  247. raise
  248. if self.verbose:
  249. traceback.print_exc()
  250. print 'ERROR:', error
  251. def report_syntax_error(self, msg, yytext, first_line, first_col,
  252. last_line, last_col):
  253. yytext = yytext.replace('\n', '\\n')
  254. args = (first_line, first_col, last_line, last_col, msg, yytext)
  255. raise BisonSyntaxError('%d.%d-%d.%d: "%s" near "%s".' % args, args)