Lichen

Annotated common.py

395:535f437d592e
2016-12-13 Paul Boddie Propagate the actual text for constant literals from the program code.
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@124 112
        # Retain the assignment value expression and track invocations.
paul@124 113
paul@124 114
        self.in_assignment = None
paul@124 115
        self.in_invocation = False
paul@124 116
paul@124 117
        # Attribute chain state management.
paul@0 118
paul@0 119
        self.attrs = []
paul@124 120
        self.chain_assignment = []
paul@124 121
        self.chain_invocation = []
paul@0 122
paul@0 123
    def __repr__(self):
paul@0 124
        return "CommonModule(%r, %r)" % (self.name, self.importer)
paul@0 125
paul@0 126
    def parse_file(self, filename):
paul@0 127
paul@0 128
        "Parse the file with the given 'filename', initialising attributes."
paul@0 129
paul@0 130
        self.filename = filename
paul@0 131
        self.astnode = compiler.parseFile(filename)
paul@0 132
paul@0 133
    # Module-relative naming.
paul@0 134
paul@0 135
    def get_global_path(self, name):
paul@0 136
        return "%s.%s" % (self.name, name)
paul@0 137
paul@0 138
    def get_namespace_path(self):
paul@0 139
        return ".".join([self.name] + self.namespace_path)
paul@0 140
paul@0 141
    def get_object_path(self, name):
paul@0 142
        return ".".join([self.name] + self.namespace_path + [name])
paul@0 143
paul@0 144
    def get_parent_path(self):
paul@0 145
        return ".".join([self.name] + self.namespace_path[:-1])
paul@0 146
paul@0 147
    # Namespace management.
paul@0 148
paul@0 149
    def enter_namespace(self, name):
paul@0 150
paul@0 151
        "Enter the namespace having the given 'name'."
paul@0 152
paul@0 153
        self.namespace_path.append(name)
paul@0 154
paul@0 155
    def exit_namespace(self):
paul@0 156
paul@0 157
        "Exit the current namespace."
paul@0 158
paul@0 159
        self.namespace_path.pop()
paul@0 160
paul@0 161
    # Constant reference naming.
paul@0 162
paul@0 163
    def get_constant_name(self, value):
paul@0 164
paul@0 165
        "Add a new constant to the current namespace for 'value'."
paul@0 166
paul@0 167
        path = self.get_namespace_path()
paul@0 168
        init_item(self.constants, path, dict)
paul@0 169
        return "$c%d" % add_counter_item(self.constants[path], value)
paul@0 170
paul@0 171
    # Literal reference naming.
paul@0 172
paul@0 173
    def get_literal_name(self):
paul@0 174
paul@0 175
        "Add a new literal to the current namespace."
paul@0 176
paul@0 177
        path = self.get_namespace_path()
paul@0 178
        init_item(self.literals, path, lambda: 0)
paul@0 179
        return "$C%d" % self.literals[path]
paul@0 180
paul@0 181
    def next_literal(self):
paul@0 182
        self.literals[self.get_namespace_path()] += 1
paul@0 183
paul@0 184
    # Temporary iterator naming.
paul@0 185
paul@0 186
    def get_iterator_path(self):
paul@0 187
        return self.in_function and self.get_namespace_path() or self.name
paul@0 188
paul@0 189
    def get_iterator_name(self):
paul@0 190
        path = self.get_iterator_path()
paul@0 191
        init_item(self.iterators, path, lambda: 0)
paul@0 192
        return "$i%d" % self.iterators[path]
paul@0 193
paul@0 194
    def next_iterator(self):
paul@0 195
        self.iterators[self.get_iterator_path()] += 1
paul@0 196
paul@0 197
    # Temporary variable naming.
paul@0 198
paul@0 199
    def get_temporary_name(self):
paul@0 200
        path = self.get_namespace_path()
paul@0 201
        init_item(self.temp, path, lambda: 0)
paul@0 202
        return "$t%d" % self.temp[path]
paul@0 203
paul@0 204
    def next_temporary(self):
paul@0 205
        self.temp[self.get_namespace_path()] += 1
paul@0 206
paul@0 207
    # Arbitrary function naming.
paul@0 208
paul@0 209
    def get_lambda_name(self):
paul@0 210
        path = self.get_namespace_path()
paul@0 211
        init_item(self.lambdas, path, lambda: 0)
paul@0 212
        name = "$l%d" % self.lambdas[path]
paul@0 213
        self.lambdas[path] += 1
paul@0 214
        return name
paul@0 215
paul@0 216
    def reset_lambdas(self):
paul@0 217
        self.lambdas = {}
paul@0 218
paul@0 219
    # Constant and literal recording.
paul@0 220
paul@394 221
    def get_constant_value(self, value):
