Lichen

Annotated lplc

587:87f11937a8e8
2017-02-14 Paul Boddie Record the context for accesses that are immediately invoked so that it may be used by the invocation operation. method-wrapper-for-context
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@562 3
"""
paul@562 4
Lichen Python-like compiler tool.
paul@562 5
paul@562 6
Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>
paul@562 7
paul@562 8
This program is free software; you can redistribute it and/or modify it under
paul@562 9
the terms of the GNU General Public License as published by the Free Software
paul@562 10
Foundation; either version 3 of the License, or (at your option) any later
paul@562 11
version.
paul@562 12
paul@562 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@562 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@562 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@562 16
details.
paul@562 17
paul@562 18
You should have received a copy of the GNU General Public License along with
paul@562 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@562 20
"""
paul@562 21
paul@562 22
VERSION = "0.1"
paul@562 23
paul@0 24
from errors import *
paul@567 25
from os import environ, rename
paul@562 26
from os.path import abspath, exists, isfile, join, split
paul@445 27
from pyparser import error
paul@442 28
from subprocess import Popen, PIPE
paul@0 29
from time import time
paul@126 30
import importer, deducer, optimiser, generator, translator
paul@0 31
import sys
paul@0 32
paul@0 33
libdirs = [
paul@0 34
    join(split(__file__)[0], "lib"),
paul@567 35
    split(__file__)[0],
paul@0 36
    "/usr/share/lichen/lib"
paul@0 37
    ]
paul@0 38
paul@0 39
def load_module(filename, module_name):
paul@0 40
    for libdir in libdirs:
paul@0 41
        path = join(libdir, filename)
paul@0 42
        if exists(path):
paul@0 43
            return i.load_from_file(path, module_name)
paul@0 44
    return None
paul@0 45
paul@41 46
def show_missing(missing):
paul@41 47
    missing = list(missing)
paul@41 48
    missing.sort()
paul@41 49
    for module_name, name in missing:
paul@41 50
        print >>sys.stderr, "Module %s references an unknown object: %s" % (module_name, name)
paul@41 51
paul@445 52
def show_syntax_error(exc):
paul@445 53
    print >>sys.stderr, "Syntax error at column %d on line %d in file %s:" % (exc.offset, exc.lineno, exc.filename)
paul@445 54
    print >>sys.stderr
paul@445 55
    print >>sys.stderr, exc.text.rstrip()
paul@445 56
    print >>sys.stderr, " " * exc.offset + "^"
paul@445 57
paul@0 58
def stopwatch(activity, now):
paul@0 59
    print >>sys.stderr, "%s took %.2f seconds" % (activity, time() - now)
paul@0 60
    return time()
paul@0 61
paul@442 62
def call(tokens, verbose=False):
paul@442 63
    out = not verbose and PIPE or None
paul@442 64
    cmd = Popen(tokens, stdout=out, stderr=out)
paul@442 65
    stdout, stderr = cmd.communicate()
paul@442 66
    return cmd.wait()
paul@442 67
paul@558 68
def start_arg_list(l, arg, prefix, needed):
paul@558 69
paul@558 70
    """
paul@558 71
    Add to 'l' any value given as part of 'arg' having the given option
paul@558 72
    'prefix'. The 'needed' number of values is provided in case no value is
paul@558 73
    found.
paul@558 74
paul@558 75
    Return 'l' and 'needed' decremented by 1 together in a tuple.
paul@558 76
    """
paul@558 77
paul@558 78
    s = arg[len(prefix):].strip()
paul@558 79
    if s:
paul@558 80
        l.append(s)
paul@558 81
        return l, needed - 1
paul@558 82
    else:
paul@558 83
        return l, needed
paul@558 84
paul@0 85
# Main program.
paul@0 86
paul@0 87
if __name__ == "__main__":
paul@558 88
    basename = split(sys.argv[0])[-1]
paul@442 89
    args = sys.argv[1:]
paul@525 90
    path = libdirs
paul@0 91
paul@562 92
    # Show help text if requested or if no arguments are given.
paul@562 93
paul@567 94
    if "--help" in args or "-h" in args or "-?" in args or not args:
