| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- """
- Wrapper module for interfacing with Bison (yacc)
- Written April 2004 by David McNab <david@freenet.org.nz>
- Copyright (c) 2004 by David McNab, all rights reserved.
- Released under the GNU General Public License, a copy of which should appear in
- this distribution in the file called 'COPYING'. If this file is missing, then
- you can obtain a copy of the GPL license document from the GNU website at
- http://www.gnu.org.
- This software is released with no warranty whatsoever. Use it at your own
- risk.
- If you wish to use this software in a commercial application, and wish to
- depart from the GPL licensing requirements, please contact the author and apply
- for a commercial license.
- """
- import sys
- import traceback
- from bison_ import ParserEngine
- from .node import BisonNode
- class BisonSyntaxError(Exception):
- def __init__(self, msg, args):
- super(BisonSyntaxError, self).__init__(msg)
- self.first_line, self.first_col, self.last_line, self.last_col, \
- self.message, self.token_value = args
- class TimeoutError(Exception):
- pass
- class BisonParser(object):
- """
- Base parser class
- You should subclass this, and provide a bunch of methods called
- 'on_TargetName', where 'TargetName' is the name of each target in
- your grammar (.y) file.
- """
- # ---------------------------------------
- # override these if you need to
- # Command and options for running yacc/bison, except for filename arg
- bisonCmd = ['bison', '-d', '-v', '-t']
- bisonFile = 'tmp.y'
- bisonCFile = 'tmp.tab.c'
- # Name of header file generated by bison cmd.
- bisonHFile = 'tmp.tab.h'
- # C output file from bison gets renamed to this.
- bisonCFile1 = 'tmp.bison.c'
- # Bison-generated header file gets renamed to this.
- bisonHFile1 = 'tokens.h'
- # command and options for running [f]lex, except for filename arg.
- flexCmd = ['flex', ]
- flexFile = 'tmp.l'
- flexCFile = 'lex.yy.c'
- # C output file from flex gets renamed to this.
- flexCFile1 = 'tmp.lex.c'
- # CFLAGS added before all command line arguments.
- cflags_pre = ['-fPIC']
- # CFLAGS added after all command line arguments.
- cflags_post = ['-O3', '-g']
- # Directory used to store the generated / compiled files.
- buildDirectory = './'
- # Add debugging symbols to the binary files.
- debugSymbols = 1
- # Enable verbose debug message sent to stdout.
- verbose = 0
- # Timeout in seconds after which the parser is terminated.
- # TODO: this is currently not implemented.
- timeout = 1
- # Default to sys.stdin.
- file = None
- # Last parsed target, top of parse tree.
- last = None
- # Enable this to keep all temporary engine build files.
- keepfiles = 0
- # Prefix of the shared object / dll file. Defaults to 'modulename-engine'.
- # If the module is executed directly, "__main__" will be used (since that
- # that is the "module name", in that case).
- bisonEngineLibName = None
- # Class to use by default for creating new parse nodes. If set to None,
- # BisonNode will be used.
- default_node_class = BisonNode
- def __init__(self, **kw):
- """
- Abstract representation of parser
- Keyword arguments:
- - read - a callable accepting an int arg (nbytes) and returning a string,
- default is this class' read() method
- - file - a file object, or string of a pathname to open as a file, defaults
- to sys.stdin. Note that you can leave this blank, and pass a file keyword
- argument to the .run() method.
- - verbose - set to 1 to enable verbose output messages, default 0
- - keepfiles - if non-zero, keeps any files generated in the
- course of building the parser engine; by default, all these
- files get deleted upon a successful engine build
- - defaultNodeClass - the class to use for creating parse nodes, default
- is self.defaultNodeClass (in this base class, BisonNode)
- """
- # setup
- read = kw.get('read', None)
- if read:
- self.read = read
- fileobj = kw.get('file', None)
- if fileobj:
- if isinstance(fileobj, str):
- try:
- fileobj = open(fileobj, 'rb')
- except:
- raise Exception('Cannot open input file %s' % fileobj)
- self.file = fileobj
- else:
- self.file = sys.stdin
- nodeClass = kw.get('defaultNodeClass', None)
- if nodeClass:
- self.defaultNodeClass = nodeClass
- self.verbose = kw.get('verbose', 0)
- if kw.has_key('keepfiles'):
- self.keepfiles = kw['keepfiles']
- # if engine lib name not declared, invent ont
- if not self.bisonEngineLibName:
- self.bisonEngineLibName = self.__class__.__module__ + '-parser'
- # get an engine
- self.engine = ParserEngine(self)
- def __getitem__(self, idx):
- return self.last[idx]
- def _handle(self, targetname, option, names, values):
- """
- Callback which receives a target from parser, as a targetname
- and list of term names and values.
- Tries to dispatch to on_TargetName() methods if they exist,
- otherwise wraps the target in a BisonNode object
- """
- handler = getattr(self, 'on_' + targetname, None)
- if handler:
- if self.verbose:
- try:
- hdlrline = handler.func_code.co_firstlineno
- except:
- hdlrline = handler.__init__.func_code.co_firstlineno
- print 'BisonParser._handle: call handler at line %s with: %s' \
- % (hdlrline, str((targetname, option, names, values)))
- self.last = handler(target=targetname, option=option, names=names,
- values=values)
- #if self.verbose:
- # print 'handler for %s returned %s' \
- # % (targetname, repr(self.last))
- else:
- if self.verbose:
- print 'no handler for %s, using default' % targetname
- cls = self.default_node_class
- self.last = cls(target=targetname, option=option, names=names,
- values=values)
- # assumedly the last thing parsed is at the top of the tree
- return self.last
- def handle_timeout(self, signum, frame):
- raise TimeoutError('Computation exceeded timeout limit.')
- def run(self, **kw):
- """
- Runs the parser, and returns the top-most parse target.
- Keywords:
- - file - either a string, comprising a file to open and read input from, or
- a Python file object
- - debug - enables garrulous parser debugging output, default 0
- """
- if self.verbose:
- print 'Parser.run: calling engine'
- # grab keywords
- fileobj = kw.get('file', self.file)
- if isinstance(fileobj, str):
- filename = fileobj
- try:
- fileobj = open(fileobj, 'rb')
- except:
- raise Exception('Cannot open input file "%s"' % fileobj)
- else:
- filename = None
- fileobj = None
- read = kw.get('read', self.read)
- debug = kw.get('debug', 0)
- # back up existing attribs
- oldfile = self.file
- oldread = self.read
- # plug in new ones, if given
- if fileobj:
- self.file = fileobj
- if read:
- self.read = read
- if self.verbose and self.file.closed:
- print 'Parser.run(): self.file', self.file, 'is closed'
- # TODO: add option to fail on first error.
- while not self.file.closed:
- # do the parsing job, spew if error
- self.last = None
- try:
- self.engine.runEngine(debug)
- except Exception as e:
- self.report_last_error(filename, e)
- if self.verbose:
- print 'Parser.run: back from engine'
- if hasattr(self, 'hook_run'):
- self.last = self.hook_run(filename, self.last)
- if self.verbose and not self.file.closed:
- print 'last:', self.last
- if self.verbose:
- print 'last:', self.last
- # restore old values
- self.file = oldfile
- self.read = oldread
- if self.verbose:
- print '------------------ result=', self.last
- # TODO: return last result (see while loop):
- # return self.last[:-1]
- return self.last
- def read(self, nbytes):
- """
- Override this in your subclass, if you desire.
- Arguments:
- - nbytes - the maximum length of the string which you may return.
- DO NOT return a string longer than this, or else Bad Things will
- happen.
- """
- # default to stdin
- if self.verbose:
- print 'Parser.read: want %s bytes' % nbytes
- bytes = self.file.readline(nbytes)
- if self.verbose:
- print 'Parser.read: got %s bytes' % len(bytes)
- return bytes
- def report_last_error(self, filename, error):
- """
- Report a raised exception. Depending on the mode in which the parser is
- running, it will:
-
- - write a verbose message to stderr (verbose=True; interactive=True).
- The written error message will include the type, value and traceback
- of the raised exception.
- - write a minimal message to stderr (verbose=False; interactive=True).
- The written error message will only include the type and value of
- the raised exception.
- """
- #if filename != None:
- # msg = '%s:%d: "%s" near "%s"' \
- # % ((filename,) + error)
- # if not self.interactive:
- # raise BisonSyntaxError(msg)
- # print >>sys.stderr, msg
- #elif hasattr(error, '__getitem__') and isinstance(error[0], int):
- # msg = 'Line %d: "%s" near "%s"' % error
- # if not self.interactive:
- # raise BisonSyntaxError(msg)
- # print >>sys.stderr, msg
- #else:
- if not self.interactive:
- raise
- if self.verbose:
- traceback.print_exc()
- print 'ERROR:', error
- def report_syntax_error(self, msg, yytext, first_line, first_col,
- last_line, last_col):
- yytext = yytext.replace('\n', '\\n')
- args = (first_line, first_col, last_line, last_col, msg, yytext)
- raise BisonSyntaxError('%d.%d-%d.%d: "%s" near "%s".' % args, args)
|