paul@394 222
paul@394 223
        "Encode the 'value' if appropriate."
paul@394 224
paul@394 225
        if isinstance(value, unicode):
paul@394 226
            value = value.encode("utf-8")
paul@394 227
        return value
paul@394 228
paul@0 229
    def get_constant_reference(self, ref, value):
paul@0 230
paul@0 231
        "Return a constant reference for the given 'ref' type and 'value'."
paul@0 232
paul@0 233
        constant_name = self.get_constant_name(value)
paul@0 234
paul@0 235
        # Return a reference for the constant.
paul@0 236
paul@0 237
        objpath = self.get_object_path(constant_name)
paul@338 238
        name_ref = ConstantValueRef(constant_name, ref.instance_of(objpath), value)
paul@0 239
paul@0 240
        # Record the value and type for the constant.
paul@0 241
paul@251 242
        self._reserve_constant(objpath, name_ref.value, name_ref.get_origin())
paul@0 243
        return name_ref
paul@0 244
paul@251 245
    def reserve_constant(self, objpath, value, origin):
paul@251 246
paul@251 247
        """
paul@251 248
        Reserve a constant within 'objpath' with the given 'value' and having a
paul@251 249
        type with the given 'origin'.
paul@251 250
        """
paul@251 251
paul@251 252
        constant_name = self.get_constant_name(value)
paul@251 253
        objpath = self.get_object_path(constant_name)
paul@251 254
        self._reserve_constant(objpath, value, origin)
paul@251 255
paul@251 256
    def _reserve_constant(self, objpath, value, origin):
paul@251 257
paul@251 258
        "Store a constant for 'objpath' with the given 'value' and 'origin'."
paul@251 259
paul@251 260
        self.constant_values[objpath] = value, origin
paul@251 261
paul@0 262
    def get_literal_reference(self, name, ref, items, cls):
paul@0 263
paul@11 264
        """
paul@11 265
        Return a literal reference for the given type 'name', literal 'ref',
paul@11 266
        node 'items' and employing the given 'cls' as the class of the returned
paul@11 267
        reference object.
paul@11 268
        """
paul@11 269
paul@0 270
        # Construct an invocation using the items as arguments.
paul@0 271
paul@0 272
        typename = "$L%s" % name
paul@0 273
paul@0 274
        invocation = compiler.ast.CallFunc(
paul@0 275
            compiler.ast.Name(typename),
paul@0 276
            items
paul@0 277
            )
paul@0 278
paul@0 279
        # Get a name for the actual literal.
paul@0 280
paul@0 281
        instname = self.get_literal_name()
paul@0 282
        self.next_literal()
paul@0 283
paul@0 284
        # Record the type for the literal.
paul@0 285
paul@0 286
        objpath = self.get_object_path(instname)
paul@0 287
        self.literal_types[objpath] = ref.get_origin()
paul@0 288
paul@0 289
        # Return a wrapper for the invocation exposing the items.
paul@0 290
paul@0 291
        return cls(
paul@0 292
            instname,
paul@0 293
            ref.instance_of(),
paul@0 294
            self.process_structure_node(invocation),
paul@0 295
            invocation.args
paul@0 296
            )
paul@0 297
paul@0 298
    # Node handling.
paul@0 299
paul@0 300
    def process_structure(self, node):
paul@0 301
paul@0 302
        """
paul@0 303
        Within the given 'node', process the program structure.
paul@0 304
paul@0 305
        During inspection, this will process global declarations, adjusting the
paul@0 306
        module namespace, and import statements, building a module dependency
paul@0 307
        hierarchy.
paul@0 308
paul@0 309
        During translation, this will consult deduced program information and
paul@0 310
        output translated code.
paul@0 311
        """
paul@0 312
paul@0 313
        l = []
paul@0 314
        for n in node.getChildNodes():
paul@0 315
            l.append(self.process_structure_node(n))
paul@0 316
        return l
paul@0 317
paul@0 318
    def process_augassign_node(self, n):
paul@0 319
paul@0 320
        "Process the given augmented assignment node 'n'."
paul@0 321
paul@0 322
        op = operator_functions[n.op]
paul@0 323
paul@0 324
        if isinstance(n.node, compiler.ast.Getattr):
paul@0 325
            target = compiler.ast.AssAttr(n.node.expr, n.node.attrname, "OP_ASSIGN")
paul@0 326
        elif isinstance(n.node, compiler.ast.Name):
paul@0 327
            target = compiler.ast.AssName(n.node.name, "OP_ASSIGN")
paul@0 328
        else:
paul@0 329
            target = n.node
