Lichen

Annotated common.py

617:170ffd386826
2017-02-24 Paul Boddie Write compilation options to the generator and translator output details, causing output removal if the options are changed, preserving output where appropriate otherwise.
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@508 7
              2014, 2015, 2016, 2017 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@512 23
from compiler.transformer import Transformer
paul@430 24
from errors import InspectError
paul@0 25
from os import listdir, makedirs, remove
paul@609 26
from os.path import exists, getmtime, isdir, join, split
paul@11 27
from results import ConstantValueRef, LiteralSequenceRef, NameRef
paul@405 28
import compiler.ast
paul@0 29
paul@0 30
class CommonOutput:
paul@0 31
paul@0 32
    "Common output functionality."
paul@0 33
paul@617 34
    def check_output(self, options=None):
paul@0 35
paul@0 36
        "Check the existing output and remove it if irrelevant."
paul@0 37
paul@0 38
        if not exists(self.output):
paul@0 39
            makedirs(self.output)
paul@0 40
paul@0 41
        details = self.importer.get_cache_details()
paul@0 42
        recorded_details = self.get_output_details()
paul@0 43
paul@617 44
        # Combine cache details with any options.
paul@617 45
paul@617 46
        full_details = options and (details + " " + options) or details
paul@617 47
paul@617 48
        if recorded_details != full_details:
paul@0 49
            self.remove_output()
paul@0 50
paul@617 51
        writefile(self.get_output_details_filename(), full_details)
paul@0 52
paul@0 53
    def get_output_details_filename(self):
paul@0 54
paul@0 55
        "Return the output details filename."
paul@0 56
paul@0 57
        return join(self.output, "$details")
paul@0 58
paul@0 59
    def get_output_details(self):
paul@0 60
paul@0 61
        "Return details of the existing output."
paul@0 62
paul@0 63
        details_filename = self.get_output_details_filename()
paul@0 64
paul@0 65
        if not exists(details_filename):
paul@0 66
            return None
paul@0 67
        else:
paul@0 68
            return readfile(details_filename)
paul@0 69
paul@0 70
    def remove_output(self, dirname=None):
paul@0 71
paul@0 72
        "Remove the output."
paul@0 73
paul@0 74
        dirname = dirname or self.output
paul@0 75
paul@0 76
        for filename in listdir(dirname):
paul@0 77
            path = join(dirname, filename)
paul@0 78
            if isdir(path):
paul@0 79
                self.remove_output(path)
paul@0 80
            else:
paul@0 81
                remove(path)
paul@0 82
paul@609 83
def copy(source, target, only_if_newer=True):
paul@609 84
paul@609 85
    "Copy a text file from 'source' to 'target'."
paul@609 86
paul@609 87
    if isdir(target):
paul@609 88
        target = join(target, split(source)[-1])
paul@609 89
paul@609 90
    if only_if_newer and not is_newer(source, target):
paul@609 91
        return
paul@609 92
paul@609 93
    infile = open(source)
paul@609 94
    outfile = open(target, "w")
paul@609 95
paul@609 96
    try:
paul@609 97
        outfile.write(infile.read())
paul@609 98
    finally:
paul@609 99
        outfile.close()
paul@609 100
        infile.close()
paul@609 101
paul@609 102
def is_newer(source, target):
paul@609 103
paul@609 104
    "Return whether 'source' is newer than 'target'."
paul@609 105
paul@609 106
    if exists(target):
paul@609 107
        target_mtime = getmtime(target)
paul@609 108
        source_mtime = getmtime(source)
paul@609 109
        return source_mtime > target_mtime
paul@609 110
paul@609 111
    return True
paul@609 112
paul@0 113
class CommonModule:
paul@0 114
paul@0 115
    "A common module representation."
paul@0 116
paul@0 117
    def __init__(self, name, importer):
paul@0 118
paul@0 119
        """
paul@0 120
        Initialise this module with the given 'name' and an 'importer' which is
paul@0 121
        used to provide access to other modules when required.
paul@0 122
        """
paul@0 123
paul@0 124
        self.name = name
paul@0 125
        self.importer = importer
paul@0 126
        self.filename = None
paul@0 127
paul@0 128
        # Inspection-related attributes.
paul@0 129
paul@0 130
        self.astnode = None
paul@405 131
        self.encoding = None
paul@0 132
        self.iterators = {}
paul@0 133
        self.temp = {}
paul@0 134
        self.lambdas = {}
paul@0 135
paul@0 136
        # Constants, literals and values.
paul@0 137
paul@0 138
        self.constants = {}
paul@0 139
        self.constant_values = {}
paul@0 140
        self.literals = {}
paul@0 141
        self.literal_types = {}
paul@0 142
paul@0 143
        # Nested namespaces.
paul@0 144
paul@0 145
        self.namespace_path = []
paul@0 146
        self.in_function = False
paul@0 147
paul@124 148
        # Retain the assignment value expression and track invocations.
paul@124 149
paul@124 150
        self.in_assignment = None
paul@553 151
        self.in_invocation = None
paul@124 152
paul@124 153
        # Attribute chain state management.
paul@0 154
paul@0 155
        self.attrs = []
paul@124 156
        self.chain_assignment = []
paul@124 157
        self.chain_invocation = []
paul@0 158
paul@0 159
    def __repr__(self):
paul@0 160
        return "CommonModule(%r, %r)" % (self.name, self.importer)
paul@0 161
paul@0 162
    def parse_file(self, filename):
paul@0 163
paul@0 164
        "Parse the file with the given 'filename', initialising attributes."
paul@0 165
paul@0 166
        self.filename = filename
paul@405 167
paul@405 168
        # Use the Transformer directly to obtain encoding information.
paul@405 169
paul@405 170
        t = Transformer()
paul@405 171
        f = open(filename)
paul@405 172
paul@405 173
        try:
paul@405 174
            self.astnode = t.parsesuite(f.read() + "\n")
paul@405 175
            self.encoding = t.encoding
paul@405 176
        finally:
paul@405 177
            f.close()
paul@0 178
paul@0 179
    # Module-relative naming.
paul@0 180
paul@0 181
    def get_global_path(self, name):
paul@0 182
        return "%s.%s" % (self.name, name)
paul@0 183
paul@0 184
    def get_namespace_path(self):
paul@0 185
        return ".".join([self.name] + self.namespace_path)
paul@0 186
paul@0 187
    def get_object_path(self, name):
paul@0 188
        return ".".join([self.name] + self.namespace_path + [name])
paul@0 189
paul@0 190
    def get_parent_path(self):
paul@0 191
        return ".".join([self.name] + self.namespace_path[:-1])
paul@0 192
paul@0 193
    # Namespace management.
paul@0 194
paul@0 195
    def enter_namespace(self, name):
paul@0 196
paul@0 197
        "Enter the namespace having the given 'name'."
paul@0 198
paul@0 199
        self.namespace_path.append(name)
paul@0 200
paul@0 201
    def exit_namespace(self):
paul@0 202
paul@0 203
        "Exit the current namespace."
paul@0 204
paul@0 205
        self.namespace_path.pop()
paul@0 206
paul@0 207
    # Constant reference naming.
