Lichen

lplc

702:2e22fd27e941
2017-03-11 Paul Boddie Merged slightly differing default branches.
     1 #!/usr/bin/env python     2      3 """     4 Lichen Python-like compiler tool.     5      6 Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 VERSION = "0.1"    23     24 from errors import *    25 from os import environ, listdir, remove, rename    26 from os.path import abspath, exists, extsep, isdir, isfile, join, split    27 from pyparser import error    28 from subprocess import Popen, PIPE    29 from time import time    30 import importer, deducer, optimiser, generator, translator    31 import sys    32     33 libdirs = [    34     join(split(__file__)[0], "lib"),    35     split(__file__)[0],    36     "/usr/share/lichen/lib"    37     ]    38     39 def load_module(filename, module_name):    40     for libdir in libdirs:    41         path = join(libdir, filename)    42         if exists(path):    43             return i.load_from_file(path, module_name)    44     return None    45     46 def show_missing(missing):    47     missing = list(missing)    48     missing.sort()    49     for module_name, name in missing:    50         print >>sys.stderr, "Module %s references an unknown object: %s" % (module_name, name)    51     52 def show_syntax_error(exc):    53     print >>sys.stderr, "Syntax error at column %d on line %d in file %s:" % (exc.offset, exc.lineno, exc.filename)    54     print >>sys.stderr    55     print >>sys.stderr, exc.text.rstrip()    56     print >>sys.stderr, " " * exc.offset + "^"    57     58 def stopwatch(activity, now):    59     print >>sys.stderr, "%s took %.2f seconds" % (activity, time() - now)    60     return time()    61     62 def call(tokens, verbose=False):    63     out = not verbose and PIPE or None    64     cmd = Popen(tokens, stdout=out, stderr=out)    65     stdout, stderr = cmd.communicate()    66     return cmd.wait()    67     68 def start_arg_list(l, arg, needed):    69     70     """    71     Add to 'l' any value given as part of 'arg'. The 'needed' number of values    72     is provided in case no value is found.    73     74     Return 'l' and 'needed' decremented by 1 together in a tuple.    75     """    76     77     if arg.startswith("--"):    78         try:    79             prefix_length = arg.index("=") + 1    80         except ValueError:    81             prefix_length = len(arg)    82     else:    83         prefix_length = 2    84     85     s = arg[prefix_length:].strip()    86     if s:    87         l.append(s)    88         return l, needed - 1    89     else:    90         return l, needed    91     92 def getvalue(l, i):    93     if l and len(l) > i:    94         return l[i]    95     else:    96         return None    97     98 def remove_all(dirname):    99    100     "Remove 'dirname' and its contents."   101    102     for filename in listdir(dirname):   103         pathname = join(dirname, filename)   104         if isdir(pathname):   105             remove_all(pathname)   106         else:   107             remove(pathname)   108    109 # Main program.   110    111 if __name__ == "__main__":   112     basename = split(sys.argv[0])[-1]   113     args = sys.argv[1:]   114     path = libdirs   115    116     # Show help text if requested or if no arguments are given.   117    118     if "--help" in args or "-h" in args or "-?" in args or not args:   119         print >>sys.stderr, """\   120 Usage: %s [ <options> ] <filename>   121    122 Compile the program whose principal file is given in place of <filename>.   123 The following options may be specified:   124    125 -c          Only partially compile the program; do not build or link it   126 --compile   Equivalent to -c   127 -E          Ignore environment variables affecting the module search path   128 --no-env    Equivalent to -E   129 -g          Generate debugging information for the built executable   130 --debug     Equivalent to -g   131 -G          Remove superfluous sections of the built executable   132 --gc-sections Equivalent to -G   133 -P          Show the module search path   134 --show-path Equivalent to -P   135 -q          Silence messages produced when building an executable   136 --quiet     Equivalent to -q   137 -r          Reset (discard) cached information; inspect the whole program again   138 --reset     Equivalent to -r   139 -R          Reset (discard) all program details including translated code   140 --reset-all Equivalent to -R   141 -t          Silence timing messages   142 --no-timing Equivalent to -t   143 -tb         Provide a traceback for any internal errors (development only)   144 --traceback Equivalent to -tb   145 -v          Report compiler activities in a verbose fashion (development only)   146 --verbose   Equivalent to -v   147    148 Some options may be followed by values, either immediately after the option   149 (without any space between) or in the arguments that follow them:   150    151 -o          Indicate the output executable name   152 -W          Show warnings on the topics indicated   153    154 Currently, the following warnings are supported:   155    156 all         Show all possible warnings   157    158 args        Show invocations where a callable may be involved that cannot accept   159             the arguments provided   160    161 Control over program organisation can be exercised using the following options   162 with each requiring an input filename providing a particular form of   163 information:   164    165 --attr-codes        Attribute codes identifying named object attributes   166 --attr-locations    Attribute locations in objects   167 --param-codes       Parameter codes identifying named parameters   168 --param-locations   Parameter locations in signatures   169    170 A filename can immediately follow such an option, separated from the option by   171 an equals sign, or it can appear as the next argument after the option   172 (separated by a space).   173    174 The following informational options can be specified to produce output instead   175 of compiling a program:   176    177 --help      Show a summary of the command syntax and options   178 -h          Equivalent to --help   179 -?          Equivalent to --help   180 --version   Show version information for this tool   181 -V          Equivalent to --version   182 """ % basename   183         sys.exit(1)   184    185     # Show the version information if requested.   186    187     elif "--version" in args or "-V" in args:   188         print >>sys.stderr, """\   189 lplc %s   190 Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,   191               2014, 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk>   192 This program is free software; you may redistribute it under the terms of   193 the GNU General Public License version 3 or (at your option) a later version.   194 This program has absolutely no warranty.   195 """ % VERSION   196         sys.exit(1)   197    198     # Determine the options and arguments.   199    200     attrnames = []   201     attrlocations = []   202     debug = False   203     gc_sections = False   204     ignore_env = False   205     make = True   206     make_verbose = True   207     outputs = []   208     paramnames = []   209     paramlocations = []   210     reset = False   211     reset_all = False   212     timings = True   213     traceback = False   214     verbose = False   215     warnings = []   216    217     unrecognised = []   218     filenames = []   219    220     # Obtain program filenames by default.   221    222     l = filenames   223     needed = None   224    225     for arg in args:   226         if arg.startswith("--attr-codes"): l, needed = start_arg_list(attrnames, arg, 1)   227         elif arg.startswith("--attr-locations"): l, needed = start_arg_list(attrlocations, arg, 1)   228         elif arg in ("-c", "--compile"): make = False   229         elif arg in ("-E", "--no-env"): ignore_env = True   230         elif arg in ("-g", "--debug"): debug = True   231         elif arg in ("-G", "--gc-sections"): gc_sections = True   232         # "P" handled below.   233         elif arg.startswith("--param-codes"): l, needed = start_arg_list(paramnames, arg, 1)   234         elif arg.startswith("--param-locations"): l, needed = start_arg_list(paramlocations, arg, 1)   235         elif arg in ("-q", "--quiet"): make_verbose = False   236         elif arg in ("-r", "--reset"): reset = True   237         elif arg in ("-R", "--reset-all"): reset_all = True   238         elif arg in ("-t", "--no-timing"): timings = False   239         elif arg in ("-tb", "--traceback"): traceback = True   240         elif arg.startswith("-o"): l, needed = start_arg_list(outputs, arg, 1)   241         elif arg in ("-v", "--verbose"): verbose = True   242         elif arg.startswith("-W"): l, needed = start_arg_list(warnings, arg, 1)   243         elif arg.startswith("-"): unrecognised.append(arg)   244         else:   245             l.append(arg)   246             if needed:   247                 needed -= 1   248    249         if needed == 0:   250             l = filenames   251    252     # Report unrecognised options.   253    254     if unrecognised:   255         print >>sys.stderr, "The following options were not recognised: %s" % ", ".join(unrecognised)   256         sys.exit(1)   257    258     # Add extra components to the module search path from the environment.   259    260     if not ignore_env:   261         extra = environ.get("LICHENPATH")   262         if extra:   263             libdirs = extra.split(":") + libdirs   264    265     # Show the module search path if requested.   266    267     if "-P" in args or "--show-path" in args:   268         for libdir in libdirs:   269             print libdir   270         sys.exit(0)   271    272     # Obtain the program filename.   273    274     if len(filenames) != 1:   275         print >>sys.stderr, "One main program file must be specified."   276         sys.exit(1)   277    278     filename = abspath(filenames[0])   279    280     if not isfile(filename):   281         print >>sys.stderr, "Filename %s is not a valid input." % filenames[0]   282         sys.exit(1)   283    284     path.append(split(filename)[0])   285    286     # Obtain the output filename.   287    288     if outputs and not make:   289         print >>sys.stderr, "Output specified but building disabled."   290    291     output = outputs and outputs[0] or "_main"   292    293     # Define the output data directories.   294    295     datadir = "%s%s%s" % (output, extsep, "lplc") # _main.lplc by default   296     cache_dir = join(datadir, "_cache")   297     deduced_dir = join(datadir, "_deduced")   298     output_dir = join(datadir, "_output")   299     generated_dir = join(datadir, "_generated")   300    301     # Perform any full reset of the working data.   302    303     if reset_all:   304         remove_all(datadir)   305            306     # Load the program.   307    308     try:   309         if timings: now = time()   310    311         i = importer.Importer(path, cache_dir, verbose, warnings)   312         m = i.initialise(filename, reset)   313         success = i.finalise()   314    315         if timings: now = stopwatch("Inspection", now)   316    317         # Check for success, indicating missing references otherwise.   318    319         if not success:   320             show_missing(i.missing)   321             sys.exit(1)   322    323         d = deducer.Deducer(i, deduced_dir)   324         d.to_output()   325    326         if timings: now = stopwatch("Deduction", now)   327    328         o = optimiser.Optimiser(i, d, output_dir,   329                                 getvalue(attrnames, 0), getvalue(attrlocations, 0),   330                                 getvalue(paramnames, 0), getvalue(paramlocations, 0))   331         o.to_output()   332    333         if timings: now = stopwatch("Optimisation", now)   334    335         # Detect structure or signature changes demanding a reset of the   336         # generated sources.   337    338         reset = reset or o.need_reset()   339    340         g = generator.Generator(i, o, generated_dir)   341         g.to_output(reset, debug, gc_sections)   342    343         if timings: now = stopwatch("Generation", now)   344    345         t = translator.Translator(i, d, o, generated_dir)   346         t.to_output(reset, debug, gc_sections)   347    348         if timings: now = stopwatch("Translation", now)   349    350         # Compile the program unless otherwise indicated.   351    352         if make:   353             make_clean_cmd = ["make", "-C", generated_dir, "clean"]   354             make_cmd = make_clean_cmd[:-1]   355    356             retval = call(make_cmd, make_verbose)   357    358             if not retval:   359                 if timings: stopwatch("Compilation", now)   360             else:   361                 sys.exit(retval)   362    363             # Move the executable into the current directory.   364    365             rename(join(generated_dir, "main"), output)   366    367     # Report any errors.   368    369     except error.SyntaxError, exc:   370         show_syntax_error(exc)   371         if traceback:   372             raise   373         sys.exit(1)   374    375     except ProcessingError, exc:   376         print exc   377         if traceback:   378             raise   379         sys.exit(1)   380    381     else:   382         sys.exit(0)   383    384 # vim: tabstop=4 expandtab shiftwidth=4