Lichen

Annotated common.py

1027:dd0745ab8b8a
5 months ago Paul Boddie Reordered GCC arguments to prevent linking failures. Someone decided to change the GCC invocation or linking semantics at some point, meaning that libraries specified "too early" in the argument list no longer provide the symbols required by the program objects, whereas specifying them at the end of the argument list allows those symbols to be found and obtained.
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Common functions.
paul@0 5
paul@986 6
Copyright (C) 2007-2019, 2021, 2023 Paul Boddie <paul@boddie.org.uk>
paul@0 7
paul@0 8
This program is free software; you can redistribute it and/or modify it under
paul@0 9
the terms of the GNU General Public License as published by the Free Software
paul@0 10
Foundation; either version 3 of the License, or (at your option) any later
paul@0 11
version.
paul@0 12
paul@0 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@0 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@0 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@0 16
details.
paul@0 17
paul@0 18
You should have received a copy of the GNU General Public License along with
paul@0 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@0 20
"""
paul@0 21
paul@512 22
from compiler.transformer import Transformer
paul@430 23
from errors import InspectError
paul@0 24
from os import listdir, makedirs, remove
paul@609 25
from os.path import exists, getmtime, isdir, join, split
paul@11 26
from results import ConstantValueRef, LiteralSequenceRef, NameRef
paul@405 27
import compiler.ast
paul@0 28
paul@0 29
class CommonOutput:
paul@0 30
paul@0 31
    "Common output functionality."
paul@0 32
paul@617 33
    def check_output(self, options=None):
paul@0 34
paul@0 35
        "Check the existing output and remove it if irrelevant."
paul@0 36
paul@0 37
        if not exists(self.output):
paul@0 38
            makedirs(self.output)
paul@0 39
paul@0 40
        details = self.importer.get_cache_details()
paul@0 41
        recorded_details = self.get_output_details()
paul@0 42
paul@617 43
        # Combine cache details with any options.
paul@617 44
paul@617 45
        full_details = options and (details + " " + options) or details
paul@617 46
paul@617 47
        if recorded_details != full_details:
paul@0 48
            self.remove_output()
paul@0 49
paul@617 50
        writefile(self.get_output_details_filename(), full_details)
paul@0 51
paul@0 52
    def get_output_details_filename(self):
paul@0 53
paul@0 54
        "Return the output details filename."
paul@0 55
paul@0 56
        return join(self.output, "$details")
paul@0 57
paul@0 58
    def get_output_details(self):
paul@0 59
paul@0 60
        "Return details of the existing output."
paul@0 61
paul@0 62
        details_filename = self.get_output_details_filename()
paul@0 63
paul@0 64
        if not exists(details_filename):
paul@0 65
            return None
paul@0 66
        else:
paul@0 67
            return readfile(details_filename)
paul@0 68
paul@0 69
    def remove_output(self, dirname=None):
paul@0 70
paul@0 71
        "Remove the output."
paul@0 72
paul@0 73
        dirname = dirname or self.output
paul@0 74
paul@0 75
        for filename in listdir(dirname):
paul@0 76
            path = join(dirname, filename)
paul@0 77
            if isdir(path):
paul@0 78
                self.remove_output(path)
paul@0 79
            else:
paul@0 80
                remove(path)
paul@0 81
paul@609 82
def copy(source, target, only_if_newer=True):
paul@609 83
paul@609 84
    "Copy a text file from 'source' to 'target'."
paul@609 85
paul@609 86
    if isdir(target):
paul@609 87
        target = join(target, split(source)[-1])
paul@609 88
paul@609 89
    if only_if_newer and not is_newer(source, target):
paul@609 90
        return
paul@609 91
paul@609 92
    infile = open(source)
paul@609 93
    outfile = open(target, "w")
paul@609 94
paul@609 95
    try:
paul@609 96
        outfile.write(infile.read())
paul@609 97
    finally:
paul@609 98
        outfile.close()
paul@609 99
        infile.close()
paul@609 100
paul@609 101
def is_newer(source, target):
paul@609 102
paul@609 103
    "Return whether 'source' is newer than 'target'."
paul@609 104
paul@609 105
    if exists(target):
paul@609 106
        target_mtime = getmtime(target)
paul@609 107
        source_mtime = getmtime(source)
paul@609 108
        return source_mtime > target_mtime
paul@609 109
paul@609 110
    return True
paul@609 111
paul@0 112
class CommonModule:
paul@0 113
paul@0 114
    "A common module representation."
paul@0 115
paul@0 116
    def __init__(self, name, importer):
paul@0 117
paul@0 118
        """
paul@0 119
        Initialise this module with the given 'name' and an 'importer' which is
paul@0 120
        used to provide access to other modules when required.
paul@0 121
        """
paul@0 122
paul@0 123
        self.name = name
paul@0 124
        self.importer = importer
paul@0 125
        self.filename = None
paul@0 126
paul@0 127
        # Inspection-related attributes.
paul@0 128
paul@0 129
        self.astnode = None
paul@405 130
        self.encoding = None
paul@0 131
        self.temp = {}
paul@0 132
        self.lambdas = {}
paul@0 133
paul@0 134
        # Constants, literals and values.
paul@0 135
paul@0 136
        self.constants = {}
paul@0 137
        self.constant_values = {}
paul@0 138
        self.literals = {}
paul@0 139
        self.literal_types = {}
paul@0 140
paul@0 141
        # Nested namespaces.
paul@0 142
paul@0 143
        self.namespace_path = []
paul@0 144
        self.in_function = False
paul@0 145
paul@124 146
        # Retain the assignment value expression and track invocations.
paul@124 147
paul@124 148
        self.in_assignment = None
paul@553 149
        self.in_invocation = None
paul@124 150
paul@124 151
        # Attribute chain state management.
paul@0 152
paul@0 153
        self.attrs = []
paul@124 154
        self.chain_assignment = []
paul@124 155
        self.chain_invocation = []
paul@0 156
paul@0 157
    def __repr__(self):
paul@0 158
        return "CommonModule(%r, %r)" % (self.name, self.importer)
paul@0 159
paul@0 160
    def parse_file(self, filename):
paul@0 161
paul@0 162
        "Parse the file with the given 'filename', initialising attributes."
paul@0 163
paul@0 164
        self.filename = filename
paul@405 165
paul@405 166
        # Use the Transformer directly to obtain encoding information.
paul@405 167
paul@405 168
        t = Transformer()
paul@405 169
        f = open(filename)
paul@405 170
paul@405 171
        try:
paul@405 172
            self.astnode = t.parsesuite(f.read() + "\n")
paul@405 173
            self.encoding = t.encoding
paul@405 174
        finally:
paul@405 175
            f.close()
paul@0 176
paul@0 177
    # Module-relative naming.
paul@0 178
paul@0 179
    def get_global_path(self, name):
paul@0 180
        return "%s.%s" % (self.name, name)
paul@0 181
paul@0 182
    def get_namespace_path(self):
paul@0 183
        return ".".join([self.name] + self.namespace_path)
paul@0 184
paul@0 185
    def get_object_path(self, name):
paul@0 186
        return ".".join([self.name] + self.namespace_path + [name])
paul@0 187
paul@0 188
    # Namespace management.
paul@0 189
paul@0 190
    def enter_namespace(self, name):
paul@0 191
paul@0 192
        "Enter the namespace having the given 'name'."
paul@0 193
paul@0 194
        self.namespace_path.append(name)
paul@0 195
paul@0 196
    def exit_namespace(self):
paul@0 197
paul@0 198
        "Exit the current namespace."
paul@0 199
paul@0 200
        self.namespace_path.pop()
paul@0 201
paul@0 202
    # Constant reference naming.
paul@0 203
paul@406 204
    def get_constant_name(self, value, value_type, encoding=None):
paul@0 205
paul@397 206
        """
paul@397 207
        Add a new constant to the current namespace for 'value' with
paul@397 208
        'value_type'.
