Lichen

Annotated generator.py

313:6acc19256bb9
2016-12-05 Paul Boddie Attempted to improve the computation of module ordering.
paul@126 1
#!/usr/bin/env python
paul@126 2
paul@126 3
"""
paul@126 4
Generate C code from object layouts and other deduced information.
paul@126 5
paul@126 6
Copyright (C) 2015, 2016 Paul Boddie <paul@boddie.org.uk>
paul@126 7
paul@126 8
This program is free software; you can redistribute it and/or modify it under
paul@126 9
the terms of the GNU General Public License as published by the Free Software
paul@126 10
Foundation; either version 3 of the License, or (at your option) any later
paul@126 11
version.
paul@126 12
paul@126 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@126 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@126 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@126 16
details.
paul@126 17
paul@126 18
You should have received a copy of the GNU General Public License along with
paul@126 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@126 20
"""
paul@126 21
paul@126 22
from common import CommonOutput
paul@126 23
from encoders import encode_bound_reference, encode_function_pointer, \
paul@136 24
                     encode_instantiator_pointer, \
paul@136 25
                     encode_literal_constant, encode_literal_constant_member, \
paul@159 26
                     encode_literal_constant_value, \
paul@283 27
                     encode_literal_data_initialiser, \
paul@159 28
                     encode_literal_instantiator, encode_literal_reference, \
paul@136 29
                     encode_path, \
paul@150 30
                     encode_predefined_reference, encode_size, \
paul@150 31
                     encode_symbol, encode_tablename, \
paul@132 32
                     encode_type_attribute
paul@126 33
from os import listdir
paul@183 34
from os.path import exists, isdir, join, split
paul@126 35
from referencing import Reference
paul@126 36
paul@126 37
def copy(source, target):
paul@126 38
paul@126 39
    "Copy a text file from 'source' to 'target'."
paul@126 40
paul@126 41
    if isdir(target):
paul@126 42
        target = join(target, split(source)[-1])
paul@126 43
    infile = open(source)
paul@126 44
    outfile = open(target, "w")
paul@126 45
    try:
paul@126 46
        outfile.write(infile.read())
paul@126 47
    finally:
paul@126 48
        outfile.close()
paul@126 49
        infile.close()
paul@126 50
paul@126 51
class Generator(CommonOutput):
paul@126 52
paul@126 53
    "A code generator."
paul@126 54
paul@274 55
    # NOTE: These must be synchronised with the library.
paul@274 56
paul@126 57
    function_type = "__builtins__.core.function"
paul@306 58
    memory_error_type = "__builtins__.core.MemoryError"
paul@306 59
    overflow_error_type = "__builtins__.core.OverflowError"
paul@233 60
    type_error_type = "__builtins__.core.TypeError"
paul@274 61
    type_type = "__builtins__.core.type"
paul@306 62
    zero_division_error_type = "__builtins__.core.ZeroDivisionError"
paul@158 63
paul@136 64
    predefined_constant_members = (
paul@158 65
        ("__builtins__.boolean", "False"),
paul@158 66
        ("__builtins__.boolean", "True"),
paul@136 67
        ("__builtins__.none", "None"),
paul@136 68
        ("__builtins__.notimplemented", "NotImplemented"),
paul@136 69
        )
paul@136 70
paul@283 71
    literal_mapping_types = (
paul@159 72
        "__builtins__.dict.dict",
paul@283 73
        )
paul@283 74
paul@283 75
    literal_sequence_types = (
paul@159 76
        "__builtins__.list.list",
paul@159 77
        "__builtins__.tuple.tuple",
paul@159 78
        )
paul@159 79
paul@283 80
    literal_instantiator_types = literal_mapping_types + literal_sequence_types
paul@283 81
paul@126 82
    def __init__(self, importer, optimiser, output):
paul@126 83
        self.importer = importer
paul@126 84
        self.optimiser = optimiser
paul@126 85
        self.output = output
paul@126 86
paul@183 87
    def to_output(self, debug=False):
paul@126 88
paul@126 89
        "Write the generated code."
paul@126 90
paul@126 91
        self.check_output()
paul@126 92
        self.write_structures()
paul@183 93
        self.copy_templates(debug)
paul@126 94
paul@183 95
    def copy_templates(self, debug=False):
paul@126 96
paul@126 97
        "Copy template files to the generated output directory."
paul@126 98
paul@126 99
        templates = join(split(__file__)[0], "templates")
paul@126 100
paul@126 101
        for filename in listdir(templates):
paul@183 102
            target = self.output
paul@183 103
paul@183 104
            # Handle debug resources.
paul@183 105
paul@183 106
            if filename.endswith("-debug"):
paul@183 107
                if debug:
paul@183 108
                    target = join(self.output, filename[:-len("-debug")])
paul@183 109
                else:
paul@183 110
                    continue
paul@183 111
paul@183 112
            # Handle non-debug resources.
paul@183 113
paul@183 114
            if debug and exists(join(templates, "%s-debug" % filename)):
paul@183 115
                continue
paul@183 116
paul@183 117
            copy(join(templates, filename), target)
paul@126 118
paul@126 119
    def write_structures(self):
paul@126 120
paul@126 121
        "Write structures used by the program."
paul@126 122
paul@126 123
        f_consts = open(join(self.output, "progconsts.h"), "w")
paul@126 124
        f_defs = open(join(self.output, "progtypes.c"), "w")
paul@126 125
        f_decls = open(join(self.output, "progtypes.h"), "w")
paul@126 126
        f_signatures = open(join(self.output, "main.h"), "w")
paul@126 127
        f_code = open(join(self.output, "main.c"), "w")
paul@126 128
paul@126 129
        try:
paul@126 130
            # Output boilerplate.
