Lichen

Annotated lplc

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