paul@0 208
paul@406 209
    def get_constant_name(self, value, value_type, encoding=None):
paul@0 210
paul@397 211
        """
paul@397 212
        Add a new constant to the current namespace for 'value' with
paul@397 213
        'value_type'.
paul@397 214
        """
paul@0 215
paul@0 216
        path = self.get_namespace_path()
paul@0 217
        init_item(self.constants, path, dict)
paul@406 218
        return "$c%d" % add_counter_item(self.constants[path], (value, value_type, encoding))
paul@0 219
paul@0 220
    # Literal reference naming.
paul@0 221
paul@0 222
    def get_literal_name(self):
paul@0 223
paul@0 224
        "Add a new literal to the current namespace."
paul@0 225
paul@0 226
        path = self.get_namespace_path()
paul@0 227
        init_item(self.literals, path, lambda: 0)
paul@0 228
        return "$C%d" % self.literals[path]
paul@0 229
paul@0 230
    def next_literal(self):
paul@0 231
        self.literals[self.get_namespace_path()] += 1
paul@0 232
paul@0 233
    # Temporary iterator naming.
paul@0 234
paul@0 235
    def get_iterator_path(self):
paul@0 236
        return self.in_function and self.get_namespace_path() or self.name
paul@0 237
paul@0 238
    def get_iterator_name(self):
paul@0 239
        path = self.get_iterator_path()
paul@0 240
        init_item(self.iterators, path, lambda: 0)
paul@0 241
        return "$i%d" % self.iterators[path]
paul@0 242
paul@0 243
    def next_iterator(self):
paul@0 244
        self.iterators[self.get_iterator_path()] += 1
paul@0 245
paul@0 246
    # Temporary variable naming.
paul@0 247
paul@0 248
    def get_temporary_name(self):
paul@0 249
        path = self.get_namespace_path()
paul@0 250
        init_item(self.temp, path, lambda: 0)
paul@0 251
        return "$t%d" % self.temp[path]
paul@0 252
paul@0 253
    def next_temporary(self):
paul@0 254
        self.temp[self.get_namespace_path()] += 1
paul@0 255
paul@0 256
    # Arbitrary function naming.
paul@0 257
paul@0 258
    def get_lambda_name(self):
paul@0 259
        path = self.get_namespace_path()
paul@0 260
        init_item(self.lambdas, path, lambda: 0)
paul@0 261
        name = "$l%d" % self.lambdas[path]
paul@0 262
        self.lambdas[path] += 1
paul@0 263
        return name
paul@0 264
paul@0 265
    def reset_lambdas(self):
paul@0 266
        self.lambdas = {}
paul@0 267
paul@0 268
    # Constant and literal recording.
paul@0 269
paul@537 270
    def get_constant_value(self, value, literals=None):
paul@394 271
paul@406 272
        """
paul@406 273
        Encode the 'value' if appropriate, returning a value, a typename and any
paul@406 274
        encoding.
paul@406 275
        """
paul@394 276
paul@394 277
        if isinstance(value, unicode):
paul@406 278
            return value.encode("utf-8"), "unicode", self.encoding
paul@405 279
paul@405 280
        # Attempt to convert plain strings to text.
paul@405 281
paul@405 282
        elif isinstance(value, str) and self.encoding:
paul@513 283
            try:
paul@537 284
                return get_string_details(literals, self.encoding)
paul@513 285
            except UnicodeDecodeError:
paul@513 286
                pass
paul@405 287
paul@406 288
        return value, value.__class__.__name__, None
paul@394 289
paul@406 290
    def get_constant_reference(self, ref, value, encoding=None):
paul@0 291
paul@406 292
        """
paul@406 293
        Return a constant reference for the given 'ref' type and 'value', with
paul@406 294
        the optional 'encoding' applying to text values.
paul@406 295
        """
paul@0 296
paul@406 297
        constant_name = self.get_constant_name(value, ref.get_origin(), encoding)
paul@0 298
paul@0 299
        # Return a reference for the constant.
paul@0 300
paul@0 301
        objpath = self.get_object_path(constant_name)
paul@338 302
        name_ref = ConstantValueRef(constant_name, ref.instance_of(objpath), value)
paul@0 303
paul@0 304
        # Record the value and type for the constant.
paul@0 305
paul@406 306
        self._reserve_constant(objpath, name_ref.value, name_ref.get_origin(), encoding)
paul@0 307
        return name_ref
paul@0 308
paul@406 309
    def reserve_constant(self, objpath, value, origin, encoding=None):
paul@251 310
paul@251 311
        """
paul@251 312
        Reserve a constant within 'objpath' with the given 'value' and having a
paul@406 313
        type with the given 'origin', with the optional 'encoding' applying to
paul@406 314
        text values.
paul@251 315
        """
paul@251 316
paul@397 317
        constant_name = self.get_constant_name(value, origin)
paul@251 318
        objpath = self.get_object_path(constant_name)
paul@406 319
        self._reserve_constant(objpath, value, origin, encoding)
paul@251 320
paul@406 321
    def _reserve_constant(self, objpath, value, origin, encoding):
paul@251 322
paul@406 323
        """
paul@406 324
        Store a constant for 'objpath' with the given 'value' and 'origin', with
paul@406 325
        the optional 'encoding' applying to text values.
paul@406 326
        """
paul@251 327
paul@406 328
        self.constant_values[objpath] = value, origin, encoding
paul@251 329
paul@0 330
    def get_literal_reference(self, name, ref, items, cls):
paul@0 331
paul@11 332
        """
paul@11 333
        Return a literal reference for the given type 'name', literal 'ref',
paul@11 334
        node 'items' and employing the given 'cls' as the class of the returned
paul@11 335
        reference object.
paul@11 336
        """
paul@11 337
paul@0 338
        # Construct an invocation using the items as arguments.
paul@0 339
paul@0 340
        typename = "$L%s" % name
paul@0 341
paul@0 342
        invocation = compiler.ast.CallFunc(
paul@0 343
            compiler.ast.Name(typename),
paul@0 344
            items
paul@0 345
            )
paul@0 346
paul@0 347
        # Get a name for the actual literal.
paul@0 348
paul@0 349
        instname = self.get_literal_name()
paul@0 350
        self.next_literal()
paul@0 351
paul@0 352
        # Record the type for the literal.
paul@0 353
paul@0 354
        objpath = self.get_object_path(instname)
paul@0 355
        self.literal_types[objpath] = ref.get_origin()
paul@0 356
paul@0 357
        # Return a wrapper for the invocation exposing the items.
paul@0 358
paul@0 359
        return cls(
paul@0 360
            instname,
paul@0 361
            ref.instance_of(),
paul@0 362
            self.process_structure_node(invocation),
paul@0 363
            invocation.args
paul@0 364
            )
paul@0 365
paul@0 366
    # Node handling.
paul@0 367
paul@0 368
    def process_structure(self, node):
paul@0 369
paul@0 370
        """
paul@0 371
        Within the given 'node', process the program structure.
paul@0 372
paul@0 373
        During inspection, this will process global declarations, adjusting the
paul@0 374
        module namespace, and import statements, building a module dependency
paul@0 375
        hierarchy.
paul@0 376
paul@0 377
        During translation, this will consult deduced program information and
paul@0 378
        output translated code.
paul@0 379
        """