paul@126 131
paul@126 132
            print >>f_consts, """\
paul@126 133
#ifndef __PROGCONSTS_H__
paul@126 134
#define __PROGCONSTS_H__
paul@126 135
"""
paul@126 136
            print >>f_decls, """\
paul@126 137
#ifndef __PROGTYPES_H__
paul@126 138
#define __PROGTYPES_H__
paul@126 139
paul@126 140
#include "progconsts.h"
paul@126 141
#include "types.h"
paul@126 142
"""
paul@126 143
            print >>f_defs, """\
paul@126 144
#include "progtypes.h"
paul@132 145
#include "progops.h"
paul@126 146
#include "main.h"
paul@126 147
"""
paul@126 148
            print >>f_signatures, """\
paul@126 149
#ifndef __MAIN_H__
paul@126 150
#define __MAIN_H__
paul@126 151
paul@126 152
#include "types.h"
paul@126 153
"""
paul@126 154
            print >>f_code, """\
paul@126 155
#include <string.h>
paul@159 156
#include <stdio.h>
paul@126 157
#include "types.h"
paul@159 158
#include "exceptions.h"
paul@126 159
#include "ops.h"
paul@126 160
#include "progconsts.h"
paul@126 161
#include "progtypes.h"
paul@126 162
#include "progops.h"
paul@126 163
#include "main.h"
paul@126 164
"""
paul@126 165
paul@126 166
            # Generate table and structure data.
paul@126 167
paul@126 168
            function_instance_attrs = None
paul@126 169
            objects = self.optimiser.attr_table.items()
paul@126 170
            objects.sort()
paul@126 171
paul@126 172
            for ref, indexes in objects:
paul@126 173
                attrnames = self.get_attribute_names(indexes)
paul@126 174
paul@126 175
                kind = ref.get_kind()
paul@126 176
                path = ref.get_origin()
paul@150 177
                table_name = encode_tablename(kind, path)
paul@150 178
                structure_size = encode_size(kind, path)
paul@126 179
paul@126 180
                # Generate structures for classes and modules.
paul@126 181
paul@126 182
                if kind != "<instance>":
paul@126 183
                    structure = []
paul@126 184
                    attrs = self.get_static_attributes(kind, path, attrnames)
paul@126 185
paul@126 186
                    # Set a special instantiator on the class.
paul@126 187
paul@126 188
                    if kind == "<class>":
paul@126 189
                        attrs["__fn__"] = path
paul@126 190
                        attrs["__args__"] = encode_size("pmin", path)
paul@126 191
paul@126 192
                        # Write instantiator declarations based on the
paul@126 193
                        # applicable initialiser.
paul@126 194
paul@126 195
                        init_ref = attrs["__init__"]
paul@126 196
paul@126 197
                        # Write instantiator definitions.
paul@126 198
paul@159 199
                        self.write_instantiator(f_code, f_signatures, path, init_ref)
paul@126 200
paul@126 201
                        # Write parameter table.
paul@126 202
paul@126 203
                        self.make_parameter_table(f_decls, f_defs, path, init_ref.get_origin())
paul@126 204
paul@126 205
                    self.populate_structure(Reference(kind, path), attrs, kind, structure)
paul@136 206
paul@136 207
                    if kind == "<class>":
paul@136 208
                        self.write_instance_structure(f_decls, path, structure_size)
paul@136 209
paul@211 210
                    self.write_structure(f_decls, f_defs, path, table_name, structure,
paul@136 211
                        kind == "<class>" and path)
paul@126 212
paul@126 213
                # Record function instance details for function generation below.
paul@126 214
paul@126 215
                else:
paul@126 216
                    attrs = self.get_instance_attributes(path, attrnames)
paul@126 217
                    if path == self.function_type:
paul@126 218
                        function_instance_attrs = attrs
paul@126 219
paul@126 220
                # Write a table for all objects.
paul@126 221
paul@126 222
                table = []
paul@126 223
                self.populate_table(Reference(kind, path), table)
paul@126 224
                self.write_table(f_decls, f_defs, table_name, structure_size, table)
paul@126 225
paul@126 226
            # Generate function instances.
paul@126 227
paul@195 228
            functions = self.importer.function_parameters.keys()
paul@126 229
            functions.sort()
paul@211 230
            extra_function_instances = []
paul@126 231
paul@126 232
            for path in functions:
paul@195 233
paul@195 234
                # Instantiators are generated above.
paul@195 235
paul@195 236
                if self.importer.classes.has_key(path) or not self.importer.get_object(path):
paul@195 237
                    continue
paul@195 238
paul@126 239
                cls = self.function_type
paul@150 240
                table_name = encode_tablename("<instance>", cls)
paul@211 241
                structure_size = encode_size("<instance>", path)
paul@126 242
paul@126 243
                # Set a special callable attribute on the instance.
paul@126 244
paul@126 245
                function_instance_attrs["__fn__"] = path
paul@126 246
                function_instance_attrs["__args__"] = encode_size("pmin", path)
paul@126 247
paul@126 248
                # Produce two structures where a method is involved.
paul@126 249
paul@195 250
                parent, name = path.rsplit(".", 1)
paul@195 251
                parent_ref = self.importer.get_object(parent)
paul@126 252
                parent_kind = parent_ref and parent_ref.get_kind()
paul@126 253
paul@126 254
                # Populate and write each structure.
paul@126 255
paul@126 256
                if parent_kind == "<class>":
paul@126 257
paul@132 258
                    # A bound version of a method.
paul@132 259
paul@132 260
                    structure = self.populate_function(path, function_instance_attrs, False)
paul@211 261
                    self.write_structure(f_decls, f_defs, encode_bound_reference(path), table_name, structure)