paul@0 330
paul@0 331
        assignment = compiler.ast.Assign(
paul@0 332
            [target],
paul@0 333
            compiler.ast.CallFunc(
paul@0 334
                compiler.ast.Name("$op%s" % op),
paul@0 335
                [n.node, n.expr]))
paul@0 336
paul@0 337
        return self.process_structure_node(assignment)
paul@0 338
paul@320 339
    def process_assignment_for_object(self, original_name, source):
paul@0 340
paul@0 341
        """
paul@0 342
        Return an assignment operation making 'original_name' refer to the given
paul@196 343
        'source'.
paul@0 344
        """
paul@0 345
paul@0 346
        assignment = compiler.ast.Assign(
paul@0 347
            [compiler.ast.AssName(original_name, "OP_ASSIGN")],
paul@196 348
            source
paul@0 349
            )
paul@0 350
paul@0 351
        return self.process_structure_node(assignment)
paul@0 352
paul@0 353
    def process_assignment_node_items(self, n, expr):
paul@0 354
paul@0 355
        """
paul@0 356
        Process the given assignment node 'n' whose children are to be assigned
paul@0 357
        items of 'expr'.
paul@0 358
        """
paul@0 359
paul@0 360
        name_ref = self.process_structure_node(expr)
paul@0 361
paul@0 362
        # Either unpack the items and present them directly to each assignment
paul@0 363
        # node.
paul@0 364
paul@0 365
        if isinstance(name_ref, LiteralSequenceRef):
paul@0 366
            self.process_literal_sequence_items(n, name_ref)
paul@0 367
paul@0 368
        # Or have the assignment nodes access each item via the sequence API.
paul@0 369
paul@0 370
        else:
paul@0 371
            self.process_assignment_node_items_by_position(n, expr, name_ref)
paul@0 372
paul@0 373
    def process_assignment_node_items_by_position(self, n, expr, name_ref):
paul@0 374
paul@0 375
        """
paul@0 376
        Process the given sequence assignment node 'n', converting the node to
paul@0 377
        the separate assignment of each target using positional access on a
paul@0 378
        temporary variable representing the sequence. Use 'expr' as the assigned
paul@0 379
        value and 'name_ref' as the reference providing any existing temporary
paul@0 380
        variable.
paul@0 381
        """
paul@0 382
paul@0 383
        assignments = []
paul@0 384
paul@0 385
        if isinstance(name_ref, NameRef):
paul@0 386
            temp = name_ref.name
paul@0 387
        else:
paul@0 388
            temp = self.get_temporary_name()
paul@0 389
            self.next_temporary()
paul@0 390
paul@0 391
            assignments.append(
paul@0 392
                compiler.ast.Assign([compiler.ast.AssName(temp, "OP_ASSIGN")], expr)
paul@0 393
                )
paul@0 394
paul@0 395
        for i, node in enumerate(n.nodes):
paul@0 396
            assignments.append(
paul@0 397
                compiler.ast.Assign([node], compiler.ast.Subscript(
paul@395 398
                    compiler.ast.Name(temp), "OP_APPLY", [compiler.ast.Const(i, str(i))]))
paul@0 399
                )
paul@0 400
paul@0 401
        return self.process_structure_node(compiler.ast.Stmt(assignments))
paul@0 402
paul@0 403
    def process_literal_sequence_items(self, n, name_ref):
paul@0 404
paul@0 405
        """
paul@0 406
        Process the given assignment node 'n', obtaining from the given
paul@0 407
        'name_ref' the items to be assigned to the assignment targets.
paul@0 408
        """
paul@0 409
paul@0 410
        if len(n.nodes) == len(name_ref.items):
paul@0 411
            for node, item in zip(n.nodes, name_ref.items):
paul@0 412
                self.process_assignment_node(node, item)
paul@0 413
        else:
paul@0 414
            raise InspectError("In %s, item assignment needing %d items is given %d items." % (
paul@0 415
                self.get_namespace_path(), len(n.nodes), len(name_ref.items)))
paul@0 416
paul@0 417
    def process_compare_node(self, n):
paul@0 418
paul@0 419
        """
paul@0 420
        Process the given comparison node 'n', converting an operator sequence
paul@0 421
        from...
paul@0 422
paul@0 423
        <expr1> <op1> <expr2> <op2> <expr3>
paul@0 424
paul@0 425
        ...to...
paul@0 426
paul@0 427
        <op1>(<expr1>, <expr2>) and <op2>(<expr2>, <expr3>)
paul@0 428
        """
paul@0 429
paul@0 430
        invocations = []
paul@0 431
        last = n.expr
paul@0 432
paul@0 433
        for op, op_node in n.ops:
paul@0 434
            op = operator_functions.get(op)
