Lichen

Annotated common.py

95:a0f513d3a7b1
2016-10-13 Paul Boddie Fixed instruction plan test operations, optimised the initial accessor to avoid redundant assignments and to use the context where appropriate, introduced accessor and attribute name parameterisation in the generated instructions, introduced a generic expression placeholder in place of any local name.
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Common functions.
paul@0 5
paul@0 6
Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013,
paul@0 7
              2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
paul@0 8
paul@0 9
This program is free software; you can redistribute it and/or modify it under
paul@0 10
the terms of the GNU General Public License as published by the Free Software
paul@0 11
Foundation; either version 3 of the License, or (at your option) any later
paul@0 12
version.
paul@0 13
paul@0 14
This program is distributed in the hope that it will be useful, but WITHOUT
paul@0 15
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@0 16
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@0 17
details.
paul@0 18
paul@0 19
You should have received a copy of the GNU General Public License along with
paul@0 20
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@0 21
"""
paul@0 22
paul@0 23
from errors import *
paul@0 24
from os import listdir, makedirs, remove
paul@0 25
from os.path import exists, isdir, join, split
paul@11 26
from results import ConstantValueRef, LiteralSequenceRef, NameRef
paul@0 27
import compiler
paul@0 28
paul@0 29
class CommonOutput:
paul@0 30
paul@0 31
    "Common output functionality."
paul@0 32
paul@0 33
    def check_output(self):
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@0 43
        if recorded_details != details:
paul@0 44
            self.remove_output()
paul@0 45
paul@0 46
        writefile(self.get_output_details_filename(), details)
paul@0 47
paul@0 48
    def get_output_details_filename(self):
paul@0 49
paul@0 50
        "Return the output details filename."
paul@0 51
paul@0 52
        return join(self.output, "$details")
paul@0 53
paul@0 54
    def get_output_details(self):
paul@0 55
paul@0 56
        "Return details of the existing output."
paul@0 57
paul@0 58
        details_filename = self.get_output_details_filename()
paul@0 59
paul@0 60
        if not exists(details_filename):
paul@0 61
            return None
paul@0 62
        else:
paul@0 63
            return readfile(details_filename)
paul@0 64
paul@0 65
    def remove_output(self, dirname=None):
paul@0 66
paul@0 67
        "Remove the output."
paul@0 68
paul@0 69
        dirname = dirname or self.output
paul@0 70
paul@0 71
        for filename in listdir(dirname):
paul@0 72
            path = join(dirname, filename)
paul@0 73
            if isdir(path):
paul@0 74
                self.remove_output(path)
paul@0 75
            else:
paul@0 76
                remove(path)
paul@0 77
paul@0 78
class CommonModule:
paul@0 79
paul@0 80
    "A common module representation."
paul@0 81
paul@0 82
    def __init__(self, name, importer):
paul@0 83
paul@0 84
        """
paul@0 85
        Initialise this module with the given 'name' and an 'importer' which is
paul@0 86
        used to provide access to other modules when required.