paul@397 209
        """
paul@0 210
paul@0 211
        path = self.get_namespace_path()
paul@0 212
        init_item(self.constants, path, dict)
paul@406 213
        return "$c%d" % add_counter_item(self.constants[path], (value, value_type, encoding))
paul@0 214
paul@0 215
    # Literal reference naming.
paul@0 216
paul@0 217
    def get_literal_name(self):
paul@0 218
paul@0 219
        "Add a new literal to the current namespace."
paul@0 220
paul@0 221
        path = self.get_namespace_path()
paul@0 222
        init_item(self.literals, path, lambda: 0)
paul@0 223
        return "$C%d" % self.literals[path]
paul@0 224
paul@0 225
    def next_literal(self):
paul@0 226
        self.literals[self.get_namespace_path()] += 1
paul@0 227
paul@0 228
    # Temporary variable naming.
paul@0 229
paul@0 230
    def get_temporary_name(self):
paul@0 231
        path = self.get_namespace_path()
paul@0 232
        init_item(self.temp, path, lambda: 0)
paul@0 233
        return "$t%d" % self.temp[path]
paul@0 234
paul@0 235
    def next_temporary(self):
paul@0 236
        self.temp[self.get_namespace_path()] += 1
paul@0 237
paul@0 238
    # Arbitrary function naming.
paul@0 239
paul@0 240
    def get_lambda_name(self):
paul@0 241
        path = self.get_namespace_path()
paul@0 242
        init_item(self.lambdas, path, lambda: 0)
paul@0 243
        name = "$l%d" % self.lambdas[path]
paul@0 244
        self.lambdas[path] += 1
paul@0 245
        return name
paul@0 246
paul@0 247
    def reset_lambdas(self):
paul@0 248
        self.lambdas = {}
paul@0 249
paul@0 250
    # Constant and literal recording.
paul@0 251
paul@537 252
    def get_constant_value(self, value, literals=None):
paul@394 253
paul@406 254
        """
paul@406 255
        Encode the 'value' if appropriate, returning a value, a typename and any
paul@406 256
        encoding.
paul@406 257
        """
paul@394 258
paul@394 259
        if isinstance(value, unicode):
paul@406 260
            return value.encode("utf-8"), "unicode", self.encoding
paul@405 261
paul@405 262
        # Attempt to convert plain strings to text.
paul@405 263
paul@405 264
        elif isinstance(value, str) and self.encoding:
paul@513 265
            try:
paul@537 266
                return get_string_details(literals, self.encoding)
paul@513 267
            except UnicodeDecodeError:
paul@513 268
                pass
paul@405 269
paul@406 270
        return value, value.__class__.__name__, None
paul@394 271
paul@406 272
    def get_constant_reference(self, ref, value, encoding=None):
paul@0 273
paul@406 274
        """
paul@406 275
        Return a constant reference for the given 'ref' type and 'value', with
paul@406 276
        the optional 'encoding' applying to text values.
paul@406 277
        """
paul@0 278
paul@406 279
        constant_name = self.get_constant_name(value, ref.get_origin(), encoding)
paul@0 280
paul@0 281
        # Return a reference for the constant.
paul@0 282
paul@0 283
        objpath = self.get_object_path(constant_name)
paul@338 284
        name_ref = ConstantValueRef(constant_name, ref.instance_of(objpath), value)
paul@0 285
paul@0 286
        # Record the value and type for the constant.
paul@0 287
paul@406 288
        self._reserve_constant(objpath, name_ref.value, name_ref.get_origin(), encoding)
paul@0 289
        return name_ref
paul@0 290
paul@406 291
    def reserve_constant(self, objpath, value, origin, encoding=None):
paul@251 292
paul@251 293
        """
paul@251 294
        Reserve a constant within 'objpath' with the given 'value' and having a
paul@406 295
        type with the given 'origin', with the optional 'encoding' applying to
paul@406 296
        text values.
paul@251 297
        """
paul@251 298
paul@397 299
        constant_name = self.get_constant_name(value, origin)
paul@251 300
        objpath = self.get_object_path(constant_name)
paul@406 301
        self._reserve_constant(objpath, value, origin, encoding)
paul@251 302
paul@406 303
    def _reserve_constant(self, objpath, value, origin, encoding):
paul@251 304
paul@406 305
        """
paul@406 306
        Store a constant for 'objpath' with the given 'value' and 'origin', with
paul@406 307
        the optional 'encoding' applying to text values.
paul@406 308
        """
paul@251 309
paul@406 310
        self.constant_values[objpath] = value, origin, encoding
paul@251 311
paul@0 312
    def get_literal_reference(self, name, ref, items, cls):
paul@0 313
paul@11 314
        """
paul@11 315
        Return a literal reference for the given type 'name', literal 'ref',
paul@11 316
        node 'items' and employing the given 'cls' as the class of the returned
paul@11 317
        reference object.
paul@11 318
        """
paul@11 319
paul@0 320
        # Construct an invocation using the items as arguments.
paul@0 321
paul@0 322
        typename = "$L%s" % name
paul@0 323
paul@0 324
        invocation = compiler.ast.CallFunc(
paul@0 325
            compiler.ast.Name(typename),
paul@0 326
            items
paul@0 327
            )
paul@0 328
paul@0 329
        # Get a name for the actual literal.
paul@0 330
paul@0 331
        instname = self.get_literal_name()
paul@0 332
        self.next_literal()
paul@0 333
paul@0 334
        # Record the type for the literal.
paul@0 335
paul@0 336
        objpath = self.get_object_path(instname)
paul@0 337
        self.literal_types[objpath] = ref.get_origin()
paul@0 338
paul@0 339
        # Return a wrapper for the invocation exposing the items.
paul@0 340
paul@0 341
        return cls(
paul@0 342
            instname,
paul@0 343
            ref.instance_of(),
paul@0 344
            self.process_structure_node(invocation),
paul@0 345
            invocation.args
paul@0 346
            )
paul@0 347
paul@0 348
    # Node handling.
paul@0 349
paul@0 350
    def process_structure(self, node):
paul@0 351
paul@0 352
        """
paul@0 353
        Within the given 'node', process the program structure.
paul@0 354
paul@0 355
        During inspection, this will process global declarations, adjusting the
paul@0 356
        module namespace, and import statements, building a module dependency
paul@0 357
        hierarchy.
paul@0 358
paul@0 359
        During translation, this will consult deduced program information and
paul@0 360
        output translated code.
paul@0 361
        """
paul@0 362
paul@0 363
        l = []
paul@0 364
        for n in node.getChildNodes():
paul@986 365
            l.append(self.process_statement_node(n))
paul@0 366
        return l
paul@0 367
paul@986 368
    def process_statement_node(self, node):
paul@986 369
paul@986 370
        "Process the given statement 'node'."
paul@986 371
paul@986 372
        return self.process_structure_node(node)
paul@986 373
paul@0 374
    def process_augassign_node(self, n):
paul@0 375
paul@0 376
        "Process the given augmented assignment node 'n'."
paul@0 377
paul@0 378
        op = operator_functions[n.op]
paul@0 379
paul@0 380
        if isinstance(n.node, compiler.ast.Getattr):
paul@0 381
            target = compiler.ast.AssAttr(n.node.expr, n.node.attrname, "OP_ASSIGN")
paul@0 382
        elif isinstance(n.node, compiler.ast.Name):
paul@0 383
            target = compiler.ast.AssName(n.node.name, "OP_ASSIGN")
paul@0 384
        else:
paul@0 385
            target = n.node
paul@0 386
paul@0 387
        assignment = compiler.ast.Assign(
paul@0 388
            [target],
paul@0 389
            compiler.ast.CallFunc(
paul@0 390
                compiler.ast.Name("$op%s" % op),
paul@0 391
                [n.node, n.expr]))
paul@0 392
paul@0 393
        return self.process_structure_node(assignment)
paul@0 394
paul@320 395
    def process_assignment_for_object(self, original_name, source):
paul@0 396
paul@0 397
        """
paul@0 398
        Return an assignment operation making 'original_name' refer to the given
paul@196 399
        'source'.
paul@0 400
        """
paul@0 401
paul@0 402
        assignment = compiler.ast.Assign(
paul@0 403
            [compiler.ast.AssName(original_name, "OP_ASSIGN")],
paul@196 404
            source
paul@0 405
            )
paul@0 406
paul@0 407
        return self.process_structure_node(assignment)
paul@0 408
paul@0 409
    def process_assignment_node_items(self, n, expr):
paul@0 410
paul@0 411
        """
paul@0 412
        Process the given assignment node 'n' whose children are to be assigned
paul@0 413
        items of 'expr'.
paul@0 414
        """
paul@0 415
paul@0 416
        name_ref = self.process_structure_node(expr)
paul@0 417
paul@509 418
        # Either unpack the items and present them directly to each assignment
paul@509 419
        # node.
paul@509 420
paul@509 421
        if isinstance(name_ref, LiteralSequenceRef) and \
paul@509 422
           self.process_literal_sequence_items(n, name_ref):
paul@0 423
paul@509 424
            pass
paul@509 425
paul@509 426
        # Or have the assignment nodes access each item via the sequence API.
paul@509 427
paul@509 428
        else:
paul@509 429
            self.process_assignment_node_items_by_position(n, expr, name_ref)
paul@0 430
paul@0 431
    def process_assignment_node_items_by_position(self, n, expr, name_ref):
paul@0 432
paul@0 433
        """
paul@0 434
        Process the given sequence assignment node 'n', converting the node to
paul@0 435
        the separate assignment of each target using positional access on a
paul@0 436
        temporary variable representing the sequence. Use 'expr' as the assigned
paul@0 437
        value and 'name_ref' as the reference providing any existing temporary
paul@0 438
        variable.
