Lichen

lplc

665:932a6b249199
2017-03-07 Paul Boddie Fixed argument number tests for empty parameter lists. Fixed various exception instantiations.
     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, prefix, needed):    69     70     """    71     Add to 'l' any value given as part of 'arg' having the given option    72     'prefix'. The 'needed' number of values is provided in case no value is    73     found.    74     75     Return 'l' and 'needed' decremented by 1 together in a tuple.    76     """    77     78     s = arg[len(prefix):].strip()    79     if s:    80         l.append(s)    81         return l, needed - 1    82     else:    83         return l, needed    84     85 def getvalue(l, i):    86     if l and len(l) > i:    87         return l[i]    88     else:    89         return None    90     91 def remove_all(dirname):    92     93     "Remove 'dirname' and its contents."    94     95     for filename in listdir(dirname):    96         pathname = join(dirname, filename)    97         if isdir(pathname):    98             remove_all(pathname)    99         else:   100             remove(pathname)   101    102 # Main program.   103    104 if __name__ == "__main__":   105     basename = split(sys.argv[0])[-1]   106     args = sys.argv[1:]   107     path = libdirs   108    109     # Show help text if requested or if no arguments are given.   110    111     if "--help" in args or "-h" in args or "-?" in args or not args:   112         print >>sys.stderr, """\   113 Usage: %s [ <options> ] <filename>   114    115 Compile the program whose principal file is given in place of <filename>.   116 The following options may be specified:   117    118 -c          Only partially compile the program; do not build or link it   119 --compile   Equivalent to -c   120 -E          Ignore environment variables affecting the module search path   121 --no-env    Equivalent to -E   122 -g          Generate debugging information for the built executable   123 --debug     Equivalent to -g   124 -G          Remove superfluous sections of the built executable   125 --gc-sections Equivalent to -G   126 -P          Show the module search path   127 --show-path Equivalent to -P   128 -q          Silence messages produced when building an executable   129 --quiet     Equivalent to -q   130 -r          Reset (discard) cached information; inspect the whole program again   131 --reset     Equivalent to -r   132 -R          Reset (discard) all program details including translated code   133 --reset-all Equivalent to -R   134 -t          Silence timing messages   135 --no-timing Equivalent to -t   136 -tb         Provide a traceback for any internal errors (development only)   137 --traceback Equivalent to -tb   138 -v          Report compiler activities in a verbose fashion (development only)   139 --verbose   Equivalent to -v   140    141 Some options may be followed by values, either immediately after the option   142 (without any space between) or in the arguments that follow them:   143    144 -o          Indicate the output executable name   145 -W          Show warnings on the topics indicated   146    147 Currently, the following warnings are supported:   148    149 all         Show all possible warnings   150    151 args        Show invocations where a callable may be involved that cannot accept   152             the arguments provided   153    154 Control over program organisation can be exercised using the following options   155 with each requiring an input filename providing a particular form of   156 information:   157    158 --attr-codes        Attribute codes identifying named object attributes   159 --attr-locations    Attribute locations in objects   160 --param-codes       Parameter codes identifying named parameters   161 --param-locations   Parameter locations in signatures   162    163 The following informational options can be specified to produce output instead   164 of compiling a program:   165    166 --help      Show a summary of the command syntax and options   167 -h          Equivalent to --help   168 -?          Equivalent to --help   169 --version   Show version information for this tool   170 -V          Equivalent to --version   171 """ % basename   172         sys.exit(1)   173    174     # Show the version information if requested.   175    176     elif "--version" in args or "-V" in args:   177         print >>sys.stderr, """\   178 lplc %s   179 Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,   180               2014, 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk>   181 This program is free software; you may redistribute it under the terms of   182 the GNU General Public License version 3 or (at your option) a later version.   183 This program has absolutely no warranty.   184 """ % VERSION   185         sys.exit(1)   186    187     # Determine the options and arguments.   188    189     attrnames = []   190     attrlocations = []   191     debug = False   192     gc_sections = False   193     ignore_env = False   194     make = True   195     make_verbose = True   196     outputs = []   197     paramnames = []   198     paramlocations = []   199     reset = False   200     reset_all = False   201     timings = True   202     traceback = False   203     verbose = False   204     warnings = []   205    206     unrecognised = []   207     filenames = []   208    209     # Obtain program filenames by default.   210    211     l = filenames   212     needed = None   213    214     for arg in args:   215         if arg == "--attr-codes": l = attrnames; needed = 1   216         elif arg == "--attr-locations": l = attrlocations; needed = 1   217         elif arg in ("-c", "--compile"): make = False   218         elif arg in ("-E", "--no-env"): ignore_env = True   219         elif arg in ("-g", "--debug"): debug = True   220         elif arg in ("-G", "--gc-sections"): gc_sections = True   221         # "P" handled below.   222         elif arg == "--param-codes": l = paramnames; needed = 1   223         elif arg == "--param-locations": l = paramlocations; needed = 1   224         elif arg in ("-q", "--quiet"): make_verbose = False   225         elif arg in ("-r", "--reset"): reset = True   226         elif arg in ("-R", "--reset-all"): reset_all = True   227         elif arg in ("-t", "--no-timing"): timings = False   228         elif arg in ("-tb", "--traceback"): traceback = True   229         elif arg.startswith("-o"): l, needed = start_arg_list(outputs, arg, "-o", 1)   230         elif arg in ("-v", "--verbose"): verbose = True   231         elif arg.startswith("-W"): l, needed = start_arg_list(warnings, arg, "-W", 1)   232         elif arg.startswith("-"): unrecognised.append(arg)   233         else:   234             l.append(arg)   235             if needed:   236                 needed -= 1   237    238         if needed == 0:   239             l = filenames   240    241     # Report unrecognised options.   242    243     if unrecognised:   244         print >>sys.stderr, "The following options were not recognised: %s" % ", ".join(unrecognised)   245         sys.exit(1)   246    247     # Add extra components to the module search path from the environment.   248    249     if not ignore_env:   250         extra = environ.get("LICHENPATH")   251         if extra:   252             libdirs = extra.split(":") + libdirs   253    254     # Show the module search path if requested.   255    256     if "-P" in args or "--show-path" in args:   257         for libdir in libdirs:   258             print libdir   259         sys.exit(0)   260    261     # Obtain the program filename.   262    263     if len(filenames) != 1:   264         print >>sys.stderr, "One main program file must be specified."   265         sys.exit(1)   266    267     filename = abspath(filenames[0])   268    269     if not isfile(filename):   270         print >>sys.stderr, "Filename %s is not a valid input." % filenames[0]   271         sys.exit(1)   272    273     path.append(split(filename)[0])   274    275     # Obtain the output filename.   276    277     if outputs and not make:   278         print >>sys.stderr, "Output specified but building disabled."   279    280     output = outputs and outputs[0] or "_main"   281    282     # Define the output data directories.   283    284     datadir = "%s%s%s" % (output, extsep, "lplc") # _main.lplc by default   285     cache_dir = join(datadir, "_cache")   286     deduced_dir = join(datadir, "_deduced")   287     output_dir = join(datadir, "_output")   288     generated_dir = join(datadir, "_generated")   289    290     # Perform any full reset of the working data.   291    292     if reset_all:   293         remove_all(datadir)   294            295     # Load the program.   296    297     try:   298         if timings: now = time()   299    300         i = importer.Importer(path, cache_dir, verbose, warnings)   301         m = i.initialise(filename, reset)   302         success = i.finalise()   303    304         if timings: now = stopwatch("Inspection", now)   305    306         # Check for success, indicating missing references otherwise.   307    308         if not success:   309             show_missing(i.missing)   310             sys.exit(1)   311    312         d = deducer.Deducer(i, deduced_dir)   313         d.to_output()   314    315         if timings: now = stopwatch("Deduction", now)   316    317         o = optimiser.Optimiser(i, d, output_dir,   318                                 getvalue(attrnames, 0), getvalue(attrlocations, 0),   319                                 getvalue(paramnames, 0), getvalue(paramlocations, 0))   320         o.to_output()   321    322         if timings: now = stopwatch("Optimisation", now)   323    324         # Detect structure or signature changes demanding a reset of the   325         # generated sources.   326    327         reset = reset or o.need_reset()   328    329         g = generator.Generator(i, o, generated_dir)   330         g.to_output(reset, debug, gc_sections)   331    332         if timings: now = stopwatch("Generation", now)   333    334         t = translator.Translator(i, d, o, generated_dir)   335         t.to_output(reset, debug, gc_sections)   336    337         if timings: now = stopwatch("Translation", now)   338    339         # Compile the program unless otherwise indicated.   340    341         if make:   342             make_clean_cmd = ["make", "-C", generated_dir, "clean"]   343             make_cmd = make_clean_cmd[:-1]   344    345             retval = call(make_cmd, make_verbose)   346    347             if not retval:   348                 if timings: stopwatch("Compilation", now)   349             else:   350                 sys.exit(retval)   351    352             # Move the executable into the current directory.   353    354             rename(join(generated_dir, "main"), output)   355    356     # Report any errors.   357    358     except error.SyntaxError, exc:   359         show_syntax_error(exc)   360         if traceback:   361             raise   362         sys.exit(1)   363    364     except ProcessingError, exc:   365         print exc   366         if traceback:   367             raise   368         sys.exit(1)   369    370     else:   371         sys.exit(0)   372    373 # vim: tabstop=4 expandtab shiftwidth=4