Lichen

Annotated common.py

1045:e6010ccae0c0
5 months ago Paul Boddie Fixed list element assignment, overlooked in previous value replacement changes. value-replacement
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@1042 384
        elif isinstance(n.node, compiler.ast.Subscript):
paul@1042 385
            target = compiler.ast.Subscript(n.node.expr, "OP_ASSIGN", n.node.subs)
paul@0 386
        else:
paul@0 387
            target = n.node
paul@0 388
paul@0 389
        assignment = compiler.ast.Assign(
paul@0 390
            [target],
paul@0 391
            compiler.ast.CallFunc(
paul@0 392
                compiler.ast.Name("$op%s" % op),
paul@0 393
                [n.node, n.expr]))
paul@0 394
paul@0 395
        return self.process_structure_node(assignment)
paul@0 396
paul@320 397
    def process_assignment_for_object(self, original_name, source):
paul@0 398
paul@0 399
        """
paul@0 400
        Return an assignment operation making 'original_name' refer to the given
paul@196 401
        'source'.
paul@0 402
        """
paul@0 403
paul@0 404
        assignment = compiler.ast.Assign(
paul@0 405
            [compiler.ast.AssName(original_name, "OP_ASSIGN")],
paul@196 406
            source
paul@0 407
            )
paul@0 408
paul@0 409
        return self.process_structure_node(assignment)
paul@0 410
paul@0 411
    def process_assignment_node_items(self, n, expr):
paul@0 412
paul@0 413
        """
paul@0 414
        Process the given assignment node 'n' whose children are to be assigned
paul@0 415
        items of 'expr'.
paul@0 416
        """
paul@0 417
paul@0 418
        name_ref = self.process_structure_node(expr)
paul@0 419
paul@509 420
        # Either unpack the items and present them directly to each assignment
paul@509 421
        # node.
paul@509 422
paul@509 423
        if isinstance(name_ref, LiteralSequenceRef) and \
paul@509 424
           self.process_literal_sequence_items(n, name_ref):
paul@0 425
paul@509 426
            pass
paul@509 427
paul@509 428
        # Or have the assignment nodes access each item via the sequence API.
paul@509 429
paul@509 430
        else:
paul@509 431
            self.process_assignment_node_items_by_position(n, expr, name_ref)
paul@0 432
paul@0 433
    def process_assignment_node_items_by_position(self, n, expr, name_ref):
paul@0 434
paul@0 435
        """
paul@0 436
        Process the given sequence assignment node 'n', converting the node to
paul@0 437
        the separate assignment of each target using positional access on a
paul@0 438
        temporary variable representing the sequence. Use 'expr' as the assigned
paul@0 439
        value and 'name_ref' as the reference providing any existing temporary
paul@0 440
        variable.
paul@0 441
        """
paul@0 442
paul@835 443
        statements = []
paul@0 444
paul@508 445
        # Employ existing names to access the sequence.
paul@508 446
        # Literal sequences do not provide names of accessible objects.
paul@508 447
paul@508 448
        if isinstance(name_ref, NameRef) and not isinstance(name_ref, LiteralSequenceRef):
paul@0 449
            temp = name_ref.name
paul@508 450
paul@508 451
        # For other expressions, create a temporary name to reference the items.
paul@508 452
paul@0 453
        else:
paul@0 454
            temp = self.get_temporary_name()
paul@0 455
            self.next_temporary()
paul@0 456
paul@835 457
            statements.append(
paul@0 458
                compiler.ast.Assign([compiler.ast.AssName(temp, "OP_ASSIGN")], expr)
paul@0 459
                )
paul@0 460
paul@835 461
        # Generate a test for the length of the expression object.
paul@835 462
paul@835 463
        statements.append(compiler.ast.Discard(
paul@835 464
            compiler.ast.CallFunc(compiler.ast.Name("$seq_test_length"),
paul@835 465
                [compiler.ast.Name(temp), compiler.ast.Const(len(n.nodes))])))
paul@835 466
paul@508 467
        # Assign the items to the target nodes.
paul@508 468
paul@0 469
        for i, node in enumerate(n.nodes):
paul@835 470
            statements.append(
paul@836 471
                compiler.ast.Assign([node], compiler.ast.CallFunc(
paul@845 472
                    compiler.ast.Getattr(compiler.ast.Name(temp),
paul@845 473
                                         "__get_single_item_unchecked__",
paul@845 474
                                         privileged=True),
paul@836 475
                    [compiler.ast.Const(i, str(i))]))
paul@0 476
                )
paul@0 477
paul@835 478
        return self.process_structure_node(compiler.ast.Stmt(statements))
paul@0 479
paul@0 480
    def process_literal_sequence_items(self, n, name_ref):
paul@0 481
paul@0 482
        """
paul@0 483
        Process the given assignment node 'n', obtaining from the given
paul@0 484
        'name_ref' the items to be assigned to the assignment targets.
paul@509 485
paul@509 486
        Return whether this method was able to process the assignment node as
paul@509 487
        a sequence of direct assignments.
paul@0 488
        """
paul@0 489
paul@0 490
        if len(n.nodes) == len(name_ref.items):
paul@509 491
            assigned_names, count = get_names_from_nodes(n.nodes)
paul@509 492
            accessed_names, _count = get_names_from_nodes(name_ref.items)
paul@509 493
paul@509 494
            # Only assign directly between items if all assigned names are
paul@509 495
            # plain names (not attribute assignments), and if the assigned names
paul@509 496
            # do not appear in the accessed names.
paul@509 497
paul@509 498
            if len(assigned_names) == count and \
paul@509 499
               not assigned_names.intersection(accessed_names):
paul@509 500
paul@509 501
                for node, item in zip(n.nodes, name_ref.items):
paul@509 502
                    self.process_assignment_node(node, item)
paul@509 503
paul@509 504
                return True
paul@509 505
paul@509 506
            # Otherwise, use the position-based mechanism to obtain values.
paul@509 507
paul@509 508
            else:
paul@509 509
                return False
paul@0 510
        else:
paul@0 511
            raise InspectError("In %s, item assignment needing %d items is given %d items." % (
paul@0 512
                self.get_namespace_path(), len(n.nodes), len(name_ref.items)))
paul@0 513
paul@0 514
    def process_compare_node(self, n):
paul@0 515
paul@0 516
        """
paul@0 517
        Process the given comparison node 'n', converting an operator sequence
paul@0 518
        from...
paul@0 519
paul@0 520
        <expr1> <op1> <expr2> <op2> <expr3>
paul@0 521
paul@0 522
        ...to...
paul@0 523
paul@0 524
        <op1>(<expr1>, <expr2>) and <op2>(<expr2>, <expr3>)
paul@0 525
        """
paul@0 526
paul@0 527
        invocations = []
paul@0 528
        last = n.expr
paul@0 529
paul@0 530
        for op, op_node in n.ops:
paul@0 531
            op = operator_functions.get(op)