paul@0 439
        """
paul@0 440
paul@835 441
        statements = []
paul@0 442
paul@508 443
        # Employ existing names to access the sequence.
paul@508 444
        # Literal sequences do not provide names of accessible objects.
paul@508 445
paul@508 446
        if isinstance(name_ref, NameRef) and not isinstance(name_ref, LiteralSequenceRef):
paul@0 447
            temp = name_ref.name
paul@508 448
paul@508 449
        # For other expressions, create a temporary name to reference the items.
paul@508 450
paul@0 451
        else:
paul@0 452
            temp = self.get_temporary_name()
paul@0 453
            self.next_temporary()
paul@0 454
paul@835 455
            statements.append(
paul@0 456
                compiler.ast.Assign([compiler.ast.AssName(temp, "OP_ASSIGN")], expr)
paul@0 457
                )
paul@0 458
paul@835 459
        # Generate a test for the length of the expression object.
paul@835 460
paul@835 461
        statements.append(compiler.ast.Discard(
paul@835 462
            compiler.ast.CallFunc(compiler.ast.Name("$seq_test_length"),
paul@835 463
                [compiler.ast.Name(temp), compiler.ast.Const(len(n.nodes))])))
paul@835 464
paul@508 465
        # Assign the items to the target nodes.
paul@508 466
paul@0 467
        for i, node in enumerate(n.nodes):
paul@835 468
            statements.append(
paul@836 469
                compiler.ast.Assign([node], compiler.ast.CallFunc(
paul@845 470
                    compiler.ast.Getattr(compiler.ast.Name(temp),
paul@845 471
                                         "__get_single_item_unchecked__",
paul@845 472
                                         privileged=True),
paul@836 473
                    [compiler.ast.Const(i, str(i))]))
paul@0 474
                )
paul@0 475
paul@835 476
        return self.process_structure_node(compiler.ast.Stmt(statements))
paul@0 477
paul@0 478
    def process_literal_sequence_items(self, n, name_ref):
paul@0 479
paul@0 480
        """
paul@0 481
        Process the given assignment node 'n', obtaining from the given
paul@0 482
        'name_ref' the items to be assigned to the assignment targets.
paul@509 483
paul@509 484
        Return whether this method was able to process the assignment node as
paul@509 485
        a sequence of direct assignments.
paul@0 486
        """
paul@0 487
paul@0 488
        if len(n.nodes) == len(name_ref.items):
paul@509 489
            assigned_names, count = get_names_from_nodes(n.nodes)
paul@509 490
            accessed_names, _count = get_names_from_nodes(name_ref.items)
paul@509 491
paul@509 492
            # Only assign directly between items if all assigned names are
paul@509 493
            # plain names (not attribute assignments), and if the assigned names
paul@509 494
            # do not appear in the accessed names.
paul@509 495
paul@509 496
            if len(assigned_names) == count and \
paul@509 497
               not assigned_names.intersection(accessed_names):
paul@509 498
paul@509 499
                for node, item in zip(n.nodes, name_ref.items):
paul@509 500
                    self.process_assignment_node(node, item)
paul@509 501
paul@509 502
                return True
paul@509 503
paul@509 504
            # Otherwise, use the position-based mechanism to obtain values.
paul@509 505
paul@509 506
            else:
paul@509 507
                return False
paul@0 508
        else:
paul@0 509
            raise InspectError("In %s, item assignment needing %d items is given %d items." % (
paul@0 510
                self.get_namespace_path(), len(n.nodes), len(name_ref.items)))
paul@0 511
paul@0 512
    def process_compare_node(self, n):
paul@0 513
paul@0 514
        """
paul@0 515
        Process the given comparison node 'n', converting an operator sequence
paul@0 516
        from...
paul@0 517
paul@0 518
        <expr1> <op1> <expr2> <op2> <expr3>
paul@0 519
paul@0 520
        ...to...
paul@0 521
paul@0 522
        <op1>(<expr1>, <expr2>) and <op2>(<expr2>, <expr3>)
paul@0 523
        """
paul@0 524
paul@0 525
        invocations = []
paul@0 526
        last = n.expr
paul@0 527
paul@0 528
        for op, op_node in n.ops:
paul@0 529
            op = operator_functions.get(op)
paul@0 530
paul@0 531
            invocations.append(compiler.ast.CallFunc(
paul@0 532
                compiler.ast.Name("$op%s" % op),
paul@0 533
                [last, op_node]))
paul@0 534
paul@0 535
            last = op_node
paul@0 536
paul@0 537
        if len(invocations) > 1:
paul@0 538
            result = compiler.ast.And(invocations)
paul@0 539
        else:
paul@0 540
            result = invocations[0]
paul@0 541
paul@0 542
        return self.process_structure_node(result)
paul@0 543
paul@0 544
    def process_dict_node(self, node):
paul@0 545
paul@0 546
        """
paul@0 547
        Process the given dictionary 'node', returning a list of (key, value)
paul@0 548
        tuples.
paul@0 549
        """
paul@0 550
paul@0 551
        l = []
paul@0 552
        for key, value in node.items:
paul@0 553
            l.append((
paul@0 554
                self.process_structure_node(key),
paul@0 555
                self.process_structure_node(value)))
paul@0 556
        return l
paul@0 557
paul@0 558
    def process_for_node(self, n):
paul@0 559
paul@0 560
        """
paul@0 561
        Generate attribute accesses for {n.list}.__iter__ and the next method on
paul@0 562
        the iterator, producing a replacement node for the original.
paul@0 563
        """
paul@0 564
paul@705 565
        t0 = self.get_temporary_name()
paul@705 566
        self.next_temporary()
paul@705 567
        t1 = self.get_temporary_name()
paul@705 568
        self.next_temporary()
paul@832 569
        t2 = self.get_temporary_name()
paul@832 570
        self.next_temporary()
paul@904 571
        t3 = self.get_temporary_name()
paul@904 572
        self.next_temporary()
paul@704 573
paul@0 574
        node = compiler.ast.Stmt([
paul@0 575
paul@705 576
            # <t0> = {n.list}
paul@705 577
            # <t1> = <t0>.__iter__()
paul@705 578
paul@705 579
            compiler.ast.Assign(
paul@705 580
                [compiler.ast.AssName(t0, "OP_ASSIGN")],
paul@705 581
                n.list),
paul@705 582
paul@705 583
            compiler.ast.Assign(
paul@705 584
                [compiler.ast.AssName(t1, "OP_ASSIGN")],
paul@705 585
                compiler.ast.CallFunc(
paul@705 586
                    compiler.ast.Getattr(compiler.ast.Name(t0), "__iter__"),
paul@705 587
                    [])),
paul@0 588
paul@832 589
            # <t2> = <t1>.next
paul@0 590
            # try:
paul@0 591
            #     while True:
paul@868 592
            #         try:
paul@904 593
            #             <t3> = <t2>()
paul@868 594
            #         except StopIteration:
paul@868 595
            #             raise LoopExit
paul@904 596
            #         <var>... = <t3>
paul@868 597
            #         {n.body}
paul@868 598
            # except LoopExit:
paul@866 599
            #     {n.else_}
paul@0 600
            #     pass
paul@0 601
paul@832 602
            compiler.ast.Assign(
paul@832 603
                [compiler.ast.AssName(t2, "OP_ASSIGN")],
paul@832 604
                compiler.ast.Getattr(compiler.ast.Name(t1), "next")),
paul@832 605
paul@909 606
            # try:
paul@909 607
paul@0 608
            compiler.ast.TryExcept(
paul@909 609
paul@909 610
                # while True:
paul@909 611
paul@0 612
                compiler.ast.While(
paul@0 613
                    compiler.ast.Name("True"),
paul@0 614
                    compiler.ast.Stmt([
paul@909 615
paul@909 616
                        # try:
paul@909 617
paul@868 618
                        compiler.ast.TryExcept(
paul@909 619
paul@909 620
                            # <t3> = <t2>()
paul@909 621
paul@868 622
                            compiler.ast.Assign(
paul@904 623
                                [compiler.ast.AssName(t3, "OP_ASSIGN")],
paul@868 624
                                compiler.ast.CallFunc(
paul@868 625
                                    compiler.ast.Name(t2),
paul@868 626
                                    [])),
paul@909 627
paul@909 628
                        # except StopIteration:
paul@909 629
                        #     raise LoopExit
paul@909 630
paul@868 631
                            [(compiler.ast.Name("StopIteration"), None,
paul@868 632
                              compiler.ast.Raise(compiler.ast.Name("LoopExit")))],
paul@868 633
                            None),
paul@909 634
paul@909 635
                        # <var>... = <t3>
paul@909 636
paul@904 637
                        compiler.ast.Assign(
paul@904 638
                            [n.assign],
paul@904 639
                            compiler.ast.Name(t3)),
paul@0 640
                        n.body]),
paul@0 641
                    None),
paul@909 642
paul@909 643
            # except LoopExit:
paul@909 644
            #     {n.else_}
paul@909 645
            #     pass
paul@909 646
paul@868 647
                [(compiler.ast.Name("LoopExit"), None, n.else_ or compiler.ast.Pass())],
paul@0 648
                None)
paul@0 649
            ])
paul@0 650
paul@0 651
        self.process_structure_node(node)
paul@0 652
paul@0 653
    def process_literal_sequence_node(self, n, name, ref, cls):
paul@0 654
paul@0 655
        """
