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