paul@0 532
paul@0 533
            invocations.append(compiler.ast.CallFunc(
paul@0 534
                compiler.ast.Name("$op%s" % op),
paul@0 535
                [last, op_node]))
paul@0 536
paul@0 537
            last = op_node
paul@0 538
paul@0 539
        if len(invocations) > 1:
paul@0 540
            result = compiler.ast.And(invocations)
paul@0 541
        else:
paul@0 542
            result = invocations[0]
paul@0 543
paul@0 544
        return self.process_structure_node(result)
paul@0 545
paul@0 546
    def process_dict_node(self, node):
paul@0 547
paul@0 548
        """
paul@0 549
        Process the given dictionary 'node', returning a list of (key, value)
paul@0 550
        tuples.
paul@0 551
        """
paul@0 552
paul@0 553
        l = []
paul@0 554
        for key, value in node.items:
paul@0 555
            l.append((
paul@0 556
                self.process_structure_node(key),
paul@0 557
                self.process_structure_node(value)))
paul@0 558
        return l
paul@0 559
paul@0 560
    def process_for_node(self, n):
paul@0 561
paul@0 562
        """
paul@0 563
        Generate attribute accesses for {n.list}.__iter__ and the next method on
paul@0 564
        the iterator, producing a replacement node for the original.
paul@0 565
        """
paul@0 566
paul@705 567
        t0 = self.get_temporary_name()
paul@705 568
        self.next_temporary()
paul@705 569
        t1 = self.get_temporary_name()
paul@705 570
        self.next_temporary()
paul@832 571
        t2 = self.get_temporary_name()
paul@832 572
        self.next_temporary()
paul@904 573
        t3 = self.get_temporary_name()
paul@904 574
        self.next_temporary()
paul@704 575
paul@0 576
        node = compiler.ast.Stmt([
paul@0 577
paul@705 578
            # <t0> = {n.list}
paul@705 579
            # <t1> = <t0>.__iter__()
paul@705 580
paul@705 581
            compiler.ast.Assign(
paul@705 582
                [compiler.ast.AssName(t0, "OP_ASSIGN")],
paul@705 583
                n.list),
paul@705 584
paul@705 585
            compiler.ast.Assign(
paul@705 586
                [compiler.ast.AssName(t1, "OP_ASSIGN")],
paul@705 587
                compiler.ast.CallFunc(
paul@705 588
                    compiler.ast.Getattr(compiler.ast.Name(t0), "__iter__"),
paul@705 589
                    [])),
paul@0 590
paul@832 591
            # <t2> = <t1>.next
paul@0 592
            # try:
paul@0 593
            #     while True:
paul@868 594
            #         try:
paul@904 595
            #             <t3> = <t2>()
paul@868 596
            #         except StopIteration:
paul@868 597
            #             raise LoopExit
paul@904 598
            #         <var>... = <t3>
paul@868 599
            #         {n.body}
paul@868 600
            # except LoopExit:
paul@866 601
            #     {n.else_}
paul@0 602
            #     pass
paul@0 603
paul@832 604
            compiler.ast.Assign(
paul@832 605
                [compiler.ast.AssName(t2, "OP_ASSIGN")],
paul@832 606
                compiler.ast.Getattr(compiler.ast.Name(t1), "next")),
paul@832 607
paul@909 608
            # try:
paul@909 609
paul@0 610
            compiler.ast.TryExcept(
paul@909 611
paul@909 612
                # while True:
paul@909 613
paul@0 614
                compiler.ast.While(
paul@0 615
                    compiler.ast.Name("True"),
paul@0 616
                    compiler.ast.Stmt([
paul@909 617
paul@909 618
                        # try:
paul@909 619
paul@868 620
                        compiler.ast.TryExcept(
paul@909 621
paul@909 622
                            # <t3> = <t2>()
paul@909 623
paul@868 624
                            compiler.ast.Assign(
paul@904 625
                                [compiler.ast.AssName(t3, "OP_ASSIGN")],
paul@868 626
                                compiler.ast.CallFunc(
paul@868 627
                                    compiler.ast.Name(t2),
paul@868 628
                                    [])),
paul@909 629
paul@909 630
                        # except StopIteration:
paul@909 631
                        #     raise LoopExit
paul@909 632
paul@868 633
                            [(compiler.ast.Name("StopIteration"), None,
paul@983 634
                              compiler.ast.Raise(compiler.ast.Name("__loop_exit")))],
paul@868 635
                            None),
paul@909 636
paul@909 637
                        # <var>... = <t3>
paul@909 638
paul@904 639
                        compiler.ast.Assign(
paul@904 640
                            [n.assign],
paul@904 641
                            compiler.ast.Name(t3)),
paul@0 642
                        n.body]),
paul@0 643
                    None),
paul@909 644
paul@909 645
            # except LoopExit:
paul@909 646
            #     {n.else_}
paul@909 647
            #     pass
paul@909 648
paul@868 649
                [(compiler.ast.Name("LoopExit"), None, n.else_ or compiler.ast.Pass())],
paul@0 650
                None)
paul@0 651
            ])
paul@0 652
paul@0 653
        self.process_structure_node(node)
paul@0 654
paul@0 655
    def process_literal_sequence_node(self, n, name, ref, cls):
paul@0 656
paul@0 657
        """
paul@0 658
        Process the given literal sequence node 'n' as a function invocation,
paul@0 659
        with 'name' indicating the type of the sequence, and 'ref' being a
paul@0 660
        reference to the type. The 'cls' is used to instantiate a suitable name
paul@0 661
        reference.
paul@0 662
        """
paul@0 663
paul@0 664
        if name == "dict":
paul@0 665
            items = []
paul@0 666
            for key, value in n.items:
paul@0 667
                items.append(compiler.ast.Tuple([key, value]))
paul@0 668
        else: # name in ("list", "tuple"):
paul@0 669
            items = n.nodes
paul@0 670
paul@0 671
        return self.get_literal_reference(name, ref, items, cls)
paul@0 672
paul@0 673
    def process_operator_node(self, n):
paul@0 674
paul@0 675
        """
paul@0 676
        Process the given operator node 'n' as an operator function invocation.
paul@0 677
        """
paul@0 678
paul@797 679
        opname = n.__class__.__name__
paul@797 680
        operands = n.getChildNodes()
paul@797 681
paul@797 682
        # Convert a unary operation to an invocation.
paul@797 683
paul@797 684
        op = unary_operator_functions.get(opname)
paul@797 685
paul@797 686
        if op:
paul@797 687
            invocation = compiler.ast.CallFunc(
paul@797 688
                compiler.ast.Name("$op%s" % op),
paul@797 689
                [operands[0]]
paul@797 690
                )
paul@797 691
paul@797 692
        # Convert a single operator with a list of operands to a combination of
paul@797 693
        # pairwise operations.
paul@797 694
paul@797 695
        else:
paul@797 696
            op = operator_functions[opname]
paul@797 697
            invocation = self._process_operator_node(op, operands)
paul@797 698
paul@797 699
        return self.process_structure_node(invocation)