paul@0 656
        Process the given literal sequence node 'n' as a function invocation,
paul@0 657
        with 'name' indicating the type of the sequence, and 'ref' being a
paul@0 658
        reference to the type. The 'cls' is used to instantiate a suitable name
paul@0 659
        reference.
paul@0 660
        """
paul@0 661
paul@0 662
        if name == "dict":
paul@0 663
            items = []
paul@0 664
            for key, value in n.items:
paul@0 665
                items.append(compiler.ast.Tuple([key, value]))
paul@0 666
        else: # name in ("list", "tuple"):
paul@0 667
            items = n.nodes
paul@0 668
paul@0 669
        return self.get_literal_reference(name, ref, items, cls)
paul@0 670
paul@0 671
    def process_operator_node(self, n):
paul@0 672
paul@0 673
        """
paul@0 674
        Process the given operator node 'n' as an operator function invocation.
paul@0 675
        """
paul@0 676
paul@797 677
        opname = n.__class__.__name__
paul@797 678
        operands = n.getChildNodes()
paul@797 679
paul@797 680
        # Convert a unary operation to an invocation.
paul@797 681
paul@797 682
        op = unary_operator_functions.get(opname)
paul@797 683
paul@797 684
        if op:
paul@797 685
            invocation = compiler.ast.CallFunc(
paul@797 686
                compiler.ast.Name("$op%s" % op),
paul@797 687
                [operands[0]]
paul@797 688
                )
paul@797 689
paul@797 690
        # Convert a single operator with a list of operands to a combination of
paul@797 691
        # pairwise operations.
paul@797 692
paul@797 693
        else:
paul@797 694
            op = operator_functions[opname]
paul@797 695
            invocation = self._process_operator_node(op, operands)
paul@797 696
paul@797 697
        return self.process_structure_node(invocation)
paul@797 698
paul@797 699
    def _process_operator_node(self, op, operands):
paul@797 700
paul@797 701
        """
paul@797 702
        Process the given 'op', being an operator function, together with the
paul@797 703
        supplied 'operands', returning either a single remaining operand or an
paul@797 704
        invocation combining the operands.
paul@797 705
        """
paul@797 706
paul@797 707
        remaining = operands[1:]
paul@797 708
        if not remaining:
paul@797 709
            return operands[0]
paul@797 710
paul@797 711
        return compiler.ast.CallFunc(
paul@0 712
            compiler.ast.Name("$op%s" % op),
paul@797 713
            [operands[0], self._process_operator_node(op, remaining)]
paul@0 714
            )
paul@0 715
paul@173 716
    def process_print_node(self, n):
paul@173 717
paul@173 718
        """
paul@173 719
        Process the given print node 'n' as an invocation on a stream of the
paul@173 720
        form...
paul@173 721
paul@173 722
        $print(dest, args, nl)
paul@173 723
paul@173 724
        The special function name will be translated elsewhere.
paul@173 725
        """
paul@173 726
paul@173 727
        nl = isinstance(n, compiler.ast.Printnl)
paul@173 728
        invocation = compiler.ast.CallFunc(
paul@173 729
            compiler.ast.Name("$print"),
paul@173 730
            [n.dest or compiler.ast.Name("None"),
paul@173 731
             compiler.ast.List(list(n.nodes)),
paul@359 732
             nl and compiler.ast.Name("True") or compiler.ast.Name("False")]
paul@173 733
            )
paul@173 734
        return self.process_structure_node(invocation)
paul@173 735
paul@0 736
    def process_slice_node(self, n, expr=None):
paul@0 737
paul@0 738
        """
paul@784 739
        Process the given slice node 'n' as a method invocation.
paul@0 740
        """
paul@0 741
paul@784 742
        if n.flags == "OP_ASSIGN": op = "__setslice__"
paul@784 743
        elif n.flags == "OP_DELETE": op = "__delslice__"
paul@784 744
        else: op = "__getslice__"
paul@548 745
paul@0 746
        invocation = compiler.ast.CallFunc(
paul@784 747
            compiler.ast.Getattr(n.expr, op),
paul@784 748
            [n.lower or compiler.ast.Name("None"), n.upper or compiler.ast.Name("None")] +
paul@0 749
                (expr and [expr] or [])
paul@0 750
            )
paul@548 751
paul@548 752
        # Fix parse tree structure.
paul@548 753
paul@784 754
        if op == "__delslice__":
paul@548 755
            invocation = compiler.ast.Discard(invocation)
paul@548 756
paul@0 757
        return self.process_structure_node(invocation)
paul@0 758
paul@0 759
    def process_sliceobj_node(self, n):
paul@0 760
paul@0 761
        """
paul@0 762
        Process the given slice object node 'n' as a slice constructor.
paul@0 763
        """
paul@0 764
paul@0 765
        op = "slice"
paul@0 766
        invocation = compiler.ast.CallFunc(
paul@0 767
            compiler.ast.Name("$op%s" % op),
paul@0 768
            n.nodes
paul@0 769
            )
paul@0 770
        return self.process_structure_node(invocation)
paul@0 771
paul@0 772
    def process_subscript_node(self, n, expr=None):
paul@0 773
paul@0 774
        """
paul@784 775
        Process the given subscript node 'n' as a method invocation.
paul@0 776
        """
paul@0 777
paul@784 778
        if n.flags == "OP_ASSIGN": op = "__setitem__"
paul@784 779
        elif n.flags == "OP_DELETE": op = "__delitem__"
paul@784 780
        else: op = "__getitem__"
paul@548 781
paul@0 782
        invocation = compiler.ast.CallFunc(
paul@784 783
            compiler.ast.Getattr(n.expr, op),
paul@784 784
            list(n.subs) + (expr and [expr] or [])
paul@0 785
            )
paul@548 786
paul@548 787
        # Fix parse tree structure.
paul@548 788
paul@784 789
        if op == "__delitem__":
paul@548 790
            invocation = compiler.ast.Discard(invocation)
paul@548 791
paul@0 792
        return self.process_structure_node(invocation)
paul@0 793
paul@0 794
    def process_attribute_chain(self, n):
paul@0 795
paul@0 796
        """
paul@0 797
        Process the given attribute access node 'n'. Return a reference
paul@0 798
        describing the expression.
paul@0 799
        """
paul@0 800
paul@0 801
        # AssAttr/Getattr are nested with the outermost access being the last
paul@0 802
        # access in any chain.
paul@0 803
paul@0 804
        self.attrs.insert(0, n.attrname)
paul@0 805
        attrs = self.attrs
paul@0 806
paul@0 807
        # Break attribute chains where non-access nodes are found.
paul@0 808
paul@0 809
        if not self.have_access_expression(n):
paul@110 810
            self.reset_attribute_chain()
paul@0 811
paul@0 812
        # Descend into the expression, extending backwards any existing chain,
paul@0 813
        # or building another for the expression.
paul@0 814
paul@0 815
        name_ref = self.process_structure_node(n.expr)
paul@0 816
paul@0 817
        # Restore chain information applying to this node.
paul@0 818
paul@110 819
        if not self.have_access_expression(n):
paul@110 820
            self.restore_attribute_chain(attrs)
paul@0 821
paul@0 822
        # Return immediately if the expression was another access and thus a
paul@0 823
        # continuation backwards along the chain. The above processing will
paul@0 824
        # have followed the chain all the way to its conclusion.
paul@0 825
paul@0 826
        if self.have_access_expression(n):
paul@0 827
            del self.attrs[0]
paul@0 828
paul@0 829
        return name_ref
paul@0 830
paul@124 831
    # Attribute chain handling.
paul@124 832
paul@110 833
    def reset_attribute_chain(self):
paul@110 834
paul@110 835
        "Reset the attribute chain for a subexpression of an attribute access."
paul@110 836
paul@110 837
        self.attrs = []
paul@124 838
        self.chain_assignment.append(self.in_assignment)
paul@124 839
        self.chain_invocation.append(self.in_invocation)
paul@124 840
        self.in_assignment = None
paul@553 841
        self.in_invocation = None
paul@110 842
paul@110 843
    def restore_attribute_chain(self, attrs):
paul@110 844
paul@110 845
        "Restore the attribute chain for an attribute access."
paul@110 846
paul@110 847
        self.attrs = attrs
paul@124 848
        self.in_assignment = self.chain_assignment.pop()
paul@124 849
        self.in_invocation = self.chain_invocation.pop()
paul@110 850
paul@0 851
    def have_access_expression(self, node):
paul@0 852
paul@0 853
        "Return whether the expression associated with 'node' is Getattr."
paul@0 854
paul@0 855
        return isinstance(node.expr, compiler.ast.Getattr)
paul@0 856
paul@678 857
    def get_name_for_tracking(self, name, name_ref=None, is_global=False):
paul@0 858
paul@0 859
        """