paul@0 380
paul@0 381
        l = []
paul@0 382
        for n in node.getChildNodes():
paul@0 383
            l.append(self.process_structure_node(n))
paul@0 384
        return l
paul@0 385
paul@0 386
    def process_augassign_node(self, n):
paul@0 387
paul@0 388
        "Process the given augmented assignment node 'n'."
paul@0 389
paul@0 390
        op = operator_functions[n.op]
paul@0 391
paul@0 392
        if isinstance(n.node, compiler.ast.Getattr):
paul@0 393
            target = compiler.ast.AssAttr(n.node.expr, n.node.attrname, "OP_ASSIGN")
paul@0 394
        elif isinstance(n.node, compiler.ast.Name):
paul@0 395
            target = compiler.ast.AssName(n.node.name, "OP_ASSIGN")
paul@0 396
        else:
paul@0 397
            target = n.node
paul@0 398
paul@0 399
        assignment = compiler.ast.Assign(
paul@0 400
            [target],
paul@0 401
            compiler.ast.CallFunc(
paul@0 402
                compiler.ast.Name("$op%s" % op),
paul@0 403
                [n.node, n.expr]))
paul@0 404
paul@0 405
        return self.process_structure_node(assignment)
paul@0 406
paul@320 407
    def process_assignment_for_object(self, original_name, source):
paul@0 408
paul@0 409
        """
paul@0 410
        Return an assignment operation making 'original_name' refer to the given
paul@196 411
        'source'.
paul@0 412
        """
paul@0 413
paul@0 414
        assignment = compiler.ast.Assign(
paul@0 415
            [compiler.ast.AssName(original_name, "OP_ASSIGN")],
paul@196 416
            source
paul@0 417
            )
paul@0 418
paul@0 419
        return self.process_structure_node(assignment)
paul@0 420
paul@0 421
    def process_assignment_node_items(self, n, expr):
paul@0 422
paul@0 423
        """
paul@0 424
        Process the given assignment node 'n' whose children are to be assigned
paul@0 425
        items of 'expr'.
paul@0 426
        """
paul@0 427
paul@0 428
        name_ref = self.process_structure_node(expr)
paul@0 429
paul@509 430
        # Either unpack the items and present them directly to each assignment
paul@509 431
        # node.
paul@509 432
paul@509 433
        if isinstance(name_ref, LiteralSequenceRef) and \
paul@509 434
           self.process_literal_sequence_items(n, name_ref):
paul@0 435
paul@509 436
            pass
paul@509 437
paul@509 438
        # Or have the assignment nodes access each item via the sequence API.
paul@509 439
paul@509 440
        else:
paul@509 441
            self.process_assignment_node_items_by_position(n, expr, name_ref)
paul@0 442
paul@0 443
    def process_assignment_node_items_by_position(self, n, expr, name_ref):
paul@0 444
paul@0 445
        """
paul@0 446
        Process the given sequence assignment node 'n', converting the node to
paul@0 447
        the separate assignment of each target using positional access on a
paul@0 448
        temporary variable representing the sequence. Use 'expr' as the assigned
paul@0 449
        value and 'name_ref' as the reference providing any existing temporary
paul@0 450
        variable.
paul@0 451
        """
paul@0 452
paul@0 453
        assignments = []
paul@0 454
paul@508 455
        # Employ existing names to access the sequence.
paul@508 456
        # Literal sequences do not provide names of accessible objects.
paul@508 457
paul@508 458
        if isinstance(name_ref, NameRef) and not isinstance(name_ref, LiteralSequenceRef):
paul@0 459
            temp = name_ref.name
paul@508 460
paul@508 461
        # For other expressions, create a temporary name to reference the items.
paul@508 462
paul@0 463
        else:
paul@0 464
            temp = self.get_temporary_name()
paul@0 465
            self.next_temporary()
paul@0 466
paul@0 467
            assignments.append(
paul@0 468
                compiler.ast.Assign([compiler.ast.AssName(temp, "OP_ASSIGN")], expr)
paul@0 469
                )
paul@0 470
paul@508 471
        # Assign the items to the target nodes.
paul@508 472
paul@0 473
        for i, node in enumerate(n.nodes):
paul@0 474
            assignments.append(
paul@0 475
                compiler.ast.Assign([node], compiler.ast.Subscript(
paul@395 476
                    compiler.ast.Name(temp), "OP_APPLY", [compiler.ast.Const(i, str(i))]))
paul@0 477
                )
paul@0 478
paul@0 479
        return self.process_structure_node(compiler.ast.Stmt(assignments))
paul@0 480
paul@0 481
    def process_literal_sequence_items(self, n, name_ref):
paul@0 482
paul@0 483
        """
paul@0 484
        Process the given assignment node 'n', obtaining from the given
paul@0 485
        'name_ref' the items to be assigned to the assignment targets.
paul@509 486
paul@509 487
        Return whether this method was able to process the assignment node as
paul@509 488
        a sequence of direct assignments.
paul@0 489
        """
paul@0 490
paul@0 491
        if len(n.nodes) == len(name_ref.items):
paul@509 492
            assigned_names, count = get_names_from_nodes(n.nodes)
paul@509 493
            accessed_names, _count = get_names_from_nodes(name_ref.items)
paul@509 494
paul@509 495
            # Only assign directly between items if all assigned names are
paul@509 496
            # plain names (not attribute assignments), and if the assigned names
paul@509 497
            # do not appear in the accessed names.
paul@509 498
paul@509 499
            if len(assigned_names) == count and \
paul@509 500
               not assigned_names.intersection(accessed_names):
paul@509 501
paul@509 502
                for node, item in zip(n.nodes, name_ref.items):
paul@509 503
                    self.process_assignment_node(node, item)
paul@509 504
paul@509 505
                return True
paul@509 506
paul@509 507
            # Otherwise, use the position-based mechanism to obtain values.
paul@509 508
paul@509 509
            else:
paul@509 510
                return False
paul@0 511
        else:
paul@0 512
            raise InspectError("In %s, item assignment needing %d items is given %d items." % (
paul@0 513
                self.get_namespace_path(), len(n.nodes), len(name_ref.items)))
paul@0 514
paul@0 515
    def process_compare_node(self, n):
paul@0 516
paul@0 517
        """
paul@0 518
        Process the given comparison node 'n', converting an operator sequence
paul@0 519
        from...
paul@0 520
paul@0 521
        <expr1> <op1> <expr2> <op2> <expr3>
paul@0 522
paul@0 523
        ...to...
paul@0 524
paul@0 525
        <op1>(<expr1>, <expr2>) and <op2>(<expr2>, <expr3>)
paul@0 526
        """
paul@0 527
paul@0 528
        invocations = []
paul@0 529
        last = n.expr
paul@0 530
paul@0 531
        for op, op_node in n.ops:
paul@0 532
            op = operator_functions.get(op)
paul@0 533
paul@0 534
            invocations.append(compiler.ast.CallFunc(
paul@0 535
                compiler.ast.Name("$op%s" % op),
paul@0 536
                [last, op_node]))
