Lichen

Annotated transresults.py

1021:06335a0e4113
14 months ago Paul Boddie Prevent inadvertent object copying during __data__ attribute assignment. value-replacement
paul@636 1
#!/usr/bin/env python
paul@636 2
paul@636 3
"""
paul@636 4
Translation result abstractions.
paul@636 5
paul@982 6
Copyright (C) 2016, 2017, 2018, 2021, 2023 Paul Boddie <paul@boddie.org.uk>
paul@636 7
paul@636 8
This program is free software; you can redistribute it and/or modify it under
paul@636 9
the terms of the GNU General Public License as published by the Free Software
paul@636 10
Foundation; either version 3 of the License, or (at your option) any later
paul@636 11
version.
paul@636 12
paul@636 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@636 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@636 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@636 16
details.
paul@636 17
paul@636 18
You should have received a copy of the GNU General Public License along with
paul@636 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@636 20
"""
paul@636 21
paul@636 22
from common import first, InstructionSequence
paul@971 23
from encoders import encode_instructions, encode_literal_constant, encode_path, \
paul@971 24
                     encode_symbol
paul@685 25
from results import ConstantValueRef, InstanceRef, LiteralSequenceRef, NameRef, \
paul@636 26
                    ResolvedNameRef, Result
paul@636 27
paul@636 28
# Classes representing intermediate translation results.
paul@636 29
paul@636 30
class ReturnRef:
paul@636 31
paul@636 32
    "Indicates usage of a return statement."
paul@636 33
paul@636 34
    pass
paul@636 35
paul@636 36
class Expression(Result):
paul@636 37
paul@636 38
    "A general expression."
paul@636 39
paul@636 40
    def __init__(self, s):
paul@637 41
        if isinstance(s, Result):
paul@637 42
            self.s = str(s)
paul@637 43
            self.expr = s
paul@637 44
        else:
paul@637 45
            self.s = s
paul@637 46
            self.expr = None
paul@637 47
paul@637 48
    def discards_temporary(self, test=True):
paul@637 49
paul@637 50
        """
paul@637 51
        Return a list of temporary names that can be discarded if 'test' is
paul@637 52
        specified as a true value (or omitted).
paul@637 53
        """
paul@637 54
paul@637 55
        return self.expr and self.expr.discards_temporary(False)
paul@637 56
paul@636 57
    def __str__(self):
paul@636 58
        return self.s
paul@637 59
paul@636 60
    def __repr__(self):
paul@636 61
        return "Expression(%r)" % self.s
paul@636 62
paul@636 63
class TrResolvedNameRef(ResolvedNameRef):
paul@636 64
paul@636 65
    "A reference to a name in the translation."
paul@636 66
paul@664 67
    def __init__(self, name, ref, expr=None, is_global=False, location=None):
paul@636 68
        ResolvedNameRef.__init__(self, name, ref, expr, is_global)
paul@636 69
        self.location = location
paul@636 70
paul@959 71
        # For sources, any identified static origin will be constant and thus
paul@959 72
        # usable directly. For targets, no constant should be assigned and thus
paul@959 73
        # the alias (or any plain name) will be used.
paul@959 74
paul@959 75
        self.static_ref = self.static()
paul@959 76
        origin = self.static_ref and self.get_origin()
paul@959 77
        self.static_name = origin and encode_path(origin)
paul@959 78
paul@959 79
        # Determine whether a qualified name is involved.
paul@959 80
paul@959 81
        t = (not self.is_constant_alias() and self.get_name() or self.name).rsplit(".", 1)
paul@959 82
        self.parent = len(t) > 1 and t[0] or None
paul@959 83
        self.attrname = t[-1] and encode_path(t[-1])
paul@959 84
paul@636 85
    def access_location(self):
paul@636 86
        return self.location
paul@636 87
paul@636 88
    def __str__(self):
paul@636 89
paul@636 90
        "Return an output representation of the referenced name."