paul@0 87
        """
paul@0 88
paul@0 89
        self.name = name
paul@0 90
        self.importer = importer
paul@0 91
        self.filename = None
paul@0 92
paul@0 93
        # Inspection-related attributes.
paul@0 94
paul@0 95
        self.astnode = None
paul@0 96
        self.iterators = {}
paul@0 97
        self.temp = {}
paul@0 98
        self.lambdas = {}
paul@0 99
paul@0 100
        # Constants, literals and values.
paul@0 101
paul@0 102
        self.constants = {}
paul@0 103
        self.constant_values = {}
paul@0 104
        self.literals = {}
paul@0 105
        self.literal_types = {}
paul@0 106
paul@0 107
        # Nested namespaces.
paul@0 108
paul@0 109
        self.namespace_path = []
paul@0 110
        self.in_function = False
paul@0 111
paul@0 112
        # Attribute chains.
paul@0 113
paul@0 114
        self.attrs = []
paul@0 115
paul@0 116
    def __repr__(self):
paul@0 117
        return "CommonModule(%r, %r)" % (self.name, self.importer)
paul@0 118
paul@0 119
    def parse_file(self, filename):
paul@0 120
paul@0 121
        "Parse the file with the given 'filename', initialising attributes."
paul@0 122
paul@0 123
        self.filename = filename
paul@0 124
        self.astnode = compiler.parseFile(filename)
paul@0 125
paul@0 126
    # Module-relative naming.
paul@0 127
paul@0 128
    def get_global_path(self, name):
paul@0 129
        return "%s.%s" % (self.name, name)
paul@0 130
paul@0 131
    def get_namespace_path(self):
paul@0 132
        return ".".join([self.name] + self.namespace_path)
paul@0 133
paul@0 134
    def get_object_path(self, name):
paul@0 135
        return ".".join([self.name] + self.namespace_path + [name])
paul@0 136
paul@0 137
    def get_parent_path(self):
paul@0 138
        return ".".join([self.name] + self.namespace_path[:-1])
paul@0 139
paul@0 140
    # Namespace management.
paul@0 141
paul@0 142
    def enter_namespace(self, name):
paul@0 143
paul@0 144
        "Enter the namespace having the given 'name'."
paul@0 145
paul@0 146
        self.namespace_path.append(name)
paul@0 147
paul@0 148
    def exit_namespace(self):
paul@0 149
paul@0 150
        "Exit the current namespace."
paul@0 151
paul@0 152
        self.namespace_path.pop()
paul@0 153
paul@0 154
    # Constant reference naming.
paul@0 155
paul@0 156
    def get_constant_name(self, value):
paul@0 157
paul@0 158
        "Add a new constant to the current namespace for 'value'."
paul@0 159
paul@0 160
        path = self.get_namespace_path()
paul@0 161
        init_item(self.constants, path, dict)
paul@0 162
        return "$c%d" % add_counter_item(self.constants[path], value)
paul@0 163
paul@0 164
    # Literal reference naming.
paul@0 165
paul@0 166
    def get_literal_name(self):
paul@0 167
paul@0 168
        "Add a new literal to the current namespace."
paul@0 169
paul@0 170
        path = self.get_namespace_path()
paul@0 171
        init_item(self.literals, path, lambda: 0)
paul@0 172
        return "$C%d" % self.literals[path]
paul@0 173
paul@0 174
    def next_literal(self):
paul@0 175
        self.literals[self.get_namespace_path()] += 1
paul@0 176
paul@0 177
    # Temporary iterator naming.
paul@0 178
paul@0 179
    def get_iterator_path(self):
paul@0 180
        return self.in_function and self.get_namespace_path() or self.name
paul@0 181
paul@0 182
    def get_iterator_name(self):
paul@0 183
        path = self.get_iterator_path()
paul@0 184
        init_item(self.iterators, path, lambda: 0)
paul@0 185
        return "$i%d" % self.iterators[path]
paul@0 186
paul@0 187
    def next_iterator(self):
paul@0 188
        self.iterators[self.get_iterator_path()] += 1
paul@0 189
paul@0 190
    # Temporary variable naming.
paul@0 191
paul@0 192
    def get_temporary_name(self):
paul@0 193
        path = self.get_namespace_path()
paul@0 194
        init_item(self.temp, path, lambda: 0)
paul@0 195
        return "$t%d" % self.temp[path]
paul@0 196
paul@0 197
    def next_temporary(self):
paul@0 198
        self.temp[self.get_namespace_path()] += 1
paul@0 199
paul@0 200
    # Arbitrary function naming.
paul@0 201
paul@0 202
    def get_lambda_name(self):
paul@0 203
        path = self.get_namespace_path()
paul@0 204
        init_item(self.lambdas, path, lambda: 0)
paul@0 205
        name = "$l%d" % self.lambdas[path]
paul@0 206
        self.lambdas[path] += 1
paul@0 207
        return name
paul@0 208
paul@0 209
    def reset_lambdas(self):
paul@0 210
        self.lambdas = {}
paul@0 211
paul@0 212
    # Constant and literal recording.
paul@0 213
paul@0 214
    def get_constant_reference(self, ref, value):
paul@0 215
paul@0 216
        "Return a constant reference for the given 'ref' type and 'value'."
paul@0 217
paul@0 218
        constant_name = self.get_constant_name(value)
paul@0 219
paul@0 220
        # Return a reference for the constant.
paul@0 221
paul@0 222
        objpath = self.get_object_path(constant_name)
paul@0 223
        name_ref = ConstantValueRef(constant_name, ref.instance_of(), value)
paul@0 224
paul@0 225
        # Record the value and type for the constant.
paul@0 226
paul@0 227
        self.constant_values[objpath] = name_ref.value, name_ref.get_origin()
paul@0 228
        return name_ref
paul@0 229
paul@0 230
    def get_literal_reference(self, name, ref, items, cls):
paul@0 231
paul@11 232
        """
paul@11 233
        Return a literal reference for the given type 'name', literal 'ref',
paul@11 234
        node 'items' and employing the given 'cls' as the class of the returned
paul@11 235
        reference object.