paul@0 435
paul@0 436
            invocations.append(compiler.ast.CallFunc(
paul@0 437
                compiler.ast.Name("$op%s" % op),
paul@0 438
                [last, op_node]))
paul@0 439
paul@0 440
            last = op_node
paul@0 441
paul@0 442
        if len(invocations) > 1:
paul@0 443
            result = compiler.ast.And(invocations)
paul@0 444
        else:
paul@0 445
            result = invocations[0]
paul@0 446
paul@0 447
        return self.process_structure_node(result)
paul@0 448
paul@0 449
    def process_dict_node(self, node):
paul@0 450
paul@0 451
        """
paul@0 452
        Process the given dictionary 'node', returning a list of (key, value)
paul@0 453
        tuples.
paul@0 454
        """
paul@0 455
paul@0 456
        l = []
paul@0 457
        for key, value in node.items:
paul@0 458
            l.append((
paul@0 459
                self.process_structure_node(key),
paul@0 460
                self.process_structure_node(value)))
paul@0 461
        return l
paul@0 462
paul@0 463
    def process_for_node(self, n):
paul@0 464
paul@0 465
        """
paul@0 466
        Generate attribute accesses for {n.list}.__iter__ and the next method on
paul@0 467
        the iterator, producing a replacement node for the original.
paul@0 468
        """
paul@0 469
paul@0 470
        node = compiler.ast.Stmt([
paul@0 471
paul@0 472
            # <iterator> = {n.list}.__iter__
paul@0 473
paul@0 474
            compiler.ast.Assign(
paul@0 475
                [compiler.ast.AssName(self.get_iterator_name(), "OP_ASSIGN")],
paul@0 476
                compiler.ast.CallFunc(
paul@0 477
                    compiler.ast.Getattr(n.list, "__iter__"),
paul@0 478
                    []
paul@0 479
                    )),
paul@0 480
paul@0 481
            # try:
paul@0 482
            #     while True:
paul@0 483
            #         <var>... = <iterator>.next()
paul@0 484
            #         ...
paul@0 485
            # except StopIteration:
paul@0 486
            #     pass
paul@0 487
paul@0 488
            compiler.ast.TryExcept(
paul@0 489
                compiler.ast.While(
paul@0 490
                    compiler.ast.Name("True"),
paul@0 491
                    compiler.ast.Stmt([
paul@0 492
                        compiler.ast.Assign(
paul@0 493
                            [n.assign],
paul@0 494
                            compiler.ast.CallFunc(
paul@0 495
                                compiler.ast.Getattr(compiler.ast.Name(self.get_iterator_name()), "next"),
paul@0 496
                                []
paul@0 497
                                )),
paul@0 498
                        n.body]),
paul@0 499
                    None),
paul@0 500
                [(compiler.ast.Name("StopIteration"), None, compiler.ast.Stmt([compiler.ast.Pass()]))],
paul@0 501
                None)
paul@0 502
            ])
paul@0 503
paul@0 504
        self.next_iterator()
paul@0 505
        self.process_structure_node(node)
paul@0 506
paul@0 507
    def process_literal_sequence_node(self, n, name, ref, cls):
paul@0 508
paul@0 509
        """
paul@0 510
        Process the given literal sequence node 'n' as a function invocation,
paul@0 511
        with 'name' indicating the type of the sequence, and 'ref' being a
paul@0 512
        reference to the type. The 'cls' is used to instantiate a suitable name
paul@0 513
        reference.
paul@0 514
        """
paul@0 515
paul@0 516
        if name == "dict":
paul@0 517
            items = []
paul@0 518
            for key, value in n.items:
paul@0 519
                items.append(compiler.ast.Tuple([key, value]))
paul@0 520
        else: # name in ("list", "tuple"):
paul@0 521
            items = n.nodes
paul@0 522
paul@0 523
        return self.get_literal_reference(name, ref, items, cls)
paul@0 524
paul@0 525
    def process_operator_node(self, n):
paul@0 526
paul@0 527
        """
paul@0 528
        Process the given operator node 'n' as an operator function invocation.
paul@0 529
        """
paul@0 530
paul@0 531
        op = operator_functions[n.__class__.__name__]
paul@0 532
        invocation = compiler.ast.CallFunc(
paul@0 533
            compiler.ast.Name("$op%s" % op),
paul@0 534
            list(n.getChildNodes())
paul@0 535
            )
paul@0 536
        return self.process_structure_node(invocation)
paul@0 537
paul@173 538
    def process_print_node(self, n):
paul@173 539
paul@173 540
        """
paul@173 541
        Process the given print node 'n' as an invocation on a stream of the
paul@173 542
        form...
paul@173 543
paul@173 544
        $print(dest, args, nl)
paul@173 545
paul@173 546
        The special function name will be translated elsewhere.
paul@173 547
        """