paul@558 95
        print >>sys.stderr, """\
paul@558 96
Usage: %s [ <options> ] <filename>
paul@558 97
paul@558 98
Compile the program whose principal file is given in place of <filename>.
paul@558 99
The following options may be specified:
paul@558 100
paul@558 101
-c   Only partially compile the program; do not attempt to build or link it
paul@567 102
-E   Ignore environment variables affecting the module search path
paul@558 103
-g   Generate debugging information for the built executable
paul@567 104
-P   Show the module search path
paul@558 105
-q   Silence messages produced when building an executable
paul@558 106
-r   Reset (discard) cached program information; inspect the whole program again
paul@562 107
-t   Silence timing messages
paul@558 108
-tb  Provide a traceback for any internal errors (development only)
paul@558 109
-v   Report compiler activities in a verbose fashion (development only)
paul@558 110
paul@558 111
Some options may be followed by values, either immediately after the option
paul@558 112
(without any space between) or in the arguments that follow them:
paul@558 113
paul@558 114
-o   Indicate the output executable name
paul@558 115
-W   Show warnings on the topics indicated
paul@558 116
paul@558 117
Currently, the following warnings are supported:
paul@558 118
paul@562 119
all         Show all possible warnings
paul@562 120
paul@562 121
args        Show invocations where a callable may be involved that cannot accept
paul@562 122
            the arguments provided
paul@562 123
paul@562 124
The following informational options can be specified to produce output instead
paul@562 125
of compiling a program:
paul@558 126
paul@562 127
--help      Show a summary of the command syntax and options
paul@567 128
-h          Equivalent to --help
paul@567 129
-?          Equivalent to --help
paul@562 130
--version   Show version information for this tool
paul@567 131
-V          Equivalent to --version
paul@558 132
""" % basename
paul@558 133
        sys.exit(1)
paul@558 134
paul@562 135
    # Show the version information if requested.
paul@562 136
paul@567 137
    elif "--version" in args or "-V" in args:
paul@562 138
        print >>sys.stderr, """\
paul@562 139
lplc %s
paul@562 140
Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
paul@562 141
              2014, 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk>
paul@562 142
This program is free software; you may redistribute it under the terms of
paul@562 143
the GNU General Public License version 3 or (at your option) a later version.
paul@562 144
This program has absolutely no warranty.
paul@562 145
""" % VERSION
paul@562 146
        sys.exit(1)
paul@562 147
paul@442 148
    # Determine the options and arguments.
paul@442 149
paul@442 150
    debug = False
paul@567 151
    ignore_env = False
paul@442 152
    make = True
paul@442 153
    make_verbose = True
paul@525 154
    reset = False
paul@562 155
    timings = True
paul@474 156
    traceback = False
paul@525 157
    verbose = False
paul@558 158
    warnings = []
paul@442 159
paul@442 160
    filenames = []
paul@442 161
    outputs = []
paul@442 162
paul@442 163
    # Obtain program filenames by default.
paul@442 164
paul@442 165
    l = filenames
paul@558 166
    needed = None
paul@442 167
paul@442 168
    for arg in args:
paul@525 169
        if arg == "-c": make = False
paul@567 170
        elif arg == "-E": ignore_env = True
paul@442 171
        elif arg == "-g": debug = True
paul@442 172
        elif arg == "-q": make_verbose = False
paul@525 173
        elif arg == "-r": reset = True
paul@562 174
        elif arg == "-t": timings = False
paul@474 175
        elif arg == "-tb": traceback = True
paul@558 176
        elif arg.startswith("-o"): l, needed = start_arg_list(outputs, arg, "-o", 1)
paul@525 177
        elif arg == "-v": verbose = True
paul@558 178
        elif arg.startswith("-W"): l, needed = start_arg_list(warnings, arg, "-W", 1)
paul@442 179
        else:
paul@442 180
            l.append(arg)
paul@558 181
            if needed:
paul@558 182
                needed -= 1
paul@442 183
paul@558 184
        if needed == 0:
paul@558 185
            l = filenames
paul@442 186
paul@567 187
    # Add extra components to the module search path from the environment.
paul@567 188
paul@567 189
    if not ignore_env:
paul@567 190
        extra = environ.get("LICHENPATH")
paul@567 191
        if extra:
paul@567 192
            libdirs = extra.split(":") + libdirs
paul@567 193
paul@567 194
    # Show the module search path if requested.
paul@567 195
paul@567 196
    if "-P" in args:
paul@567 197
        for libdir in libdirs:
paul@567 198
            print libdir
