1.1 --- a/deducer.py Sun Sep 03 18:18:33 2023 +0200
1.2 +++ b/deducer.py Sun Sep 03 21:25:51 2023 +0200
1.3 @@ -3,7 +3,7 @@
1.4 """
1.5 Deduce types for usage observations.
1.6
1.7 -Copyright (C) 2014, 2015, 2016, 2017, 2018 Paul Boddie <paul@boddie.org.uk>
1.8 +Copyright (C) 2014-2018, 2023 Paul Boddie <paul@boddie.org.uk>
1.9
1.10 This program is free software; you can redistribute it and/or modify it under
1.11 the terms of the GNU General Public License as published by the Free Software
1.12 @@ -477,23 +477,7 @@
1.13 locations.sort()
1.14
1.15 for location in locations:
1.16 - name, test, test_type, base, \
1.17 - traversed, traversal_modes, attrnames, \
1.18 - context, context_test, \
1.19 - first_method, final_method, \
1.20 - attr, accessor_kinds = self.access_plans[location]
1.21 -
1.22 - print >>f_attrs, encode_access_location(location), \
1.23 - name or "{}", \
1.24 - test and "-".join(test) or "{}", \
1.25 - test_type or "{}", \
1.26 - base or "{}", \
1.27 - ".".join(traversed) or "{}", \
1.28 - ".".join(traversal_modes) or "{}", \
1.29 - ".".join(attrnames) or "{}", \
1.30 - context, context_test, \
1.31 - first_method, final_method, attr or "{}", \
1.32 - ",".join(accessor_kinds)
1.33 + self.access_plans[location].write(f_attrs, location)
1.34
1.35 finally:
1.36 f_attrs.close()
1.37 @@ -2637,25 +2621,17 @@
1.38 (base and "base" or "original-accessor") or \
1.39 "final-accessor"
1.40
1.41 - return name, test, test_type, base, \
1.42 - traversed, traversal_modes, remaining, \
1.43 - context, context_test, \
1.44 - first_method, final_method, \
1.45 - origin, accessor_kinds
1.46 + return AccessPlan(name, test, test_type, base,
1.47 + traversed, traversal_modes, remaining,
1.48 + context, context_test,
1.49 + first_method, final_method,
1.50 + origin, accessor_kinds)
1.51
1.52 def initialise_access_instructions(self):
1.53
1.54 "Expand access plans into instruction sequences."
1.55
1.56 - for access_location, access_plan in self.access_plans.items():
1.57 -
1.58 - # Obtain the access details.
1.59 -
1.60 - name, test, test_type, base, \
1.61 - traversed, traversal_modes, attrnames, \
1.62 - context, context_test, \
1.63 - first_method, final_method, \
1.64 - origin, accessor_kinds = access_plan
1.65 + for access_location, p in self.access_plans.items():
1.66
1.67 # Emit instructions by appending them to a list.
1.68
1.69 @@ -2664,40 +2640,30 @@
1.70
1.71 # Identify any static original accessor.
1.72
1.73 - if base:
1.74 - original_accessor = base
1.75 -
1.76 - # Employ names as contexts unless the context needs testing and
1.77 - # potentially updating. In such cases, temporary context storage is
1.78 - # used instead.
1.79 -
1.80 - elif name and not (context_test == "test" and
1.81 - final_method in ("access-invoke", "static-invoke")):
1.82 - original_accessor = "<name>" # refers to the name
1.83 -
1.84 - # Use a generic placeholder representing the access expression in
1.85 - # the general case.
1.86 -
1.87 - else:
1.88 - original_accessor = "<expr>"
1.89 + original_accessor = p.get_original_accessor()
1.90
1.91 # Prepare for any first attribute access.
1.92
1.93 + traversed = p.traversed
1.94 + traversal_modes = p.traversal_modes
1.95 + remaining = p.remaining
1.96 +
1.97 if traversed:
1.98 attrname = traversed[0]
1.99 del traversed[0]
1.100 - elif attrnames:
1.101 - attrname = attrnames[0]
1.102 - del attrnames[0]
1.103 + del traversal_modes[0]
1.104 + elif remaining:
1.105 + attrname = remaining[0]
1.106 + del remaining[0]
1.107
1.108 # Perform the first access explicitly if at least one operation
1.109 # requires it.
1.110
1.111 - access_first_attribute = final_method in ("access", "access-invoke", "assign") or traversed or attrnames
1.112 + access_first_attribute = p.final_method in ("access", "access-invoke", "assign") or traversed or remaining
1.113
1.114 # Determine whether the first access involves assignment.
1.115
1.116 - assigning = not traversed and not attrnames and final_method == "assign"
1.117 + assigning = not traversed and not remaining and p.final_method == "assign"
1.118 set_accessor = assigning and "<set_target_accessor>" or "<set_accessor>"
1.119 stored_accessor = assigning and "<target_accessor>" or "<accessor>"
1.120
1.121 @@ -2705,14 +2671,14 @@
1.122
1.123 context_var = None
1.124
1.125 - if context == "base":
1.126 - accessor = context_var = (base,)
1.127 - elif context == "original-accessor":
1.128 + if p.context == "base":
1.129 + accessor = context_var = (p.base,)
1.130 + elif p.context == "original-accessor":
1.131
1.132 # Prevent re-evaluation of any dynamic expression by storing it.
1.133
1.134 if original_accessor == "<expr>":
1.135 - if final_method in ("access-invoke", "static-invoke"):
1.136 + if p.final_method in ("access-invoke", "static-invoke"):
1.137 emit(("<set_context>", original_accessor))
1.138 accessor = context_var = ("<context>",)
1.139 else:
1.140 @@ -2723,7 +2689,7 @@
1.141
1.142 # Assigning does not set the context.
1.143
1.144 - elif context in ("final-accessor", "unset") and access_first_attribute:
1.145 + elif p.context in ("final-accessor", "unset") and access_first_attribute:
1.146
1.147 # Prevent re-evaluation of any dynamic expression by storing it.
1.148
1.149 @@ -2737,49 +2703,49 @@
1.150
1.151 # Apply any test.
1.152
1.153 - if test[0] == "test":
1.154 - test_accessor = accessor = ("__%s_%s_%s" % test, accessor, test_type)
1.155 + if p.test[0] == "test":
1.156 + test_accessor = accessor = ("__%s_%s_%s" % p.test, accessor, p.test_type)
1.157 else:
1.158 test_accessor = None
1.159
1.160 # Perform the first or final access.
1.161 # The access only needs performing if the resulting accessor is used.
1.162
1.163 - remaining = len(traversed + attrnames)
1.164 + num_remaining = len(traversed + remaining)
1.165
1.166 if access_first_attribute:
1.167
1.168 - if first_method == "relative-class":
1.169 + if p.first_method == "relative-class":
1.170 if assigning:
1.171 emit(("__store_via_class", accessor, attrname, "<assexpr>"))
1.172 else:
1.173 accessor = ("__load_via_class", accessor, attrname)
1.174
1.175 - elif first_method == "relative-object":
1.176 + elif p.first_method == "relative-object":
1.177 if assigning:
1.178 emit(("__store_via_object", accessor, attrname, "<assexpr>"))
1.179 else:
1.180 accessor = ("__load_via_object", accessor, attrname)
1.181
1.182 - elif first_method == "relative-object-class":
1.183 + elif p.first_method == "relative-object-class":
1.184 if assigning:
1.185 emit(("__get_class_and_store", accessor, attrname, "<assexpr>"))
1.186 else:
1.187 accessor = ("__get_class_and_load", accessor, attrname)
1.188
1.189 - elif first_method == "check-class":
1.190 + elif p.first_method == "check-class":
1.191 if assigning:
1.192 emit(("__check_and_store_via_class", accessor, attrname, "<assexpr>"))
1.193 else:
1.194 accessor = ("__check_and_load_via_class", accessor, attrname)
1.195
1.196 - elif first_method == "check-object":
1.197 + elif p.first_method == "check-object":
1.198 if assigning:
1.199 emit(("__check_and_store_via_object", accessor, attrname, "<assexpr>"))
1.200 else:
1.201 accessor = ("__check_and_load_via_object", accessor, attrname)
1.202
1.203 - elif first_method == "check-object-class":
1.204 + elif p.first_method == "check-object-class":
1.205 if assigning:
1.206 emit(("__check_and_store_via_any", accessor, attrname, "<assexpr>"))
1.207 else:
1.208 @@ -2788,17 +2754,17 @@
1.209 # Traverse attributes using the accessor.
1.210
1.211 if traversed:
1.212 - for attrname, traversal_mode in zip(traversed, traversal_modes):
1.213 - assigning = remaining == 1 and final_method == "assign"
1.214 + for attrname, traversal_mode in zip(traversed, p.traversal_modes):
1.215 + assigning = num_remaining == 1 and p.final_method == "assign"
1.216
1.217 # Set the context, if appropriate.
1.218
1.219 - if remaining == 1 and final_method != "assign" and context == "final-accessor":
1.220 + if num_remaining == 1 and p.final_method != "assign" and p.context == "final-accessor":
1.221
1.222 # Invoked attributes employ a separate context accessed
1.223 # during invocation.
1.224
1.225 - if final_method in ("access-invoke", "static-invoke"):
1.226 + if p.final_method in ("access-invoke", "static-invoke"):
1.227 emit(("<set_context>", accessor))
1.228 accessor = context_var = "<context>"
1.229
1.230 @@ -2811,7 +2777,7 @@
1.231
1.232 # Perform the access only if not achieved directly.
1.233
1.234 - if remaining > 1 or final_method in ("access", "access-invoke", "assign"):
1.235 + if num_remaining > 1 or p.final_method in ("access", "access-invoke", "assign"):
1.236
1.237 if traversal_mode == "class":
1.238 if assigning:
1.239 @@ -2824,20 +2790,20 @@
1.240 else:
1.241 accessor = ("__load_via_object", accessor, attrname)
1.242
1.243 - remaining -= 1
1.244 -
1.245 - if attrnames:
1.246 - for attrname in attrnames:
1.247 - assigning = remaining == 1 and final_method == "assign"
1.248 + num_remaining -= 1
1.249 +
1.250 + if remaining:
1.251 + for attrname in remaining:
1.252 + assigning = num_remaining == 1 and p.final_method == "assign"
1.253
1.254 # Set the context, if appropriate.
1.255
1.256 - if remaining == 1 and final_method != "assign" and context == "final-accessor":
1.257 + if num_remaining == 1 and p.final_method != "assign" and p.context == "final-accessor":
1.258
1.259 # Invoked attributes employ a separate context accessed
1.260 # during invocation.
1.261
1.262 - if final_method in ("access-invoke", "static-invoke"):
1.263 + if p.final_method in ("access-invoke", "static-invoke"):
1.264 emit(("<set_context>", accessor))
1.265 accessor = context_var = "<context>"
1.266
1.267 @@ -2850,7 +2816,7 @@
1.268
1.269 # Perform the access only if not achieved directly.
1.270
1.271 - if remaining > 1 or final_method in ("access", "access-invoke", "assign"):
1.272 + if num_remaining > 1 or p.final_method in ("access", "access-invoke", "assign"):
1.273
1.274 # Constrain instructions involving certain special
1.275 # attribute names.
1.276 @@ -2862,7 +2828,7 @@
1.277 else:
1.278 accessor = ("__check_and_load_via_%s" % to_search, accessor, attrname)
1.279
1.280 - remaining -= 1
1.281 + num_remaining -= 1
1.282
1.283 # Make any accessor test available if not emitted.
1.284
1.285 @@ -2870,41 +2836,41 @@
1.286
1.287 # Define or emit the means of accessing the actual target.
1.288
1.289 - if final_method in ("static", "static-assign", "static-invoke"):
1.290 + if p.final_method in ("static", "static-assign", "static-invoke"):
1.291
1.292 if test_accessor:
1.293 emit(test_accessor)
1.294
1.295 # Assignments to known attributes.
1.296
1.297 - if final_method == "static-assign":
1.298 - parent, attrname = origin.rsplit(".", 1)
1.299 + if p.final_method == "static-assign":
1.300 + parent, attrname = p.origin.rsplit(".", 1)
1.301 emit(("__store_via_object", parent, attrname, "<assexpr>"))
1.302
1.303 # Invoked attributes employ a separate context.
1.304
1.305 - elif final_method in ("static", "static-invoke"):
1.306 - accessor = ("__load_static_ignore", origin)
1.307 + elif p.final_method in ("static", "static-invoke"):
1.308 + accessor = ("__load_static_ignore", p.origin)
1.309
1.310 # Wrap accesses in context operations.
1.311
1.312 - if context_test == "test":
1.313 + if p.context_test == "test":
1.314
1.315 # Test and combine the context with static attribute details.
1.316
1.317 - if final_method == "static":
1.318 - emit(("__load_static_test", context_var, origin))
1.319 + if p.final_method == "static":
1.320 + emit(("__load_static_test", context_var, p.origin))
1.321
1.322 # Test the context, storing it separately if required for the
1.323 # immediately invoked static attribute.
1.324
1.325 - elif final_method == "static-invoke":
1.326 - emit(("<test_context_static>", context_var, origin))
1.327 + elif p.final_method == "static-invoke":
1.328 + emit(("<test_context_static>", context_var, p.origin))
1.329
1.330 # Test the context, storing it separately if required for an
1.331 # immediately invoked attribute.
1.332
1.333 - elif final_method == "access-invoke":
1.334 + elif p.final_method == "access-invoke":
1.335 emit(("<test_context_revert>", context_var, accessor))
1.336
1.337 # Test the context and update the attribute details if
1.338 @@ -2913,23 +2879,23 @@
1.339 else:
1.340 emit(("__test_context", context_var, accessor))
1.341
1.342 - elif context_test == "replace":
1.343 + elif p.context_test == "replace":
1.344
1.345 # Produce an object with updated context.
1.346
1.347 - if final_method == "static":
1.348 - emit(("__load_static_replace", context_var, origin))
1.349 + if p.final_method == "static":
1.350 + emit(("__load_static_replace", context_var, p.origin))
1.351
1.352 # Omit the context update operation where the target is static
1.353 # and the context is recorded separately.
1.354
1.355 - elif final_method == "static-invoke":
1.356 + elif p.final_method == "static-invoke":
1.357 pass
1.358
1.359 # If a separate context is used for an immediate invocation,
1.360 # produce the attribute details unchanged.
1.361
1.362 - elif final_method == "access-invoke":
1.363 + elif p.final_method == "access-invoke":
1.364 emit(accessor)
1.365
1.366 # Update the context in the attribute details.
1.367 @@ -2941,7 +2907,7 @@
1.368 # targets. Otherwise, emit the accessor which may involve the
1.369 # invocation of a test.
1.370
1.371 - elif final_method not in ("assign", "static-assign", "static-invoke"):
1.372 + elif p.final_method not in ("assign", "static-assign", "static-invoke"):
1.373 emit(accessor)
1.374
1.375 # Produce an advisory instruction regarding the context.
1.376 @@ -2951,8 +2917,8 @@
1.377 # Only verify the context for invocation purposes if a suitable
1.378 # test has been performed.
1.379
1.380 - if context_test in ("ignore", "replace") or \
1.381 - final_method in ("access-invoke", "static-invoke"):
1.382 + if p.context_test in ("ignore", "replace") or \
1.383 + p.final_method in ("access-invoke", "static-invoke"):
1.384
1.385 emit(("<context_identity_verified>", context_var))
1.386 else:
1.387 @@ -2960,10 +2926,72 @@
1.388
1.389 # Produce an advisory instruction regarding the final attribute.
1.390
1.391 - if origin:
1.392 - emit(("<final_identity>", origin))
1.393 + if p.origin:
1.394 + emit(("<final_identity>", p.origin))
1.395
1.396 self.access_instructions[access_location] = instructions
1.397 - self.accessor_kinds[access_location] = accessor_kinds
1.398 + self.accessor_kinds[access_location] = p.accessor_kinds
1.399 +
1.400 +class AccessPlan:
1.401 +
1.402 + "An access plan."
1.403 +
1.404 + def __init__(self, name, test, test_type, base, traversed, traversal_modes,
1.405 + remaining, context, context_test, first_method, final_method,
1.406 + origin, accessor_kinds):
1.407 +
1.408 + "Initialise the plan."
1.409 +
1.410 + (self.name, self.test, self.test_type, self.base,
1.411 + self.traversed, self.traversal_modes, self.remaining,
1.412 + self.context, self.context_test,
1.413 + self.first_method, self.final_method,
1.414 + self.origin, self.accessor_kinds) = (
1.415 +
1.416 + name, test, test_type, base,
1.417 + traversed, traversal_modes, remaining,
1.418 + context, context_test,
1.419 + first_method, final_method,
1.420 + origin, accessor_kinds)
1.421 +
1.422 + def get_original_accessor(self):
1.423 +
1.424 + "Return the original accessor details."
1.425 +
1.426 + # Identify any static original accessor.
1.427 +
1.428 + if self.base:
1.429 + return self.base
1.430 +
1.431 + # Employ names as contexts unless the context needs testing and
1.432 + # potentially updating. In such cases, temporary context storage is
1.433 + # used instead.
1.434 +
1.435 + elif self.name and not (self.context_test == "test" and
1.436 + self.final_method in ("access-invoke", "static-invoke")):
1.437 +
1.438 + return "<name>"
1.439 +
1.440 + # Use a generic placeholder representing the access expression in
1.441 + # the general case.
1.442 +
1.443 + else:
1.444 + return "<expr>"
1.445 +
1.446 + def write(self, f, location):
1.447 +
1.448 + "Write the plan to file 'f' with the given 'location' information."
1.449 +
1.450 + print >>f, encode_access_location(location), \
1.451 + self.name or "{}", \
1.452 + self.test and "-".join(self.test) or "{}", \
1.453 + self.test_type or "{}", \
1.454 + self.base or "{}", \
1.455 + ".".join(self.traversed) or "{}", \
1.456 + ".".join(self.traversal_modes) or "{}", \
1.457 + ".".join(self.remaining) or "{}", \
1.458 + self.context, self.context_test, \
1.459 + self.first_method, self.final_method, self.origin or "{}", \
1.460 + ",".join(self.accessor_kinds)
1.461
1.462 # vim: tabstop=4 expandtab shiftwidth=4