paul@11 236
        """
paul@11 237
paul@0 238
        # Construct an invocation using the items as arguments.
paul@0 239
paul@0 240
        typename = "$L%s" % name
paul@0 241
paul@0 242
        invocation = compiler.ast.CallFunc(
paul@0 243
            compiler.ast.Name(typename),
paul@0 244
            items
paul@0 245
            )
paul@0 246
paul@0 247
        # Get a name for the actual literal.
paul@0 248
paul@0 249
        instname = self.get_literal_name()
paul@0 250
        self.next_literal()
paul@0 251
paul@0 252
        # Record the type for the literal.
paul@0 253
paul@0 254
        objpath = self.get_object_path(instname)
paul@0 255
        self.literal_types[objpath] = ref.get_origin()
paul@0 256
paul@0 257
        # Return a wrapper for the invocation exposing the items.
paul@0 258
paul@0 259
        return cls(
paul@0 260
            instname,
paul@0 261
            ref.instance_of(),
paul@0 262
            self.process_structure_node(invocation),
paul@0 263
            invocation.args
paul@0 264
            )
paul@0 265
paul@0 266
    # Node handling.
paul@0 267
paul@0 268
    def process_structure(self, node):
paul@0 269
paul@0 270
        """
paul@0 271
        Within the given 'node', process the program structure.
paul@0 272
paul@0 273
        During inspection, this will process global declarations, adjusting the
paul@0 274
        module namespace, and import statements, building a module dependency
paul@0 275
        hierarchy.
paul@0 276
paul@0 277
        During translation, this will consult deduced program information and
paul@0 278
        output translated code.
paul@0 279
        """
paul@0 280
paul@0 281
        l = []
paul@0 282
        for n in node.getChildNodes():
paul@0 283
            l.append(self.process_structure_node(n))
paul@0 284
        return l
paul@0 285
paul@0 286
    def process_augassign_node(self, n):
paul@0 287
paul@0 288
        "Process the given augmented assignment node 'n'."
paul@0 289
paul@0 290
        op = operator_functions[n.op]
paul@0 291
paul@0 292
        if isinstance(n.node, compiler.ast.Getattr):
paul@0 293
            target = compiler.ast.AssAttr(n.node.expr, n.node.attrname, "OP_ASSIGN")
paul@0 294
        elif isinstance(n.node, compiler.ast.Name):
paul@0 295
            target = compiler.ast.AssName(n.node.name, "OP_ASSIGN")
paul@0 296
        else:
paul@0 297
            target = n.node
paul@0 298
paul@0 299
        assignment = compiler.ast.Assign(
paul@0 300
            [target],
paul@0 301
            compiler.ast.CallFunc(
paul@0 302
                compiler.ast.Name("$op%s" % op),
paul@0 303
                [n.node, n.expr]))
paul@0 304
paul@0 305
        return self.process_structure_node(assignment)
paul@0 306
paul@0 307
    def process_assignment_for_function(self, original_name, name):
paul@0 308
paul@0 309
        """
paul@0 310
        Return an assignment operation making 'original_name' refer to the given
paul@0 311
        'name'.
paul@0 312
        """
paul@0 313
paul@0 314
        assignment = compiler.ast.Assign(
paul@0 315
            [compiler.ast.AssName(original_name, "OP_ASSIGN")],
paul@0 316
            compiler.ast.Name(name)
paul@0 317
            )
paul@0 318
paul@0 319
        return self.process_structure_node(assignment)
paul@0 320
paul@0 321
    def process_assignment_node_items(self, n, expr):
paul@0 322
paul@0 323
        """
paul@0 324
        Process the given assignment node 'n' whose children are to be assigned
paul@0 325
        items of 'expr'.
paul@0 326
        """
paul@0 327
paul@0 328
        name_ref = self.process_structure_node(expr)
paul@0 329
paul@0 330
        # Either unpack the items and present them directly to each assignment
paul@0 331
        # node.
paul@0 332
paul@0 333
        if isinstance(name_ref, LiteralSequenceRef):
paul@0 334
            self.process_literal_sequence_items(n, name_ref)
paul@0 335
paul@0 336
        # Or have the assignment nodes access each item via the sequence API.
paul@0 337
paul@0 338
        else:
paul@0 339
            self.process_assignment_node_items_by_position(n, expr, name_ref)
paul@0 340
paul@0 341
    def process_assignment_node_items_by_position(self, n, expr, name_ref):
paul@0 342
paul@0 343
        """
paul@0 344
        Process the given sequence assignment node 'n', converting the node to
paul@0 345
        the separate assignment of each target using positional access on a