paul@0 537
paul@0 538
            last = op_node
paul@0 539
paul@0 540
        if len(invocations) > 1:
paul@0 541
            result = compiler.ast.And(invocations)
paul@0 542
        else:
paul@0 543
            result = invocations[0]
paul@0 544
paul@0 545
        return self.process_structure_node(result)
paul@0 546
paul@0 547
    def process_dict_node(self, node):
paul@0 548
paul@0 549
        """
paul@0 550
        Process the given dictionary 'node', returning a list of (key, value)
paul@0 551
        tuples.
paul@0 552
        """
paul@0 553
paul@0 554
        l = []
paul@0 555
        for key, value in node.items:
paul@0 556
            l.append((
paul@0 557
                self.process_structure_node(key),
paul@0 558
                self.process_structure_node(value)))
paul@0 559
        return l
paul@0 560
paul@0 561
    def process_for_node(self, n):
paul@0 562
paul@0 563
        """
paul@0 564
        Generate attribute accesses for {n.list}.__iter__ and the next method on
paul@0 565
        the iterator, producing a replacement node for the original.
paul@0 566
        """
paul@0 567
paul@0 568
        node = compiler.ast.Stmt([
paul@0 569
paul@533 570
            # <next> = {n.list}.__iter__().next
paul@0 571
paul@0 572
            compiler.ast.Assign(
paul@0 573
                [compiler.ast.AssName(self.get_iterator_name(), "OP_ASSIGN")],
paul@533 574
                compiler.ast.Getattr(
paul@533 575
                    compiler.ast.CallFunc(
paul@533 576
                        compiler.ast.Getattr(n.list, "__iter__"),
paul@533 577
                        []
paul@533 578
                        ), "next")),
paul@0 579
paul@0 580
            # try:
paul@0 581
            #     while True:
paul@533 582
            #         <var>... = <next>()
paul@0 583
            #         ...
paul@0 584
            # except StopIteration:
paul@0 585
            #     pass
paul@0 586
paul@0 587
            compiler.ast.TryExcept(
paul@0 588
                compiler.ast.While(
paul@0 589
                    compiler.ast.Name("True"),
paul@0 590
                    compiler.ast.Stmt([
paul@0 591
                        compiler.ast.Assign(
paul@0 592
                            [n.assign],
paul@0 593
                            compiler.ast.CallFunc(
paul@533 594
                                compiler.ast.Name(self.get_iterator_name()),
paul@0 595
                                []
paul@0 596
                                )),
paul@0 597
                        n.body]),
paul@0 598
                    None),
paul@0 599
                [(compiler.ast.Name("StopIteration"), None, compiler.ast.Stmt([compiler.ast.Pass()]))],
paul@0 600
                None)
paul@0 601
            ])
paul@0 602
paul@0 603
        self.next_iterator()
paul@0 604
        self.process_structure_node(node)
paul@0 605
paul@0 606
    def process_literal_sequence_node(self, n, name, ref, cls):
paul@0 607
paul@0 608
        """
paul@0 609
        Process the given literal sequence node 'n' as a function invocation,
paul@0 610
        with 'name' indicating the type of the sequence, and 'ref' being a
paul@0 611
        reference to the type. The 'cls' is used to instantiate a suitable name
paul@0 612
        reference.
paul@0 613
        """
paul@0 614
paul@0 615
        if name == "dict":
paul@0 616
            items = []
paul@0 617
            for key, value in n.items:
paul@0 618
                items.append(compiler.ast.Tuple([key, value]))
paul@0 619
        else: # name in ("list", "tuple"):
paul@0 620
            items = n.nodes
paul@0 621
paul@0 622
        return self.get_literal_reference(name, ref, items, cls)
paul@0 623
paul@0 624
    def process_operator_node(self, n):
paul@0 625
paul@0 626
        """
paul@0 627
        Process the given operator node 'n' as an operator function invocation.
paul@0 628
        """
paul@0 629
paul@0 630
        op = operator_functions[n.__class__.__name__]
paul@0 631
        invocation = compiler.ast.CallFunc(
paul@0 632
            compiler.ast.Name("$op%s" % op),
paul@0 633
            list(n.getChildNodes())
paul@0 634
            )
paul@0 635
        return self.process_structure_node(invocation)
paul@0 636
paul@173 637
    def process_print_node(self, n):
paul@173 638
paul@173 639
        """
paul@173 640
        Process the given print node 'n' as an invocation on a stream of the
paul@173 641
        form...
paul@173 642
paul@173 643
        $print(dest, args, nl)
paul@173 644
paul@173 645
        The special function name will be translated elsewhere.
paul@173 646
        """
paul@173 647
paul@173 648
        nl = isinstance(n, compiler.ast.Printnl)
paul@173 649
        invocation = compiler.ast.CallFunc(
paul@173 650
            compiler.ast.Name("$print"),
paul@173 651
            [n.dest or compiler.ast.Name("None"),
paul@173 652
             compiler.ast.List(list(n.nodes)),
paul@359 653
             nl and compiler.ast.Name("True") or compiler.ast.Name("False")]
paul@173 654
            )
paul@173 655
        return self.process_structure_node(invocation)
paul@173 656
paul@0 657
    def process_slice_node(self, n, expr=None):
paul@0 658
paul@0 659
        """
paul@0 660
        Process the given slice node 'n' as an operator function invocation.
paul@0 661
        """
paul@0 662
paul@548 663
        if n.flags == "OP_ASSIGN": op = "setslice"
paul@548 664
        elif n.flags == "OP_DELETE": op = "delslice"
paul@548 665
        else: op = "getslice"
paul@548 666
paul@0 667
        invocation = compiler.ast.CallFunc(
paul@0 668
            compiler.ast.Name("$op%s" % op),
paul@0 669
            [n.expr, n.lower or compiler.ast.Name("None"), n.upper or compiler.ast.Name("None")] +
paul@0 670
                (expr and [expr] or [])
paul@0 671
            )
paul@548 672
paul@548 673
        # Fix parse tree structure.
paul@548 674
paul@548 675
        if op == "delslice":
paul@548 676
            invocation = compiler.ast.Discard(invocation)
paul@548 677
paul@0 678
        return self.process_structure_node(invocation)
paul@0 679
paul@0 680
    def process_sliceobj_node(self, n):
paul@0 681
paul@0 682
        """
paul@0 683
        Process the given slice object node 'n' as a slice constructor.
paul@0 684
        """
paul@0 685
paul@0 686
        op = "slice"
paul@0 687
        invocation = compiler.ast.CallFunc(
paul@0 688
            compiler.ast.Name("$op%s" % op),
paul@0 689
            n.nodes
paul@0 690
            )
paul@0 691
        return self.process_structure_node(invocation)
paul@0 692
paul@0 693
    def process_subscript_node(self, n, expr=None):
paul@0 694
paul@0 695
        """
paul@0 696
        Process the given subscript node 'n' as an operator function invocation.
paul@0 697
        """
paul@0 698
paul@548 699
        if n.flags == "OP_ASSIGN": op = "setitem"
paul@548 700
        elif n.flags == "OP_DELETE": op = "delitem"
