Lichen

Annotated common.py

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