paul@797 700
paul@797 701
    def _process_operator_node(self, op, operands):
paul@797 702
paul@797 703
        """
paul@797 704
        Process the given 'op', being an operator function, together with the
paul@797 705
        supplied 'operands', returning either a single remaining operand or an
paul@797 706
        invocation combining the operands.
paul@797 707
        """
paul@797 708
paul@797 709
        remaining = operands[1:]
paul@797 710
        if not remaining:
paul@797 711
            return operands[0]
paul@797 712
paul@797 713
        return compiler.ast.CallFunc(
paul@0 714
            compiler.ast.Name("$op%s" % op),
paul@797 715
            [operands[0], self._process_operator_node(op, remaining)]
paul@0 716
            )
paul@0 717
paul@173 718
    def process_print_node(self, n):
paul@173 719
paul@173 720
        """
paul@173 721
        Process the given print node 'n' as an invocation on a stream of the
paul@173 722
        form...
paul@173 723
paul@173 724
        $print(dest, args, nl)
paul@173 725
paul@173 726
        The special function name will be translated elsewhere.
paul@173 727
        """
paul@173 728
paul@173 729
        nl = isinstance(n, compiler.ast.Printnl)
paul@173 730
        invocation = compiler.ast.CallFunc(
paul@173 731
            compiler.ast.Name("$print"),
paul@173 732
            [n.dest or compiler.ast.Name("None"),
paul@173 733
             compiler.ast.List(list(n.nodes)),
paul@359 734
             nl and compiler.ast.Name("True") or compiler.ast.Name("False")]
paul@173 735
            )
paul@173 736
        return self.process_structure_node(invocation)
paul@173 737
paul@0 738
    def process_slice_node(self, n, expr=None):
paul@0 739
paul@0 740
        """
paul@784 741
        Process the given slice node 'n' as a method invocation.
paul@0 742
        """
paul@0 743
paul@784 744
        if n.flags == "OP_ASSIGN": op = "__setslice__"
paul@784 745
        elif n.flags == "OP_DELETE": op = "__delslice__"
paul@784 746
        else: op = "__getslice__"
paul@548 747
paul@0 748
        invocation = compiler.ast.CallFunc(
paul@784 749
            compiler.ast.Getattr(n.expr, op),
paul@784 750
            [n.lower or compiler.ast.Name("None"), n.upper or compiler.ast.Name("None")] +
paul@0 751
                (expr and [expr] or [])
paul@0 752
            )
paul@548 753
paul@548 754
        # Fix parse tree structure.
paul@548 755
paul@784 756
        if op == "__delslice__":
paul@548 757
            invocation = compiler.ast.Discard(invocation)
paul@548 758
paul@0 759
        return self.process_structure_node(invocation)
paul@0 760
paul@0 761
    def process_sliceobj_node(self, n):
paul@0 762
paul@0 763
        """
paul@0 764
        Process the given slice object node 'n' as a slice constructor.
paul@0 765
        """
paul@0 766
paul@0 767
        op = "slice"
paul@0 768
        invocation = compiler.ast.CallFunc(
paul@0 769
            compiler.ast.Name("$op%s" % op),
paul@0 770
            n.nodes
paul@0 771
            )
paul@0 772
        return self.process_structure_node(invocation)
paul@0 773
paul@0 774
    def process_subscript_node(self, n, expr=None):
paul@0 775
paul@0 776
        """
paul@784 777
        Process the given subscript node 'n' as a method invocation.
paul@0 778
        """
paul@0 779
paul@784 780
        if n.flags == "OP_ASSIGN": op = "__setitem__"
paul@784 781
        elif n.flags == "OP_DELETE": op = "__delitem__"
paul@784 782
        else: op = "__getitem__"
paul@548 783
paul@0 784
        invocation = compiler.ast.CallFunc(
paul@784 785
            compiler.ast.Getattr(n.expr, op),
paul@784 786
            list(n.subs) + (expr and [expr] or [])
paul@0 787
            )
paul@548 788
paul@548 789
        # Fix parse tree structure.
paul@548 790
paul@784 791
        if op == "__delitem__":
paul@548 792
            invocation = compiler.ast.Discard(invocation)
paul@548 793
paul@0 794
        return self.process_structure_node(invocation)
paul@0 795
paul@0 796
    def process_attribute_chain(self, n):
paul@0 797
paul@0 798
        """
paul@0 799
        Process the given attribute access node 'n'. Return a reference
paul@0 800
        describing the expression.
paul@0 801
        """
paul@0 802
paul@0 803
        # AssAttr/Getattr are nested with the outermost access being the last
paul@0 804
        # access in any chain.
paul@0 805
paul@0 806
        self.attrs.insert(0, n.attrname)
paul@0 807
        attrs = self.attrs
paul@0 808
paul@0 809
        # Break attribute chains where non-access nodes are found.
paul@0 810
paul@0 811
        if not self.have_access_expression(n):
paul@110 812
            self.reset_attribute_chain()
paul@0 813
paul@0 814
        # Descend into the expression, extending backwards any existing chain,
paul@0 815
        # or building another for the expression.
paul@0 816
paul@0 817
        name_ref = self.process_structure_node(n.expr)
paul@0 818
paul@0 819
        # Restore chain information applying to this node.
paul@0 820
paul@110 821
        if not self.have_access_expression(n):
paul@110 822
            self.restore_attribute_chain(attrs)
paul@0 823
paul@0 824
        # Return immediately if the expression was another access and thus a
paul@0 825
        # continuation backwards along the chain. The above processing will
paul@0 826
        # have followed the chain all the way to its conclusion.
paul@0 827
paul@0 828
        if self.have_access_expression(n):
paul@0 829
            del self.attrs[0]
paul@0 830
paul@0 831
        return name_ref
paul@0 832
paul@124 833
    # Attribute chain handling.
paul@124 834
paul@110 835
    def reset_attribute_chain(self):
paul@110 836
paul@110 837
        "Reset the attribute chain for a subexpression of an attribute access."
paul@110 838
paul@110 839
        self.attrs = []
paul@124 840
        self.chain_assignment.append(self.in_assignment)
paul@124 841
        self.chain_invocation.append(self.in_invocation)
paul@124 842
        self.in_assignment = None
paul@553 843
        self.in_invocation = None
paul@110 844
paul@110 845
    def restore_attribute_chain(self, attrs):
paul@110 846
paul@110 847
        "Restore the attribute chain for an attribute access."
paul@110 848
paul@110 849
        self.attrs = attrs
paul@124 850
        self.in_assignment = self.chain_assignment.pop()
paul@124 851
        self.in_invocation = self.chain_invocation.pop()
paul@110 852
paul@0 853
    def have_access_expression(self, node):
paul@0 854
paul@0 855
        "Return whether the expression associated with 'node' is Getattr."
paul@0 856
paul@0 857
        return isinstance(node.expr, compiler.ast.Getattr)
paul@0 858
paul@678 859
    def get_name_for_tracking(self, name, name_ref=None, is_global=False):