paul@132 262
paul@126 263
                    # An unbound version of a method.
paul@126 264
paul@126 265
                    structure = self.populate_function(path, function_instance_attrs, True)
paul@211 266
                    self.write_structure(f_decls, f_defs, path, table_name, structure)
paul@126 267
paul@132 268
                else:
paul@132 269
                    # A normal function.
paul@126 270
paul@126 271
                    structure = self.populate_function(path, function_instance_attrs, False)
paul@211 272
                    self.write_structure(f_decls, f_defs, path, table_name, structure)
paul@126 273
paul@154 274
                # Functions with defaults need to declare instance structures.
paul@154 275
paul@154 276
                if self.importer.function_defaults.get(path):
paul@154 277
                    self.write_instance_structure(f_decls, path, structure_size)
paul@211 278
                    extra_function_instances.append(path)
paul@154 279
paul@126 280
                # Write function declarations.
paul@126 281
                # Signature: __attr <name>(__attr[]);
paul@126 282
paul@126 283
                print >>f_signatures, "__attr %s(__attr args[]);" % encode_function_pointer(path)
paul@126 284
paul@126 285
                # Write parameter table.
paul@126 286
paul@126 287
                self.make_parameter_table(f_decls, f_defs, path, path)
paul@126 288
paul@136 289
            # Generate predefined constants.
paul@136 290
paul@136 291
            for path, name in self.predefined_constant_members:
paul@136 292
                self.make_predefined_constant(f_decls, f_defs, path, name)
paul@136 293
paul@136 294
            # Generate literal constants.
paul@136 295
paul@136 296
            for value, n in self.optimiser.constants.items():
paul@136 297
                self.make_literal_constant(f_decls, f_defs, n, value)
paul@136 298
paul@146 299
            # Finish the main source file.
paul@146 300
paul@146 301
            self.write_main_program(f_code, f_signatures)
paul@146 302
paul@211 303
            # Record size information for certain function instances as well as
paul@211 304
            # for classes, modules and other instances.
paul@211 305
paul@211 306
            size_tables = {}
paul@211 307
paul@211 308
            for kind in ["<class>", "<module>", "<instance>"]:
paul@211 309
                size_tables[kind] = {}
paul@211 310
paul@211 311
            # Generate structure size data.
paul@211 312
paul@211 313
            for ref, structure in self.optimiser.structures.items():
paul@211 314
                size_tables[ref.get_kind()][ref.get_origin()] = len(structure)
paul@211 315
paul@211 316
            for path in extra_function_instances:
paul@211 317
                defaults = self.importer.function_defaults[path]
paul@211 318
                size_tables["<instance>"][path] = size_tables["<instance>"][self.function_type] + len(defaults)
paul@211 319
paul@211 320
            size_tables = size_tables.items()
paul@211 321
            size_tables.sort()
paul@211 322
paul@211 323
            for kind, sizes in size_tables:
paul@211 324
                self.write_size_constants(f_consts, kind, sizes, 0)
paul@211 325
paul@211 326
            # Generate parameter table size data.
paul@211 327
paul@211 328
            min_sizes = {}
paul@211 329
            max_sizes = {}
paul@211 330
paul@211 331
            for path, parameters in self.optimiser.parameters.items():
paul@211 332
                argmin, argmax = self.get_argument_limits(path)
paul@211 333
                min_sizes[path] = argmin
paul@211 334
                max_sizes[path] = argmax
paul@211 335
paul@211 336
                # Record instantiator limits.
paul@211 337
paul@211 338
                if path.endswith(".__init__"):
paul@211 339
                    path = path[:-len(".__init__")]
paul@211 340
paul@211 341
            self.write_size_constants(f_consts, "pmin", min_sizes, 0)
paul@211 342
            self.write_size_constants(f_consts, "pmax", max_sizes, 0)
paul@211 343
paul@211 344
            # Generate parameter codes.
paul@211 345
paul@211 346
            self.write_code_constants(f_consts, self.optimiser.all_paramnames, self.optimiser.arg_locations, "pcode", "ppos")
paul@211 347
paul@211 348
            # Generate attribute codes.
paul@211 349
paul@211 350
            self.write_code_constants(f_consts, self.optimiser.all_attrnames, self.optimiser.locations, "code", "pos")
paul@211 351
paul@126 352
            # Output more boilerplate.
paul@126 353
paul@126 354
            print >>f_consts, """\
paul@126 355
paul@126 356
#endif /* __PROGCONSTS_H__ */"""
paul@126 357
paul@126 358
            print >>f_decls, """\
paul@126 359
paul@126 360
#define __FUNCTION_TYPE %s
paul@126 361
#define __FUNCTION_INSTANCE_SIZE %s
paul@306 362
#define __MEMORY_ERROR_INSTANTIATOR %s
paul@306 363
#define __OVERFLOW_ERROR_INSTANTIATOR %s
paul@233 364
#define __TYPE_ERROR_INSTANTIATOR %s
paul@306 365
#define __ZERO_DIVISION_ERROR_INSTANTIATOR %s
paul@274 366
#define __TYPE_CLASS_TYPE %s
paul@274 367
#define __TYPE_CLASS_POS %s
paul@274 368
#define __TYPE_CLASS_CODE %s
paul@126 369
paul@126 370
#endif /* __PROGTYPES_H__ */""" % (
paul@126 371
    encode_path(self.function_type),
paul@233 372
    encode_size("<instance>", self.function_type),
paul@306 373
    encode_instantiator_pointer(self.memory_error_type),
paul@306 374
    encode_instantiator_pointer(self.overflow_error_type),
paul@260 375
    encode_instantiator_pointer(self.type_error_type),
paul@306 376
    encode_instantiator_pointer(self.zero_division_error_type),
paul@274 377
    encode_path(self.type_type),
paul@274 378
    encode_symbol("pos", encode_type_attribute(self.type_type)),
paul@274 379
    encode_symbol("code", encode_type_attribute(self.type_type)),
paul@126 380
    )
