1.1 --- a/optimiser.py Fri Mar 03 13:41:13 2017 +0100
1.2 +++ b/optimiser.py Fri Mar 03 13:44:32 2017 +0100
1.3 @@ -31,18 +31,32 @@
1.4
1.5 "Optimise objects in a program."
1.6
1.7 - def __init__(self, importer, deducer, output):
1.8 + def __init__(self, importer, deducer, output,
1.9 + attrnames_filename=None, locations_filename=None,
1.10 + paramnames_filename=None, parameter_locations_filename=None):
1.11
1.12 """
1.13 Initialise an instance using the given 'importer' and 'deducer' that
1.14 will perform the arrangement of attributes for program objects, writing
1.15 the results to the given 'output' directory.
1.16 +
1.17 + If 'attrnames_filename', 'locations_filename', 'paramnames_filename', or
1.18 + 'parameter_locations_filename' are given, they will be used to
1.19 + explicitly indicate existing attribute code, attribute position,
1.20 + parameter code, and parameter position information respectively.
1.21 """
1.22
1.23 self.importer = importer
1.24 self.deducer = deducer
1.25 self.output = output
1.26
1.27 + # Explicitly-specified attribute and parameter sources.
1.28 +
1.29 + self.attrnames_filename = attrnames_filename
1.30 + self.locations_filename = locations_filename
1.31 + self.paramnames_filename = paramnames_filename
1.32 + self.parameter_locations_filename = parameter_locations_filename
1.33 +
1.34 # Detection of differences between any existing structure or signature
1.35 # information and the generated information.
1.36
1.37 @@ -60,6 +74,7 @@
1.38
1.39 self.all_attrnames = None
1.40 self.existing_attrnames = None
1.41 + self.indicated_attrnames = None
1.42
1.43 # Locations of parameters in parameter tables.
1.44
1.45 @@ -72,6 +87,7 @@
1.46
1.47 self.all_paramnames = None
1.48 self.existing_paramnames = None
1.49 + self.indicated_paramnames = None
1.50
1.51 # Specific attribute access information.
1.52
1.53 @@ -127,20 +143,49 @@
1.54
1.55 self.check_output()
1.56
1.57 - # Existing attribute and parameter positioning information.
1.58 + # Existing attribute and parameter positioning information. This
1.59 + # influences the positions of attributes and parameters found in the
1.60 + # program.
1.61 +
1.62 + locations_filename = self.locations_filename or \
1.63 + join(self.output, "locations")
1.64 +
1.65 + parameter_locations_filename = self.parameter_locations_filename or \
1.66 + join(self.output, "parameter_locations")
1.67
1.68 - self.existing_locations = self.read_locations("locations", self._line_to_list, list)
1.69 - self.existing_arg_locations = self.read_locations("parameter_locations", self._line_to_list, list)
1.70 + self.existing_locations = self.read_data(locations_filename, self._line_to_list, list)
1.71 + self.existing_arg_locations = self.read_data(parameter_locations_filename, self._line_to_list, list)
1.72
1.73 - # Existing attribute and parameter code information.
1.74 + # Existing attribute and parameter code information. This is used to
1.75 + # check the compatibility of the output against any assignments
1.76 + # previously made.
1.77 +
1.78 + identity = lambda x: x
1.79 + none = lambda x: None
1.80
1.81 - self.existing_attrnames = self.read_locations("attrnames", lambda x: x, lambda x: None)
1.82 - self.existing_paramnames = self.read_locations("paramnames", lambda x: x, lambda x: None)
1.83 + attrnames_filename = join(self.output, "attrnames")
1.84 + paramnames_filename = join(self.output, "paramnames")
1.85 +
1.86 + self.existing_attrnames = self.read_data(attrnames_filename, identity, none)
1.87 + self.existing_paramnames = self.read_data(paramnames_filename, identity, none)
1.88 +
1.89 + # Explicitly-specified attribute name and parameter name codes. These
1.90 + # direct assignment of codes in the program.
1.91 +
1.92 + self.indicated_attrnames = self.attrnames_filename and \
1.93 + self.read_data(self.attrnames_filename, identity, none)
1.94
1.95 - # Existing structure and signature information.
1.96 + self.indicated_paramnames = self.paramnames_filename and \
1.97 + self.read_data(self.paramnames_filename, identity, none)
1.98 +
1.99 + # Existing structure and signature information. This is used to check
1.100 + # the output and detect whether structures or signatures have changed.
1.101
1.102 - self.existing_structures = dict(self.read_locations("structures", self._line_to_structure_pairs, list))
1.103 - self.existing_parameters = dict(self.read_locations("parameters", self._line_to_signature_pairs, list))
1.104 + structures_filename = join(self.output, "structures")
1.105 + parameters_filename = join(self.output, "parameters")
1.106 +
1.107 + self.existing_structures = dict(self.read_data(structures_filename, self._line_to_structure_pairs, list))
1.108 + self.existing_parameters = dict(self.read_data(parameters_filename, self._line_to_signature_pairs, list))
1.109
1.110 def _line_to_list(self, line):
1.111
1.112 @@ -171,7 +216,7 @@
1.113 values = map(lambda x: x != '-' and x or None, line.split(", "))
1.114 return (decode_reference(ref), values)
1.115
1.116 - def read_locations(self, filename, decode, empty):
1.117 + def read_data(self, filename, decode, empty):
1.118
1.119 """
1.120 Read location details from 'filename', using 'decode' to convert each
1.121 @@ -179,7 +224,6 @@
1.122 line, returning a collection.
1.123 """
1.124
1.125 - filename = join(self.output, filename)
1.126 collection = []
1.127
1.128 if exists(filename):
1.129 @@ -839,7 +883,13 @@
1.130 these identifiers.
1.131 """
1.132
1.133 - self.all_attrnames, d = self._get_name_mapping(self.attr_locations, self.existing_attrnames)
1.134 + # Initialise the mapping from attribute names to codes.
1.135 +
1.136 + l = self.all_attrnames = []; d = {}
1.137 + self._init_name_mapping(l, d, self.existing_attrnames)
1.138 + if self.indicated_attrnames:
1.139 + self._init_name_mapping(l, d, self.indicated_attrnames)
1.140 + self._update_name_mapping(l, d, self.attr_locations)
1.141
1.142 # Record the numbers indicating the locations of the names.
1.143
1.144 @@ -851,7 +901,13 @@
1.145 else:
1.146 l.append(d[attrname])
1.147
1.148 - self.all_paramnames, d = self._get_name_mapping(self.param_locations, self.existing_paramnames)
1.149 + # Initialise the mapping from parameter names to codes.
1.150 +
1.151 + l = self.all_paramnames = []; d = {}
1.152 + self._init_name_mapping(l, d, self.existing_paramnames)
1.153 + if self.indicated_paramnames:
1.154 + self._init_name_mapping(l, d, self.indicated_paramnames)
1.155 + self._update_name_mapping(l, d, self.param_locations)
1.156
1.157 # Record the numbers indicating the locations of the names.
1.158
1.159 @@ -864,43 +920,57 @@
1.160 name, pos = value
1.161 l.append((d[name], pos))
1.162
1.163 - def _get_name_mapping(self, locations, existing=None):
1.164 + def _init_name_mapping(self, l, d, existing):
1.165
1.166 """
1.167 - Get a sorted list of names from 'locations', then map them to
1.168 - identifying numbers. Preserve the identifiers from the 'existing' list,
1.169 - if specified. Return the list and the mapping.
1.170 + Initialise the name collection 'l', with mapping 'd', using the
1.171 + 'existing' mapping.
1.172 """
1.173
1.174 - d = {}
1.175 - l = []
1.176 -
1.177 i = 0
1.178 - all_names = set(locations.keys())
1.179 +
1.180 + for name in existing:
1.181 +
1.182 + # Test for the name in another position.
1.183 +
1.184 + if d.has_key(name):
1.185 + if d[name] != i:
1.186 + raise OptimiseError, "Name %s has conflicting codes: %d and %d." % \
1.187 + (name, d[name], i)
1.188 + else:
1.189
1.190 - # Preserve the existing identifiers, if available.
1.191 + # Test for other usage of the position.
1.192
1.193 - if existing:
1.194 - for name in existing:
1.195 + if i < len(l):
1.196 + if l[i] != name:
1.197 + raise OptimiseError, "Position %d has conflicting names: %s and %s." % \
1.198 + (i, name, d[name])
1.199 + l[i] = name
1.200 + else:
1.201 + l.append(name)
1.202 +
1.203 d[name] = i
1.204 - l.append(name)
1.205 - if name in all_names:
1.206 - all_names.remove(name)
1.207 - i += 1
1.208 +
1.209 + i += 1
1.210 +
1.211 + def _update_name_mapping(self, l, d, locations):
1.212
1.213 - # Include all remaining names in order.
1.214 + """
1.215 + Using any existing identifiers supplied by 'l' and 'd', update the
1.216 + identifiers using a sorted list of names from 'locations'.
1.217 + """
1.218
1.219 - all_names = list(all_names)
1.220 + all_names = list(locations.keys())
1.221 all_names.sort()
1.222
1.223 + i = len(l)
1.224 +
1.225 for name in all_names:
1.226 if not d.has_key(name):
1.227 d[name] = i
1.228 l.append(name)
1.229 i += 1
1.230
1.231 - return l, d
1.232 -
1.233 def populate_constants(self):
1.234
1.235 """