paul@636 91
paul@733 92
        # Temporary names are output program locals.
paul@733 93
paul@733 94
        if self.name.startswith("$t"):
paul@733 95
            if self.expr:
paul@733 96
                return "%s = %s" % (encode_path(self.name), self.expr)
paul@733 97
            else:
paul@733 98
                return encode_path(self.name)
paul@733 99
paul@636 100
        # Assignments.
paul@636 101
paul@636 102
        if self.expr:
paul@636 103
paul@1021 104
            store = self.attrname != "__data__" and "__store_via_attr_ref" or "__store_via_attr_ref__"
paul@1021 105
paul@636 106
            # Eliminate assignments between constants.
paul@636 107
paul@959 108
            if self.static_ref and self.expr.static():
paul@636 109
                return ""
paul@636 110
paul@636 111
            # Qualified names must be converted into parent-relative assignments.
paul@636 112
paul@959 113
            elif self.parent:
paul@1021 114
                return "%s(__get_object_attr_ref(&%s, %s), %s)" % (
paul@1021 115
                    store, encode_path(self.parent), self.attrname, self.expr)
paul@636 116
paul@636 117
            # All other assignments involve the names as they were given.
paul@971 118
            # To support value replacement, a special operation is used.
paul@636 119
paul@636 120
            else:
paul@1021 121
                return "%s(&%s, %s)" % (store, self.attrname, self.expr)
paul@636 122
paul@636 123
        # Expressions.
paul@636 124
paul@959 125
        elif self.static_name:
paul@959 126
            return "__ATTRVALUE(&%s)" % self.static_name
paul@636 127
paul@636 128
        # Qualified names must be converted into parent-relative accesses.
paul@636 129
paul@959 130
        elif self.parent:
paul@636 131
            return "__load_via_object(&%s, %s)" % (
paul@959 132
                encode_path(self.parent), self.attrname)
paul@636 133
paul@636 134
        # All other accesses involve the names as they were given.
paul@636 135
paul@636 136
        else:
paul@959 137
            return "(%s)" % self.attrname
paul@636 138
paul@636 139
class TrConstantValueRef(ConstantValueRef):
paul@636 140
paul@636 141
    "A constant value reference in the translation."
paul@636 142
paul@636 143
    def __str__(self):
paul@758 144
paul@758 145
        # NOTE: Should reference a common variable for the type name.
paul@758 146
paul@758 147
        if self.ref.get_origin() == "__builtins__.int.int":
paul@758 148
            return "__INTVALUE(%s)" % self.value
paul@758 149
        else:
paul@758 150
            return encode_literal_constant(self.number)
paul@636 151
paul@636 152
class TrLiteralSequenceRef(LiteralSequenceRef):
paul@636 153
paul@636 154
    "A reference representing a sequence of values."
paul@636 155
paul@636 156
    def __str__(self):
paul@636 157
        return str(self.node)
paul@636 158
paul@636 159
class TrInstanceRef(InstanceRef):
paul@636 160
paul@636 161
    "A reference representing instantiation of a class."
paul@636 162
paul@636 163
    def __init__(self, ref, expr):
paul@636 164
paul@636 165
        """
paul@636 166
        Initialise the reference with 'ref' indicating the nature of the
paul@636 167
        reference and 'expr' being an expression used to create the instance.
paul@636 168
        """
paul@636 169
paul@636 170
        InstanceRef.__init__(self, ref)
paul@636 171
        self.expr = expr
paul@636 172
paul@636 173
    def __str__(self):
paul@636 174
        return self.expr
paul@636 175
paul@636 176
    def __repr__(self):
paul@636 177
        return "TrResolvedInstanceRef(%r, %r)" % (self.ref, self.expr)
paul@636 178
paul@636 179
class AttrResult(Result, InstructionSequence):
paul@636 180
paul@636 181
    "A translation result for an attribute access."
