Lichen

Annotated lplc

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