paul@0 346
        temporary variable representing the sequence. Use 'expr' as the assigned
paul@0 347
        value and 'name_ref' as the reference providing any existing temporary
paul@0 348
        variable.
paul@0 349
        """
paul@0 350
paul@0 351
        assignments = []
paul@0 352
paul@0 353
        if isinstance(name_ref, NameRef):
paul@0 354
            temp = name_ref.name
paul@0 355
        else:
paul@0 356
            temp = self.get_temporary_name()
paul@0 357
            self.next_temporary()
paul@0 358
paul@0 359
            assignments.append(
paul@0 360
                compiler.ast.Assign([compiler.ast.AssName(temp, "OP_ASSIGN")], expr)
paul@0 361
                )
paul@0 362
paul@0 363
        for i, node in enumerate(n.nodes):
paul@0 364
            assignments.append(
paul@0 365
                compiler.ast.Assign([node], compiler.ast.Subscript(
paul@0 366
                    compiler.ast.Name(temp), "OP_APPLY", [compiler.ast.Const(i)]))
paul@0 367
                )
paul@0 368
paul@0 369
        return self.process_structure_node(compiler.ast.Stmt(assignments))
paul@0 370
paul@0 371
    def process_literal_sequence_items(self, n, name_ref):
paul@0 372
paul@0 373
        """
paul@0 374
        Process the given assignment node 'n', obtaining from the given
paul@0 375
        'name_ref' the items to be assigned to the assignment targets.
paul@0 376
        """
paul@0 377
paul@0 378
        if len(n.nodes) == len(name_ref.items):
paul@0 379
            for node, item in zip(n.nodes, name_ref.items):
paul@0 380
                self.process_assignment_node(node, item)
paul@0 381
        else:
paul@0 382
            raise InspectError("In %s, item assignment needing %d items is given %d items." % (
paul@0 383
                self.get_namespace_path(), len(n.nodes), len(name_ref.items)))
paul@0 384
paul@0 385
    def process_compare_node(self, n):
paul@0 386
paul@0 387
        """
paul@0 388
        Process the given comparison node 'n', converting an operator sequence
paul@0 389
        from...
paul@0 390
paul@0 391
        <expr1> <op1> <expr2> <op2> <expr3>
paul@0 392
paul@0 393
        ...to...
paul@0 394
paul@0 395
        <op1>(<expr1>, <expr2>) and <op2>(<expr2>, <expr3>)
paul@0 396
        """
paul@0 397
paul@0 398
        invocations = []
paul@0 399
        last = n.expr
paul@0 400
paul@0 401
        for op, op_node in n.ops:
paul@0 402
            op = operator_functions.get(op)
paul@0 403
paul@0 404
            invocations.append(compiler.ast.CallFunc(
paul@0 405
                compiler.ast.Name("$op%s" % op),
paul@0 406
                [last, op_node]))
paul@0 407
paul@0 408
            last = op_node
paul@0 409
paul@0 410
        if len(invocations) > 1:
paul@0 411
            result = compiler.ast.And(invocations)
paul@0 412
        else:
paul@0 413
            result = invocations[0]
paul@0 414
paul@0 415
        return self.process_structure_node(result)
paul@0 416
paul@0 417
    def process_dict_node(self, node):
paul@0 418
paul@0 419
        """
paul@0 420
        Process the given dictionary 'node', returning a list of (key, value)
paul@0 421
        tuples.
paul@0 422
        """
paul@0 423
paul@0 424
        l = []
paul@0 425
        for key, value in node.items:
paul@0 426
            l.append((
paul@0 427
                self.process_structure_node(key),
paul@0 428
                self.process_structure_node(value)))
paul@0 429
        return l
paul@0 430
paul@0 431
    def process_for_node(self, n):
paul@0 432
paul@0 433
        """
paul@0 434
        Generate attribute accesses for {n.list}.__iter__ and the next method on
paul@0 435
        the iterator, producing a replacement node for the original.
