Lichen

lplc

918:e6da95e33e43
2021-06-22 Paul Boddie Added the ability to specify make options when building the tests.
     1 #!/usr/bin/env python     2      3 """     4 Lichen Python-like compiler tool.     5      6 Copyright (C) 2016-2018, 2021 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 -j          Number of processes to be used when compiling   155 -o          Indicate the output executable name   156 -W          Show warnings on the topics indicated   157    158 Currently, the following warnings are supported:   159    160 all         Show all possible warnings   161    162 args        Show invocations where a callable may be involved that cannot accept   163             the arguments provided   164    165 Control over program organisation can be exercised using the following options   166 with each requiring an input filename providing a particular form of   167 information:   168    169 --attr-codes        Attribute codes identifying named object attributes   170 --attr-locations    Attribute locations in objects   171 --param-codes       Parameter codes identifying named parameters   172 --param-locations   Parameter locations in signatures   173    174 A filename can immediately follow such an option, separated from the option by   175 an equals sign, or it can appear as the next argument after the option   176 (separated by a space).   177    178 The following informational options can be specified to produce output instead   179 of compiling a program:   180    181 --help      Show a summary of the command syntax and options   182 -h          Equivalent to --help   183 -?          Equivalent to --help   184 --version   Show version information for this tool   185 -V          Equivalent to --version   186 """ % basename   187         sys.exit(1)   188    189     # Show the version information if requested.   190    191     elif "--version" in args or "-V" in args:   192         print >>sys.stderr, """\   193 lplc %s   194 Copyright (C) 2006-2018, 2021 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_processes = []   210     make_verbose = True   211     outputs = []   212     paramnames = []   213     paramlocations = []   214     reset = False   215     reset_all = False   216     timings = True   217     traceback = False   218     verbose = False   219     warnings = []   220    221     unrecognised = []   222     filenames = []   223    224     # Obtain program filenames by default.   225    226     l = filenames   227     needed = None   228    229     for arg in args:   230         if arg.startswith("--attr-codes"): l, needed = start_arg_list(attrnames, arg, 1)   231         elif arg.startswith("--attr-locations"): l, needed = start_arg_list(attrlocations, arg, 1)   232         elif arg in ("-c", "--compile"): make = False   233         elif arg in ("-E", "--no-env"): ignore_env = True   234         elif arg in ("-g", "--debug"): debug = True   235         elif arg in ("-G", "--gc-sections"): gc_sections = True   236         elif arg.startswith("-j"): l, needed = start_arg_list(make_processes, arg, 1)   237         # "P" handled below.   238         elif arg.startswith("--param-codes"): l, needed = start_arg_list(paramnames, arg, 1)   239         elif arg.startswith("--param-locations"): l, needed = start_arg_list(paramlocations, arg, 1)   240         elif arg in ("-q", "--quiet"): make_verbose = False   241         elif arg in ("-r", "--reset"): reset = True   242         elif arg in ("-R", "--reset-all"): reset_all = True   243         elif arg in ("-t", "--no-timing"): timings = False   244         elif arg in ("-tb", "--traceback"): traceback = True   245         elif arg.startswith("-o"): l, needed = start_arg_list(outputs, arg, 1)   246         elif arg in ("-v", "--verbose"): verbose = True   247         elif arg.startswith("-W"): l, needed = start_arg_list(warnings, arg, 1)   248         elif arg.startswith("-"): unrecognised.append(arg)   249         else:   250             l.append(arg)   251             if needed:   252                 needed -= 1   253    254         if needed == 0:   255             l = filenames   256    257     # Report unrecognised options.   258    259     if unrecognised:   260         print >>sys.stderr, "The following options were not recognised: %s" % ", ".join(unrecognised)   261         sys.exit(1)   262    263     # Add extra components to the module search path from the environment.   264    265     if not ignore_env:   266         extra = environ.get("LICHENPATH")   267         if extra:   268             libdirs = extra.split(":") + libdirs   269    270     # Show the module search path if requested.   271    272     if "-P" in args or "--show-path" in args:   273         for libdir in libdirs:   274             print libdir   275         sys.exit(0)   276    277     # Obtain the program filename.   278    279     if len(filenames) != 1:   280         print >>sys.stderr, "One main program file must be specified."   281         sys.exit(1)   282    283     filename = abspath(filenames[0])   284    285     if not isfile(filename):   286         print >>sys.stderr, "Filename %s is not a valid input." % filenames[0]   287         sys.exit(1)   288    289     path.append(split(filename)[0])   290    291     # Obtain the output filename.   292    293     if outputs and not make:   294         print >>sys.stderr, "Output specified but building disabled."   295    296     output = outputs and outputs[0] or "_main"   297    298     # Define the output data directories.   299    300     datadir = "%s%s%s" % (output, extsep, "lplc") # _main.lplc by default   301     cache_dir = join(datadir, "_cache")   302     deduced_dir = join(datadir, "_deduced")   303     output_dir = join(datadir, "_output")   304     generated_dir = join(datadir, "_generated")   305    306     # Perform any full reset of the working data.   307    308     if reset_all:   309         remove_all(datadir)   310            311     # Load the program.   312    313     try:   314         if timings: now = time()   315    316         i = importer.Importer(path, cache_dir, verbose, warnings)   317         m = i.initialise(filename, reset)   318         success = i.finalise()   319    320         if timings: now = stopwatch("Inspection", now)   321    322         # Check for success, indicating missing references otherwise.   323    324         if not success:   325             show_missing(i.missing)   326             sys.exit(1)   327    328         d = deducer.Deducer(i, deduced_dir)   329         d.to_output()   330    331         if timings: now = stopwatch("Deduction", now)   332    333         o = optimiser.Optimiser(i, d, output_dir,   334                                 getvalue(attrnames, 0), getvalue(attrlocations, 0),   335                                 getvalue(paramnames, 0), getvalue(paramlocations, 0))   336         o.to_output()   337    338         if timings: now = stopwatch("Optimisation", now)   339    340         # Detect structure or signature changes demanding a reset of the   341         # generated sources.   342    343         reset = reset or o.need_reset()   344    345         g = generator.Generator(i, o, generated_dir)   346         g.to_output(reset, debug, gc_sections)   347    348         if timings: now = stopwatch("Generation", now)   349    350         t = translator.Translator(i, d, o, generated_dir)   351         t.to_output(reset, debug, gc_sections)   352    353         if timings: now = stopwatch("Translation", now)   354    355         # Compile the program unless otherwise indicated.   356    357         if make:   358             processes = make_processes and ["-j"] + make_processes or []   359             make_clean_cmd = ["make", "-C", generated_dir] + processes + ["clean"]   360             make_cmd = make_clean_cmd[:-1]   361    362             retval = call(make_cmd, make_verbose)   363    364             if not retval:   365                 if timings: stopwatch("Compilation", now)   366             else:   367                 sys.exit(retval)   368    369             # Move the executable into the current directory.   370    371             rename(join(generated_dir, "main"), output)   372    373     # Report any errors.   374    375     except error.SyntaxError, exc:   376         show_syntax_error(exc)   377         if traceback:   378             raise   379         sys.exit(1)   380    381     except ProcessingError, exc:   382         print exc   383         if traceback:   384             raise   385         sys.exit(1)   386    387     else:   388         sys.exit(0)   389    390 # vim: tabstop=4 expandtab shiftwidth=4