Lichen

lplc

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