paul@0 436
        """
paul@0 437
paul@0 438
        node = compiler.ast.Stmt([
paul@0 439
paul@0 440
            # <iterator> = {n.list}.__iter__
paul@0 441
paul@0 442
            compiler.ast.Assign(
paul@0 443
                [compiler.ast.AssName(self.get_iterator_name(), "OP_ASSIGN")],
paul@0 444
                compiler.ast.CallFunc(
paul@0 445
                    compiler.ast.Getattr(n.list, "__iter__"),
paul@0 446
                    []
paul@0 447
                    )),
paul@0 448
paul@0 449
            # try:
paul@0 450
            #     while True:
paul@0 451
            #         <var>... = <iterator>.next()
paul@0 452
            #         ...
paul@0 453
            # except StopIteration:
paul@0 454
            #     pass
paul@0 455
paul@0 456
            compiler.ast.TryExcept(
paul@0 457
                compiler.ast.While(
paul@0 458
                    compiler.ast.Name("True"),
paul@0 459
                    compiler.ast.Stmt([
paul@0 460
                        compiler.ast.Assign(
paul@0 461
                            [n.assign],
paul@0 462
                            compiler.ast.CallFunc(
paul@0 463
                                compiler.ast.Getattr(compiler.ast.Name(self.get_iterator_name()), "next"),
paul@0 464
                                []
paul@0 465
                                )),
paul@0 466
                        n.body]),
paul@0 467
                    None),
paul@0 468
                [(compiler.ast.Name("StopIteration"), None, compiler.ast.Stmt([compiler.ast.Pass()]))],
paul@0 469
                None)
paul@0 470
            ])
paul@0 471
paul@0 472
        self.next_iterator()
paul@0 473
        self.process_structure_node(node)
paul@0 474
paul@0 475
    def process_literal_sequence_node(self, n, name, ref, cls):
paul@0 476
paul@0 477
        """
paul@0 478
        Process the given literal sequence node 'n' as a function invocation,
paul@0 479
        with 'name' indicating the type of the sequence, and 'ref' being a
paul@0 480
        reference to the type. The 'cls' is used to instantiate a suitable name
paul@0 481
        reference.
paul@0 482
        """
paul@0 483
paul@0 484
        if name == "dict":
paul@0 485
            items = []
paul@0 486
            for key, value in n.items:
paul@0 487
                items.append(compiler.ast.Tuple([key, value]))
paul@0 488
        else: # name in ("list", "tuple"):
paul@0 489
            items = n.nodes
paul@0 490
paul@0 491
        return self.get_literal_reference(name, ref, items, cls)
paul@0 492
paul@0 493
    def process_operator_node(self, n):
paul@0 494
paul@0 495
        """
paul@0 496
        Process the given operator node 'n' as an operator function invocation.
paul@0 497
        """
paul@0 498
paul@0 499
        op = operator_functions[n.__class__.__name__]
paul@0 500
        invocation = compiler.ast.CallFunc(
paul@0 501
            compiler.ast.Name("$op%s" % op),
paul@0 502
            list(n.getChildNodes())
paul@0 503
            )
paul@0 504
        return self.process_structure_node(invocation)
paul@0 505
paul@0 506
    def process_slice_node(self, n, expr=None):
paul@0 507
paul@0 508
        """
paul@0 509
        Process the given slice node 'n' as an operator function invocation.
paul@0 510
        """
paul@0 511
paul@0 512
        op = n.flags == "OP_ASSIGN" and "setslice" or "getslice"
paul@0 513
        invocation = compiler.ast.CallFunc(
paul@0 514
            compiler.ast.Name("$op%s" % op),
paul@0 515
            [n.expr, n.lower or compiler.ast.Name("None"), n.upper or compiler.ast.Name("None")] +
paul@0 516
                (expr and [expr] or [])
paul@0 517
            )
paul@0 518
        return self.process_structure_node(invocation)
paul@0 519
paul@0 520
    def process_sliceobj_node(self, n):
paul@0 521
paul@0 522
        """
paul@0 523
        Process the given slice object node 'n' as a slice constructor.
paul@0 524
        """
paul@0 525
paul@0 526
        op = "slice"
paul@0 527
        invocation = compiler.ast.CallFunc(
paul@0 528
            compiler.ast.Name("$op%s" % op),
paul@0 529
            n.nodes
paul@0 530
            )
paul@0 531
        return self.process_structure_node(invocation)
paul@0 532
paul@0 533
    def process_subscript_node(self, n, expr=None):
paul@0 534
paul@0 535
        """
paul@0 536
        Process the given subscript node 'n' as an operator function invocation.
paul@0 537
        """
paul@0 538
paul@0 539
        op = n.flags == "OP_ASSIGN" and "setitem" or "getitem"
paul@0 540
        invocation = compiler.ast.CallFunc(
paul@0 541
            compiler.ast.Name("$op%s" % op),
paul@0 542
            [n.expr] + list(n.subs) + (expr and [expr] or [])
paul@0 543
            )
paul@0 544
        return self.process_structure_node(invocation)
paul@0 545
paul@0 546
    def process_attribute_chain(self, n):
paul@0 547
paul@0 548
        """
paul@0 549
        Process the given attribute access node 'n'. Return a reference