paul@173 548
paul@173 549
        nl = isinstance(n, compiler.ast.Printnl)
paul@173 550
        invocation = compiler.ast.CallFunc(
paul@173 551
            compiler.ast.Name("$print"),
paul@173 552
            [n.dest or compiler.ast.Name("None"),
paul@173 553
             compiler.ast.List(list(n.nodes)),
paul@359 554
             nl and compiler.ast.Name("True") or compiler.ast.Name("False")]
paul@173 555
            )
paul@173 556
        return self.process_structure_node(invocation)
paul@173 557
paul@0 558
    def process_slice_node(self, n, expr=None):
paul@0 559
paul@0 560
        """
paul@0 561
        Process the given slice node 'n' as an operator function invocation.
paul@0 562
        """
paul@0 563
paul@0 564
        op = n.flags == "OP_ASSIGN" and "setslice" or "getslice"
paul@0 565
        invocation = compiler.ast.CallFunc(
paul@0 566
            compiler.ast.Name("$op%s" % op),
paul@0 567
            [n.expr, n.lower or compiler.ast.Name("None"), n.upper or compiler.ast.Name("None")] +
paul@0 568
                (expr and [expr] or [])
paul@0 569
            )
paul@0 570
        return self.process_structure_node(invocation)
paul@0 571
paul@0 572
    def process_sliceobj_node(self, n):
paul@0 573
paul@0 574
        """
paul@0 575
        Process the given slice object node 'n' as a slice constructor.
paul@0 576
        """
paul@0 577
paul@0 578
        op = "slice"
paul@0 579
        invocation = compiler.ast.CallFunc(
paul@0 580
            compiler.ast.Name("$op%s" % op),
paul@0 581
            n.nodes
paul@0 582
            )
paul@0 583
        return self.process_structure_node(invocation)
paul@0 584
paul@0 585
    def process_subscript_node(self, n, expr=None):
paul@0 586
paul@0 587
        """
paul@0 588
        Process the given subscript node 'n' as an operator function invocation.
paul@0 589
        """
paul@0 590
paul@0 591
        op = n.flags == "OP_ASSIGN" and "setitem" or "getitem"
paul@0 592
        invocation = compiler.ast.CallFunc(
paul@0 593
            compiler.ast.Name("$op%s" % op),
paul@0 594
            [n.expr] + list(n.subs) + (expr and [expr] or [])
paul@0 595
            )
paul@0 596
        return self.process_structure_node(invocation)
paul@0 597
paul@0 598
    def process_attribute_chain(self, n):
paul@0 599
paul@0 600
        """
paul@0 601
        Process the given attribute access node 'n'. Return a reference
paul@0 602
        describing the expression.
paul@0 603
        """
paul@0 604
paul@0 605
        # AssAttr/Getattr are nested with the outermost access being the last
paul@0 606
        # access in any chain.
paul@0 607
paul@0 608
        self.attrs.insert(0, n.attrname)
paul@0 609
        attrs = self.attrs
paul@0 610
paul@0 611
        # Break attribute chains where non-access nodes are found.
paul@0 612
paul@0 613
        if not self.have_access_expression(n):
paul@110 614
            self.reset_attribute_chain()
paul@0 615
paul@0 616
        # Descend into the expression, extending backwards any existing chain,
paul@0 617
        # or building another for the expression.
paul@0 618
paul@0 619
        name_ref = self.process_structure_node(n.expr)
paul@0 620
paul@0 621
        # Restore chain information applying to this node.
paul@0 622
paul@110 623
        if not self.have_access_expression(n):
paul@110 624
            self.restore_attribute_chain(attrs)
paul@0 625
paul@0 626
        # Return immediately if the expression was another access and thus a
paul@0 627
        # continuation backwards along the chain. The above processing will
paul@0 628
        # have followed the chain all the way to its conclusion.
paul@0 629
paul@0 630
        if self.have_access_expression(n):
paul@0 631
            del self.attrs[0]
paul@0 632
paul@0 633
        return name_ref
paul@0 634
paul@124 635
    # Attribute chain handling.
paul@124 636
paul@110 637
    def reset_attribute_chain(self):
paul@110 638
paul@110 639
        "Reset the attribute chain for a subexpression of an attribute access."
paul@110 640
paul@110 641
        self.attrs = []
paul@124 642
        self.chain_assignment.append(self.in_assignment)
paul@124 643
        self.chain_invocation.append(self.in_invocation)
paul@124 644
        self.in_assignment = None
paul@124 645
        self.in_invocation = False
paul@110 646
paul@110 647
    def restore_attribute_chain(self, attrs):
paul@110 648
paul@110 649
        "Restore the attribute chain for an attribute access."