paul@0 860
        Return the name to be used for attribute usage observations involving
paul@603 861
        the given 'name' in the current namespace.
paul@603 862
paul@603 863
        If the name is being used outside a function, and if 'name_ref' is
paul@678 864
        given and indicates a global or if 'is_global' is specified as a true
paul@678 865
        value, a path featuring the name in the global namespace is returned.
paul@678 866
        Otherwise, a path computed using the current namespace and the given
paul@678 867
        name is returned.
paul@0 868
paul@0 869
        The intention of this method is to provide a suitably-qualified name
paul@0 870
        that can be tracked across namespaces. Where globals are being
paul@0 871
        referenced in class namespaces, they should be referenced using their
paul@0 872
        path within the module, not using a path within each class.
paul@0 873
paul@0 874
        It may not be possible to identify a global within a function at the
paul@0 875
        time of inspection (since a global may appear later in a file).
paul@0 876
        Consequently, globals are identified by their local name rather than
paul@0 877
        their module-qualified path.
paul@0 878
        """
paul@0 879
paul@0 880
        # For functions, use the appropriate local names.
paul@0 881
paul@0 882
        if self.in_function:
paul@0 883
            return name
paul@0 884
paul@603 885
        # For global names outside functions, use a global name.
paul@597 886
paul@678 887
        elif is_global or name_ref and name_ref.is_global_name():
paul@603 888
            return self.get_global_path(name)
paul@0 889
paul@152 890
        # Otherwise, establish a name in the current namespace.
paul@0 891
paul@0 892
        else:
paul@0 893
            return self.get_object_path(name)
paul@0 894
paul@0 895
    def get_path_for_access(self):
paul@0 896
paul@0 897
        "Outside functions, register accesses at the module level."
paul@0 898
paul@0 899
        if not self.in_function:
paul@0 900
            return self.name
paul@0 901
        else:
paul@0 902
            return self.get_namespace_path()
paul@0 903
paul@0 904
    def get_module_name(self, node):
paul@0 905
paul@0 906
        """
paul@0 907
        Using the given From 'node' in this module, calculate any relative import
paul@0 908
        information, returning a tuple containing a module to import along with any
paul@0 909
        names to import based on the node's name information.
paul@0 910
paul@0 911
        Where the returned module is given as None, whole module imports should
paul@0 912
        be performed for the returned modules using the returned names.
paul@0 913
        """
paul@0 914
paul@0 915
        # Absolute import.
paul@0 916
paul@0 917
        if node.level == 0:
paul@0 918
            return node.modname, node.names
paul@0 919
paul@0 920
        # Relative to an ancestor of this module.
paul@0 921
paul@0 922
        else:
paul@0 923
            path = self.name.split(".")
paul@0 924
            level = node.level
paul@0 925
paul@0 926
            # Relative imports treat package roots as submodules.
paul@0 927
paul@0 928
            if split(self.filename)[-1] == "__init__.py":
paul@0 929
                level -= 1
paul@0 930
paul@0 931
            if level > len(path):
paul@0 932
                raise InspectError("Relative import %r involves too many levels up from module %r" % (
paul@0 933
                    ("%s%s" % ("." * node.level, node.modname or "")), self.name))
paul@0 934
paul@0 935
            basename = ".".join(path[:len(path)-level])
paul@0 936
paul@0 937
        # Name imports from a module.
paul@0 938
paul@0 939
        if node.modname:
paul@0 940
            return "%s.%s" % (basename, node.modname), node.names
paul@0 941
paul@0 942
        # Relative whole module imports.
paul@0 943
paul@0 944
        else:
paul@0 945
            return basename, node.names
paul@0 946
paul@0 947
def get_argnames(args):
paul@0 948
paul@0 949
    """
paul@0 950
    Return a list of all names provided by 'args'. Since tuples may be
paul@0 951
    employed, the arguments are traversed depth-first.
paul@0 952
    """
paul@0 953
paul@0 954
    l = []
paul@0 955
    for arg in args:
paul@0 956
        if isinstance(arg, tuple):
paul@0 957
            l += get_argnames(arg)
paul@0 958
        else:
paul@0 959
            l.append(arg)
paul@0 960
    return l
paul@0 961
paul@509 962
def get_names_from_nodes(nodes):
paul@509 963
paul@509 964
    """
paul@509 965
    Return the names employed in the given 'nodes' along with the number of
paul@509 966
    nodes excluding sequences.
paul@509 967
    """
paul@509 968
paul@509 969
    names = set()
paul@509 970
    count = 0
paul@509 971
paul@509 972
    for node in nodes:
paul@509 973
paul@509 974
        # Add names and count them.
paul@509 975
paul@509 976
        if isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
paul@509 977
            names.add(node.name)
paul@509 978
            count += 1
paul@509 979
paul@509 980
        # Add names from sequences and incorporate their counts.
paul@509 981
paul@509 982
        elif isinstance(node, (compiler.ast.AssList, compiler.ast.AssTuple,
paul@509 983
                               compiler.ast.List, compiler.ast.Set,
paul@509 984
                               compiler.ast.Tuple)):
paul@509 985
            _names, _count = get_names_from_nodes(node.nodes)
paul@509 986
            names.update(_names)
paul@509 987
            count += _count
paul@509 988
paul@509 989
        # Count non-name, non-sequence nodes.
paul@509 990
paul@509 991
        else:
paul@509 992
            count += 1
paul@509 993
paul@509 994
    return names, count
paul@509 995
paul@791 996
# Location classes.
paul@791 997
paul@791 998
class Location:
paul@791 999
paul@791 1000
    "A generic program location."
paul@791 1001
paul@791 1002
    def __init__(self, path, name, attrnames=None, version=None, access_number=None):
paul@791 1003
        self.path = path
paul@791 1004
        self.name = name
paul@791 1005
        self.attrnames = attrnames
paul@791 1006
        self.version = version
paul@791 1007
        self.access_number = access_number
paul@791 1008
paul@791 1009
    def __repr__(self):
paul@791 1010
        return "Location(%r, %r, %r, %r, %r)" % self.as_tuple()
paul@791 1011
paul@791 1012
    def as_tuple(self):
paul@791 1013
        return (self.path, self.name, self.attrnames, self.version, self.access_number)
paul@791 1014
paul@791 1015
    def __hash__(self):
paul@791 1016
        return hash(self.as_tuple())
paul@791 1017
paul@791 1018
    def __eq__(self, other):
paul@791 1019
        return self.as_tuple() == other.as_tuple()
paul@791 1020
paul@791 1021
    def __cmp__(self, other):
paul@791 1022
        return cmp(self.as_tuple(), other.as_tuple())
paul@791 1023
paul@791 1024
    def get_attrname(self):
paul@791 1025
paul@791 1026
        """
paul@791 1027
        Extract the first attribute from the attribute names employed in this
paul@791 1028
        location.
paul@791 1029
        """
paul@791 1030
paul@791 1031
        attrnames = self.attrnames
paul@791 1032
        if not attrnames:
paul@791 1033
            return attrnames
paul@791 1034
        return get_attrnames(attrnames)[0]
paul@791 1035
paul@791 1036
class AccessLocation(Location):
paul@791 1037
paul@791 1038
    "A specialised access location."
paul@791 1039
paul@791 1040
    def __init__(self, path, name, attrnames, access_number):
paul@791 1041
paul@791 1042
        """
paul@791 1043
        Initialise an access location featuring 'path', 'name', 'attrnames' and
paul@791 1044
        'access_number'.
paul@791 1045
        """
paul@791 1046
paul@791 1047
        Location.__init__(self, path, name, attrnames, None, access_number)
paul@791 1048
paul@791 1049
    def __repr__(self):
paul@791 1050
        return "AccessLocation(%r, %r, %r, %r)" % (self.path, self.name, self.attrnames, self.access_number)
paul@791 1051
paul@491 1052
# Result classes.
paul@491 1053
paul@491 1054
class InstructionSequence:
paul@491 1055
paul@491 1056
    "A generic sequence of instructions."
paul@491 1057
paul@491 1058
    def __init__(self, instructions):
paul@491 1059
        self.instructions = instructions
paul@491 1060
paul@491 1061
    def get_value_instruction(self):
paul@491 1062
        return self.instructions[-1]
paul@491 1063
paul@491 1064
    def get_init_instructions(self):
paul@491 1065
        return self.instructions[:-1]
paul@491 1066
paul@0 1067
# Dictionary utilities.
paul@0 1068
paul@0 1069
def init_item(d, key, fn):
paul@0 1070
paul@0 1071
    """