paul@126 381
paul@126 382
            print >>f_signatures, """\
paul@126 383
paul@126 384
#endif /* __MAIN_H__ */"""
paul@126 385
paul@126 386
        finally:
paul@126 387
            f_consts.close()
paul@126 388
            f_defs.close()
paul@126 389
            f_decls.close()
paul@126 390
            f_signatures.close()
paul@126 391
            f_code.close()
paul@126 392
paul@136 393
    def make_literal_constant(self, f_decls, f_defs, n, value):
paul@136 394
paul@136 395
        """
paul@136 396
        Write literal constant details to 'f_decls' (to declare a structure) and
paul@136 397
        to 'f_defs' (to define the contents) for the constant with the number
paul@136 398
        'n' with the given literal 'value'.
paul@136 399
        """
paul@136 400
paul@136 401
        const_path = encode_literal_constant(n)
paul@136 402
        structure_name = encode_literal_reference(n)
paul@136 403
paul@136 404
        # NOTE: This makes assumptions about the __builtins__ structure.
paul@136 405
paul@188 406
        modname = value.__class__.__name__
paul@188 407
        typename = modname == "str" and "string" or modname
paul@188 408
        ref = Reference("<instance>", "__builtins__.%s.%s" % (modname, typename))
paul@136 409
paul@136 410
        self.make_constant(f_decls, f_defs, ref, const_path, structure_name, value)
paul@136 411
paul@136 412
    def make_predefined_constant(self, f_decls, f_defs, path, name):
paul@136 413
paul@136 414
        """
paul@136 415
        Write predefined constant details to 'f_decls' (to declare a structure)
paul@136 416
        and to 'f_defs' (to define the contents) for the constant located in
paul@136 417
        'path' with the given 'name'.
paul@136 418
        """
paul@136 419
paul@136 420
        # Determine the details of the constant.
paul@136 421
paul@136 422
        attr_path = "%s.%s" % (path, name)
paul@136 423
        structure_name = encode_predefined_reference(attr_path)
paul@136 424
        ref = self.importer.get_object(attr_path)
paul@136 425
paul@136 426
        self.make_constant(f_decls, f_defs, ref, attr_path, structure_name)
paul@136 427
paul@136 428
    def make_constant(self, f_decls, f_defs, ref, const_path, structure_name, data=None):
paul@136 429
paul@136 430
        """
paul@136 431
        Write constant details to 'f_decls' (to declare a structure) and to
paul@136 432
        'f_defs' (to define the contents) for the constant described by 'ref'
paul@136 433
        having the given 'path' and 'structure_name' (for the constant structure
paul@136 434
        itself).
paul@136 435
        """
paul@136 436
paul@136 437
        # Obtain the attributes.
paul@136 438
paul@136 439
        cls = ref.get_origin()
paul@136 440
        indexes = self.optimiser.attr_table[ref]
paul@136 441
        attrnames = self.get_attribute_names(indexes)
paul@136 442
        attrs = self.get_instance_attributes(cls, attrnames)
paul@136 443
paul@136 444
        # Set the data, if provided.
paul@136 445
paul@136 446
        if data is not None:
paul@136 447
            attrs["__data__"] = data
paul@136 448
paul@136 449
        # Define the structure details. An object is created for the constant,
paul@136 450
        # but an attribute is provided, referring to the object, for access to
paul@136 451
        # the constant in the program.
paul@136 452
paul@136 453
        structure = []
paul@150 454
        table_name = encode_tablename("<instance>", cls)
paul@136 455
        self.populate_structure(ref, attrs, ref.get_kind(), structure)
paul@211 456
        self.write_structure(f_decls, f_defs, structure_name, table_name, structure)
paul@136 457
paul@136 458
        # Define a macro for the constant.
paul@136 459
paul@136 460
        attr_name = encode_path(const_path)
paul@136 461
        print >>f_decls, "#define %s ((__attr) {&%s, &%s})" % (attr_name, structure_name, structure_name)
paul@136 462
paul@126 463
    def make_parameter_table(self, f_decls, f_defs, path, function_path):
paul@126 464
paul@126 465
        """
paul@126 466
        Write parameter table details to 'f_decls' (to declare a table) and to
paul@126 467
        'f_defs' (to define the contents) for the function with the given
paul@126 468
        'path', using 'function_path' to obtain the parameter details. The
paul@126 469
        latter two arguments may differ when describing an instantiator using
paul@126 470
        the details of an initialiser.
paul@126 471
        """
paul@126 472
paul@126 473
        table = []
paul@150 474
        table_name = encode_tablename("<function>", path)
paul@126 475
        structure_size = encode_size("pmax", path)
paul@126 476
        self.populate_parameter_table(function_path, table)
paul@126 477
        self.write_parameter_table(f_decls, f_defs, table_name, structure_size, table)
paul@126 478
paul@126 479
    def write_size_constants(self, f_consts, size_prefix, sizes, padding):
paul@126 480
paul@126 481
        """
paul@126 482
        Write size constants to 'f_consts' for the given 'size_prefix', using
paul@126 483
        the 'sizes' dictionary to populate the definition, adding the given
paul@126 484
        'padding' to the basic sizes.
paul@126 485
        """
paul@126 486
paul@126 487
        print >>f_consts, "enum %s {" % encode_size(size_prefix)
paul@126 488
        first = True
paul@126 489
        for path, size in sizes.items():
paul@126 490
            if not first:
paul@126 491
                print >>f_consts, ","
paul@126 492
            else:
paul@126 493
                first = False
paul@126 494
            f_consts.write("    %s = %d" % (encode_size(size_prefix, path), size + padding))
paul@126 495
        print >>f_consts, "\n    };"
paul@126 496
paul@132 497
    def write_code_constants(self, f_consts, attrnames, locations, code_prefix, pos_prefix):
paul@126 498
paul@126 499
        """
paul@126 500
        Write code constants to 'f_consts' for the given 'attrnames' and
paul@126 501
        attribute 'locations'.
paul@126 502
        """
paul@126 503
paul@132 504
        print >>f_consts, "enum %s {" % encode_symbol(code_prefix)
paul@126 505
        first = True
paul@126 506
        for i, attrname in enumerate(attrnames):
paul@126 507
            if not first:
paul@126 508
                print >>f_consts, ","
paul@126 509
            else:
paul@126 510
                first = False
paul@132 511
            f_consts.write("    %s = %d" % (encode_symbol(code_prefix, attrname), i))
paul@126 512
        print >>f_consts, "\n    };"
paul@126 513
paul@132 514
        print >>f_consts, "enum %s {" % encode_symbol(pos_prefix)
paul@126 515
        first = True
paul@126 516
        for i, attrnames in enumerate(locations):
paul@126 517
            for attrname in attrnames:
paul@126 518
                if not first:
paul@126 519
                    print >>f_consts, ","
paul@126 520
                else:
paul@126 521
                    first = False
paul@132 522
                f_consts.write("    %s = %d" % (encode_symbol(pos_prefix, attrname), i))
paul@126 523
        print >>f_consts, "\n    };"
paul@126 524
paul@126 525
    def write_table(self, f_decls, f_defs, table_name, structure_size, table):
paul@126 526
paul@126 527
        """
paul@126 528
        Write the declarations to 'f_decls' and definitions to 'f_defs' for
paul@126 529
        the object having the given 'table_name' and the given 'structure_size',
paul@126 530
        with 'table' details used to populate the definition.
paul@126 531
        """
paul@126 532
paul@126 533
        print >>f_decls, "extern const __table %s;\n" % table_name
paul@126 534
paul@126 535
        # Write the corresponding definition.
paul@126 536
paul@126 537
        print >>f_defs, "const __table %s = {\n    %s,\n    {\n        %s\n        }\n    };\n" % (
paul@126 538
            table_name, structure_size,
paul@126 539
            ",\n        ".join(table))
paul@126 540
paul@126 541
    def write_parameter_table(self, f_decls, f_defs, table_name, structure_size, table):
paul@126 542
paul@126 543
        """
paul@126 544
        Write the declarations to 'f_decls' and definitions to 'f_defs' for
paul@126 545
        the object having the given 'table_name' and the given 'structure_size',
paul@126 546
        with 'table' details used to populate the definition.
paul@126 547
        """
paul@126 548
paul@126 549
        print >>f_decls, "extern const __ptable %s;\n" % table_name
paul@126 550
paul@126 551
        # Write the corresponding definition.
paul@126 552
paul@126 553
        print >>f_defs, "const __ptable %s = {\n    %s,\n    {\n        %s\n        }\n    };\n" % (
paul@126 554
            table_name, structure_size,
paul@126 555
            ",\n        ".join([("{%s, %s}" % t) for t in table]))
paul@126 556
paul@136 557
    def write_instance_structure(self, f_decls, path, structure_size):
paul@126 558
paul@126 559
        """
paul@136 560
        Write a declaration to 'f_decls' for the object having the given 'path'
paul@136 561
        and the given 'structure_size'.
paul@126 562
        """
paul@126 563
paul@126 564
        # Write an instance-specific type definition for instances of classes.
paul@126 565
        # See: templates/types.h
paul@126 566
paul@126 567
        print >>f_decls, """\
paul@126 568
typedef struct {
paul@126 569
    const __table * table;
paul@126 570
    unsigned int pos;
paul@126 571
    __attr attrs[%s];
paul@126 572
} %s;
paul@136 573
""" % (structure_size, encode_symbol("obj", path))
paul@136 574
paul@211 575
    def write_structure(self, f_decls, f_defs, structure_name, table_name, structure, path=None):
paul@126 576
paul@136 577
        """
paul@136 578
        Write the declarations to 'f_decls' and definitions to 'f_defs' for
paul@136 579
        the object having the given 'structure_name', the given 'table_name',
paul@211 580
        and the given 'structure' details used to populate the definition.
paul@136 581
        """
paul@126 582
paul@136 583
        if f_decls:
paul@136 584
            print >>f_decls, "extern __obj %s;\n" % encode_path(structure_name)
paul@136 585
paul@136 586
        is_class = path and self.importer.get_object(path).has_kind("<class>")
paul@132 587
        pos = is_class and encode_symbol("pos", encode_type_attribute(path)) or "0"
paul@132 588
paul@132 589
        print >>f_defs, """\
paul@132 590
__obj %s = {
paul@132 591
    &%s,
paul@132 592
    %s,
paul@132 593
    {
paul@132 594
        %s
paul@132 595
    }};
paul@132 596
""" % (
paul@136 597
            encode_path(structure_name), table_name, pos,
paul@126 598
            ",\n        ".join(structure))
paul@126 599
paul@132 600
    def get_argument_limits(self, path):
paul@126 601
paul@132 602
        """
paul@132 603
        Return the argument minimum and maximum for the callable at 'path',
paul@132 604
        adding an argument position for a universal context.
paul@132 605
        """
paul@132 606
paul@126 607
        parameters = self.importer.function_parameters[path]
paul@126 608
        defaults = self.importer.function_defaults.get(path)
paul@132 609
        num_parameters = len(parameters) + 1