paul@110 650
paul@110 651
        self.attrs = attrs
paul@124 652
        self.in_assignment = self.chain_assignment.pop()
paul@124 653
        self.in_invocation = self.chain_invocation.pop()
paul@110 654
paul@0 655
    def have_access_expression(self, node):
paul@0 656
paul@0 657
        "Return whether the expression associated with 'node' is Getattr."
paul@0 658
paul@0 659
        return isinstance(node.expr, compiler.ast.Getattr)
paul@0 660
paul@0 661
    def get_name_for_tracking(self, name, path=None):
paul@0 662
paul@0 663
        """
paul@0 664
        Return the name to be used for attribute usage observations involving
paul@0 665
        the given 'name' in the current namespace. If 'path' is indicated and
paul@0 666
        the name is being used outside a function, return the path value;
paul@0 667
        otherwise, return a path computed using the current namespace and the
paul@0 668
        given name.
paul@0 669
paul@0 670
        The intention of this method is to provide a suitably-qualified name
paul@0 671
        that can be tracked across namespaces. Where globals are being
paul@0 672
        referenced in class namespaces, they should be referenced using their
paul@0 673
        path within the module, not using a path within each class.
paul@0 674
paul@0 675
        It may not be possible to identify a global within a function at the
paul@0 676
        time of inspection (since a global may appear later in a file).
paul@0 677
        Consequently, globals are identified by their local name rather than
paul@0 678
        their module-qualified path.
paul@0 679
        """
paul@0 680
paul@0 681
        # For functions, use the appropriate local names.
paul@0 682
paul@0 683
        if self.in_function:
paul@0 684
            return name
paul@0 685
paul@0 686
        # For static namespaces, use the given qualified name.
paul@0 687
paul@0 688
        elif path:
paul@0 689
            return path
paul@0 690
paul@152 691
        # Otherwise, establish a name in the current namespace.
paul@0 692
paul@0 693
        else:
paul@0 694
            return self.get_object_path(name)
paul@0 695
paul@0 696
    def get_path_for_access(self):
paul@0 697
paul@0 698
        "Outside functions, register accesses at the module level."
paul@0 699
paul@0 700
        if not self.in_function:
paul@0 701
            return self.name
paul@0 702
        else:
paul@0 703
            return self.get_namespace_path()
paul@0 704
paul@0 705
    def get_module_name(self, node):
paul@0 706
paul@0 707
        """
paul@0 708
        Using the given From 'node' in this module, calculate any relative import
paul@0 709
        information, returning a tuple containing a module to import along with any
paul@0 710
        names to import based on the node's name information.
paul@0 711
paul@0 712
        Where the returned module is given as None, whole module imports should
paul@0 713
        be performed for the returned modules using the returned names.
paul@0 714
        """
paul@0 715
paul@0 716
        # Absolute import.
paul@0 717
paul@0 718
        if node.level == 0:
paul@0 719
            return node.modname, node.names
paul@0 720
paul@0 721
        # Relative to an ancestor of this module.
paul@0 722
paul@0 723
        else:
paul@0 724
            path = self.name.split(".")
paul@0 725
            level = node.level
paul@0 726
paul@0 727
            # Relative imports treat package roots as submodules.
paul@0 728
paul@0 729
            if split(self.filename)[-1] == "__init__.py":
paul@0 730
                level -= 1
paul@0 731
paul@0 732
            if level > len(path):
paul@0 733
                raise InspectError("Relative import %r involves too many levels up from module %r" % (
paul@0 734
                    ("%s%s" % ("." * node.level, node.modname or "")), self.name))
paul@0 735
paul@0 736
            basename = ".".join(path[:len(path)-level])
paul@0 737
paul@0 738
        # Name imports from a module.
paul@0 739
paul@0 740
        if node.modname:
paul@0 741
            return "%s.%s" % (basename, node.modname), node.names
paul@0 742
paul@0 743
        # Relative whole module imports.
paul@0 744
paul@0 745
        else:
paul@0 746
            return basename, node.names
paul@0 747
paul@0 748
def get_argnames(args):
paul@0 749
paul@0 750
    """
paul@0 751
    Return a list of all names provided by 'args'. Since tuples may be
paul@0 752
    employed, the arguments are traversed depth-first.
paul@0 753
    """
paul@0 754
paul@0 755
    l = []
paul@0 756
    for arg in args:
paul@0 757
        if isinstance(arg, tuple):
paul@0 758
            l += get_argnames(arg)
paul@0 759
        else:
paul@0 760
            l.append(arg)
paul@0 761
    return l