paul@0 550
        describing the expression.
paul@0 551
        """
paul@0 552
paul@0 553
        # AssAttr/Getattr are nested with the outermost access being the last
paul@0 554
        # access in any chain.
paul@0 555
paul@0 556
        self.attrs.insert(0, n.attrname)
paul@0 557
        attrs = self.attrs
paul@0 558
paul@0 559
        # Break attribute chains where non-access nodes are found.
paul@0 560
paul@0 561
        if not self.have_access_expression(n):
paul@0 562
            self.attrs = []
paul@0 563
paul@0 564
        # Descend into the expression, extending backwards any existing chain,
paul@0 565
        # or building another for the expression.
paul@0 566
paul@0 567
        name_ref = self.process_structure_node(n.expr)
paul@0 568
paul@0 569
        # Restore chain information applying to this node.
paul@0 570
paul@0 571
        self.attrs = attrs
paul@0 572
paul@0 573
        # Return immediately if the expression was another access and thus a
paul@0 574
        # continuation backwards along the chain. The above processing will
paul@0 575
        # have followed the chain all the way to its conclusion.
paul@0 576
paul@0 577
        if self.have_access_expression(n):
paul@0 578
            del self.attrs[0]
paul@0 579
paul@0 580
        return name_ref
paul@0 581
paul@0 582
    def have_access_expression(self, node):
paul@0 583
paul@0 584
        "Return whether the expression associated with 'node' is Getattr."
paul@0 585
paul@0 586
        return isinstance(node.expr, compiler.ast.Getattr)
paul@0 587
paul@0 588
    def get_name_for_tracking(self, name, path=None):
paul@0 589
paul@0 590
        """
paul@0 591
        Return the name to be used for attribute usage observations involving
paul@0 592
        the given 'name' in the current namespace. If 'path' is indicated and
paul@0 593
        the name is being used outside a function, return the path value;
paul@0 594
        otherwise, return a path computed using the current namespace and the
paul@0 595
        given name.
paul@0 596
paul@0 597
        The intention of this method is to provide a suitably-qualified name
paul@0 598
        that can be tracked across namespaces. Where globals are being
paul@0 599
        referenced in class namespaces, they should be referenced using their
paul@0 600
        path within the module, not using a path within each class.
paul@0 601
paul@0 602
        It may not be possible to identify a global within a function at the
paul@0 603
        time of inspection (since a global may appear later in a file).
paul@0 604
        Consequently, globals are identified by their local name rather than
paul@0 605
        their module-qualified path.
paul@0 606
        """
paul@0 607
paul@0 608
        # For functions, use the appropriate local names.
paul@0 609
paul@0 610
        if self.in_function:
paul@0 611
            return name
paul@0 612
paul@0 613
        # For static namespaces, use the given qualified name.
paul@0 614
paul@0 615
        elif path:
paul@0 616
            return path
paul@0 617
paul@0 618
        # Otherwise, establish a name in the current (module) namespace.
paul@0 619
paul@0 620
        else:
paul@0 621
            return self.get_object_path(name)
paul@0 622
paul@0 623
    def get_path_for_access(self):
paul@0 624
paul@0 625
        "Outside functions, register accesses at the module level."
paul@0 626
paul@0 627
        if not self.in_function:
paul@0 628
            return self.name
paul@0 629
        else:
paul@0 630
            return self.get_namespace_path()
paul@0 631
paul@0 632
    def get_module_name(self, node):
paul@0 633
paul@0 634
        """
paul@0 635
        Using the given From 'node' in this module, calculate any relative import
paul@0 636
        information, returning a tuple containing a module to import along with any
paul@0 637
        names to import based on the node's name information.
paul@0 638
paul@0 639
        Where the returned module is given as None, whole module imports should
paul@0 640
        be performed for the returned modules using the returned names.
paul@0 641
        """
paul@0 642
paul@0 643
        # Absolute import.
paul@0 644
paul@0 645
        if node.level == 0:
paul@0 646
            return node.modname, node.names
paul@0 647
paul@0 648
        # Relative to an ancestor of this module.
paul@0 649
paul@0 650
        else:
paul@0 651
            path = self.name.split(".")
paul@0 652
            level = node.level
paul@0 653
paul@0 654
            # Relative imports treat package roots as submodules.
paul@0 655
paul@0 656
            if split(self.filename)[-1] == "__init__.py":
paul@0 657
                level -= 1
paul@0 658
paul@0 659
            if level > len(path):
paul@0 660
                raise InspectError("Relative import %r involves too many levels up from module %r" % (
paul@0 661
                    ("%s%s" % ("." * node.level, node.modname or "")), self.name))
paul@0 662
paul@0 663
            basename = ".".join(path[:len(path)-level])
paul@0 664
paul@0 665
        # Name imports from a module.
paul@0 666
paul@0 667
        if node.modname:
paul@0 668
            return "%s.%s" % (basename, node.modname), node.names
paul@0 669
paul@0 670
        # Relative whole module imports.
paul@0 671
paul@0 672
        else:
paul@0 673
            return basename, node.names
paul@0 674
paul@0 675
def get_argnames(args):
paul@0 676
paul@0 677
    """
