Lichen

Annotated common.py

118:900d641f42d6
2016-10-20 Paul Boddie Added some more support for generating invocation code, distinguishing between static invocation targets that are identified and whose functions can be obtained directly and other kinds of targets whose functions must be obtained via the special attribute.
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@110 562
            self.reset_attribute_chain()
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@110 571
        if not self.have_access_expression(n):
paul@110 572
            self.restore_attribute_chain(attrs)
paul@0 573
paul@0 574
        # Return immediately if the expression was another access and thus a
paul@0 575
        # continuation backwards along the chain. The above processing will
paul@0 576
        # have followed the chain all the way to its conclusion.
paul@0 577
paul@0 578
        if self.have_access_expression(n):
paul@0 579
            del self.attrs[0]
paul@0 580
paul@0 581
        return name_ref
paul@0 582
paul@110 583
    def reset_attribute_chain(self):
paul@110 584
paul@110 585
        "Reset the attribute chain for a subexpression of an attribute access."
paul@110 586
paul@110 587
        self.attrs = []
paul@110 588
paul@110 589
    def restore_attribute_chain(self, attrs):
paul@110 590
paul@110 591
        "Restore the attribute chain for an attribute access."
paul@110 592
paul@110 593
        self.attrs = attrs
paul@110 594
paul@0 595
    def have_access_expression(self, node):
paul@0 596
paul@0 597
        "Return whether the expression associated with 'node' is Getattr."
paul@0 598
paul@0 599
        return isinstance(node.expr, compiler.ast.Getattr)
paul@0 600
paul@0 601
    def get_name_for_tracking(self, name, path=None):
paul@0 602
paul@0 603
        """
paul@0 604
        Return the name to be used for attribute usage observations involving
paul@0 605
        the given 'name' in the current namespace. If 'path' is indicated and
paul@0 606
        the name is being used outside a function, return the path value;
paul@0 607
        otherwise, return a path computed using the current namespace and the
paul@0 608
        given name.
paul@0 609
paul@0 610
        The intention of this method is to provide a suitably-qualified name
paul@0 611
        that can be tracked across namespaces. Where globals are being
paul@0 612
        referenced in class namespaces, they should be referenced using their
paul@0 613
        path within the module, not using a path within each class.
paul@0 614
paul@0 615
        It may not be possible to identify a global within a function at the
paul@0 616
        time of inspection (since a global may appear later in a file).
paul@0 617
        Consequently, globals are identified by their local name rather than
paul@0 618
        their module-qualified path.
paul@0 619
        """
paul@0 620
paul@0 621
        # For functions, use the appropriate local names.
paul@0 622
paul@0 623
        if self.in_function:
paul@0 624
            return name
paul@0 625
paul@0 626
        # For static namespaces, use the given qualified name.
paul@0 627
paul@0 628
        elif path:
paul@0 629
            return path
paul@0 630
paul@0 631
        # Otherwise, establish a name in the current (module) namespace.
paul@0 632
paul@0 633
        else:
paul@0 634
            return self.get_object_path(name)
paul@0 635
paul@0 636
    def get_path_for_access(self):
paul@0 637
paul@0 638
        "Outside functions, register accesses at the module level."
paul@0 639
paul@0 640
        if not self.in_function:
paul@0 641
            return self.name
paul@0 642
        else:
paul@0 643
            return self.get_namespace_path()
paul@0 644
paul@0 645
    def get_module_name(self, node):
paul@0 646
paul@0 647
        """
paul@0 648
        Using the given From 'node' in this module, calculate any relative import
paul@0 649
        information, returning a tuple containing a module to import along with any
paul@0 650
        names to import based on the node's name information.
paul@0 651
paul@0 652
        Where the returned module is given as None, whole module imports should
paul@0 653
        be performed for the returned modules using the returned names.
paul@0 654
        """
paul@0 655
paul@0 656
        # Absolute import.
paul@0 657
paul@0 658
        if node.level == 0:
paul@0 659
            return node.modname, node.names
paul@0 660
paul@0 661
        # Relative to an ancestor of this module.
paul@0 662
paul@0 663
        else:
paul@0 664
            path = self.name.split(".")
paul@0 665
            level = node.level
paul@0 666
paul@0 667
            # Relative imports treat package roots as submodules.
paul@0 668
paul@0 669
            if split(self.filename)[-1] == "__init__.py":
paul@0 670
                level -= 1
paul@0 671
paul@0 672
            if level > len(path):
paul@0 673
                raise InspectError("Relative import %r involves too many levels up from module %r" % (
paul@0 674
                    ("%s%s" % ("." * node.level, node.modname or "")), self.name))
paul@0 675
paul@0 676
            basename = ".".join(path[:len(path)-level])