paul@548 701
        else: op = "getitem"
paul@548 702
paul@0 703
        invocation = compiler.ast.CallFunc(
paul@0 704
            compiler.ast.Name("$op%s" % op),
paul@0 705
            [n.expr] + list(n.subs) + (expr and [expr] or [])
paul@0 706
            )
paul@548 707
paul@548 708
        # Fix parse tree structure.
paul@548 709
paul@548 710
        if op == "delitem":
paul@548 711
            invocation = compiler.ast.Discard(invocation)
paul@548 712
paul@0 713
        return self.process_structure_node(invocation)
paul@0 714
paul@0 715
    def process_attribute_chain(self, n):
paul@0 716
paul@0 717
        """
paul@0 718
        Process the given attribute access node 'n'. Return a reference
paul@0 719
        describing the expression.
paul@0 720
        """
paul@0 721
paul@0 722
        # AssAttr/Getattr are nested with the outermost access being the last
paul@0 723
        # access in any chain.
paul@0 724
paul@0 725
        self.attrs.insert(0, n.attrname)
paul@0 726
        attrs = self.attrs
paul@0 727
paul@0 728
        # Break attribute chains where non-access nodes are found.
paul@0 729
paul@0 730
        if not self.have_access_expression(n):
paul@110 731
            self.reset_attribute_chain()
paul@0 732
paul@0 733
        # Descend into the expression, extending backwards any existing chain,
paul@0 734
        # or building another for the expression.
paul@0 735
paul@0 736
        name_ref = self.process_structure_node(n.expr)
paul@0 737
paul@0 738
        # Restore chain information applying to this node.
paul@0 739
paul@110 740
        if not self.have_access_expression(n):
paul@110 741
            self.restore_attribute_chain(attrs)
paul@0 742
paul@0 743
        # Return immediately if the expression was another access and thus a
paul@0 744
        # continuation backwards along the chain. The above processing will
paul@0 745
        # have followed the chain all the way to its conclusion.
paul@0 746
paul@0 747
        if self.have_access_expression(n):
paul@0 748
            del self.attrs[0]
paul@0 749
paul@0 750
        return name_ref
paul@0 751
paul@124 752
    # Attribute chain handling.
paul@124 753
paul@110 754
    def reset_attribute_chain(self):
paul@110 755
paul@110 756
        "Reset the attribute chain for a subexpression of an attribute access."
paul@110 757
paul@110 758
        self.attrs = []
paul@124 759
        self.chain_assignment.append(self.in_assignment)
paul@124 760
        self.chain_invocation.append(self.in_invocation)
paul@124 761
        self.in_assignment = None
paul@553 762
        self.in_invocation = None
paul@110 763
paul@110 764
    def restore_attribute_chain(self, attrs):
paul@110 765
paul@110 766
        "Restore the attribute chain for an attribute access."
paul@110 767
paul@110 768
        self.attrs = attrs
paul@124 769
        self.in_assignment = self.chain_assignment.pop()
paul@124 770
        self.in_invocation = self.chain_invocation.pop()
paul@110 771
paul@0 772
    def have_access_expression(self, node):
paul@0 773
paul@0 774
        "Return whether the expression associated with 'node' is Getattr."
paul@0 775
paul@0 776
        return isinstance(node.expr, compiler.ast.Getattr)
paul@0 777
paul@603 778
    def get_name_for_tracking(self, name, name_ref=None):
paul@0 779
paul@0 780
        """
paul@0 781
        Return the name to be used for attribute usage observations involving
paul@603 782
        the given 'name' in the current namespace.
paul@603 783
paul@603 784
        If the name is being used outside a function, and if 'name_ref' is
paul@603 785
        given, a path featuring the name in the global namespace is returned
paul@605 786
        where 'name_ref' indicates a global. Otherwise, a path computed using
paul@603 787
        the current namespace and the given name is returned.
paul@0 788
paul@0 789
        The intention of this method is to provide a suitably-qualified name
paul@0 790
        that can be tracked across namespaces. Where globals are being
paul@0 791
        referenced in class namespaces, they should be referenced using their
paul@0 792
        path within the module, not using a path within each class.
paul@0 793
paul@0 794
        It may not be possible to identify a global within a function at the
paul@0 795
        time of inspection (since a global may appear later in a file).
paul@0 796
        Consequently, globals are identified by their local name rather than
paul@0 797
        their module-qualified path.
paul@0 798
        """
paul@0 799
paul@0 800
        # For functions, use the appropriate local names.
paul@0 801
paul@0 802
        if self.in_function:
paul@0 803
            return name
paul@0 804
paul@603 805
        # For global names outside functions, use a global name.
paul@597 806
paul@603 807
        elif name_ref and name_ref.is_global_name():
paul@603 808
            return self.get_global_path(name)
paul@0 809
paul@152 810
        # Otherwise, establish a name in the current namespace.
paul@0 811
paul@0 812
        else:
paul@0 813
            return self.get_object_path(name)
paul@0 814
paul@0 815
    def get_path_for_access(self):
paul@0 816
paul@0 817
        "Outside functions, register accesses at the module level."
paul@0 818
paul@0 819
        if not self.in_function:
paul@0 820
            return self.name
paul@0 821
        else:
paul@0 822
            return self.get_namespace_path()
paul@0 823
paul@0 824
    def get_module_name(self, node):
paul@0 825
paul@0 826
        """
paul@0 827
        Using the given From 'node' in this module, calculate any relative import
paul@0 828
        information, returning a tuple containing a module to import along with any
paul@0 829
        names to import based on the node's name information.
paul@0 830
paul@0 831
        Where the returned module is given as None, whole module imports should
paul@0 832
        be performed for the returned modules using the returned names.
paul@0 833
        """
paul@0 834
paul@0 835
        # Absolute import.
paul@0 836
paul@0 837
        if node.level == 0:
paul@0 838
            return node.modname, node.names
paul@0 839
paul@0 840
        # Relative to an ancestor of this module.
paul@0 841
paul@0 842
        else:
paul@0 843
            path = self.name.split(".")
paul@0 844
            level = node.level
paul@0 845
paul@0 846
            # Relative imports treat package roots as submodules.
paul@0 847
paul@0 848
            if split(self.filename)[-1] == "__init__.py":
paul@0 849
                level -= 1
paul@0 850
paul@0 851
            if level > len(path):
paul@0 852
                raise InspectError("Relative import %r involves too many levels up from module %r" % (
paul@0 853
                    ("%s%s" % ("." * node.level, node.modname or "")), self.name))
paul@0 854
paul@0 855
            basename = ".".join(path[:len(path)-level])
paul@0 856
paul@0 857
        # Name imports from a module.
paul@0 858
paul@0 859
        if node.modname:
paul@0 860
            return "%s.%s" % (basename, node.modname), node.names
paul@0 861
paul@0 862
        # Relative whole module imports.
paul@0 863
paul@0 864
        else:
paul@0 865
            return basename, node.names
paul@0 866
paul@0 867
def get_argnames(args):
paul@0 868
paul@0 869
    """
paul@0 870
    Return a list of all names provided by 'args'. Since tuples may be
paul@0 871
    employed, the arguments are traversed depth-first.
paul@0 872
    """