paul@132 610
        return num_parameters - (defaults and len(defaults) or 0), num_parameters
paul@126 611
paul@126 612
    def get_attribute_names(self, indexes):
paul@126 613
paul@126 614
        """
paul@126 615
        Given a list of attribute table 'indexes', return a list of attribute
paul@126 616
        names.
paul@126 617
        """
paul@126 618
paul@126 619
        all_attrnames = self.optimiser.all_attrnames
paul@126 620
        attrnames = []
paul@126 621
        for i in indexes:
paul@126 622
            if i is None:
paul@126 623
                attrnames.append(None)
paul@126 624
            else:
paul@126 625
                attrnames.append(all_attrnames[i])
paul@126 626
        return attrnames
paul@126 627
paul@126 628
    def get_static_attributes(self, kind, name, attrnames):
paul@126 629
paul@126 630
        """
paul@126 631
        Return a mapping of attribute names to paths for attributes belonging
paul@126 632
        to objects of the given 'kind' (being "<class>" or "<module>") with
paul@126 633
        the given 'name' and supporting the given 'attrnames'.
paul@126 634
        """
paul@126 635
paul@126 636
        attrs = {}
paul@126 637
paul@126 638
        for attrname in attrnames:
paul@126 639
            if attrname is None:
paul@126 640
                continue
paul@126 641
            if kind == "<class>":
paul@126 642
                path = self.importer.all_class_attrs[name][attrname]
paul@126 643
            elif kind == "<module>":
paul@126 644
                path = "%s.%s" % (name, attrname)
paul@126 645
            else:
paul@126 646
                continue
paul@126 647
paul@126 648
            # The module may be hidden.
paul@126 649
paul@126 650
            attr = self.importer.get_object(path)
paul@126 651
            if not attr:
paul@126 652
                module = self.importer.hidden.get(path)
paul@126 653
                if module:
paul@126 654
                    attr = Reference(module.name, "<module>")
paul@126 655
            attrs[attrname] = attr
paul@126 656
paul@126 657
        return attrs
paul@126 658
paul@126 659
    def get_instance_attributes(self, name, attrnames):
paul@126 660
paul@126 661
        """
paul@126 662
        Return a mapping of attribute names to references for attributes
paul@126 663
        belonging to instances of the class with the given 'name', where the
paul@126 664
        given 'attrnames' are supported.
paul@126 665
        """
paul@126 666
paul@126 667
        consts = self.importer.all_instance_attr_constants[name]
paul@126 668
        attrs = {}
paul@126 669
        for attrname in attrnames:
paul@126 670
            if attrname is None:
paul@126 671
                continue
paul@126 672
            const = consts.get(attrname)
paul@126 673
            attrs[attrname] = const or Reference("<var>", "%s.%s" % (name, attrname))
paul@126 674
        return attrs
paul@126 675
paul@126 676
    def populate_table(self, key, table):
paul@126 677
paul@126 678
        """
paul@126 679
        Traverse the attributes in the determined order for the structure having
paul@126 680
        the given 'key', adding entries to the attribute 'table'.
paul@126 681
        """
paul@126 682
paul@126 683
        for attrname in self.optimiser.structures[key]:
paul@126 684
paul@126 685
            # Handle gaps in the structure.
paul@126 686
paul@126 687
            if attrname is None:
paul@126 688
                table.append("0")
paul@126 689
            else:
paul@126 690
                table.append(encode_symbol("code", attrname))
paul@126 691
paul@126 692
    def populate_parameter_table(self, key, table):
paul@126 693
paul@126 694
        """
paul@126 695
        Traverse the parameters in the determined order for the structure having
paul@126 696
        the given 'key', adding entries to the attribute 'table'.
paul@126 697
        """
paul@126 698
paul@126 699
        for value in self.optimiser.parameters[key]:
paul@126 700
paul@126 701
            # Handle gaps in the structure.
paul@126 702
paul@126 703
            if value is None:
paul@126 704
                table.append(("0", "0"))
paul@126 705
            else:
paul@126 706
                name, pos = value
paul@126 707
                table.append((encode_symbol("pcode", name), pos))
paul@126 708
paul@126 709
    def populate_function(self, path, function_instance_attrs, unbound=False):
paul@126 710
paul@126 711
        """
paul@126 712
        Populate a structure for the function with the given 'path'. The given
paul@126 713
        'attrs' provide the instance attributes, and if 'unbound' is set to a
paul@126 714
        true value, an unbound method structure is produced (as opposed to a
paul@126 715
        callable bound method structure).
paul@126 716
        """
paul@126 717
paul@126 718
        structure = []
paul@174 719
        self.populate_structure(Reference("<function>", path), function_instance_attrs, "<instance>", structure, unbound)
paul@126 720
paul@126 721
        # Append default members.
paul@126 722
paul@126 723
        self.append_defaults(path, structure)
paul@126 724
        return structure
paul@126 725
paul@126 726
    def populate_structure(self, ref, attrs, kind, structure, unbound=False):
paul@126 727
paul@126 728
        """
paul@126 729
        Traverse the attributes in the determined order for the structure having
paul@126 730
        the given 'ref' whose members are provided by the 'attrs' mapping, in a
paul@126 731
        structure of the given 'kind', adding entries to the object 'structure'.
paul@126 732
        If 'unbound' is set to a true value, an unbound method function pointer
paul@126 733
        will be employed, with a reference to the bound method incorporated into
paul@126 734
        the special __fn__ attribute.
paul@126 735
        """
paul@126 736
paul@174 737
        # Populate function instance structures for functions.
paul@174 738
paul@174 739
        if ref.has_kind("<function>"):
paul@174 740
            origin = self.function_type
paul@174 741
            structure_ref = Reference("<instance>", self.function_type)