paul@0 677
paul@0 678
        # Name imports from a module.
paul@0 679
paul@0 680
        if node.modname:
paul@0 681
            return "%s.%s" % (basename, node.modname), node.names
paul@0 682
paul@0 683
        # Relative whole module imports.
paul@0 684
paul@0 685
        else:
paul@0 686
            return basename, node.names
paul@0 687
paul@0 688
def get_argnames(args):
paul@0 689
paul@0 690
    """
paul@0 691
    Return a list of all names provided by 'args'. Since tuples may be
paul@0 692
    employed, the arguments are traversed depth-first.
paul@0 693
    """
paul@0 694
paul@0 695
    l = []
paul@0 696
    for arg in args:
paul@0 697
        if isinstance(arg, tuple):
paul@0 698
            l += get_argnames(arg)
paul@0 699
        else:
paul@0 700
            l.append(arg)
paul@0 701
    return l
paul@0 702
paul@0 703
# Dictionary utilities.
paul@0 704
paul@0 705
def init_item(d, key, fn):
paul@0 706
paul@0 707
    """
paul@0 708
    Add to 'd' an entry for 'key' using the callable 'fn' to make an initial
paul@0 709
    value where no entry already exists.
paul@0 710
    """
paul@0 711
paul@0 712
    if not d.has_key(key):
paul@0 713
        d[key] = fn()
paul@0 714
    return d[key]
paul@0 715
paul@0 716
def dict_for_keys(d, keys):
paul@0 717
paul@0 718
    "Return a new dictionary containing entries from 'd' for the given 'keys'."
paul@0 719
paul@0 720
    nd = {}
paul@0 721
    for key in keys:
paul@0 722
        if d.has_key(key):
paul@0 723
            nd[key] = d[key]
paul@0 724
    return nd
paul@0 725
paul@0 726
def make_key(s):
paul@0 727
paul@0 728
    "Make sequence 's' into a tuple-based key, first sorting its contents."
paul@0 729
paul@0 730
    l = list(s)
paul@0 731
    l.sort()
paul@0 732
    return tuple(l)
paul@0 733
paul@0 734
def add_counter_item(d, key):
paul@0 735
paul@0 736
    """
paul@0 737
    Make a mapping in 'd' for 'key' to the number of keys added before it, thus
paul@0 738
    maintaining a mapping of keys to their order of insertion.
paul@0 739
    """
paul@0 740
paul@0 741
    if not d.has_key(key):
paul@0 742
        d[key] = len(d.keys())
paul@0 743
    return d[key] 
paul@0 744
paul@0 745
def remove_items(d1, d2):
paul@0 746
paul@0 747
    "Remove from 'd1' all items from 'd2'."
paul@0 748
paul@0 749
    for key in d2.keys():
paul@0 750
        if d1.has_key(key):
paul@0 751
            del d1[key]
paul@0 752
paul@0 753
# Set utilities.
paul@0 754
paul@0 755
def first(s):
paul@0 756
    return list(s)[0]
paul@0 757
paul@0 758
def same(s1, s2):
paul@0 759
    return set(s1) == set(s2)
paul@0 760
paul@0 761
# General input/output.
paul@0 762
paul@0 763
def readfile(filename):
paul@0 764
paul@0 765
    "Return the contents of 'filename'."
paul@0 766
paul@0 767
    f = open(filename)
paul@0 768
    try:
paul@0 769
        return f.read()
paul@0 770
    finally:
paul@0 771
        f.close()
paul@0 772
paul@0 773
def writefile(filename, s):
paul@0 774
paul@0 775
    "Write to 'filename' the string 's'."
paul@0 776
paul@0 777
    f = open(filename, "w")
paul@0 778
    try:
paul@0 779
        f.write(s)
paul@0 780
    finally:
paul@0 781
        f.close()
paul@0 782
paul@0 783
# General encoding.
paul@0 784
paul@0 785
def sorted_output(x):
paul@0 786
paul@0 787
    "Sort sequence 'x' and return a string with commas separating the values."
paul@0 788
paul@0 789
    x = map(str, x)
paul@0 790
    x.sort()
paul@0 791
    return ", ".join(x)
paul@0 792
paul@0 793
# Attribute chain decoding.
paul@0 794
paul@0 795
def get_attrnames(attrnames):
paul@11 796
paul@11 797
    """
paul@11 798
    Split the qualified attribute chain 'attrnames' into its components,
paul@11 799
    handling special attributes starting with "#" that indicate type
paul@11 800
    conformance.
paul@11 801
    """
paul@11 802
paul@0 803
    if attrnames.startswith("#"):
paul@0 804
        return [attrnames]
paul@0 805
    else:
paul@0 806
        return attrnames.split(".")