paul@0 873
paul@0 874
    l = []
paul@0 875
    for arg in args:
paul@0 876
        if isinstance(arg, tuple):
paul@0 877
            l += get_argnames(arg)
paul@0 878
        else:
paul@0 879
            l.append(arg)
paul@0 880
    return l
paul@0 881
paul@509 882
def get_names_from_nodes(nodes):
paul@509 883
paul@509 884
    """
paul@509 885
    Return the names employed in the given 'nodes' along with the number of
paul@509 886
    nodes excluding sequences.
paul@509 887
    """
paul@509 888
paul@509 889
    names = set()
paul@509 890
    count = 0
paul@509 891
paul@509 892
    for node in nodes:
paul@509 893
paul@509 894
        # Add names and count them.
paul@509 895
paul@509 896
        if isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
paul@509 897
            names.add(node.name)
paul@509 898
            count += 1
paul@509 899
paul@509 900
        # Add names from sequences and incorporate their counts.
paul@509 901
paul@509 902
        elif isinstance(node, (compiler.ast.AssList, compiler.ast.AssTuple,
paul@509 903
                               compiler.ast.List, compiler.ast.Set,
paul@509 904
                               compiler.ast.Tuple)):
paul@509 905
            _names, _count = get_names_from_nodes(node.nodes)
paul@509 906
            names.update(_names)
paul@509 907
            count += _count
paul@509 908
paul@509 909
        # Count non-name, non-sequence nodes.
paul@509 910
paul@509 911
        else:
paul@509 912
            count += 1
paul@509 913
paul@509 914
    return names, count
paul@509 915
paul@491 916
# Result classes.
paul@491 917
paul@491 918
class InstructionSequence:
paul@491 919
paul@491 920
    "A generic sequence of instructions."
paul@491 921
paul@491 922
    def __init__(self, instructions):
paul@491 923
        self.instructions = instructions
paul@491 924
paul@491 925
    def get_value_instruction(self):
paul@491 926
        return self.instructions[-1]
paul@491 927
paul@491 928
    def get_init_instructions(self):
paul@491 929
        return self.instructions[:-1]
paul@491 930
paul@0 931
# Dictionary utilities.
paul@0 932
paul@0 933
def init_item(d, key, fn):
paul@0 934
paul@0 935
    """
paul@0 936
    Add to 'd' an entry for 'key' using the callable 'fn' to make an initial
paul@0 937
    value where no entry already exists.
paul@0 938
    """
paul@0 939
paul@0 940
    if not d.has_key(key):
paul@0 941
        d[key] = fn()
paul@0 942
    return d[key]
paul@0 943
paul@0 944
def dict_for_keys(d, keys):
paul@0 945
paul@0 946
    "Return a new dictionary containing entries from 'd' for the given 'keys'."
paul@0 947
paul@0 948
    nd = {}
paul@0 949
    for key in keys:
paul@0 950
        if d.has_key(key):
paul@0 951
            nd[key] = d[key]
paul@0 952
    return nd
paul@0 953
paul@0 954
def make_key(s):
paul@0 955
paul@0 956
    "Make sequence 's' into a tuple-based key, first sorting its contents."
paul@0 957
paul@0 958
    l = list(s)
paul@0 959
    l.sort()
paul@0 960
    return tuple(l)
paul@0 961
paul@0 962
def add_counter_item(d, key):
paul@0 963
paul@0 964
    """
paul@0 965
    Make a mapping in 'd' for 'key' to the number of keys added before it, thus
paul@0 966
    maintaining a mapping of keys to their order of insertion.
paul@0 967
    """
paul@0 968
paul@0 969
    if not d.has_key(key):
paul@0 970
        d[key] = len(d.keys())
paul@0 971
    return d[key] 
paul@0 972
paul@0 973
def remove_items(d1, d2):
paul@0 974
paul@0 975
    "Remove from 'd1' all items from 'd2'."
paul@0 976
paul@0 977
    for key in d2.keys():
paul@0 978
        if d1.has_key(key):
paul@0 979
            del d1[key]
paul@0 980
paul@0 981
# Set utilities.
paul@0 982
paul@0 983
def first(s):
paul@0 984
    return list(s)[0]
paul@0 985
paul@0 986
def same(s1, s2):
paul@0 987
    return set(s1) == set(s2)
paul@0 988
paul@0 989
# General input/output.
paul@0 990
paul@0 991
def readfile(filename):
paul@0 992
paul@0 993
    "Return the contents of 'filename'."
paul@0 994
paul@0 995
    f = open(filename)
paul@0 996
    try:
paul@0 997
        return f.read()
paul@0 998
    finally:
paul@0 999
        f.close()
paul@0 1000
paul@0 1001
def writefile(filename, s):
paul@0 1002
paul@0 1003
    "Write to 'filename' the string 's'."
paul@0 1004
paul@0 1005
    f = open(filename, "w")
paul@0 1006
    try:
paul@0 1007
        f.write(s)
paul@0 1008
    finally:
paul@0 1009
        f.close()
paul@0 1010
paul@0 1011
# General encoding.
paul@0 1012
paul@0 1013
def sorted_output(x):
paul@0 1014
paul@0 1015
    "Sort sequence 'x' and return a string with commas separating the values."
paul@0 1016
paul@0 1017
    x = map(str, x)
paul@0 1018
    x.sort()
paul@0 1019
    return ", ".join(x)
paul@0 1020
paul@537 1021
def get_string_details(literals, encoding):
paul@512 1022
paul@512 1023
    """
paul@537 1024
    Determine whether 'literals' represent Unicode strings or byte strings,
paul@537 1025
    using 'encoding' to reproduce byte sequences.
paul@537 1026
paul@537 1027
    Each literal is the full program representation including prefix and quotes
paul@537 1028
    recoded by the parser to UTF-8. Thus, any literal found to represent a byte
paul@537 1029
    string needs to be translated back to its original encoding.
paul@537 1030
paul@537 1031
    Return a single encoded literal value, a type name, and the original
paul@537 1032
    encoding as a tuple.
paul@537 1033
    """
paul@537 1034
paul@537 1035
    typename = "unicode"
paul@537 1036
paul@537 1037
    l = []
paul@537 1038
paul@537 1039
    for s in literals:
paul@537 1040
        out, _typename = get_literal_details(s)
paul@537 1041
        if _typename == "str":
paul@537 1042
            typename = "str"
paul@537 1043
        l.append(out)
paul@537 1044
paul@537 1045
    out = "".join(l)
paul@537 1046
paul@537 1047
    # For Unicode values, convert to the UTF-8 program representation.
paul@537 1048
paul@537 1049
    if typename == "unicode":
paul@537 1050
        return out.encode("utf-8"), typename, encoding
paul@537 1051
paul@537 1052
    # For byte string values, convert back to the original encoding.
paul@537 1053
paul@537 1054
    else:
paul@537 1055
        return out.encode(encoding), typename, encoding