paul@0 678
    Return a list of all names provided by 'args'. Since tuples may be
paul@0 679
    employed, the arguments are traversed depth-first.
paul@0 680
    """
paul@0 681
paul@0 682
    l = []
paul@0 683
    for arg in args:
paul@0 684
        if isinstance(arg, tuple):
paul@0 685
            l += get_argnames(arg)
paul@0 686
        else:
paul@0 687
            l.append(arg)
paul@0 688
    return l
paul@0 689
paul@0 690
# Dictionary utilities.
paul@0 691
paul@0 692
def init_item(d, key, fn):
paul@0 693
paul@0 694
    """
paul@0 695
    Add to 'd' an entry for 'key' using the callable 'fn' to make an initial
paul@0 696
    value where no entry already exists.
paul@0 697
    """
paul@0 698
paul@0 699
    if not d.has_key(key):
paul@0 700
        d[key] = fn()
paul@0 701
    return d[key]
paul@0 702
paul@0 703
def dict_for_keys(d, keys):
paul@0 704
paul@0 705
    "Return a new dictionary containing entries from 'd' for the given 'keys'."
paul@0 706
paul@0 707
    nd = {}
paul@0 708
    for key in keys:
paul@0 709
        if d.has_key(key):
paul@0 710
            nd[key] = d[key]
paul@0 711
    return nd
paul@0 712
paul@0 713
def make_key(s):
paul@0 714
paul@0 715
    "Make sequence 's' into a tuple-based key, first sorting its contents."
paul@0 716
paul@0 717
    l = list(s)
paul@0 718
    l.sort()
paul@0 719
    return tuple(l)
paul@0 720
paul@0 721
def add_counter_item(d, key):
paul@0 722
paul@0 723
    """
paul@0 724
    Make a mapping in 'd' for 'key' to the number of keys added before it, thus
paul@0 725
    maintaining a mapping of keys to their order of insertion.
paul@0 726
    """
paul@0 727
paul@0 728
    if not d.has_key(key):
paul@0 729
        d[key] = len(d.keys())
paul@0 730
    return d[key] 
paul@0 731
paul@0 732
def remove_items(d1, d2):
paul@0 733
paul@0 734
    "Remove from 'd1' all items from 'd2'."
paul@0 735
paul@0 736
    for key in d2.keys():
paul@0 737
        if d1.has_key(key):
paul@0 738
            del d1[key]
paul@0 739
paul@0 740
# Set utilities.
paul@0 741
paul@0 742
def first(s):
paul@0 743
    return list(s)[0]
paul@0 744
paul@0 745
def same(s1, s2):
paul@0 746
    return set(s1) == set(s2)
paul@0 747
paul@0 748
# General input/output.
paul@0 749
paul@0 750
def readfile(filename):
paul@0 751
paul@0 752
    "Return the contents of 'filename'."
paul@0 753
paul@0 754
    f = open(filename)
paul@0 755
    try:
paul@0 756
        return f.read()
paul@0 757
    finally:
paul@0 758
        f.close()
paul@0 759
paul@0 760
def writefile(filename, s):
paul@0 761
paul@0 762
    "Write to 'filename' the string 's'."
paul@0 763
paul@0 764
    f = open(filename, "w")
paul@0 765
    try:
paul@0 766
        f.write(s)
paul@0 767
    finally:
paul@0 768
        f.close()
paul@0 769
paul@0 770
# General encoding.
paul@0 771
paul@0 772
def sorted_output(x):
paul@0 773
paul@0 774
    "Sort sequence 'x' and return a string with commas separating the values."
paul@0 775
paul@0 776
    x = map(str, x)
paul@0 777
    x.sort()
paul@0 778
    return ", ".join(x)
paul@0 779
paul@0 780
# Attribute chain decoding.
paul@0 781
paul@0 782
def get_attrnames(attrnames):
paul@11 783
paul@11 784
    """
paul@11 785
    Split the qualified attribute chain 'attrnames' into its components,
