# HG changeset patch # User Paul Boddie # Date 1487632843 -3600 # Node ID 3a80900f08ffb9206a8d7733f4ec851dd4b6370f # Parent 1053537493ffecf3d105fe18cde7d73ed3a54c9a Only copy changed template files and only generate updated program files, removing the "make clean" invocation and permitting incremental builds. Changed constant labelling to employ content digests so that constant names remain stable and do not confuse already-generated code. diff -r 1053537493ff -r 3a80900f08ff common.py --- a/common.py Mon Feb 20 18:50:16 2017 +0100 +++ b/common.py Tue Feb 21 00:20:43 2017 +0100 @@ -23,7 +23,7 @@ from compiler.transformer import Transformer from errors import InspectError from os import listdir, makedirs, remove -from os.path import exists, isdir, join, split +from os.path import exists, getmtime, isdir, join, split from results import ConstantValueRef, LiteralSequenceRef, NameRef import compiler.ast @@ -76,6 +76,36 @@ else: remove(path) +def copy(source, target, only_if_newer=True): + + "Copy a text file from 'source' to 'target'." + + if isdir(target): + target = join(target, split(source)[-1]) + + if only_if_newer and not is_newer(source, target): + return + + infile = open(source) + outfile = open(target, "w") + + try: + outfile.write(infile.read()) + finally: + outfile.close() + infile.close() + +def is_newer(source, target): + + "Return whether 'source' is newer than 'target'." + + if exists(target): + target_mtime = getmtime(target) + source_mtime = getmtime(source) + return source_mtime > target_mtime + + return True + class CommonModule: "A common module representation." diff -r 1053537493ff -r 3a80900f08ff encoders.py --- a/encoders.py Mon Feb 20 18:50:16 2017 +0100 +++ b/encoders.py Tue Feb 21 00:20:43 2017 +0100 @@ -21,6 +21,21 @@ from common import first, InstructionSequence + + +# Value digest computation. + +from base64 import b64encode +from hashlib import sha1 + +def digest(values): + m = sha1() + for value in values: + m.update(str(value)) + return b64encode(m.digest()).replace("+", "__").replace("/", "_").rstrip("=") + + + # Output encoding and decoding for the summary files. def encode_attrnames(attrnames): @@ -394,7 +409,7 @@ "Encode a name for the literal constant with the number 'n'." - return "__const%d" % n + return "__const%s" % n def encode_literal_constant_size(value): @@ -454,7 +469,7 @@ "Encode a reference to a literal constant with the number 'n'." - return "__constvalue%d" % n + return "__constvalue%s" % n diff -r 1053537493ff -r 3a80900f08ff generator.py --- a/generator.py Mon Feb 20 18:50:16 2017 +0100 +++ b/generator.py Tue Feb 21 00:20:43 2017 +0100 @@ -19,7 +19,7 @@ this program. If not, see . """ -from common import CommonOutput +from common import CommonOutput, copy from encoders import encode_function_pointer, \ encode_instantiator_pointer, \ encode_literal_constant, encode_literal_constant_member, \ @@ -31,24 +31,10 @@ encode_symbol, encode_tablename, \ encode_type_attribute, decode_type_attribute, \ is_type_attribute -from os import listdir, mkdir -from os.path import exists, isdir, join, split +from os import listdir, mkdir, remove +from os.path import exists, isdir, join, split, splitext from referencing import Reference -def copy(source, target): - - "Copy a text file from 'source' to 'target'." - - if isdir(target): - target = join(target, split(source)[-1]) - infile = open(source) - outfile = open(target, "w") - try: - outfile.write(infile.read()) - finally: - outfile.close() - infile.close() - class Generator(CommonOutput): "A code generator." @@ -124,9 +110,34 @@ if not exists(target): mkdir(target) - for filename in listdir(pathname): + existing = listdir(target) + needed = listdir(pathname) + + # Determine which files are superfluous by comparing their + # basenames (without extensions) to those of the needed + # filenames. This should preserve object files for needed source + # files, only discarding completely superfluous files from the + # target directory. + + needed_basenames = set() + for filename in needed: + needed_basenames.add(splitext(filename)[0]) + + superfluous = [] + for filename in existing: + if splitext(filename)[0] not in needed_basenames: + superfluous.append(filename) + + # Copy needed files. + + for filename in needed: copy(join(pathname, filename), target) + # Remove superfluous files. + + for filename in superfluous: + remove(join(target, filename)) + def write_structures(self): "Write structures used by the program." @@ -522,7 +533,7 @@ # Employ a special alias that will be tested specifically in # encode_member. - encoding_ref = Reference("", self.string_type, "$c%d" % n) + encoding_ref = Reference("", self.string_type, "$c%s" % n) # Use None where no encoding was indicated. @@ -959,7 +970,7 @@ constant_name = "$c%d" % local_number attr_path = "%s.%s" % (path, constant_name) constant_number = self.optimiser.constant_numbers[attr_path] - constant_value = "__const%d" % constant_number + constant_value = "__const%s" % constant_number structure.append("%s /* %s */" % (constant_value, attrname)) continue @@ -1021,7 +1032,7 @@ # Use the alias directly if appropriate. if alias.startswith("$c"): - constant_value = encode_literal_constant(int(alias[2:])) + constant_value = encode_literal_constant(alias[2:]) return "%s /* %s */" % (constant_value, name) # Obtain a constant value directly assigned to the attribute. diff -r 1053537493ff -r 3a80900f08ff lplc --- a/lplc Mon Feb 20 18:50:16 2017 +0100 +++ b/lplc Tue Feb 21 00:20:43 2017 +0100 @@ -270,9 +270,7 @@ make_clean_cmd = ["make", "-C", generated_dir, "clean"] make_cmd = make_clean_cmd[:-1] - retval = call(make_clean_cmd, make_verbose) - if not retval: - retval = call(make_cmd, make_verbose) + retval = call(make_cmd, make_verbose) if not retval: if timings: stopwatch("Compilation", now) diff -r 1053537493ff -r 3a80900f08ff optimiser.py --- a/optimiser.py Mon Feb 20 18:50:16 2017 +0100 +++ b/optimiser.py Tue Feb 21 00:20:43 2017 +0100 @@ -21,7 +21,7 @@ from common import add_counter_item, get_attrname_from_location, init_item, \ sorted_output -from encoders import encode_access_location, encode_instruction, get_kinds +from encoders import digest, encode_access_location, encode_instruction, get_kinds from os.path import exists, join from os import makedirs from referencing import Reference @@ -723,7 +723,7 @@ # Each constant is actually (value, value_type, encoding). for constant, n in constants.items(): - add_counter_item(self.constants, constant) + self.constants[constant] = digest(constant) self.constant_numbers = {} diff -r 1053537493ff -r 3a80900f08ff translator.py --- a/translator.py Mon Feb 20 18:50:16 2017 +0100 +++ b/translator.py Tue Feb 21 00:20:43 2017 +0100 @@ -20,7 +20,8 @@ """ from common import CommonModule, CommonOutput, InstructionSequence, \ - first, get_builtin_class, init_item, predefined_constants + first, get_builtin_class, init_item, is_newer, \ + predefined_constants from encoders import encode_access_instruction, \ encode_function_pointer, encode_literal_constant, \ encode_literal_instantiator, encode_instantiator_pointer, \ @@ -45,22 +46,33 @@ self.deducer = deducer self.optimiser = optimiser self.output = output - self.modules = {} def to_output(self): + + "Write a program to the configured output directory." + + # Make a directory for the final sources. + output = join(self.output, "src") if not exists(output): makedirs(output) + # Clean the output directory of irrelevant data. + self.check_output() for module in self.importer.modules.values(): + output_filename = join(output, "%s.c" % module.name) + + # Do not generate modules in the native package. They are provided + # by native functionality source files. + parts = module.name.split(".") - if parts[0] != "native": + + if parts[0] != "native" and is_newer(module.filename, output_filename): tm = TranslatedModule(module.name, self.importer, self.deducer, self.optimiser) - tm.translate(module.filename, join(output, "%s.c" % module.name)) - self.modules[module.name] = tm + tm.translate(module.filename, output_filename) # Classes representing intermediate translation results.