paul@537 1056
paul@537 1057
def get_literal_details(s):
paul@537 1058
paul@537 1059
    """
paul@537 1060
    Determine whether 's' represents a Unicode string or a byte string, where
paul@537 1061
    's' contains the full program representation of a literal including prefix
paul@537 1062
    and quotes, recoded by the parser to UTF-8.
paul@512 1063
paul@512 1064
    Find and convert Unicode values starting with <backslash>u or <backslash>U,
paul@512 1065
    and byte or Unicode values starting with <backslash><octal digit> or
paul@512 1066
    <backslash>x.
paul@512 1067
paul@512 1068
    Literals prefixed with "u" cause <backslash><octal digit> and <backslash>x
paul@512 1069
    to be considered as Unicode values. Otherwise, they produce byte values and
paul@512 1070
    cause unprefixed strings to be considered as byte strings.
paul@512 1071
paul@512 1072
    Literals prefixed with "r" do not have their backslash-encoded values
paul@512 1073
    converted unless also prefixed with "u", in which case only the above value
paul@512 1074
    formats are converted, not any of the other special sequences for things
paul@512 1075
    like newlines.
paul@512 1076
paul@537 1077
    Return the literal value as a Unicode object together with the appropriate
paul@537 1078
    type name in a tuple.
paul@512 1079
    """
paul@512 1080
paul@512 1081
    l = []
paul@512 1082
paul@512 1083
    # Identify the quote character and use it to identify the prefix.
paul@512 1084
paul@512 1085
    quote_type = s[-1]
paul@512 1086
    prefix_end = s.find(quote_type)
paul@512 1087
    prefix = s[:prefix_end].lower()
paul@512 1088
paul@512 1089
    if prefix not in ("", "b", "br", "r", "u", "ur"):
paul@512 1090
        raise ValueError, "String literal does not have a supported prefix: %s" % s
paul@512 1091
paul@513 1092
    if "b" in prefix:
paul@513 1093
        typename = "str"
paul@513 1094
    else:
paul@513 1095
        typename = "unicode"
paul@513 1096
paul@512 1097
    # Identify triple quotes or single quotes.
paul@512 1098
paul@512 1099
    if len(s) >= 6 and s[-2] == quote_type and s[-3] == quote_type:
paul@512 1100
        quote = s[prefix_end:prefix_end+3]
paul@512 1101
        current = prefix_end + 3
paul@512 1102
        end = len(s) - 3
paul@512 1103
    else:
paul@512 1104
        quote = s[prefix_end]
paul@512 1105
        current = prefix_end + 1
paul@512 1106
        end = len(s) - 1
paul@512 1107
paul@512 1108
    # Conversions of some quoted values.
paul@512 1109
paul@512 1110
    searches = {
paul@512 1111
        "u" : (6, 16),
paul@512 1112
        "U" : (10, 16),
paul@512 1113
        "x" : (4, 16),
paul@512 1114
        }
paul@512 1115
paul@512 1116
    octal_digits = map(str, range(0, 8))
paul@512 1117
paul@512 1118
    # Translations of some quoted values.
paul@512 1119
paul@512 1120
    escaped = {
paul@512 1121
        "\\" : "\\", "'" : "'", '"' : '"',
paul@512 1122
        "a" : "\a", "b" : "\b", "f" : "\f",
paul@512 1123
        "n" : "\n", "r" : "\r", "t" : "\t",
paul@512 1124
        }
paul@512 1125
paul@512 1126
    while current < end:
paul@512 1127
paul@512 1128
        # Look for quoted values.
paul@512 1129
paul@512 1130
        index = s.find("\\", current)
paul@512 1131
        if index == -1 or index + 1 == end:
paul@512 1132
            l.append(s[current:end])
paul@512 1133
            break
paul@512 1134
paul@512 1135
        # Add the preceding text.
paul@512 1136
paul@512 1137
        l.append(s[current:index])
paul@512 1138
paul@512 1139
        # Handle quoted text.
paul@512 1140
paul@512 1141
        term = s[index+1]
paul@512 1142
paul@512 1143
        # Add Unicode values. Where a string is u-prefixed, even \o and \x
paul@512 1144
        # produce Unicode values.
paul@512 1145
paul@513 1146
        if typename == "unicode" and (
paul@513 1147
            term in ("u", "U") or 
paul@513 1148
            "u" in prefix and (term == "x" or term in octal_digits)):
paul@512 1149
paul@512 1150
            needed, base = searches.get(term, (4, 8))
paul@512 1151
            value = convert_quoted_value(s, index, needed, end, base, unichr)
paul@512 1152
            l.append(value)
paul@512 1153
            current = index + needed
paul@512 1154
paul@512 1155
        # Add raw byte values, changing the string type.
paul@512 1156
paul@512 1157
        elif "r" not in prefix and (
paul@512 1158
             term == "x" or term in octal_digits):
paul@512 1159
paul@512 1160
            needed, base = searches.get(term, (4, 8))
paul@512 1161
            value = convert_quoted_value(s, index, needed, end, base, chr)
paul@512 1162
            l.append(value)
paul@512 1163
            typename = "str"
paul@512 1164
            current = index + needed
paul@512 1165
paul@512 1166
        # Add other escaped values.
paul@512 1167
paul@512 1168
        elif "r" not in prefix and escaped.has_key(term):
paul@512 1169
            l.append(escaped[term])
paul@512 1170
            current = index + 2
paul@512 1171
paul@512 1172
        # Add other text as found.
paul@512 1173
paul@512 1174
        else:
paul@512 1175
            l.append(s[index:index+2])
paul@512 1176
            current = index + 2
paul@512 1177
paul@537 1178
    # Collect the components into a single Unicode object. Since the literal
paul@537 1179
    # text was already in UTF-8 form, interpret plain strings as UTF-8
paul@537 1180
    # sequences.
paul@512 1181
paul@537 1182
    out = []
paul@512 1183
paul@537 1184
    for value in l:
paul@537 1185
        if isinstance(value, unicode):
paul@537 1186
            out.append(value)
paul@537 1187
        else:
paul@537 1188
            out.append(unicode(value, "utf-8"))
paul@512 1189
paul@537 1190
    return "".join(out), typename
paul@512 1191
paul@512 1192
def convert_quoted_value(s, index, needed, end, base, fn):
paul@512 1193
paul@512 1194
    """
paul@512 1195
    Interpret a quoted value in 's' at 'index' with the given 'needed' number of
paul@512 1196
    positions, and with the given 'end' indicating the first position after the
paul@512 1197
    end of the actual string content.
paul@512 1198
paul@512 1199
    Use 'base' as the numerical base when interpreting the value, and use 'fn'
paul@512 1200
    to convert the value to an appropriate type.
paul@512 1201
    """
paul@512 1202
paul@512 1203
    s = s[index:min(index+needed, end)]
paul@512 1204
paul@512 1205
    # Not a complete occurrence.
paul@512 1206
paul@512 1207
    if len(s) < needed:
paul@512 1208
        return s
paul@512 1209
paul@512 1210
    # Test for a well-formed value.
paul@512 1211
paul@512 1212
    try:
paul@512 1213
        first = base == 8 and 1 or 2
paul@512 1214
        value = int(s[first:needed], base)
paul@512 1215
    except ValueError:
paul@512 1216
        return s
paul@512 1217
    else:
paul@512 1218
        return fn(value)
paul@512 1219
paul@0 1220
# Attribute chain decoding.
paul@0 1221
paul@0 1222
def get_attrnames(attrnames):
paul@11 1223
paul@11 1224
    """
paul@11 1225
    Split the qualified attribute chain 'attrnames' into its components,
paul@11 1226
    handling special attributes starting with "#" that indicate type
paul@11 1227
    conformance.
paul@11 1228
    """
paul@11 1229
paul@0 1230
    if attrnames.startswith("#"):
paul@0 1231
        return [attrnames]
paul@0 1232
    else:
paul@0 1233
        return attrnames.split(".")
paul@0 1234
paul@0 1235
def get_attrname_from_location(location):
paul@11 1236
paul@11 1237
    """
paul@11 1238
    Extract the first attribute from the attribute names employed in a
paul@11 1239
    'location'.
paul@11 1240
    """
paul@11 1241
paul@0 1242
    path, name, attrnames, access = location
paul@91 1243
    if not attrnames:
paul@91 1244
        return attrnames
paul@0 1245
    return get_attrnames(attrnames)[0]
paul@0 1246
paul@85 1247
def get_name_path(path, name):
paul@85 1248
paul@85 1249
    "Return a suitable qualified name from the given 'path' and 'name'."
paul@85 1250
paul@85 1251
    if "." in name:
paul@85 1252
        return name
paul@85 1253
    else:
paul@85 1254
        return "%s.%s" % (path, name)
paul@85 1255
paul@90 1256
# Usage-related functions.
paul@89 1257
paul@89 1258
def get_types_for_usage(attrnames, objects):
paul@89 1259
paul@89 1260
    """
paul@89 1261
    Identify the types that can support the given 'attrnames', using the
paul@89 1262
    given 'objects' as the catalogue of type details.
paul@89 1263
    """
paul@89 1264
paul@89 1265
    types = []
paul@89 1266
    for name, _attrnames in objects.items():
paul@89 1267
        if set(attrnames).issubset(_attrnames):
paul@89 1268
            types.append(name)
paul@89 1269
    return types
paul@89 1270
paul@90 1271
def get_invoked_attributes(usage):
paul@90 1272
paul@90 1273
    "Obtain invoked attribute from the given 'usage'."
paul@90 1274
paul@90 1275
    invoked = []
paul@90 1276
    if usage:
paul@107 1277
        for attrname, invocation, assignment in usage:
paul@90 1278
            if invocation:
paul@90 1279
                invoked.append(attrname)
paul@90 1280
    return invoked
paul@90 1281
paul@107 1282
def get_assigned_attributes(usage):
paul@107 1283
paul@107 1284
    "Obtain assigned attribute from the given 'usage'."
paul@107 1285
paul@107 1286
    assigned = []
paul@107 1287
    if usage:
paul@107 1288
        for attrname, invocation, assignment in usage:
paul@107 1289
            if assignment:
paul@107 1290
                assigned.append(attrname)
paul@107 1291
    return assigned
paul@107 1292
paul@366 1293
# Type and module functions.
paul@538 1294
# NOTE: This makes assumptions about the __builtins__ structure.
paul@366 1295
paul@366 1296
def get_builtin_module(name):
paul@366 1297
paul@366 1298
    "Return the module name containing the given type 'name'."
paul@366 1299
paul@394 1300
    if name == "string":
paul@538 1301
        modname = "str"
paul@394 1302
    elif name == "utf8string":
paul@538 1303
        modname = "unicode"
paul@394 1304
    elif name == "NoneType":
paul@538 1305
        modname = "none"
paul@394 1306
    else:
paul@538 1307
        modname = name
paul@538 1308
paul@538 1309
    return "__builtins__.%s" % modname
paul@366 1310
paul@366 1311
def get_builtin_type(name):
paul@366 1312
paul@366 1313
    "Return the type name provided by the given Python value 'name'."
paul@366 1314
paul@394 1315
    if name == "str":
paul@394 1316
        return "string"
paul@394 1317
    elif name == "unicode":
paul@394 1318
        return "utf8string"
paul@394 1319
    else:
paul@394 1320
        return name
paul@366 1321
paul@538 1322
def get_builtin_class(name):
paul@538 1323
paul@538 1324
    "Return the full name of the built-in class having the given 'name'."
paul@538 1325
paul@538 1326
    typename = get_builtin_type(name)
paul@538 1327
    module = get_builtin_module(typename)
paul@538 1328
    return "%s.%s" % (module, typename)
paul@538 1329
paul@0 1330
# Useful data.
paul@0 1331
paul@11 1332
predefined_constants = "False", "None", "NotImplemented", "True"
paul@0 1333
paul@0 1334
operator_functions = {
paul@0 1335
paul@0 1336
    # Fundamental operations.
paul@0 1337
paul@0 1338
    "is" : "is_",
paul@0 1339
    "is not" : "is_not",
paul@0 1340
paul@0 1341
    # Binary operations.
paul@0 1342
paul@0 1343
    "in" : "in_",
paul@0 1344
    "not in" : "not_in",
paul@0 1345
    "Add" : "add",
paul@0 1346
    "Bitand" : "and_",
paul@0 1347
    "Bitor" : "or_",
paul@0 1348
    "Bitxor" : "xor",
paul@0 1349
    "Div" : "div",
paul@0 1350
    "FloorDiv" : "floordiv",
paul@0 1351
    "LeftShift" : "lshift",
paul@0 1352
    "Mod" : "mod",
paul@0 1353
    "Mul" : "mul",
paul@0 1354
    "Power" : "pow",
paul@0 1355
    "RightShift" : "rshift",
paul@0 1356
    "Sub" : "sub",
paul@0 1357
paul@0 1358
    # Unary operations.
paul@0 1359
paul@0 1360
    "Invert" : "invert",
paul@0 1361
    "UnaryAdd" : "pos",
paul@0 1362
    "UnarySub" : "neg",
paul@0 1363
paul@0 1364
    # Augmented assignment.
paul@0 1365
paul@0 1366
    "+=" : "iadd",
paul@0 1367
    "-=" : "isub",
paul@0 1368
    "*=" : "imul",
paul@0 1369
    "/=" : "idiv",
paul@0 1370
    "//=" : "ifloordiv",
paul@0 1371
    "%=" : "imod",
paul@0 1372
    "**=" : "ipow",
paul@0 1373
    "<<=" : "ilshift",
paul@0 1374
    ">>=" : "irshift",
paul@0 1375
    "&=" : "iand",
paul@0 1376
    "^=" : "ixor",
paul@0 1377
    "|=" : "ior",
paul@0 1378
paul@0 1379
    # Comparisons.
paul@0 1380
paul@0 1381
    "==" : "eq",
paul@0 1382
    "!=" : "ne",
paul@0 1383
    "<" : "lt",
paul@0 1384
    "<=" : "le",
paul@0 1385
    ">=" : "ge",
paul@0 1386
    ">" : "gt",
paul@0 1387
    }
paul@0 1388
paul@0 1389
# vim: tabstop=4 expandtab shiftwidth=4