paul@0 762
paul@0 763
# Dictionary utilities.
paul@0 764
paul@0 765
def init_item(d, key, fn):
paul@0 766
paul@0 767
    """
paul@0 768
    Add to 'd' an entry for 'key' using the callable 'fn' to make an initial
paul@0 769
    value where no entry already exists.
paul@0 770
    """
paul@0 771
paul@0 772
    if not d.has_key(key):
paul@0 773
        d[key] = fn()
paul@0 774
    return d[key]
paul@0 775
paul@0 776
def dict_for_keys(d, keys):
paul@0 777
paul@0 778
    "Return a new dictionary containing entries from 'd' for the given 'keys'."
paul@0 779
paul@0 780
    nd = {}
paul@0 781
    for key in keys:
paul@0 782
        if d.has_key(key):
paul@0 783
            nd[key] = d[key]
paul@0 784
    return nd
paul@0 785
paul@0 786
def make_key(s):
paul@0 787
paul@0 788
    "Make sequence 's' into a tuple-based key, first sorting its contents."
paul@0 789
paul@0 790
    l = list(s)
paul@0 791
    l.sort()
paul@0 792
    return tuple(l)
paul@0 793
paul@0 794
def add_counter_item(d, key):
paul@0 795
paul@0 796
    """
paul@0 797
    Make a mapping in 'd' for 'key' to the number of keys added before it, thus
paul@0 798
    maintaining a mapping of keys to their order of insertion.
paul@0 799
    """
paul@0 800
paul@0 801
    if not d.has_key(key):
paul@0 802
        d[key] = len(d.keys())
paul@0 803
    return d[key] 
paul@0 804
paul@0 805
def remove_items(d1, d2):
paul@0 806
paul@0 807
    "Remove from 'd1' all items from 'd2'."
paul@0 808
paul@0 809
    for key in d2.keys():
paul@0 810
        if d1.has_key(key):
paul@0 811
            del d1[key]
paul@0 812
paul@0 813
# Set utilities.
paul@0 814
paul@0 815
def first(s):
paul@0 816
    return list(s)[0]
paul@0 817
paul@0 818
def same(s1, s2):
paul@0 819
    return set(s1) == set(s2)
paul@0 820
paul@0 821
# General input/output.
paul@0 822
paul@0 823
def readfile(filename):
paul@0 824
paul@0 825
    "Return the contents of 'filename'."
paul@0 826
paul@0 827
    f = open(filename)
paul@0 828
    try:
paul@0 829
        return f.read()
paul@0 830
    finally:
paul@0 831
        f.close()
paul@0 832
paul@0 833
def writefile(filename, s):
paul@0 834
paul@0 835
    "Write to 'filename' the string 's'."
paul@0 836
paul@0 837
    f = open(filename, "w")
paul@0 838
    try:
paul@0 839
        f.write(s)
paul@0 840
    finally:
paul@0 841
        f.close()
paul@0 842
paul@0 843
# General encoding.
paul@0 844
paul@0 845
def sorted_output(x):
paul@0 846
paul@0 847
    "Sort sequence 'x' and return a string with commas separating the values."
paul@0 848
paul@0 849
    x = map(str, x)
paul@0 850
    x.sort()
paul@0 851
    return ", ".join(x)
paul@0 852
paul@0 853
# Attribute chain decoding.
paul@0 854
paul@0 855
def get_attrnames(attrnames):
paul@11 856
paul@11 857
    """
paul@11 858
    Split the qualified attribute chain 'attrnames' into its components,
paul@11 859
    handling special attributes starting with "#" that indicate type
paul@11 860
    conformance.
paul@11 861
    """
paul@11 862
paul@0 863
    if attrnames.startswith("#"):
paul@0 864
        return [attrnames]
paul@0 865
    else:
paul@0 866
        return attrnames.split(".")
paul@0 867
paul@0 868
def get_attrname_from_location(location):
paul@11 869
paul@11 870
    """
paul@11 871
    Extract the first attribute from the attribute names employed in a
paul@11 872
    'location'.
paul@11 873
    """
paul@11 874
paul@0 875
    path, name, attrnames, access = location
paul@91 876
    if not attrnames:
paul@91 877
        return attrnames
paul@0 878
    return get_attrnames(attrnames)[0]
paul@0 879
paul@85 880
def get_name_path(path, name):
paul@85 881
paul@85 882
    "Return a suitable qualified name from the given 'path' and 'name'."
paul@85 883
paul@85 884
    if "." in name:
paul@85 885
        return name
paul@85 886
    else:
paul@85 887
        return "%s.%s" % (path, name)
paul@85 888
paul@90 889
# Usage-related functions.
paul@89 890
paul@89 891
def get_types_for_usage(attrnames, objects):
paul@89 892
paul@89 893
    """
paul@89 894
    Identify the types that can support the given 'attrnames', using the
paul@89 895
    given 'objects' as the catalogue of type details.
paul@89 896
    """