paul@636 182
paul@828 183
    def __init__(self, instructions, refs, location, context_identity,
paul@989 184
                 context_identity_verified, accessor_test, accessor_stored,
paul@989 185
                 attribute_ref_stored):
paul@858 186
paul@636 187
        InstructionSequence.__init__(self, instructions)
paul@636 188
        self.refs = refs
paul@636 189
        self.location = location
paul@636 190
        self.context_identity = context_identity
paul@776 191
        self.context_identity_verified = context_identity_verified
paul@828 192
        self.accessor_test = accessor_test
paul@858 193
        self.accessor_stored = accessor_stored
paul@989 194
        self.attribute_ref_stored = attribute_ref_stored
paul@636 195
paul@636 196
    def references(self):
paul@636 197
        return self.refs
paul@636 198
paul@636 199
    def access_location(self):
paul@636 200
        return self.location
paul@636 201
paul@636 202
    def context(self):
paul@636 203
        return self.context_identity
paul@636 204
paul@776 205
    def context_verified(self):
paul@776 206
        return self.context_identity_verified and self.context() or None
paul@776 207
paul@828 208
    def tests_accessor(self):
paul@828 209
        return self.accessor_test
paul@828 210
paul@858 211
    def stores_accessor(self):
paul@858 212
        return self.accessor_stored
paul@858 213
paul@989 214
    def stores_attribute_ref(self):
paul@989 215
        return self.attribute_ref_stored
paul@989 216
paul@636 217
    def get_origin(self):
paul@636 218
        return self.refs and len(self.refs) == 1 and first(self.refs).get_origin()
paul@636 219
paul@636 220
    def has_kind(self, kinds):
paul@636 221
        if not self.refs:
paul@636 222
            return False
paul@636 223
        for ref in self.refs:
paul@636 224
            if ref.has_kind(kinds):
paul@636 225
                return True
paul@636 226
        return False
paul@636 227
paul@636 228
    def __nonzero__(self):
paul@636 229
        return bool(self.instructions)
paul@636 230
paul@636 231
    def __str__(self):
paul@636 232
        return encode_instructions(self.instructions)
paul@636 233
paul@636 234
    def __repr__(self):
paul@989 235
        return "AttrResult(%r, %r, %r, %r, %r, %r, %r, %r)" % (
paul@858 236
                self.instructions, self.refs, self.location,
paul@858 237
                self.context_identity, self.context_identity_verified,
paul@989 238
                self.accessor_test, self.accessor_stored,
paul@989 239
                self.attribute_ref_stored)
paul@636 240
paul@685 241
class AliasResult(NameRef, Result):
paul@685 242
paul@685 243
    "An alias for other values."
paul@685 244
paul@736 245
    def __init__(self, name_ref, refs, location):
paul@685 246
        NameRef.__init__(self, name_ref.name, is_global=name_ref.is_global_name())
paul@685 247
        self.name_ref = name_ref
paul@685 248
        self.refs = refs
paul@736 249
        self.location = location
paul@685 250
paul@685 251
    def references(self):
paul@685 252
        ref = self.name_ref.reference()
paul@685 253
        return self.refs or ref and [ref] or None
paul@685 254
paul@685 255
    def reference(self):
paul@685 256
        refs = self.references()
paul@685 257
        return len(refs) == 1 and first(refs) or None
paul@685 258
paul@685 259
    def access_location(self):
paul@736 260
        return self.location
paul@685 261
paul@685 262
    def get_name(self):
paul@685 263
        ref = self.reference()
paul@685 264
        return ref and ref.get_name()
paul@685 265
paul@685 266
    def get_origin(self):
paul@685 267
        ref = self.reference()
paul@685 268
        return ref and ref.get_origin()
paul@685 269
paul@685 270
    def static(self):
paul@685 271
        ref = self.reference()
paul@685 272
        return ref and ref.static()
paul@685 273
paul@685 274
    def final(self):
paul@685 275
        ref = self.reference()
