#!/usr/bin/env python
########################################################################
#
# File Name:            BisonGen.py
#
# Documentation:        http://docs.4suite.com/BisonGen/BisonGen.py.html
#
"""
A Bison input file generator
WWW: http://4suite.org/BisonGen        e-mail: support@4suite.org

Copyright (c) 2001 Fourthought Inc., USA.   All Rights Reserved.
See http://4suite.org/COPYRIGHT for license and copyright information
"""

import os, sys, stat, getopt

### Main Function ###

def Generate(filename, options):
    from Reader import BisonGenReader
    import Grammar, Lexer, Python, C, Ebnf

    reader = BisonGenReader()
    parser = reader.parse(filename)
    # Command-line takes precedence over those specified in source file
    parser.options.update(options)

    bison = Grammar.Generate(parser)
    lexer = Lexer.Generate(parser)

    generated = []
    # 2005-03-20: disabled until it catches up with the C version
    # Pure Python parser 
    #if parser.options.get('mode', 'python') == 'python':
    #    file = '%s.py' % parser.name
    #    filename = Newer(parser, file)
    #    if filename:
    #        generated.append(filename)
    #        if not options['quiet']:
    #            print "Generate parser %s" % filename
    #        Python.Generate(parser, bison, lexer, filename)

    # C based parser
    if parser.options.get('mode', 'c') == 'c':
        file = '%s.c' % parser.name
        filename = Newer(parser, file)
        if filename:
            generated.append(filename)
            if not options['quiet']:
                print "Generate parser %s" % filename
            C.Generate(parser, bison, lexer, filename)

    if options.get('ebnf'):
        file = '%s.ebnf' % parser.name
        filename = Newer(parser, file)
        if filename:
            generated.append(filename)
            if not options['quiet']:
                print "Generate EBNF %s" % filename
            Ebnf.Generate(parser, filename)
    return generated


def Newer(parser, dest):
    base_dir = os.path.dirname(parser.options['input'])
    output_dir = parser.options.get('output', base_dir)
    filename = os.path.join(output_dir, dest)
    if not os.path.exists(filename):
        return filename
    if parser.last_modified > os.stat(filename)[stat.ST_MTIME]:
        return filename
    if parser.options.get('force'):
        return filename
    if not parser.options['quiet']:
        print 'Skipping %s (up-to-date)' % filename
    return None


def Run(argv):
    prog_name = os.path.split(argv[0])[1]
    short_opts = 'hmto:efdvq'

    long_opts = ['help',
                 'mapping',
                 'tuples',
                 'output-dir=',
                 'ebnf',
                 'force',
                 'debug',
                 'verbose',
                 'quiet',
                 'mode=',
                 ]

    usage = """Usage: %s [options] <XML input file>
Options:
  -h, --help                Print this message and exit
  -q, --quiet               Do not display generated filenames
  -o, --output-dir <dir>    Directory for output files
  -e, --ebnf                Write an extra output file containing the
                              EBNF structure of the grammar
  -f, --force               Force creation of parser (ignore timestamps)
  -v, --verbose             Write an extra output file describing parser
                              states and what is done at each
  -d, --debug               Generate parser with debugging enabled
  -m, --mapping             Use mapping for parse tree
  -t, --tuples              Use tuples for parse tree
      --mode <c|python>     Only generate parser for given mode
""" % (prog_name)

    command_line_error = 0
    bad_options = []
    options = {'quiet' : 0}

    finished = 0
    args = argv[1:]

    while not finished:
        try:
            optlist, args = getopt.getopt(args, short_opts, long_opts)
        except getopt.error, data:
            bad = str(data).split()[1]
            bad_options.append(bad)
            del args[0]
            command_line_error = 1
        else:
            finished = 1

    display_usage = 0
    for opt,value in optlist:
        if opt == '-e' or opt == '--ebnf':
            options['ebnf'] = 1
        elif opt == '-h' or opt == '--help':
            display_usage = 1
        elif opt == '-m' or opt == '--mapping':
            options['mapping'] = 1
        elif opt == '-t' or opt == '--tuples':
            options['tuple'] = 1
        elif opt == '-o' or opt == '--output-dir':
            options['output'] = value
        elif opt in ['-v', '--verbose']:
            options['verbose'] = 1
        elif opt in ['-d', '--debug']:
            options['debug'] = 1
        elif opt in ['-f', '--force']:
            options['force'] = 1
        elif opt in ['-q', '--quiet']:
            options['quiet'] = 1
        elif opt == '--mode':
            options['mode'] = value.strip().lower()
    spec_file = ''
    if args:
        spec_file = args[0]
        if not os.path.exists(spec_file):
            import errno
            print "%s: %s '%s'" %(prog_name, os.strerror(errno.ENOENT), spec_file)
            spec_file = ''
            command_line_error = 1
    else:
        command_line_error = 1

    if command_line_error or display_usage:
        for op in bad_options:
            print "%s: Unrecognized option '%s'" %(prog_name,op)
        if not spec_file:
            print "%s: No input files" %(prog_name)
            display_usage = 1
        if display_usage:
            print usage
        sys.exit(command_line_error)

    if not options.has_key('output'):
        options['output'] = os.path.dirname(spec_file)

    options['input'] = spec_file
    return Generate(spec_file, options)

if __name__ == '__main__':
    Run(sys.argv)