paul@89 897
paul@89 898
    types = []
paul@89 899
    for name, _attrnames in objects.items():
paul@89 900
        if set(attrnames).issubset(_attrnames):
paul@89 901
            types.append(name)
paul@89 902
    return types
paul@89 903
paul@90 904
def get_invoked_attributes(usage):
paul@90 905
paul@90 906
    "Obtain invoked attribute from the given 'usage'."
paul@90 907
paul@90 908
    invoked = []
paul@90 909
    if usage:
paul@107 910
        for attrname, invocation, assignment in usage:
paul@90 911
            if invocation:
paul@90 912
                invoked.append(attrname)
paul@90 913
    return invoked
paul@90 914
paul@107 915
def get_assigned_attributes(usage):
paul@107 916
paul@107 917
    "Obtain assigned attribute from the given 'usage'."
paul@107 918
paul@107 919
    assigned = []
paul@107 920
    if usage:
paul@107 921
        for attrname, invocation, assignment in usage:
paul@107 922
            if assignment:
paul@107 923
                assigned.append(attrname)
paul@107 924
    return assigned
paul@107 925
paul@366 926
# Type and module functions.
paul@366 927
paul@366 928
def get_builtin_module(name):
paul@366 929
paul@366 930
    "Return the module name containing the given type 'name'."
paul@366 931
paul@366 932
    # NOTE: This makes assumptions about the __builtins__ structure.
paul@366 933
paul@394 934
    if name == "string":
paul@394 935
        return "str"
paul@394 936
    elif name == "utf8string":
paul@394 937
        return "unicode"
paul@394 938
    elif name == "NoneType":
paul@394 939
        return "none"
paul@394 940
    else:
paul@394 941
        return name
paul@366 942
paul@366 943
def get_builtin_type(name):
paul@366 944
paul@366 945
    "Return the type name provided by the given Python value 'name'."
paul@366 946
paul@394 947
    if name == "str":
paul@394 948
        return "string"
paul@394 949
    elif name == "unicode":
paul@394 950
        return "utf8string"
paul@394 951
    else:
paul@394 952
        return name
paul@366 953
paul@0 954
# Useful data.
paul@0 955
paul@11 956
predefined_constants = "False", "None", "NotImplemented", "True"
paul@0 957
paul@0 958
operator_functions = {
paul@0 959
paul@0 960
    # Fundamental operations.
paul@0 961
paul@0 962
    "is" : "is_",
paul@0 963
    "is not" : "is_not",
paul@0 964
paul@0 965
    # Binary operations.
paul@0 966
paul@0 967
    "in" : "in_",
paul@0 968
    "not in" : "not_in",
paul@0 969
    "Add" : "add",
paul@0 970
    "Bitand" : "and_",
paul@0 971
    "Bitor" : "or_",
paul@0 972
    "Bitxor" : "xor",
paul@0 973
    "Div" : "div",
paul@0 974
    "FloorDiv" : "floordiv",
paul@0 975
    "LeftShift" : "lshift",
paul@0 976
    "Mod" : "mod",
paul@0 977
    "Mul" : "mul",
paul@0 978
    "Power" : "pow",
paul@0 979
    "RightShift" : "rshift",
paul@0 980
    "Sub" : "sub",
paul@0 981
paul@0 982
    # Unary operations.
paul@0 983
paul@0 984
    "Invert" : "invert",
paul@0 985
    "UnaryAdd" : "pos",
paul@0 986
    "UnarySub" : "neg",
paul@0 987
paul@0 988
    # Augmented assignment.
paul@0 989
paul@0 990
    "+=" : "iadd",
paul@0 991
    "-=" : "isub",
paul@0 992
    "*=" : "imul",
paul@0 993
    "/=" : "idiv",
paul@0 994
    "//=" : "ifloordiv",
paul@0 995
    "%=" : "imod",
paul@0 996
    "**=" : "ipow",
paul@0 997
    "<<=" : "ilshift",
paul@0 998
    ">>=" : "irshift",
paul@0 999
    "&=" : "iand",
paul@0 1000
    "^=" : "ixor",
paul@0 1001
    "|=" : "ior",
paul@0 1002
paul@0 1003
    # Comparisons.
paul@0 1004
paul@0 1005
    "==" : "eq",
paul@0 1006
    "!=" : "ne",
paul@0 1007
    "<" : "lt",
paul@0 1008
    "<=" : "le",
paul@0 1009
    ">=" : "ge",
paul@0 1010
    ">" : "gt",
paul@0 1011
    }
paul@0 1012
paul@0 1013
# vim: tabstop=4 expandtab shiftwidth=4