paul@0 1072
    Add to 'd' an entry for 'key' using the callable 'fn' to make an initial
paul@0 1073
    value where no entry already exists.
paul@0 1074
    """
paul@0 1075
paul@0 1076
    if not d.has_key(key):
paul@0 1077
        d[key] = fn()
paul@0 1078
    return d[key]
paul@0 1079
paul@0 1080
def dict_for_keys(d, keys):
paul@0 1081
paul@0 1082
    "Return a new dictionary containing entries from 'd' for the given 'keys'."
paul@0 1083
paul@0 1084
    nd = {}
paul@0 1085
    for key in keys:
paul@0 1086
        if d.has_key(key):
paul@0 1087
            nd[key] = d[key]
paul@0 1088
    return nd
paul@0 1089
paul@0 1090
def make_key(s):
paul@0 1091
paul@0 1092
    "Make sequence 's' into a tuple-based key, first sorting its contents."
paul@0 1093
paul@0 1094
    l = list(s)
paul@0 1095
    l.sort()
paul@0 1096
    return tuple(l)
paul@0 1097
paul@0 1098
def add_counter_item(d, key):
paul@0 1099
paul@0 1100
    """
paul@0 1101
    Make a mapping in 'd' for 'key' to the number of keys added before it, thus
paul@0 1102
    maintaining a mapping of keys to their order of insertion.
paul@0 1103
    """
paul@0 1104
paul@0 1105
    if not d.has_key(key):
paul@0 1106
        d[key] = len(d.keys())
paul@0 1107
    return d[key] 
paul@0 1108
paul@0 1109
def remove_items(d1, d2):
paul@0 1110
paul@0 1111
    "Remove from 'd1' all items from 'd2'."
paul@0 1112
paul@0 1113
    for key in d2.keys():
paul@0 1114
        if d1.has_key(key):
paul@0 1115
            del d1[key]
paul@0 1116
paul@0 1117
# Set utilities.
paul@0 1118
paul@0 1119
def first(s):
paul@0 1120
    return list(s)[0]
paul@0 1121
paul@0 1122
def same(s1, s2):
paul@0 1123
    return set(s1) == set(s2)
paul@0 1124
paul@724 1125
def order_dependencies(all_depends):
paul@724 1126
paul@724 1127
    """
paul@724 1128
    Produce a dependency ordering for the 'all_depends' mapping. This mapping
paul@724 1129
    has the form "A depends on B, C...". The result will order A, B, C, and so
paul@724 1130
    on.
paul@724 1131
    """
paul@724 1132
paul@726 1133
    usage = init_reverse_dependencies(all_depends)
paul@726 1134
paul@726 1135
    # Produce an ordering by obtaining exposed items (required by items already
paul@726 1136
    # processed) and putting them at the start of the list.
paul@726 1137
paul@726 1138
    ordered = []
paul@726 1139
paul@726 1140
    while usage:
paul@726 1141
        have_next = False
paul@726 1142
paul@726 1143
        for key, n in usage.items():
paul@726 1144
paul@726 1145
            # Add items needed by no other items to the ordering.
paul@726 1146
paul@726 1147
            if not n:
paul@726 1148
                remove_dependency(key, all_depends, usage, ordered)
paul@726 1149
                have_next = True
paul@726 1150
paul@726 1151
        if not have_next:
paul@726 1152
            raise ValueError, usage
paul@726 1153
paul@726 1154
    return ordered
paul@726 1155
paul@726 1156
def order_dependencies_partial(all_depends):
paul@726 1157
paul@726 1158
    """
paul@726 1159
    Produce a dependency ordering for the 'all_depends' mapping. This mapping
paul@726 1160
    has the form "A depends on B, C...". The result will order A, B, C, and so
paul@726 1161
    on. Where cycles exist, they will be broken and a partial ordering returned.
paul@726 1162
    """
paul@726 1163
paul@726 1164
    usage = init_reverse_dependencies(all_depends)
paul@726 1165
paul@726 1166
    # Duplicate the dependencies for subsequent modification.
paul@726 1167
paul@726 1168
    new_depends = {}
paul@726 1169
    for key, values in all_depends.items():
paul@726 1170
        new_depends[key] = set(values)
paul@726 1171
paul@726 1172
    all_depends = new_depends
paul@726 1173
paul@726 1174
    # Produce an ordering by obtaining exposed items (required by items already
paul@726 1175
    # processed) and putting them at the start of the list.
paul@726 1176
paul@726 1177
    ordered = []
paul@726 1178
paul@726 1179
    while usage:
paul@726 1180
        least = None
paul@726 1181
        least_key = None
paul@726 1182
paul@726 1183
        for key, n in usage.items():
paul@726 1184
paul@726 1185
            # Add items needed by no other items to the ordering.
paul@726 1186
paul@726 1187
            if not n:
paul@726 1188
                remove_dependency(key, all_depends, usage, ordered)
paul@726 1189
                least = 0
paul@726 1190
paul@726 1191
            # When breaking cycles, note the least used items.
paul@726 1192
paul@726 1193
            elif least is None or len(n) < least:
paul@726 1194
                least_key = key
paul@726 1195
                least = len(n)
paul@726 1196
paul@726 1197
        if least:
paul@726 1198
            transfer_dependencies(least_key, all_depends, usage, ordered)
paul@726 1199
paul@726 1200
    return ordered
paul@726 1201
paul@726 1202
def init_reverse_dependencies(all_depends):
paul@726 1203
paul@726 1204
    """
paul@726 1205
    From 'all_depends', providing a mapping of the form "A depends on B, C...",
paul@726 1206
    record the reverse dependencies, making a mapping of the form
paul@726 1207
    "B is needed by A", "C is needed by A", and so on.
paul@726 1208
    """
paul@724 1209
paul@724 1210
    usage = {}
paul@724 1211
paul@724 1212
    # Record path-based dependencies.
paul@724 1213
paul@724 1214
    for key in all_depends.keys():
paul@724 1215
        usage[key] = set()
paul@724 1216
paul@724 1217
    for key, depends in all_depends.items():
paul@724 1218
        for depend in depends:
paul@724 1219
            init_item(usage, depend, set)
paul@724 1220
            usage[depend].add(key)
paul@724 1221
paul@726 1222
    return usage
paul@726 1223
paul@726 1224
def transfer_dependencies(key, all_depends, usage, ordered):
paul@726 1225
paul@726 1226
    """
paul@726 1227
    Transfer items needed by 'key' to those items needing 'key', found using
paul@726 1228
    'all_depends', and updating 'usage'. Insert 'key' into the 'ordered'
paul@726 1229
    collection of dependencies.
paul@724 1230
paul@726 1231
    If "A is needed by X" and "B is needed by A", then transferring items needed
paul@726 1232
    by A will cause "B is needed by X" to be recorded as a consequence.
paul@726 1233
paul@726 1234
    Transferring items also needs to occur in the reverse mapping, so that
paul@726 1235
    "A needs B" and "X needs A", then the consequence must be recorded as
paul@726 1236
    "X needs B".
paul@726 1237
    """
paul@726 1238
paul@726 1239
    ordered.insert(0, key)
paul@724 1240
paul@726 1241
    needing = usage[key]                        # A is needed by X
paul@726 1242
    needed = all_depends.get(key)               # A needs B
paul@726 1243
paul@726 1244
    if needing:
paul@726 1245
        for depend in needing:
paul@726 1246
            l = all_depends.get(depend)
paul@726 1247
            if not l:
paul@726 1248
                continue
paul@724 1249
paul@726 1250
            l.remove(key)                       # X needs (A)
paul@726 1251
paul@726 1252
            if needed:
paul@726 1253
                l.update(needed)                # X needs B...
paul@726 1254
paul@726 1255
                # Prevent self references.
paul@726 1256
paul@726 1257
                if depend in needed:
paul@726 1258
                    l.remove(depend)
paul@724 1259
paul@726 1260
    if needed:
paul@726 1261
        for depend in needed:
paul@726 1262
            l = usage.get(depend)
paul@726 1263
            if not l:
paul@726 1264
                continue
paul@726 1265
paul@726 1266
            l.remove(key)                       # B is needed by (A)
paul@726 1267
            l.update(needing)                   # B is needed by X...
paul@724 1268
paul@726 1269
            # Prevent self references.
paul@726 1270
paul@726 1271
            if depend in needing:
paul@726 1272
                l.remove(depend)
paul@726 1273
paul@726 1274
    if needed:
paul@726 1275
        del all_depends[key]
paul@726 1276
    del usage[key]
paul@726 1277
paul@726 1278
def remove_dependency(key, all_depends, usage, ordered):
paul@724 1279
paul@726 1280
    """