paul@0 860
paul@0 861
        """
paul@0 862
        Return the name to be used for attribute usage observations involving
paul@603 863
        the given 'name' in the current namespace.
paul@603 864
paul@603 865
        If the name is being used outside a function, and if 'name_ref' is
paul@678 866
        given and indicates a global or if 'is_global' is specified as a true
paul@678 867
        value, a path featuring the name in the global namespace is returned.
paul@678 868
        Otherwise, a path computed using the current namespace and the given
paul@678 869
        name is returned.
paul@0 870
paul@0 871
        The intention of this method is to provide a suitably-qualified name
paul@0 872
        that can be tracked across namespaces. Where globals are being
paul@0 873
        referenced in class namespaces, they should be referenced using their
paul@0 874
        path within the module, not using a path within each class.
paul@0 875
paul@0 876
        It may not be possible to identify a global within a function at the
paul@0 877
        time of inspection (since a global may appear later in a file).
paul@0 878
        Consequently, globals are identified by their local name rather than
paul@0 879
        their module-qualified path.
paul@0 880
        """
paul@0 881
paul@0 882
        # For functions, use the appropriate local names.
paul@0 883
paul@0 884
        if self.in_function:
paul@0 885
            return name
paul@0 886
paul@603 887
        # For global names outside functions, use a global name.
paul@597 888
paul@678 889
        elif is_global or name_ref and name_ref.is_global_name():
paul@603 890
            return self.get_global_path(name)
paul@0 891
paul@152 892
        # Otherwise, establish a name in the current namespace.
paul@0 893
paul@0 894
        else:
paul@0 895
            return self.get_object_path(name)
paul@0 896
paul@0 897
    def get_path_for_access(self):
paul@0 898
paul@0 899
        "Outside functions, register accesses at the module level."
paul@0 900
paul@0 901
        if not self.in_function:
paul@0 902
            return self.name
paul@0 903
        else:
paul@0 904
            return self.get_namespace_path()
paul@0 905
paul@0 906
    def get_module_name(self, node):
paul@0 907
paul@0 908
        """
paul@0 909
        Using the given From 'node' in this module, calculate any relative import
paul@0 910
        information, returning a tuple containing a module to import along with any
paul@0 911
        names to import based on the node's name information.
paul@0 912
paul@0 913
        Where the returned module is given as None, whole module imports should
paul@0 914
        be performed for the returned modules using the returned names.
paul@0 915
        """
paul@0 916
paul@0 917
        # Absolute import.
paul@0 918
paul@0 919
        if node.level == 0:
paul@0 920
            return node.modname, node.names
paul@0 921
paul@0 922
        # Relative to an ancestor of this module.
paul@0 923
paul@0 924
        else:
paul@0 925
            path = self.name.split(".")
paul@0 926
            level = node.level
paul@0 927
paul@0 928
            # Relative imports treat package roots as submodules.
paul@0 929
paul@0 930
            if split(self.filename)[-1] == "__init__.py":
paul@0 931
                level -= 1
paul@0 932
paul@0 933
            if level > len(path):
paul@0 934
                raise InspectError("Relative import %r involves too many levels up from module %r" % (
paul@0 935
                    ("%s%s" % ("." * node.level, node.modname or "")), self.name))
paul@0 936
paul@0 937
            basename = ".".join(path[:len(path)-level])
paul@0 938
paul@0 939
        # Name imports from a module.
paul@0 940
paul@0 941
        if node.modname:
paul@0 942
            return "%s.%s" % (basename, node.modname), node.names
paul@0 943
paul@0 944
        # Relative whole module imports.
paul@0 945
paul@0 946
        else:
paul@0 947
            return basename, node.names
paul@0 948
paul@0 949
def get_argnames(args):
paul@0 950
paul@0 951
    """
paul@0 952
    Return a list of all names provided by 'args'. Since tuples may be
paul@0 953
    employed, the arguments are traversed depth-first.
paul@0 954
    """
paul@0 955
paul@0 956
    l = []
paul@0 957
    for arg in args:
paul@0 958
        if isinstance(arg, tuple):
paul@0 959
            l += get_argnames(arg)
paul@0 960
        else:
paul@0 961
            l.append(arg)
paul@0 962
    return l
paul@0 963
paul@509 964
def get_names_from_nodes(nodes):
paul@509 965
paul@509 966
    """
paul@509 967
    Return the names employed in the given 'nodes' along with the number of
paul@509 968
    nodes excluding sequences.
paul@509 969
    """
paul@509 970
paul@509 971
    names = set()
paul@509 972
    count = 0
paul@509 973
paul@509 974
    for node in nodes:
paul@509 975
paul@509 976
        # Add names and count them.
paul@509 977
paul@509 978
        if isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
paul@509 979
            names.add(node.name)
paul@509 980
            count += 1
paul@509 981
paul@509 982
        # Add names from sequences and incorporate their counts.
paul@509 983
paul@509 984
        elif isinstance(node, (compiler.ast.AssList, compiler.ast.AssTuple,
paul@509 985
                               compiler.ast.List, compiler.ast.Set,
paul@509 986
                               compiler.ast.Tuple)):
paul@509 987
            _names, _count = get_names_from_nodes(node.nodes)
paul@509 988
            names.update(_names)
paul@509 989
            count += _count
paul@509 990
paul@509 991
        # Count non-name, non-sequence nodes.
paul@509 992
paul@509 993
        else:
paul@509 994
            count += 1
paul@509 995
paul@509 996
    return names, count
paul@509 997
paul@791 998
# Location classes.
paul@791 999
paul@791 1000
class Location:
paul@791 1001
paul@791 1002
    "A generic program location."
paul@791 1003
paul@791 1004
    def __init__(self, path, name, attrnames=None, version=None, access_number=None):
paul@791 1005
        self.path = path
paul@791 1006
        self.name = name
paul@791 1007
        self.attrnames = attrnames
paul@791 1008
        self.version = version
paul@791 1009
        self.access_number = access_number
paul@791 1010
paul@791 1011
    def __repr__(self):
paul@791 1012
        return "Location(%r, %r, %r, %r, %r)" % self.as_tuple()
paul@791 1013
paul@791 1014
    def as_tuple(self):
paul@791 1015
        return (self.path, self.name, self.attrnames, self.version, self.access_number)
paul@791 1016
paul@791 1017
    def __hash__(self):
paul@791 1018
        return hash(self.as_tuple())
paul@791 1019
paul@791 1020
    def __eq__(self, other):
paul@791 1021
        return self.as_tuple() == other.as_tuple()
paul@791 1022
paul@791 1023
    def __cmp__(self, other):
paul@791 1024
        return cmp(self.as_tuple(), other.as_tuple())
paul@791 1025
paul@791 1026
    def get_attrname(self):
paul@791 1027
paul@791 1028
        """
paul@791 1029
        Extract the first attribute from the attribute names employed in this
paul@791 1030
        location.
paul@791 1031
        """
paul@791 1032
paul@791 1033
        attrnames = self.attrnames