paul@11 786
    handling special attributes starting with "#" that indicate type
paul@11 787
    conformance.
paul@11 788
    """
paul@11 789
paul@0 790
    if attrnames.startswith("#"):
paul@0 791
        return [attrnames]
paul@0 792
    else:
paul@0 793
        return attrnames.split(".")
paul@0 794
paul@0 795
def get_attrname_from_location(location):
paul@11 796
paul@11 797
    """
paul@11 798
    Extract the first attribute from the attribute names employed in a
paul@11 799
    'location'.
paul@11 800
    """
paul@11 801
paul@0 802
    path, name, attrnames, access = location
paul@91 803
    if not attrnames:
paul@91 804
        return attrnames
paul@0 805
    return get_attrnames(attrnames)[0]
paul@0 806
paul@85 807
def get_name_path(path, name):
paul@85 808
paul@85 809
    "Return a suitable qualified name from the given 'path' and 'name'."
paul@85 810
paul@85 811
    if "." in name:
paul@85 812
        return name
paul@85 813
    else:
paul@85 814
        return "%s.%s" % (path, name)
paul@85 815
paul@90 816
# Usage-related functions.
paul@89 817
paul@89 818
def get_types_for_usage(attrnames, objects):
paul@89 819
paul@89 820
    """
paul@89 821
    Identify the types that can support the given 'attrnames', using the
paul@89 822
    given 'objects' as the catalogue of type details.
paul@89 823
    """
paul@89 824
paul@89 825
    types = []
paul@89 826
    for name, _attrnames in objects.items():
paul@89 827
        if set(attrnames).issubset(_attrnames):
paul@89 828
            types.append(name)
paul@89 829
    return types
paul@89 830
paul@90 831
def get_invoked_attributes(usage):
paul@90 832
paul@90 833
    "Obtain invoked attribute from the given 'usage'."
paul@90 834
paul@90 835
    invoked = []
paul@90 836
    if usage:
paul@90 837
        for attrname, invocation in usage:
paul@90 838
            if invocation:
paul@90 839
                invoked.append(attrname)
paul@90 840
    return invoked
paul@90 841
paul@0 842
# Useful data.
paul@0 843
paul@11 844
predefined_constants = "False", "None", "NotImplemented", "True"
paul@0 845
paul@0 846
operator_functions = {
paul@0 847
paul@0 848
    # Fundamental operations.
paul@0 849
paul@0 850
    "is" : "is_",
paul@0 851
    "is not" : "is_not",
paul@0 852
paul@0 853
    # Binary operations.
paul@0 854
paul@0 855
    "in" : "in_",
paul@0 856
    "not in" : "not_in",
paul@0 857
    "Add" : "add",
paul@0 858
    "Bitand" : "and_",
paul@0 859
    "Bitor" : "or_",
paul@0 860
    "Bitxor" : "xor",
paul@0 861
    "Div" : "div",
paul@0 862
    "FloorDiv" : "floordiv",
paul@0 863
    "LeftShift" : "lshift",
paul@0 864
    "Mod" : "mod",
paul@0 865
    "Mul" : "mul",
paul@0 866
    "Power" : "pow",
paul@0 867
    "RightShift" : "rshift",
paul@0 868
    "Sub" : "sub",
paul@0 869
paul@0 870
    # Unary operations.
paul@0 871
paul@0 872
    "Invert" : "invert",
paul@0 873
    "UnaryAdd" : "pos",
paul@0 874
    "UnarySub" : "neg",
paul@0 875
paul@0 876
    # Augmented assignment.
paul@0 877
paul@0 878
    "+=" : "iadd",
paul@0 879
    "-=" : "isub",
paul@0 880
    "*=" : "imul",
paul@0 881
    "/=" : "idiv",
paul@0 882
    "//=" : "ifloordiv",
paul@0 883
    "%=" : "imod",
paul@0 884
    "**=" : "ipow",
paul@0 885
    "<<=" : "ilshift",
paul@0 886
    ">>=" : "irshift",
paul@0 887
    "&=" : "iand",
paul@0 888
    "^=" : "ixor",
paul@0 889
    "|=" : "ior",
paul@0 890
paul@0 891
    # Comparisons.
paul@0 892
paul@0 893
    "==" : "eq",
paul@0 894
    "!=" : "ne",
paul@0 895
    "<" : "lt",
paul@0 896
    "<=" : "le",
paul@0 897
    ">=" : "ge",
paul@0 898
    ">" : "gt",
paul@0 899
    }
paul@0 900
paul@0 901
# vim: tabstop=4 expandtab shiftwidth=4