paul@174 742
paul@174 743
        # Otherwise, just populate the appropriate structures.
paul@126 744
paul@174 745
        else:
paul@174 746
            origin = ref.get_origin()
paul@174 747
            structure_ref = ref
paul@174 748
paul@174 749
        # Refer to instantiator function tables for classes, specific function
paul@174 750
        # tables for individual functions.
paul@174 751
paul@174 752
        ptable = encode_tablename("<function>", ref.get_origin())
paul@174 753
paul@174 754
        for attrname in self.optimiser.structures[structure_ref]:
paul@126 755
paul@126 756
            # Handle gaps in the structure.
paul@126 757
paul@126 758
            if attrname is None:
paul@126 759
                structure.append("{0, 0}")
paul@126 760
paul@126 761
            # Handle non-constant and constant members.
paul@126 762
paul@126 763
            else:
paul@126 764
                attr = attrs[attrname]
paul@126 765
paul@136 766
                # Special function pointer member.
paul@136 767
paul@126 768
                if attrname == "__fn__":
paul@126 769
paul@126 770
                    # Provide bound method references and the unbound function
paul@126 771
                    # pointer if populating methods in a class.
paul@126 772
paul@126 773
                    bound_attr = None
paul@126 774
paul@126 775
                    # Classes offer instantiators.
paul@126 776
paul@126 777
                    if kind == "<class>":
paul@126 778
                        attr = encode_instantiator_pointer(attr)
paul@126 779
paul@126 780
                    # Methods offers references to bound versions and an unbound
paul@126 781
                    # method function.
paul@126 782
paul@126 783
                    elif unbound:
paul@126 784
                        bound_attr = encode_bound_reference(attr)
paul@126 785
                        attr = "__unbound_method"
paul@126 786
paul@126 787
                    # Other functions just offer function pointers.
paul@126 788
paul@126 789
                    else:
paul@126 790
                        attr = encode_function_pointer(attr)
paul@126 791
paul@132 792
                    structure.append("{%s, .fn=%s}" % (bound_attr and ".b=&%s" % bound_attr or "0", attr))
paul@126 793
                    continue
paul@126 794
paul@136 795
                # Special argument specification member.
paul@136 796
paul@126 797
                elif attrname == "__args__":
paul@174 798
                    structure.append("{.min=%s, .ptable=&%s}" % (attr, ptable))
paul@126 799
                    continue
paul@126 800
paul@136 801
                # Special internal data member.
paul@136 802
paul@136 803
                elif attrname == "__data__":
paul@136 804
                    structure.append("{0, .%s=%s}" % (encode_literal_constant_member(attr),
paul@136 805
                                                      encode_literal_constant_value(attr)))
paul@136 806
                    continue
paul@136 807
paul@251 808
                # Special cases.
paul@251 809
paul@271 810
                elif attrname in ("__file__", "__fname__",  "__mname__", "__name__"):
paul@251 811
                    path = ref.get_origin()
paul@271 812
paul@271 813
                    if attrname == "__file__":
paul@271 814
                        module = self.importer.get_module(path)
paul@271 815
                        value = module.filename
paul@271 816
                    else:
paul@271 817
                        value = path
paul@271 818
paul@271 819
                    local_number = self.importer.all_constants[path][value]
paul@251 820
                    constant_name = "$c%d" % local_number
paul@251 821
                    attr_path = "%s.%s" % (path, constant_name)
paul@251 822
                    constant_number = self.optimiser.constant_numbers[attr_path]
paul@251 823
                    constant_value = "__const%d" % constant_number
paul@251 824
                    structure.append("%s /* %s */" % (constant_value, attrname))
paul@251 825
                    continue
paul@251 826
paul@126 827
                structure.append(self.encode_member(origin, attrname, attr, kind))
paul@126 828
paul@126 829
    def encode_member(self, path, name, ref, structure_type):
paul@126 830
paul@126 831
        """
paul@126 832
        Encode within the structure provided by 'path', the member whose 'name'
paul@126 833
        provides 'ref', within the given 'structure_type'.
paul@126 834
        """
paul@126 835
paul@126 836
        kind = ref.get_kind()
paul@126 837
        origin = ref.get_origin()
paul@126 838
paul@126 839
        # References to constant literals.
paul@126 840
paul@126 841
        if kind == "<instance>":
paul@126 842
            attr_path = "%s.%s" % (path, name)
paul@126 843
paul@126 844
            # Obtain a constant value directly assigned to the attribute.
paul@126 845
paul@126 846
            if self.optimiser.constant_numbers.has_key(attr_path):
paul@126 847
                constant_number = self.optimiser.constant_numbers[attr_path]
paul@247 848
                constant_value = "__const%d" % constant_number
paul@247 849
                return "%s /* %s */" % (constant_value, name)
paul@126 850
paul@136 851
        # Predefined constant references.
paul@136 852
paul@136 853
        if (path, name) in self.predefined_constant_members:
paul@136 854
            attr_path = encode_predefined_reference("%s.%s" % (path, name))
paul@136 855
            return "{&%s, &%s} /* %s */" % (attr_path, attr_path, name)
paul@136 856
paul@126 857
        # General undetermined members.
paul@126 858
paul@126 859
        if kind in ("<var>", "<instance>"):
paul@126 860
            return "{0, 0} /* %s */" % name
paul@126 861
paul@126 862
        # Set the context depending on the kind of attribute.
paul@139 863
        # For methods:          {&<parent>, &<attr>}
paul@126 864
        # For other attributes: {&<attr>, &<attr>}
paul@126 865
paul@126 866
        else:
paul@139 867
            if kind == "<function>" and structure_type == "<class>":
paul@139 868
                parent = origin.rsplit(".", 1)[0]