paul@0 807
paul@0 808
def get_attrname_from_location(location):
paul@11 809
paul@11 810
    """
paul@11 811
    Extract the first attribute from the attribute names employed in a
paul@11 812
    'location'.
paul@11 813
    """
paul@11 814
paul@0 815
    path, name, attrnames, access = location
paul@91 816
    if not attrnames:
paul@91 817
        return attrnames
paul@0 818
    return get_attrnames(attrnames)[0]
paul@0 819
paul@85 820
def get_name_path(path, name):
paul@85 821
paul@85 822
    "Return a suitable qualified name from the given 'path' and 'name'."
paul@85 823
paul@85 824
    if "." in name:
paul@85 825
        return name
paul@85 826
    else:
paul@85 827
        return "%s.%s" % (path, name)
paul@85 828
paul@90 829
# Usage-related functions.
paul@89 830
paul@89 831
def get_types_for_usage(attrnames, objects):
paul@89 832
paul@89 833
    """
paul@89 834
    Identify the types that can support the given 'attrnames', using the
paul@89 835
    given 'objects' as the catalogue of type details.
paul@89 836
    """
paul@89 837
paul@89 838
    types = []
paul@89 839
    for name, _attrnames in objects.items():
paul@89 840
        if set(attrnames).issubset(_attrnames):
paul@89 841
            types.append(name)
paul@89 842
    return types
paul@89 843
paul@90 844
def get_invoked_attributes(usage):
paul@90 845
paul@90 846
    "Obtain invoked attribute from the given 'usage'."
paul@90 847
paul@90 848
    invoked = []
paul@90 849
    if usage:
paul@107 850
        for attrname, invocation, assignment in usage:
paul@90 851
            if invocation:
paul@90 852
                invoked.append(attrname)
paul@90 853
    return invoked
paul@90 854
paul@107 855
def get_assigned_attributes(usage):
paul@107 856
paul@107 857
    "Obtain assigned attribute from the given 'usage'."
paul@107 858
paul@107 859
    assigned = []
paul@107 860
    if usage:
paul@107 861
        for attrname, invocation, assignment in usage:
paul@107 862
            if assignment:
paul@107 863
                assigned.append(attrname)
paul@107 864
    return assigned
paul@107 865
paul@0 866
# Useful data.
paul@0 867
paul@11 868
predefined_constants = "False", "None", "NotImplemented", "True"
paul@0 869
paul@0 870
operator_functions = {
paul@0 871
paul@0 872
    # Fundamental operations.
paul@0 873
paul@0 874
    "is" : "is_",
paul@0 875
    "is not" : "is_not",
paul@0 876
paul@0 877
    # Binary operations.
paul@0 878
paul@0 879
    "in" : "in_",
paul@0 880
    "not in" : "not_in",
paul@0 881
    "Add" : "add",
paul@0 882
    "Bitand" : "and_",
paul@0 883
    "Bitor" : "or_",
paul@0 884
    "Bitxor" : "xor",
paul@0 885
    "Div" : "div",
paul@0 886
    "FloorDiv" : "floordiv",
paul@0 887
    "LeftShift" : "lshift",
paul@0 888
    "Mod" : "mod",
paul@0 889
    "Mul" : "mul",
paul@0 890
    "Power" : "pow",
paul@0 891
    "RightShift" : "rshift",
paul@0 892
    "Sub" : "sub",
paul@0 893
paul@0 894
    # Unary operations.
paul@0 895
paul@0 896
    "Invert" : "invert",
paul@0 897
    "UnaryAdd" : "pos",
paul@0 898
    "UnarySub" : "neg",
paul@0 899
paul@0 900
    # Augmented assignment.
paul@0 901
paul@0 902
    "+=" : "iadd",
paul@0 903
    "-=" : "isub",
paul@0 904
    "*=" : "imul",
paul@0 905
    "/=" : "idiv",
paul@0 906
    "//=" : "ifloordiv",
paul@0 907
    "%=" : "imod",
paul@0 908
    "**=" : "ipow",
paul@0 909
    "<<=" : "ilshift",
paul@0 910
    ">>=" : "irshift",
paul@0 911
    "&=" : "iand",
paul@0 912
    "^=" : "ixor",
paul@0 913
    "|=" : "ior",
paul@0 914
paul@0 915
    # Comparisons.
paul@0 916
paul@0 917
    "==" : "eq",
paul@0 918
    "!=" : "ne",
paul@0 919
    "<" : "lt",
paul@0 920
    "<=" : "le",
paul@0 921
    ">=" : "ge",
paul@0 922
    ">" : "gt",
paul@0 923
    }
paul@0 924
paul@0 925
# vim: tabstop=4 expandtab shiftwidth=4