paul@791 1034
        if not attrnames:
paul@791 1035
            return attrnames
paul@791 1036
        return get_attrnames(attrnames)[0]
paul@791 1037
paul@791 1038
class AccessLocation(Location):
paul@791 1039
paul@791 1040
    "A specialised access location."
paul@791 1041
paul@791 1042
    def __init__(self, path, name, attrnames, access_number):
paul@791 1043
paul@791 1044
        """
paul@791 1045
        Initialise an access location featuring 'path', 'name', 'attrnames' and
paul@791 1046
        'access_number'.
paul@791 1047
        """
paul@791 1048
paul@791 1049
        Location.__init__(self, path, name, attrnames, None, access_number)
paul@791 1050
paul@791 1051
    def __repr__(self):
paul@791 1052
        return "AccessLocation(%r, %r, %r, %r)" % (self.path, self.name, self.attrnames, self.access_number)
paul@791 1053
paul@491 1054
# Result classes.
paul@491 1055
paul@491 1056
class InstructionSequence:
paul@491 1057
paul@491 1058
    "A generic sequence of instructions."
paul@491 1059
paul@491 1060
    def __init__(self, instructions):
paul@491 1061
        self.instructions = instructions
paul@491 1062
paul@491 1063
    def get_value_instruction(self):
paul@491 1064
        return self.instructions[-1]
paul@491 1065
paul@491 1066
    def get_init_instructions(self):
paul@491 1067
        return self.instructions[:-1]
paul@491 1068
paul@0 1069
# Dictionary utilities.
paul@0 1070
paul@0 1071
def init_item(d, key, fn):
paul@0 1072
paul@0 1073
    """
paul@0 1074
    Add to 'd' an entry for 'key' using the callable 'fn' to make an initial
paul@0 1075
    value where no entry already exists.
paul@0 1076
    """
paul@0 1077
paul@0 1078
    if not d.has_key(key):
paul@0 1079
        d[key] = fn()
paul@0 1080
    return d[key]
paul@0 1081
paul@0 1082
def dict_for_keys(d, keys):
paul@0 1083
paul@0 1084
    "Return a new dictionary containing entries from 'd' for the given 'keys'."
paul@0 1085
paul@0 1086
    nd = {}
paul@0 1087
    for key in keys:
paul@0 1088
        if d.has_key(key):
paul@0 1089
            nd[key] = d[key]
paul@0 1090
    return nd
paul@0 1091
paul@0 1092
def make_key(s):
paul@0 1093
paul@0 1094
    "Make sequence 's' into a tuple-based key, first sorting its contents."
paul@0 1095
paul@0 1096
    l = list(s)
paul@0 1097
    l.sort()
paul@0 1098
    return tuple(l)
paul@0 1099
paul@0 1100
def add_counter_item(d, key):
paul@0 1101
paul@0 1102
    """
paul@0 1103
    Make a mapping in 'd' for 'key' to the number of keys added before it, thus
paul@0 1104
    maintaining a mapping of keys to their order of insertion.
paul@0 1105
    """
paul@0 1106
paul@0 1107
    if not d.has_key(key):
paul@0 1108
        d[key] = len(d.keys())
paul@0 1109
    return d[key] 
paul@0 1110
paul@0 1111
def remove_items(d1, d2):
paul@0 1112
paul@0 1113
    "Remove from 'd1' all items from 'd2'."
paul@0 1114
paul@0 1115
    for key in d2.keys():
paul@0 1116
        if d1.has_key(key):
paul@0 1117
            del d1[key]
paul@0 1118
paul@0 1119
# Set utilities.
paul@0 1120
paul@0 1121
def first(s):
paul@0 1122
    return list(s)[0]
paul@0 1123
paul@0 1124
def same(s1, s2):
paul@0 1125
    return set(s1) == set(s2)
paul@0 1126
paul@724 1127
def order_dependencies(all_depends):
paul@724 1128
paul@724 1129
    """
paul@724 1130
    Produce a dependency ordering for the 'all_depends' mapping. This mapping
paul@724 1131
    has the form "A depends on B, C...". The result will order A, B, C, and so
paul@724 1132
    on.
paul@724 1133
    """
paul@724 1134
paul@726 1135
    usage = init_reverse_dependencies(all_depends)
paul@726 1136
paul@726 1137
    # Produce an ordering by obtaining exposed items (required by items already
paul@726 1138
    # processed) and putting them at the start of the list.
paul@726 1139
paul@726 1140
    ordered = []
paul@726 1141
paul@726 1142
    while usage:
paul@726 1143
        have_next = False
paul@726 1144
paul@726 1145
        for key, n in usage.items():
paul@726 1146
paul@726 1147
            # Add items needed by no other items to the ordering.
paul@726 1148
paul@726 1149
            if not n:
paul@726 1150
                remove_dependency(key, all_depends, usage, ordered)
paul@726 1151
                have_next = True
paul@726 1152
paul@726 1153
        if not have_next:
paul@726 1154
            raise ValueError, usage
paul@726 1155
paul@726 1156
    return ordered
paul@726 1157
paul@726 1158
def order_dependencies_partial(all_depends):
paul@726 1159
paul@726 1160
    """
paul@726 1161
    Produce a dependency ordering for the 'all_depends' mapping. This mapping
paul@726 1162
    has the form "A depends on B, C...". The result will order A, B, C, and so
paul@726 1163
    on. Where cycles exist, they will be broken and a partial ordering returned.
paul@726 1164
    """
paul@726 1165
paul@726 1166
    usage = init_reverse_dependencies(all_depends)
paul@726 1167
paul@726 1168
    # Duplicate the dependencies for subsequent modification.
paul@726 1169
paul@726 1170
    new_depends = {}
paul@726 1171
    for key, values in all_depends.items():
paul@726 1172
        new_depends[key] = set(values)
paul@726 1173
paul@726 1174
    all_depends = new_depends
paul@726 1175
paul@726 1176
    # Produce an ordering by obtaining exposed items (required by items already
paul@726 1177
    # processed) and putting them at the start of the list.
paul@726 1178
paul@726 1179
    ordered = []
paul@726 1180
paul@726 1181
    while usage:
paul@726 1182
        least = None
paul@726 1183
        least_key = None
paul@726 1184
paul@726 1185
        for key, n in usage.items():
paul@726 1186
paul@726 1187
            # Add items needed by no other items to the ordering.
paul@726 1188
paul@726 1189
            if not n:
paul@726 1190
                remove_dependency(key, all_depends, usage, ordered)
paul@726 1191
                least = 0
paul@726 1192
paul@726 1193
            # When breaking cycles, note the least used items.
paul@726 1194
paul@726 1195
            elif least is None or len(n) < least:
paul@726 1196
                least_key = key
paul@726 1197
                least = len(n)
paul@726 1198
paul@726 1199
        if least:
paul@726 1200
            transfer_dependencies(least_key, all_depends, usage, ordered)
paul@726 1201
paul@726 1202
    return ordered