paul@139 869
                context = "&%s" % encode_path(parent)
paul@139 870
            elif kind == "<instance>":
paul@139 871
                context = "&%s" % encode_path(origin)
paul@139 872
            else:
paul@139 873
                context = "0"
paul@126 874
            return "{%s, &%s}" % (context, encode_path(origin))
paul@126 875
paul@126 876
    def append_defaults(self, path, structure):
paul@126 877
paul@126 878
        """
paul@126 879
        For the given 'path', append default parameter members to the given
paul@126 880
        'structure'.
paul@126 881
        """
paul@126 882
paul@126 883
        for name, default in self.importer.function_defaults.get(path):
paul@126 884
            structure.append(self.encode_member(path, name, default, "<instance>"))
paul@126 885
paul@159 886
    def write_instantiator(self, f_code, f_signatures, path, init_ref):
paul@126 887
paul@126 888
        """
paul@159 889
        Write an instantiator to 'f_code', with a signature to 'f_signatures',
paul@159 890
        for instances of the class with the given 'path', with 'init_ref' as the
paul@159 891
        initialiser function reference.
paul@126 892
paul@126 893
        NOTE: This also needs to initialise any __fn__ and __args__ members
paul@126 894
        NOTE: where __call__ is provided by the class.
paul@126 895
        """
paul@126 896
paul@132 897
        parameters = self.importer.function_parameters[init_ref.get_origin()]
paul@126 898
paul@126 899
        print >>f_code, """\
paul@132 900
__attr %s(__attr __args[])
paul@126 901
{
paul@159 902
    /* Allocate the structure. */
paul@132 903
    __args[0] = __new(&%s, &%s, sizeof(%s));
paul@159 904
paul@159 905
    /* Call the initialiser. */
paul@146 906
    %s(__args);
paul@159 907
paul@159 908
    /* Return the allocated object details. */
paul@132 909
    return __args[0];
paul@126 910
}
paul@126 911
""" % (
paul@126 912
    encode_instantiator_pointer(path),
paul@150 913
    encode_tablename("<instance>", path),
paul@150 914
    encode_path(path),
paul@150 915
    encode_symbol("obj", path),
paul@126 916
    encode_function_pointer(init_ref.get_origin())
paul@126 917
    )
paul@126 918
paul@291 919
        print >>f_signatures, "#define __HAVE_%s" % encode_path(path)
paul@159 920
        print >>f_signatures, "__attr %s(__attr[]);" % encode_instantiator_pointer(path)
paul@159 921
paul@159 922
        # Write additional literal instantiators. These do not call the
paul@159 923
        # initialisers but instead populate the structures directly.
paul@159 924
paul@159 925
        if path in self.literal_instantiator_types:
paul@283 926
            if path in self.literal_mapping_types:
paul@283 927
                style = "mapping"
paul@283 928
            else:
paul@283 929
                style = "sequence"
paul@283 930
paul@159 931
            print >>f_code, """\
paul@159 932
__attr %s(__attr __args[], unsigned int number)
paul@159 933
{
paul@159 934
    /* Allocate the structure. */
paul@159 935
    __args[0] = __new(&%s, &%s, sizeof(%s));
paul@159 936
paul@283 937
    /* Allocate a structure for the data and set it on the __data__ attribute. */
paul@283 938
    %s(__args, number);
paul@159 939
paul@159 940
    /* Return the allocated object details. */
paul@159 941
    return __args[0];
paul@159 942
}
paul@159 943
""" % (
paul@159 944
    encode_literal_instantiator(path),
paul@159 945
    encode_tablename("<instance>", path),
paul@159 946
    encode_path(path),
paul@159 947
    encode_symbol("obj", path),
paul@283 948
    encode_literal_data_initialiser(style)
paul@159 949
    )
paul@159 950
paul@232 951
            print >>f_signatures, "__attr %s(__attr[], unsigned int);" % encode_literal_instantiator(path)
paul@159 952
paul@146 953
    def write_main_program(self, f_code, f_signatures):
paul@146 954
paul@146 955
        """
paul@146 956
        Write the main program to 'f_code', invoking the program's modules. Also
paul@146 957
        write declarations for module main functions to 'f_signatures'.
paul@146 958
        """
paul@146 959
paul@146 960
        print >>f_code, """\
paul@146 961
int main(int argc, char *argv[])
paul@159 962
{
paul@190 963
    __exc __tmp_exc;
paul@272 964
paul@159 965
    __Try
paul@159 966
    {"""
paul@146 967
paul@186 968
        for name in self.importer.order_modules():
paul@146 969
            function_name = "__main_%s" % encode_path(name)
paul@146 970
            print >>f_signatures, "void %s();" % function_name
paul@146 971
paul@161 972
            # Omit the native module.
paul@146 973
paul@186 974
            if name != "native":
paul@146 975
                print >>f_code, """\
paul@165 976
        %s();""" % function_name
paul@146 977
paul@146 978
        print >>f_code, """\
paul@159 979
        return 0;
paul@159 980
    }
paul@190 981
    __Catch(__tmp_exc)
paul@159 982
    {
paul@273 983
        fprintf(stderr, "Program terminated due to exception: %%s.\\n",
paul@272 984
                __load_via_object(
paul@273 985
                    %s((__attr[]) {{0, 0}, __tmp_exc.arg}).value,
paul@273 986
                    %s).strvalue);
paul@159 987
        return 1;
paul@159 988
    }
paul@146 989
}
paul@273 990
""" % (
paul@273 991
    encode_function_pointer("__builtins__.str.str"),
paul@273 992
    encode_symbol("pos", "__data__")
paul@273 993
    )
paul@146 994
paul@126 995
# vim: tabstop=4 expandtab shiftwidth=4