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