paul@726 1281
    Remove 'key', found in 'all_depends', from 'usage', inserting it into the
paul@726 1282
    'ordered' collection of dependencies.
paul@726 1283
paul@726 1284
    Given that 'usage' for a given key A would indicate that "A needs <nothing>"
paul@726 1285
    upon removing A from 'usage', the outcome is that all keys needing A will
paul@726 1286
    have A removed from their 'usage' records.
paul@726 1287
paul@726 1288
    So, if "B needs A", removing A will cause "B needs <nothing>" to be recorded
paul@726 1289
    as a consequence.
paul@726 1290
    """
paul@724 1291
paul@726 1292
    ordered.insert(0, key)
paul@726 1293
paul@726 1294
    depends = all_depends.get(key)
paul@726 1295
paul@726 1296
    # Reduce usage of the referenced items.
paul@724 1297
paul@726 1298
    if depends:
paul@726 1299
        for depend in depends:
paul@726 1300
            usage[depend].remove(key)
paul@726 1301
paul@726 1302
    del usage[key]
paul@724 1303
paul@0 1304
# General input/output.
paul@0 1305
paul@0 1306
def readfile(filename):
paul@0 1307
paul@0 1308
    "Return the contents of 'filename'."
paul@0 1309
paul@0 1310
    f = open(filename)
paul@0 1311
    try:
paul@0 1312
        return f.read()
paul@0 1313
    finally:
paul@0 1314
        f.close()
paul@0 1315
paul@0 1316
def writefile(filename, s):
paul@0 1317
paul@0 1318
    "Write to 'filename' the string 's'."
paul@0 1319
paul@0 1320
    f = open(filename, "w")
paul@0 1321
    try:
paul@0 1322
        f.write(s)
paul@0 1323
    finally:
paul@0 1324
        f.close()
paul@0 1325
paul@0 1326
# General encoding.
paul@0 1327
paul@0 1328
def sorted_output(x):
paul@0 1329
paul@0 1330
    "Sort sequence 'x' and return a string with commas separating the values."
paul@0 1331
paul@0 1332
    x = map(str, x)
paul@0 1333
    x.sort()
paul@0 1334
    return ", ".join(x)
paul@0 1335
paul@537 1336
def get_string_details(literals, encoding):
paul@512 1337
paul@512 1338
    """
paul@537 1339
    Determine whether 'literals' represent Unicode strings or byte strings,
paul@537 1340
    using 'encoding' to reproduce byte sequences.
paul@537 1341
paul@537 1342
    Each literal is the full program representation including prefix and quotes
paul@537 1343
    recoded by the parser to UTF-8. Thus, any literal found to represent a byte
paul@537 1344
    string needs to be translated back to its original encoding.
paul@537 1345
paul@537 1346
    Return a single encoded literal value, a type name, and the original
paul@537 1347
    encoding as a tuple.
paul@537 1348
    """
paul@537 1349
paul@537 1350
    typename = "unicode"
paul@537 1351
paul@537 1352
    l = []
paul@537 1353
paul@537 1354
    for s in literals:
paul@537 1355
        out, _typename = get_literal_details(s)
paul@537 1356
        if _typename == "str":
paul@537 1357
            typename = "str"
paul@537 1358
        l.append(out)
paul@537 1359
paul@537 1360
    out = "".join(l)
paul@537 1361
paul@537 1362
    # For Unicode values, convert to the UTF-8 program representation.
paul@537 1363
paul@537 1364
    if typename == "unicode":
paul@537 1365
        return out.encode("utf-8"), typename, encoding
paul@537 1366
paul@537 1367
    # For byte string values, convert back to the original encoding.
paul@537 1368
paul@537 1369
    else:
paul@537 1370
        return out.encode(encoding), typename, encoding
paul@537 1371
paul@537 1372
def get_literal_details(s):
paul@537 1373
paul@537 1374
    """
paul@537 1375
    Determine whether 's' represents a Unicode string or a byte string, where
paul@537 1376
    's' contains the full program representation of a literal including prefix
paul@537 1377
    and quotes, recoded by the parser to UTF-8.
paul@512 1378
paul@512 1379
    Find and convert Unicode values starting with <backslash>u or <backslash>U,
paul@512 1380
    and byte or Unicode values starting with <backslash><octal digit> or
paul@512 1381
    <backslash>x.
paul@512 1382
paul@512 1383
    Literals prefixed with "u" cause <backslash><octal digit> and <backslash>x
paul@512 1384
    to be considered as Unicode values. Otherwise, they produce byte values and
paul@512 1385
    cause unprefixed strings to be considered as byte strings.
paul@512 1386
paul@512 1387
    Literals prefixed with "r" do not have their backslash-encoded values
paul@512 1388
    converted unless also prefixed with "u", in which case only the above value
paul@512 1389
    formats are converted, not any of the other special sequences for things
paul@512 1390
    like newlines.
paul@512 1391
paul@537 1392
    Return the literal value as a Unicode object together with the appropriate
paul@537 1393
    type name in a tuple.
paul@512 1394
    """
paul@512 1395
paul@512 1396
    l = []
paul@512 1397
paul@512 1398
    # Identify the quote character and use it to identify the prefix.
paul@512 1399
paul@512 1400
    quote_type = s[-1]
paul@512 1401
    prefix_end = s.find(quote_type)
paul@512 1402
    prefix = s[:prefix_end].lower()
paul@512 1403
paul@512 1404
    if prefix not in ("", "b", "br", "r", "u", "ur"):
paul@512 1405
        raise ValueError, "String literal does not have a supported prefix: %s" % s
paul@512 1406
paul@513 1407
    if "b" in prefix:
paul@513 1408
        typename = "str"
paul@513 1409
    else:
paul@513 1410
        typename = "unicode"
paul@513 1411
paul@512 1412
    # Identify triple quotes or single quotes.
paul@512 1413
paul@512 1414
    if len(s) >= 6 and s[-2] == quote_type and s[-3] == quote_type:
paul@512 1415
        quote = s[prefix_end:prefix_end+3]
paul@512 1416
        current = prefix_end + 3
paul@512 1417
        end = len(s) - 3
paul@512 1418
    else:
paul@512 1419
        quote = s[prefix_end]
paul@512 1420
        current = prefix_end + 1
paul@512 1421
        end = len(s) - 1
paul@512 1422
paul@512 1423
    # Conversions of some quoted values.
paul@512 1424
paul@512 1425
    searches = {
paul@512 1426
        "u" : (6, 16),
paul@512 1427
        "U" : (10, 16),
paul@512 1428
        "x" : (4, 16),
paul@512 1429
        }
paul@512 1430
paul@512 1431
    octal_digits = map(str, range(0, 8))
paul@512 1432
paul@512 1433
    # Translations of some quoted values.
paul@512 1434
paul@512 1435
    escaped = {
paul@512 1436
        "\\" : "\\", "'" : "'", '"' : '"',
paul@512 1437
        "a" : "\a", "b" : "\b", "f" : "\f",
paul@512 1438
        "n" : "\n", "r" : "\r", "t" : "\t",
paul@512 1439
        }
paul@512 1440
paul@512 1441
    while current < end:
paul@512 1442
paul@512 1443
        # Look for quoted values.
paul@512 1444
paul@512 1445
        index = s.find("\\", current)
paul@512 1446
        if index == -1 or index + 1 == end:
paul@512 1447
            l.append(s[current:end])
paul@512 1448
            break
paul@512 1449
paul@512 1450
        # Add the preceding text.
paul@512 1451
paul@512 1452
        l.append(s[current:index])
paul@512 1453
paul@512 1454
        # Handle quoted text.
paul@512 1455
paul@512 1456
        term = s[index+1]
paul@512 1457
paul@512 1458
        # Add Unicode values. Where a string is u-prefixed, even \o and \x
paul@512 1459
        # produce Unicode values.
paul@512 1460
paul@513 1461
        if typename == "unicode" and (
paul@513 1462
            term in ("u", "U") or 
paul@513 1463
            "u" in prefix and (term == "x" or term in octal_digits)):
paul@512 1464
paul@512 1465
            needed, base = searches.get(term, (4, 8))
paul@512 1466
            value = convert_quoted_value(s, index, needed, end, base, unichr)
paul@512 1467
            l.append(value)
paul@512 1468
            current = index + needed
paul@512 1469
paul@512 1470
        # Add raw byte values, changing the string type.
paul@512 1471
paul@512 1472
        elif "r" not in prefix and (
paul@512 1473
             term == "x" or term in octal_digits):
paul@512 1474
paul@512 1475
            needed, base = searches.get(term, (4, 8))
paul@512 1476
            value = convert_quoted_value(s, index, needed, end, base, chr)
paul@512 1477
            l.append(value)
paul@512 1478
            typename = "str"
paul@512 1479
            current = index + needed
paul@512 1480
paul@512 1481
        # Add other escaped values.
paul@512 1482
paul@512 1483
        elif "r" not in prefix and escaped.has_key(term):
paul@512 1484
            l.append(escaped[term])
paul@512 1485
            current = index + 2
paul@512 1486
paul@512 1487
        # Add other text as found.
paul@512 1488
paul@512 1489
        else:
paul@512 1490
            l.append(s[index:index+2])
paul@512 1491
            current = index + 2
paul@512 1492
paul@537 1493
    # Collect the components into a single Unicode object. Since the literal
paul@537 1494
    # text was already in UTF-8 form, interpret plain strings as UTF-8
paul@537 1495
    # sequences.
paul@512 1496
paul@537 1497
    out = []
paul@512 1498
paul@537 1499
    for value in l:
paul@537 1500
        if isinstance(value, unicode):
paul@537 1501
            out.append(value)
paul@537 1502
        else:
paul@537 1503
            out.append(unicode(value, "utf-8"))
paul@512 1504
paul@537 1505
    return "".join(out), typename
paul@512 1506
paul@512 1507
def convert_quoted_value(s, index, needed, end, base, fn):
paul@512 1508
paul@512 1509
    """