paul@726 1203
paul@726 1204
def init_reverse_dependencies(all_depends):
paul@726 1205
paul@726 1206
    """
paul@726 1207
    From 'all_depends', providing a mapping of the form "A depends on B, C...",
paul@726 1208
    record the reverse dependencies, making a mapping of the form
paul@726 1209
    "B is needed by A", "C is needed by A", and so on.
paul@726 1210
    """
paul@724 1211
paul@724 1212
    usage = {}
paul@724 1213
paul@724 1214
    # Record path-based dependencies.
paul@724 1215
paul@724 1216
    for key in all_depends.keys():
paul@724 1217
        usage[key] = set()
paul@724 1218
paul@724 1219
    for key, depends in all_depends.items():
paul@724 1220
        for depend in depends:
paul@724 1221
            init_item(usage, depend, set)
paul@724 1222
            usage[depend].add(key)
paul@724 1223
paul@726 1224
    return usage
paul@726 1225
paul@726 1226
def transfer_dependencies(key, all_depends, usage, ordered):
paul@726 1227
paul@726 1228
    """
paul@726 1229
    Transfer items needed by 'key' to those items needing 'key', found using
paul@726 1230
    'all_depends', and updating 'usage'. Insert 'key' into the 'ordered'
paul@726 1231
    collection of dependencies.
paul@724 1232
paul@726 1233
    If "A is needed by X" and "B is needed by A", then transferring items needed
paul@726 1234
    by A will cause "B is needed by X" to be recorded as a consequence.
paul@726 1235
paul@726 1236
    Transferring items also needs to occur in the reverse mapping, so that
paul@726 1237
    "A needs B" and "X needs A", then the consequence must be recorded as
paul@726 1238
    "X needs B".
paul@726 1239
    """
paul@726 1240
paul@726 1241
    ordered.insert(0, key)
paul@724 1242
paul@726 1243
    needing = usage[key]                        # A is needed by X
paul@726 1244
    needed = all_depends.get(key)               # A needs B
paul@726 1245
paul@726 1246
    if needing:
paul@726 1247
        for depend in needing:
paul@726 1248
            l = all_depends.get(depend)
paul@726 1249
            if not l:
paul@726 1250
                continue
paul@724 1251
paul@726 1252
            l.remove(key)                       # X needs (A)
paul@726 1253
paul@726 1254
            if needed:
paul@726 1255
                l.update(needed)                # X needs B...
paul@726 1256
paul@726 1257
                # Prevent self references.
paul@726 1258
paul@726 1259
                if depend in needed:
paul@726 1260
                    l.remove(depend)
paul@724 1261
paul@726 1262
    if needed:
paul@726 1263
        for depend in needed:
paul@726 1264
            l = usage.get(depend)
paul@726 1265
            if not l:
paul@726 1266
                continue
paul@726 1267
paul@726 1268
            l.remove(key)                       # B is needed by (A)
paul@726 1269
            l.update(needing)                   # B is needed by X...
paul@724 1270
paul@726 1271
            # Prevent self references.
paul@726 1272
paul@726 1273
            if depend in needing:
paul@726 1274
                l.remove(depend)
paul@726 1275
paul@726 1276
    if needed:
paul@726 1277
        del all_depends[key]
paul@726 1278
    del usage[key]
paul@726 1279
paul@726 1280
def remove_dependency(key, all_depends, usage, ordered):
paul@724 1281
paul@726 1282
    """
paul@726 1283
    Remove 'key', found in 'all_depends', from 'usage', inserting it into the
paul@726 1284
    'ordered' collection of dependencies.
paul@726 1285
paul@726 1286
    Given that 'usage' for a given key A would indicate that "A needs <nothing>"
paul@726 1287
    upon removing A from 'usage', the outcome is that all keys needing A will
paul@726 1288
    have A removed from their 'usage' records.
paul@726 1289
paul@726 1290
    So, if "B needs A", removing A will cause "B needs <nothing>" to be recorded
paul@726 1291
    as a consequence.
paul@726 1292
    """
paul@724 1293
paul@726 1294
    ordered.insert(0, key)
paul@726 1295
paul@726 1296
    depends = all_depends.get(key)
paul@726 1297
paul@726 1298
    # Reduce usage of the referenced items.
paul@724 1299
paul@726 1300
    if depends:
paul@726 1301
        for depend in depends:
paul@726 1302
            usage[depend].remove(key)
paul@726 1303
paul@726 1304
    del usage[key]
paul@724 1305
paul@0 1306
# General input/output.
paul@0 1307
paul@0 1308
def readfile(filename):
paul@0 1309
paul@0 1310
    "Return the contents of 'filename'."
paul@0 1311
paul@0 1312
    f = open(filename)
paul@0 1313
    try:
paul@0 1314
        return f.read()
paul@0 1315
    finally:
paul@0 1316
        f.close()
paul@0 1317
paul@0 1318
def writefile(filename, s):
paul@0 1319
paul@0 1320
    "Write to 'filename' the string 's'."
paul@0 1321
paul@0 1322
    f = open(filename, "w")
paul@0 1323
    try:
paul@0 1324
        f.write(s)
paul@0 1325
    finally:
paul@0 1326
        f.close()
paul@0 1327
paul@0 1328
# General encoding.
paul@0 1329
paul@0 1330
def sorted_output(x):
paul@0 1331
paul@0 1332
    "Sort sequence 'x' and return a string with commas separating the values."
paul@0 1333
paul@0 1334
    x = map(str, x)
paul@0 1335
    x.sort()
paul@0 1336
    return ", ".join(x)
paul@0 1337
paul@537 1338
def get_string_details(literals, encoding):
paul@512 1339
paul@512 1340
    """
paul@537 1341
    Determine whether 'literals' represent Unicode strings or byte strings,
paul@537 1342
    using 'encoding' to reproduce byte sequences.
paul@537 1343
paul@537 1344
    Each literal is the full program representation including prefix and quotes
paul@537 1345
    recoded by the parser to UTF-8. Thus, any literal found to represent a byte
paul@537 1346
    string needs to be translated back to its original encoding.
paul@537 1347
paul@537 1348
    Return a single encoded literal value, a type name, and the original
paul@537 1349
    encoding as a tuple.
paul@537 1350
    """
paul@537 1351
paul@537 1352
    typename = "unicode"
paul@537 1353
paul@537 1354
    l = []
paul@537 1355
paul@537 1356
    for s in literals:
paul@537 1357
        out, _typename = get_literal_details(s)
paul@537 1358
        if _typename == "str":
paul@537 1359
            typename = "str"
paul@537 1360
        l.append(out)
paul@537 1361
paul@537 1362
    out = "".join(l)
paul@537 1363
paul@537 1364
    # For Unicode values, convert to the UTF-8 program representation.
paul@537 1365
paul@537 1366
    if typename == "unicode":
paul@537 1367
        return out.encode("utf-8"), typename, encoding
paul@537 1368
paul@537 1369
    # For byte string values, convert back to the original encoding.
paul@537 1370
paul@537 1371
    else:
paul@537 1372
        return out.encode(encoding), typename, encoding
paul@537 1373
paul@537 1374
def get_literal_details(s):
paul@537 1375
paul@537 1376
    """
paul@537 1377
    Determine whether 's' represents a Unicode string or a byte string, where
paul@537 1378
    's' contains the full program representation of a literal including prefix
paul@537 1379
    and quotes, recoded by the parser to UTF-8.
paul@512 1380
paul@512 1381
    Find and convert Unicode values starting with <backslash>u or <backslash>U,
paul@512 1382
    and byte or Unicode values starting with <backslash><octal digit> or
paul@512 1383
    <backslash>x.
paul@512 1384
paul@512 1385
    Literals prefixed with "u" cause <backslash><octal digit> and <backslash>x
paul@512 1386
    to be considered as Unicode values. Otherwise, they produce byte values and
paul@512 1387
    cause unprefixed strings to be considered as byte strings.
paul@512 1388
paul@512 1389
    Literals prefixed with "r" do not have their backslash-encoded values
paul@512 1390
    converted unless also prefixed with "u", in which case only the above value
paul@512 1391
    formats are converted, not any of the other special sequences for things
paul@512 1392
    like newlines.
paul@512 1393
paul@537 1394
    Return the literal value as a Unicode object together with the appropriate
paul@537 1395
    type name in a tuple.
paul@512 1396
    """
paul@512 1397
paul@512 1398
    l = []
paul@512 1399
paul@512 1400
    # Identify the quote character and use it to identify the prefix.
paul@512 1401
paul@512 1402
    quote_type = s[-1]
paul@512 1403
    prefix_end = s.find(quote_type)
paul@512 1404
    prefix = s[:prefix_end].lower()
paul@512 1405
paul@512 1406
    if prefix not in ("", "b", "br", "r", "u", "ur"):
paul@512 1407
        raise ValueError, "String literal does not have a supported prefix: %s" % s
paul@512 1408
paul@513 1409
    if "b" in prefix:
paul@513 1410
        typename = "str"
paul@513 1411
    else:
paul@513 1412
        typename = "unicode"
paul@513 1413
paul@512 1414
    # Identify triple quotes or single quotes.
paul@512 1415
paul@512 1416
    if len(s) >= 6 and s[-2] == quote_type and s[-3] == quote_type:
paul@512 1417
        quote = s[prefix_end:prefix_end+3]
paul@512 1418
        current = prefix_end + 3
paul@512 1419
        end = len(s) - 3
paul@512 1420
    else:
paul@512 1421
        quote = s[prefix_end]
paul@512 1422
        current = prefix_end + 1
paul@512 1423
        end = len(s) - 1
paul@512 1424
paul@512 1425
    # Conversions of some quoted values.
paul@512 1426
paul@512 1427
    searches = {
paul@512 1428
        "u" : (6, 16),
paul@512 1429
        "U" : (10, 16),
paul@512 1430
        "x" : (4, 16),
paul@512 1431
        }
paul@512 1432
paul@512 1433
    octal_digits = map(str, range(0, 8))
paul@512 1434
paul@512 1435
    # Translations of some quoted values.
paul@512 1436
paul@512 1437
    escaped = {
paul@512 1438
        "\\" : "\\", "'" : "'", '"' : '"',
paul@512 1439
        "a" : "\a", "b" : "\b", "f" : "\f",
paul@512 1440
        "n" : "\n", "r" : "\r", "t" : "\t",
paul@512 1441
        }
paul@512 1442
paul@512 1443
    while current < end:
paul@512 1444
paul@512 1445
        # Look for quoted values.
paul@512 1446
paul@512 1447
        index = s.find("\\", current)
paul@512 1448
        if index == -1 or index + 1 == end:
paul@512 1449
            l.append(s[current:end])
paul@512 1450
            break
paul@512 1451
paul@512 1452
        # Add the preceding text.
paul@512 1453
paul@512 1454
        l.append(s[current:index])
paul@512 1455
paul@512 1456
        # Handle quoted text.
paul@512 1457
paul@512 1458
        term = s[index+1]
paul@512 1459
paul@512 1460
        # Add Unicode values. Where a string is u-prefixed, even \o and \x
paul@512 1461
        # produce Unicode values.
paul@512 1462
paul@513 1463
        if typename == "unicode" and (
paul@513 1464
            term in ("u", "U") or 
paul@513 1465
            "u" in prefix and (term == "x" or term in octal_digits)):
paul@512 1466
paul@512 1467
            needed, base = searches.get(term, (4, 8))
paul@512 1468
            value = convert_quoted_value(s, index, needed, end, base, unichr)
paul@512 1469
            l.append(value)
paul@512 1470
            current = index + needed
paul@512 1471
paul@512 1472
        # Add raw byte values, changing the string type.
paul@512 1473
paul@512 1474
        elif "r" not in prefix and (
paul@512 1475
             term == "x" or term in octal_digits):
paul@512 1476
paul@512 1477
            needed, base = searches.get(term, (4, 8))
paul@512 1478
            value = convert_quoted_value(s, index, needed, end, base, chr)
paul@512 1479
            l.append(value)
paul@512 1480
            typename = "str"
paul@512 1481
            current = index + needed
paul@512 1482
paul@512 1483
        # Add other escaped values.
paul@512 1484
paul@512 1485
        elif "r" not in prefix and escaped.has_key(term):
paul@512 1486
            l.append(escaped[term])
paul@512 1487
            current = index + 2
paul@512 1488
paul@512 1489
        # Add other text as found.
paul@512 1490
paul@512 1491
        else:
paul@512 1492
            l.append(s[index:index+2])
paul@512 1493
            current = index + 2
paul@512 1494
paul@537 1495
    # Collect the components into a single Unicode object. Since the literal
paul@537 1496
    # text was already in UTF-8 form, interpret plain strings as UTF-8
paul@537 1497
    # sequences.
paul@512 1498
paul@537 1499
    out = []
paul@512 1500
paul@537 1501
    for value in l:
paul@537 1502
        if isinstance(value, unicode):
paul@537 1503
            out.append(value)
paul@537 1504
        else:
paul@537 1505
            out.append(unicode(value, "utf-8"))
paul@512 1506
paul@537 1507
    return "".join(out), typename
paul@512 1508
paul@512 1509
def convert_quoted_value(s, index, needed, end, base, fn):
paul@512 1510
paul@512 1511
    """
paul@512 1512
    Interpret a quoted value in 's' at 'index' with the given 'needed' number of
paul@512 1513
    positions, and with the given 'end' indicating the first position after the
paul@512 1514
    end of the actual string content.
paul@512 1515
paul@512 1516
    Use 'base' as the numerical base when interpreting the value, and use 'fn'
paul@512 1517
    to convert the value to an appropriate type.
paul@512 1518
    """
paul@512 1519
paul@512 1520
    s = s[index:min(index+needed, end)]
paul@512 1521
paul@512 1522
    # Not a complete occurrence.