paul@685 276
        return ref and ref.final()
paul@685 277
paul@685 278
    def has_kind(self, kinds):
paul@685 279
        if not self.refs:
paul@685 280
            return self.name_ref.has_kind(kinds)
paul@685 281
paul@685 282
        for ref in self.refs:
paul@685 283
            if ref.has_kind(kinds):
paul@685 284
                return True
paul@685 285
paul@685 286
        return False
paul@685 287
paul@685 288
    def __str__(self):
paul@685 289
        return str(self.name_ref)
paul@685 290
paul@685 291
    def __repr__(self):
paul@685 292
        return "AliasResult(%r, %r)" % (self.name_ref, self.refs)
paul@685 293
paul@636 294
class InvocationResult(Result, InstructionSequence):
paul@636 295
paul@636 296
    "A translation result for an invocation."
paul@636 297
paul@978 298
    def __init__(self, result_target, instructions):
paul@978 299
        InstructionSequence.__init__(self, instructions)
paul@978 300
        self.result_target = result_target
paul@978 301
paul@636 302
    def __str__(self):
paul@636 303
        return encode_instructions(self.instructions)
paul@636 304
paul@636 305
    def __repr__(self):
paul@978 306
        return "InvocationResult(%r, %r)" % (self.result_target, self.instructions)
paul@636 307
paul@636 308
class InstantiationResult(InvocationResult, TrInstanceRef):
paul@636 309
paul@636 310
    "An instantiation result acting like an invocation result."
paul@636 311
paul@636 312
    def __init__(self, ref, instructions):
paul@636 313
        InstanceRef.__init__(self, ref)
paul@978 314
        InvocationResult.__init__(self, "__NULL", instructions)
paul@636 315
paul@636 316
    def __repr__(self):
paul@636 317
        return "InstantiationResult(%r, %r)" % (self.ref, self.instructions)
paul@636 318
paul@636 319
class PredefinedConstantRef(Result):
paul@636 320
paul@636 321
    "A predefined constant reference."
paul@636 322
paul@636 323
    def __init__(self, value, expr=None):
paul@636 324
        self.value = value
paul@636 325
        self.expr = expr
paul@636 326
paul@636 327
    def __str__(self):
paul@636 328
paul@636 329
        # Eliminate predefined constant assignments.
paul@636 330
paul@636 331
        if self.expr:
paul@636 332
            return ""
paul@636 333
paul@636 334
        # Generate the specific constants.
paul@636 335
paul@636 336
        if self.value in ("False", "True"):
paul@636 337
            return encode_path("__builtins__.boolean.%s" % self.value)
paul@636 338
        elif self.value == "None":
paul@636 339
            return encode_path("__builtins__.none.%s" % self.value)
paul@636 340
        elif self.value == "NotImplemented":
paul@636 341
            return encode_path("__builtins__.notimplemented.%s" % self.value)
paul@636 342
        else:
paul@636 343
            return self.value
paul@636 344
paul@636 345
    def __repr__(self):
paul@636 346
        return "PredefinedConstantRef(%r)" % self.value
paul@636 347
paul@636 348
class LogicalResult(Result):
paul@636 349
paul@636 350
    "A logical expression result."
paul@636 351
paul@636 352
    def _convert(self, expr):
paul@636 353
paul@636 354
        "Return 'expr' converted to a testable value."
paul@636 355
paul@636 356
        if isinstance(expr, LogicalResult):
paul@636 357
            return expr.apply_test()
paul@636 358
        else:
paul@636 359
            return "__BOOL(%s)" % expr
paul@636 360
paul@636 361
class NegationResult(LogicalResult):
paul@636 362
paul@636 363
    "A negation expression result."
paul@636 364
paul@636 365
    def __init__(self, expr):
paul@636 366
        self.expr = expr
paul@636 367
paul@636 368
    def apply_test(self):
paul@636 369
paul@636 370
        "Return the result in a form suitable for direct testing."
