1 #!/usr/bin/env python 2 3 from errors import * 4 from os import rename 5 from os.path import abspath, exists, join, split 6 from pyparser import error 7 from subprocess import Popen, PIPE 8 from time import time 9 import importer, deducer, optimiser, generator, translator 10 import sys 11 12 libdirs = [ 13 join(split(__file__)[0], "lib"), 14 "/usr/share/lichen/lib" 15 ] 16 17 def load_module(filename, module_name): 18 for libdir in libdirs: 19 path = join(libdir, filename) 20 if exists(path): 21 return i.load_from_file(path, module_name) 22 return None 23 24 def show_missing(missing): 25 missing = list(missing) 26 missing.sort() 27 for module_name, name in missing: 28 print >>sys.stderr, "Module %s references an unknown object: %s" % (module_name, name) 29 30 def show_syntax_error(exc): 31 print >>sys.stderr, "Syntax error at column %d on line %d in file %s:" % (exc.offset, exc.lineno, exc.filename) 32 print >>sys.stderr 33 print >>sys.stderr, exc.text.rstrip() 34 print >>sys.stderr, " " * exc.offset + "^" 35 36 def stopwatch(activity, now): 37 print >>sys.stderr, "%s took %.2f seconds" % (activity, time() - now) 38 return time() 39 40 def call(tokens, verbose=False): 41 out = not verbose and PIPE or None 42 cmd = Popen(tokens, stdout=out, stderr=out) 43 stdout, stderr = cmd.communicate() 44 return cmd.wait() 45 46 def start_arg_list(l, arg, prefix, needed): 47 48 """ 49 Add to 'l' any value given as part of 'arg' having the given option 50 'prefix'. The 'needed' number of values is provided in case no value is 51 found. 52 53 Return 'l' and 'needed' decremented by 1 together in a tuple. 54 """ 55 56 s = arg[len(prefix):].strip() 57 if s: 58 l.append(s) 59 return l, needed - 1 60 else: 61 return l, needed 62 63 # Main program. 64 65 if __name__ == "__main__": 66 basename = split(sys.argv[0])[-1] 67 args = sys.argv[1:] 68 path = libdirs 69 70 if "--help" in args or not args: 71 print >>sys.stderr, """\ 72 Usage: %s [ <options> ] <filename> 73 74 Compile the program whose principal file is given in place of <filename>. 75 The following options may be specified: 76 77 -c Only partially compile the program; do not attempt to build or link it 78 -g Generate debugging information for the built executable 79 -q Silence messages produced when building an executable 80 -r Reset (discard) cached program information; inspect the whole program again 81 -tb Provide a traceback for any internal errors (development only) 82 -v Report compiler activities in a verbose fashion (development only) 83 84 Some options may be followed by values, either immediately after the option 85 (without any space between) or in the arguments that follow them: 86 87 -o Indicate the output executable name 88 -W Show warnings on the topics indicated 89 90 Currently, the following warnings are supported: 91 92 all Show all possible warnings 93 94 args Show invocations where a callable may be involved that cannot accept the 95 arguments provided 96 """ % basename 97 sys.exit(1) 98 99 # Determine the options and arguments. 100 101 debug = False 102 make = True 103 make_verbose = True 104 reset = False 105 traceback = False 106 verbose = False 107 warnings = [] 108 109 filenames = [] 110 outputs = [] 111 112 # Obtain program filenames by default. 113 114 l = filenames 115 needed = None 116 117 for arg in args: 118 if arg == "-c": make = False 119 elif arg == "-g": debug = True 120 elif arg == "-q": make_verbose = False 121 elif arg == "-r": reset = True 122 elif arg == "-tb": traceback = True 123 elif arg.startswith("-o"): l, needed = start_arg_list(outputs, arg, "-o", 1) 124 elif arg == "-v": verbose = True 125 elif arg.startswith("-W"): l, needed = start_arg_list(warnings, arg, "-W", 1) 126 else: 127 l.append(arg) 128 if needed: 129 needed -= 1 130 131 if needed == 0: 132 l = filenames 133 134 # Obtain the program filename. 135 136 if len(filenames) != 1: 137 print >>sys.stderr, "One main program file must be specified." 138 sys.exit(1) 139 140 filename = abspath(filenames[0]) 141 path.append(split(filename)[0]) 142 143 # Obtain the output filename. 144 145 if outputs and not make: 146 print >>sys.stderr, "Output specified but building disabled." 147 148 output = outputs and outputs[0] or "_main" 149 150 # Define the output data directories. 151 152 datadir = "_lplc" 153 cache_dir = join(datadir, "_cache") 154 deduced_dir = join(datadir, "_deduced") 155 output_dir = join(datadir, "_output") 156 generated_dir = join(datadir, "_generated") 157 158 # Load the program. 159 160 try: 161 start = now = time() 162 163 i = importer.Importer(path, cache_dir, verbose, warnings) 164 m = i.initialise(filename, reset) 165 success = i.finalise() 166 167 now = stopwatch("Inspection", now) 168 169 # Check for success, indicating missing references otherwise. 170 171 if not success: 172 show_missing(i.missing) 173 sys.exit(1) 174 175 d = deducer.Deducer(i, deduced_dir) 176 d.to_output() 177 178 now = stopwatch("Deduction", now) 179 180 o = optimiser.Optimiser(i, d, output_dir) 181 o.to_output() 182 183 now = stopwatch("Optimisation", now) 184 185 g = generator.Generator(i, o, generated_dir) 186 g.to_output(debug) 187 188 now = stopwatch("Generation", now) 189 190 t = translator.Translator(i, d, o, generated_dir) 191 t.to_output() 192 193 now = stopwatch("Translation", now) 194 195 # Compile the program unless otherwise indicated. 196 197 if make: 198 make_clean_cmd = ["make", "-C", generated_dir, "clean"] 199 make_cmd = make_clean_cmd[:-1] 200 201 retval = call(make_clean_cmd, make_verbose) 202 if not retval: 203 retval = call(make_cmd, make_verbose) 204 205 if not retval: 206 stopwatch("Compilation", now) 207 else: 208 sys.exit(retval) 209 210 # Move the executable into the current directory. 211 212 rename(join(generated_dir, "main"), output) 213 214 # Report any errors. 215 216 except error.SyntaxError, exc: 217 show_syntax_error(exc) 218 if traceback: 219 raise 220 sys.exit(1) 221 222 except ProcessingError, exc: 223 print exc 224 if traceback: 225 raise 226 sys.exit(1) 227 228 else: 229 sys.exit(0) 230 231 # vim: tabstop=4 expandtab shiftwidth=4