paul@512 1523
paul@512 1524
    if len(s) < needed:
paul@512 1525
        return s
paul@512 1526
paul@512 1527
    # Test for a well-formed value.
paul@512 1528
paul@512 1529
    try:
paul@512 1530
        first = base == 8 and 1 or 2
paul@512 1531
        value = int(s[first:needed], base)
paul@512 1532
    except ValueError:
paul@512 1533
        return s
paul@512 1534
    else:
paul@512 1535
        return fn(value)
paul@512 1536
paul@0 1537
# Attribute chain decoding.
paul@0 1538
paul@0 1539
def get_attrnames(attrnames):
paul@11 1540
paul@11 1541
    """
paul@11 1542
    Split the qualified attribute chain 'attrnames' into its components,
paul@11 1543
    handling special attributes starting with "#" that indicate type
paul@11 1544
    conformance.
paul@11 1545
    """
paul@11 1546
paul@0 1547
    if attrnames.startswith("#"):
paul@0 1548
        return [attrnames]
paul@0 1549
    else:
paul@0 1550
        return attrnames.split(".")
paul@0 1551
paul@85 1552
def get_name_path(path, name):
paul@85 1553
paul@85 1554
    "Return a suitable qualified name from the given 'path' and 'name'."
paul@85 1555
paul@85 1556
    if "." in name:
paul@85 1557
        return name
paul@85 1558
    else:
paul@85 1559
        return "%s.%s" % (path, name)
paul@85 1560
paul@90 1561
# Usage-related functions.
paul@89 1562
paul@89 1563
def get_types_for_usage(attrnames, objects):
paul@89 1564
paul@89 1565
    """
paul@89 1566
    Identify the types that can support the given 'attrnames', using the
paul@89 1567
    given 'objects' as the catalogue of type details.
paul@89 1568
    """
paul@89 1569
paul@89 1570
    types = []
paul@89 1571
    for name, _attrnames in objects.items():
paul@89 1572
        if set(attrnames).issubset(_attrnames):
paul@89 1573
            types.append(name)
paul@89 1574
    return types
paul@89 1575
paul@90 1576
def get_invoked_attributes(usage):
paul@90 1577
paul@90 1578
    "Obtain invoked attribute from the given 'usage'."
paul@90 1579
paul@90 1580
    invoked = []
paul@90 1581
    if usage:
paul@107 1582
        for attrname, invocation, assignment in usage:
paul@90 1583
            if invocation:
paul@90 1584
                invoked.append(attrname)
paul@90 1585
    return invoked
paul@90 1586
paul@107 1587
def get_assigned_attributes(usage):
paul@107 1588
paul@107 1589
    "Obtain assigned attribute from the given 'usage'."
paul@107 1590
paul@107 1591
    assigned = []
paul@107 1592
    if usage:
paul@107 1593
        for attrname, invocation, assignment in usage:
paul@107 1594
            if assignment:
paul@107 1595
                assigned.append(attrname)
paul@107 1596
    return assigned
paul@107 1597
paul@366 1598
# Type and module functions.
paul@538 1599
# NOTE: This makes assumptions about the __builtins__ structure.
paul@366 1600
paul@366 1601
def get_builtin_module(name):
paul@366 1602
paul@366 1603
    "Return the module name containing the given type 'name'."
paul@366 1604
paul@938 1605
    if name == "NoneType":
paul@538 1606
        modname = "none"
paul@394 1607
    else:
paul@538 1608
        modname = name
paul@538 1609
paul@538 1610
    return "__builtins__.%s" % modname
paul@366 1611
paul@366 1612
def get_builtin_type(name):
paul@366 1613
paul@366 1614
    "Return the type name provided by the given Python value 'name'."
paul@366 1615
paul@938 1616
    return name
paul@366 1617
paul@538 1618
def get_builtin_class(name):
paul@538 1619
paul@538 1620
    "Return the full name of the built-in class having the given 'name'."
paul@538 1621
paul@538 1622
    typename = get_builtin_type(name)
paul@538 1623
    module = get_builtin_module(typename)
paul@538 1624
    return "%s.%s" % (module, typename)
paul@538 1625
paul@0 1626
# Useful data.
paul@0 1627
paul@11 1628
predefined_constants = "False", "None", "NotImplemented", "True"
paul@0 1629
paul@845 1630
privileged_attributes = [
paul@845 1631
    "__get_single_item_unchecked__",
paul@845 1632
    ]
paul@845 1633
paul@797 1634
unary_operator_functions = {
paul@797 1635
paul@797 1636
    # Unary operations.
paul@797 1637
paul@797 1638
    "Invert" : "invert",
paul@797 1639
    "UnaryAdd" : "pos",
paul@797 1640
    "UnarySub" : "neg",
paul@797 1641
    }
paul@797 1642
paul@0 1643
operator_functions = {
paul@0 1644
paul@0 1645
    # Fundamental operations.
paul@0 1646
paul@0 1647
    "is" : "is_",
paul@0 1648
    "is not" : "is_not",
paul@0 1649
paul@0 1650
    # Binary operations.
paul@0 1651
paul@0 1652
    "in" : "in_",
paul@0 1653
    "not in" : "not_in",
paul@0 1654
    "Add" : "add",
paul@0 1655
    "Bitand" : "and_",
paul@0 1656
    "Bitor" : "or_",
paul@0 1657
    "Bitxor" : "xor",
paul@0 1658
    "Div" : "div",
paul@0 1659
    "FloorDiv" : "floordiv",
paul@0 1660
    "LeftShift" : "lshift",
paul@0 1661
    "Mod" : "mod",
paul@0 1662
    "Mul" : "mul",
paul@0 1663
    "Power" : "pow",
paul@0 1664
    "RightShift" : "rshift",
paul@0 1665
    "Sub" : "sub",
paul@0 1666
paul@0 1667
    # Augmented assignment.
paul@0 1668
paul@0 1669
    "+=" : "iadd",
paul@0 1670
    "-=" : "isub",
paul@0 1671
    "*=" : "imul",
paul@0 1672
    "/=" : "idiv",
paul@0 1673
    "//=" : "ifloordiv",
paul@0 1674
    "%=" : "imod",
paul@0 1675
    "**=" : "ipow",
paul@0 1676
    "<<=" : "ilshift",
paul@0 1677
    ">>=" : "irshift",
paul@0 1678
    "&=" : "iand",
paul@0 1679
    "^=" : "ixor",
paul@0 1680
    "|=" : "ior",
paul@0 1681
paul@0 1682
    # Comparisons.
paul@0 1683
paul@0 1684
    "==" : "eq",
paul@0 1685
    "!=" : "ne",
paul@0 1686
    "<" : "lt",
paul@0 1687
    "<=" : "le",
paul@0 1688
    ">=" : "ge",
paul@0 1689
    ">" : "gt",
paul@0 1690
    }
paul@0 1691
paul@797 1692
operator_functions.update(unary_operator_functions)
paul@797 1693
paul@0 1694
# vim: tabstop=4 expandtab shiftwidth=4