paul@512 1510
    Interpret a quoted value in 's' at 'index' with the given 'needed' number of
paul@512 1511
    positions, and with the given 'end' indicating the first position after the
paul@512 1512
    end of the actual string content.
paul@512 1513
paul@512 1514
    Use 'base' as the numerical base when interpreting the value, and use 'fn'
paul@512 1515
    to convert the value to an appropriate type.
paul@512 1516
    """
paul@512 1517
paul@512 1518
    s = s[index:min(index+needed, end)]
paul@512 1519
paul@512 1520
    # Not a complete occurrence.
paul@512 1521
paul@512 1522
    if len(s) < needed:
paul@512 1523
        return s
paul@512 1524
paul@512 1525
    # Test for a well-formed value.
paul@512 1526
paul@512 1527
    try:
paul@512 1528
        first = base == 8 and 1 or 2
paul@512 1529
        value = int(s[first:needed], base)
paul@512 1530
    except ValueError:
paul@512 1531
        return s
paul@512 1532
    else:
paul@512 1533
        return fn(value)
paul@512 1534
paul@0 1535
# Attribute chain decoding.
paul@0 1536
paul@0 1537
def get_attrnames(attrnames):
paul@11 1538
paul@11 1539
    """
paul@11 1540
    Split the qualified attribute chain 'attrnames' into its components,
paul@11 1541
    handling special attributes starting with "#" that indicate type
paul@11 1542
    conformance.
paul@11 1543
    """
paul@11 1544
paul@0 1545
    if attrnames.startswith("#"):
paul@0 1546
        return [attrnames]
paul@0 1547
    else:
paul@0 1548
        return attrnames.split(".")
paul@0 1549
paul@85 1550
def get_name_path(path, name):
paul@85 1551
paul@85 1552
    "Return a suitable qualified name from the given 'path' and 'name'."
paul@85 1553
paul@85 1554
    if "." in name:
paul@85 1555
        return name
paul@85 1556
    else:
paul@85 1557
        return "%s.%s" % (path, name)
paul@85 1558
paul@90 1559
# Usage-related functions.
paul@89 1560
paul@89 1561
def get_types_for_usage(attrnames, objects):
paul@89 1562
paul@89 1563
    """
paul@89 1564
    Identify the types that can support the given 'attrnames', using the
paul@89 1565
    given 'objects' as the catalogue of type details.
paul@89 1566
    """
paul@89 1567
paul@89 1568
    types = []
paul@89 1569
    for name, _attrnames in objects.items():
paul@89 1570
        if set(attrnames).issubset(_attrnames):
paul@89 1571
            types.append(name)
paul@89 1572
    return types
paul@89 1573
paul@90 1574
def get_invoked_attributes(usage):
paul@90 1575
paul@90 1576
    "Obtain invoked attribute from the given 'usage'."
paul@90 1577
paul@90 1578
    invoked = []
paul@90 1579
    if usage:
paul@107 1580
        for attrname, invocation, assignment in usage:
paul@90 1581
            if invocation:
paul@90 1582
                invoked.append(attrname)
paul@90 1583
    return invoked
paul@90 1584
paul@107 1585
def get_assigned_attributes(usage):
paul@107 1586
paul@107 1587
    "Obtain assigned attribute from the given 'usage'."
paul@107 1588
paul@107 1589
    assigned = []
paul@107 1590
    if usage:
paul@107 1591
        for attrname, invocation, assignment in usage:
paul@107 1592
            if assignment:
paul@107 1593
                assigned.append(attrname)
paul@107 1594
    return assigned
paul@107 1595
paul@366 1596
# Type and module functions.
paul@538 1597
# NOTE: This makes assumptions about the __builtins__ structure.
paul@366 1598
paul@366 1599
def get_builtin_module(name):
paul@366 1600
paul@366 1601
    "Return the module name containing the given type 'name'."
paul@366 1602
paul@938 1603
    if name == "NoneType":
paul@538 1604
        modname = "none"
paul@394 1605
    else:
paul@538 1606
        modname = name
paul@538 1607
paul@538 1608
    return "__builtins__.%s" % modname
paul@366 1609
paul@366 1610
def get_builtin_type(name):
paul@366 1611
paul@366 1612
    "Return the type name provided by the given Python value 'name'."
paul@366 1613
paul@938 1614
    return name
paul@366 1615
paul@538 1616
def get_builtin_class(name):
paul@538 1617
paul@538 1618
    "Return the full name of the built-in class having the given 'name'."
paul@538 1619
paul@538 1620
    typename = get_builtin_type(name)
paul@538 1621
    module = get_builtin_module(typename)
paul@538 1622
    return "%s.%s" % (module, typename)
paul@538 1623
paul@0 1624
# Useful data.
paul@0 1625
paul@11 1626
predefined_constants = "False", "None", "NotImplemented", "True"
paul@0 1627
paul@845 1628
privileged_attributes = [
paul@845 1629
    "__get_single_item_unchecked__",
paul@845 1630
    ]
paul@845 1631
paul@797 1632
unary_operator_functions = {
paul@797 1633
paul@797 1634
    # Unary operations.
paul@797 1635
paul@797 1636
    "Invert" : "invert",
paul@797 1637
    "UnaryAdd" : "pos",
paul@797 1638
    "UnarySub" : "neg",
paul@797 1639
    }
paul@797 1640
paul@0 1641
operator_functions = {
paul@0 1642
paul@0 1643
    # Fundamental operations.
paul@0 1644
paul@0 1645
    "is" : "is_",
paul@0 1646
    "is not" : "is_not",
paul@0 1647
paul@0 1648
    # Binary operations.
paul@0 1649
paul@0 1650
    "in" : "in_",
paul@0 1651
    "not in" : "not_in",
paul@0 1652
    "Add" : "add",
paul@0 1653
    "Bitand" : "and_",
paul@0 1654
    "Bitor" : "or_",
paul@0 1655
    "Bitxor" : "xor",
paul@0 1656
    "Div" : "div",
paul@0 1657
    "FloorDiv" : "floordiv",
paul@0 1658
    "LeftShift" : "lshift",
paul@0 1659
    "Mod" : "mod",
paul@0 1660
    "Mul" : "mul",
paul@0 1661
    "Power" : "pow",
paul@0 1662
    "RightShift" : "rshift",
paul@0 1663
    "Sub" : "sub",
paul@0 1664
paul@0 1665
    # Augmented assignment.
paul@0 1666
paul@0 1667
    "+=" : "iadd",
paul@0 1668
    "-=" : "isub",
paul@0 1669
    "*=" : "imul",
paul@0 1670
    "/=" : "idiv",
paul@0 1671
    "//=" : "ifloordiv",
paul@0 1672
    "%=" : "imod",
paul@0 1673
    "**=" : "ipow",
paul@0 1674
    "<<=" : "ilshift",
paul@0 1675
    ">>=" : "irshift",
paul@0 1676
    "&=" : "iand",
paul@0 1677
    "^=" : "ixor",
paul@0 1678
    "|=" : "ior",
paul@0 1679
paul@0 1680
    # Comparisons.
paul@0 1681
paul@0 1682
    "==" : "eq",
paul@0 1683
    "!=" : "ne",
paul@0 1684
    "<" : "lt",
paul@0 1685
    "<=" : "le",
paul@0 1686
    ">=" : "ge",
paul@0 1687
    ">" : "gt",
paul@0 1688
    }
paul@0 1689
paul@797 1690
operator_functions.update(unary_operator_functions)
paul@797 1691
paul@0 1692
# vim: tabstop=4 expandtab shiftwidth=4