paul@567 199
        sys.exit(0)
paul@567 200
paul@442 201
    # Obtain the program filename.
paul@442 202
paul@558 203
    if len(filenames) != 1:
paul@558 204
        print >>sys.stderr, "One main program file must be specified."
paul@442 205
        sys.exit(1)
paul@442 206
paul@442 207
    filename = abspath(filenames[0])
paul@562 208
paul@562 209
    if not isfile(filename):
paul@562 210
        print >>sys.stderr, "Filename %s is not a valid input." % filenames[0]
paul@562 211
        sys.exit(1)
paul@562 212
paul@0 213
    path.append(split(filename)[0])
paul@0 214
paul@442 215
    # Obtain the output filename.
paul@442 216
paul@558 217
    if outputs and not make:
paul@558 218
        print >>sys.stderr, "Output specified but building disabled."
paul@558 219
paul@449 220
    output = outputs and outputs[0] or "_main"
paul@442 221
paul@442 222
    # Define the output data directories.
paul@442 223
paul@442 224
    datadir = "_lplc"
paul@442 225
    cache_dir = join(datadir, "_cache")
paul@442 226
    deduced_dir = join(datadir, "_deduced")
paul@442 227
    output_dir = join(datadir, "_output")
paul@442 228
    generated_dir = join(datadir, "_generated")
paul@0 229
paul@0 230
    # Load the program.
paul@0 231
paul@0 232
    try:
paul@562 233
        if timings: now = time()
paul@0 234
paul@558 235
        i = importer.Importer(path, cache_dir, verbose, warnings)
paul@0 236
        m = i.initialise(filename, reset)
paul@41 237
        success = i.finalise()
paul@0 238
paul@562 239
        if timings: now = stopwatch("Inspection", now)
paul@0 240
paul@41 241
        # Check for success, indicating missing references otherwise.
paul@41 242
paul@41 243
        if not success:
paul@41 244
            show_missing(i.missing)
paul@275 245
            sys.exit(1)
paul@41 246
paul@442 247
        d = deducer.Deducer(i, deduced_dir)
paul@44 248
        d.to_output()
paul@44 249
paul@562 250
        if timings: now = stopwatch("Deduction", now)
paul@44 251
paul@442 252
        o = optimiser.Optimiser(i, d, output_dir)
paul@92 253
        o.to_output()
paul@92 254
paul@562 255
        if timings: now = stopwatch("Optimisation", now)
paul@92 256
paul@442 257
        g = generator.Generator(i, o, generated_dir)
paul@183 258
        g.to_output(debug)
paul@126 259
paul@562 260
        if timings: now = stopwatch("Generation", now)
paul@126 261
paul@442 262
        t = translator.Translator(i, d, o, generated_dir)
paul@113 263
        t.to_output()
paul@113 264
paul@562 265
        if timings: now = stopwatch("Translation", now)
paul@442 266
paul@442 267
        # Compile the program unless otherwise indicated.
paul@442 268
paul@442 269
        if make:
paul@442 270
            make_clean_cmd = ["make", "-C", generated_dir, "clean"]
paul@442 271
            make_cmd = make_clean_cmd[:-1]
paul@442 272
paul@442 273
            retval = call(make_clean_cmd, make_verbose)
paul@442 274
            if not retval:
paul@442 275
                retval = call(make_cmd, make_verbose)
paul@442 276
paul@442 277
            if not retval:
paul@562 278
                if timings: stopwatch("Compilation", now)
paul@442 279
            else:
paul@442 280
                sys.exit(retval)
paul@442 281
paul@442 282
            # Move the executable into the current directory.
paul@442 283
paul@442 284
            rename(join(generated_dir, "main"), output)
paul@113 285
paul@0 286
    # Report any errors.
paul@0 287
paul@445 288
    except error.SyntaxError, exc:
paul@445 289
        show_syntax_error(exc)
paul@474 290
        if traceback:
paul@445 291
            raise
paul@445 292
        sys.exit(1)
paul@445 293
paul@0 294
    except ProcessingError, exc:
paul@0 295
        print exc
paul@474 296
        if traceback:
paul@0 297
            raise
paul@275 298
        sys.exit(1)
paul@0 299
paul@0 300
    else:
paul@275 301
        sys.exit(0)
paul@0 302
paul@0 303
# vim: tabstop=4 expandtab shiftwidth=4