paul@636 371
paul@636 372
        expr = self._convert(self.expr)
paul@636 373
        return "(!%s)" % expr
paul@636 374
paul@637 375
    def discards_temporary(self, test=True):
paul@637 376
paul@637 377
        """
paul@638 378
        Negations should have discarded their operand's temporary names when
paul@638 379
        being instantiated.
paul@637 380
        """
paul@637 381
paul@638 382
        return None
paul@637 383
paul@636 384
    def __str__(self):
paul@636 385
        return "(%s ? %s : %s)" % (
paul@636 386
            self._convert(self.expr),
paul@636 387
            PredefinedConstantRef("False"),
paul@636 388
            PredefinedConstantRef("True"))
paul@636 389
paul@636 390
    def __repr__(self):
paul@636 391
        return "NegationResult(%r)" % self.expr
paul@636 392
paul@636 393
class LogicalOperationResult(LogicalResult):
paul@636 394
paul@636 395
    "A logical operation result."
paul@636 396
paul@636 397
    def __init__(self, exprs, conjunction):
paul@636 398
        self.exprs = exprs
paul@636 399
        self.conjunction = conjunction
paul@636 400
paul@636 401
    def apply_test(self):
paul@636 402
paul@636 403
        """
paul@636 404
        Return the result in a form suitable for direct testing.
paul@636 405
paul@636 406
        Convert ... to ...
paul@636 407
paul@636 408
        <a> and <b>
paul@636 409
        ((__BOOL(<a>)) && (__BOOL(<b>)))
paul@636 410
paul@636 411
        <a> or <b>
paul@636 412
        ((__BOOL(<a>)) || (__BOOL(<b>)))
paul@636 413
        """
paul@636 414
paul@636 415
        results = []
paul@636 416
        for expr in self.exprs:
paul@636 417
            results.append(self._convert(expr))
paul@636 418
paul@636 419
        if self.conjunction:
paul@636 420
            return "(%s)" % " && ".join(results)
paul@636 421
        else:
paul@636 422
            return "(%s)" % " || ".join(results)
paul@636 423
paul@637 424
    def discards_temporary(self, test=True):
paul@637 425
paul@637 426
        """
paul@637 427
        Return a list of temporary names that can be discarded if 'test' is
paul@637 428
        specified as a true value (or omitted).
paul@637 429
        """
paul@637 430
paul@637 431
        if not test:
paul@637 432
            return None
paul@637 433
paul@637 434
        temps = ["__tmp_result"]
paul@637 435
paul@637 436
        for expr in self.exprs:
paul@637 437
            t = expr.discards_temporary(test)
paul@637 438
            if t:
paul@637 439
                temps += t
paul@637 440
paul@637 441
        return temps
paul@637 442
paul@636 443
    def __str__(self):
paul@636 444
paul@636 445
        """
paul@636 446
        Convert ... to ...
paul@636 447
paul@636 448
        <a> and <b>
paul@636 449
        (__tmp_result = <a>, !__BOOL(__tmp_result)) ? __tmp_result : <b>
paul@636 450
paul@636 451
        <a> or <b>
paul@636 452
        (__tmp_result = <a>, __BOOL(__tmp_result)) ? __tmp_result : <b>
paul@636 453
        """
paul@636 454
paul@636 455
        results = []
paul@636 456
        for expr in self.exprs[:-1]:
paul@636 457
            results.append("(__tmp_result = %s, %s__BOOL(__tmp_result)) ? __tmp_result : " % (expr, self.conjunction and "!" or ""))
paul@636 458
        results.append(str(self.exprs[-1]))
paul@636 459
paul@636 460
        return "(%s)" % "".join(results)
paul@636 461
paul@636 462
    def __repr__(self):
paul@636 463
        return "LogicalOperationResult(%r, %r)" % (self.exprs, self.conjunction)
paul@636 464
paul@636 465
# vim: tabstop=4 expandtab shiftwidth=4