Lichen

lplc

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