Lichen

Annotated encoders.py

149:6092f25f33f4
2016-10-29 Paul Boddie Fixed member assignment via names, and fixed "not" and lambda translations. Improved function default access and initialisation operations.
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Encoder functions, producing representations of program objects.
paul@0 5
paul@0 6
Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk>
paul@0 7
paul@0 8
This program is free software; you can redistribute it and/or modify it under
paul@0 9
the terms of the GNU General Public License as published by the Free Software
paul@0 10
Foundation; either version 3 of the License, or (at your option) any later
paul@0 11
version.
paul@0 12
paul@0 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@0 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@0 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@0 16
details.
paul@0 17
paul@0 18
You should have received a copy of the GNU General Public License along with
paul@0 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@0 20
"""
paul@0 21
paul@56 22
from common import first
paul@56 23
paul@0 24
# Output encoding and decoding for the summary files.
paul@0 25
paul@0 26
def encode_attrnames(attrnames):
paul@0 27
paul@0 28
    "Encode the 'attrnames' representing usage."
paul@0 29
paul@0 30
    return ", ".join(attrnames) or "{}"
paul@0 31
paul@0 32
def encode_constrained(constrained):
paul@0 33
paul@0 34
    "Encode the 'constrained' status for program summaries."
paul@0 35
paul@0 36
    return constrained and "constrained" or "deduced"
paul@0 37
paul@0 38
def encode_usage(usage):
paul@0 39
paul@0 40
    "Encode attribute details from 'usage'."
paul@0 41
paul@0 42
    all_attrnames = []
paul@0 43
    for t in usage:
paul@107 44
        attrname, invocation, assignment = t
paul@107 45
        all_attrnames.append("%s%s" % (attrname, invocation and "!" or assignment and "=" or ""))
paul@0 46
    return ", ".join(all_attrnames) or "{}"
paul@0 47
paul@88 48
def decode_usage(s):
paul@88 49
paul@88 50
    "Decode attribute details from 's'."
paul@88 51
paul@88 52
    all_attrnames = set()
paul@88 53
    for attrname_str in s.split(", "):
paul@107 54
        all_attrnames.add((attrname_str.rstrip("!="), attrname_str.endswith("!"), attrname_str.endswith("=")))
paul@88 55
paul@88 56
    all_attrnames = list(all_attrnames)
paul@88 57
    all_attrnames.sort()
paul@88 58
    return tuple(all_attrnames)
paul@88 59
paul@0 60
def encode_access_location(t):
paul@0 61
paul@0 62
    "Encode the access location 't'."
paul@0 63
paul@0 64
    path, name, attrname, version = t
paul@0 65
    return "%s %s %s:%d" % (path, name or "{}", attrname, version)
paul@0 66
paul@0 67
def encode_location(t):
paul@0 68
paul@0 69
    "Encode the general location 't' in a concise form."
paul@0 70
paul@0 71
    path, name, attrname, version = t
paul@0 72
    if name is not None and version is not None:
paul@0 73
        return "%s %s:%d" % (path, name, version)
paul@0 74
    elif name is not None:
paul@0 75
        return "%s %s" % (path, name)
paul@0 76
    else:
paul@0 77
        return "%s :%s" % (path, attrname)
paul@0 78
paul@0 79
def encode_modifiers(modifiers):
paul@0 80
paul@0 81
    "Encode assignment details from 'modifiers'."
paul@0 82
paul@0 83
    all_modifiers = []
paul@0 84
    for t in modifiers:
paul@0 85
        all_modifiers.append(encode_modifier_term(t))
paul@0 86
    return "".join(all_modifiers)
paul@0 87
paul@0 88
def encode_modifier_term(t):
paul@0 89
paul@0 90
    "Encode modifier 't' representing assignment status."
paul@0 91
paul@117 92
    assignment, invocation = t
paul@117 93
    return assignment and "=" or invocation and "!" or "_"
paul@0 94
paul@0 95
def decode_modifier_term(s):
paul@0 96
paul@0 97
    "Decode modifier term 's' representing assignment status."
paul@0 98
paul@117 99
    return (s == "=", s == "!")
paul@0 100
paul@56 101
paul@56 102
paul@56 103
# Test generation functions.
paul@56 104
paul@56 105
def get_kinds(all_types):
paul@56 106
paul@56 107
    """ 
paul@56 108
    Return object kind details for 'all_types', being a collection of
paul@56 109
    references for program types.
paul@56 110
    """
paul@56 111
paul@56 112
    return map(lambda ref: ref.get_kind(), all_types)
paul@56 113
paul@56 114
def test_for_kind(prefix, kind):
paul@56 115
paul@56 116
    "Return a test condition identifier featuring 'prefix' and 'kind'."
paul@56 117
paul@56 118
    return "%s-%s" % (prefix, kind == "<instance>" and "instance" or "type")
paul@56 119
paul@56 120
def test_for_kinds(prefix, all_kinds):
paul@56 121
paul@56 122
    """ 
paul@67 123
    Return an identifier describing test conditions incorporating the given
paul@56 124
    'prefix' and involving 'all_kinds', being a collection of object kinds.
paul@56 125
    """
paul@56 126
paul@56 127
    return test_for_kind(prefix, first(all_kinds))
paul@56 128
paul@67 129
def test_for_type(prefix, ref):
paul@56 130
paul@56 131
    """ 
paul@67 132
    Return an identifier describing a test condition incorporating the given
paul@67 133
    'prefix' and involving 'ref', being a program type reference. The kind of
paul@67 134
    the reference is employed in the identifier.
paul@56 135
    """
paul@56 136
paul@67 137
    return test_for_kind(prefix, ref.get_kind())
paul@56 138
paul@56 139
paul@56 140
paul@94 141
# Instruction representation encoding.
paul@94 142
paul@94 143
def encode_instruction(instruction):
paul@94 144
paul@94 145
    """
paul@94 146
    Encode the 'instruction' - a sequence starting with an operation and
paul@94 147
    followed by arguments, each of which may be an instruction sequence or a
paul@94 148
    plain value - to produce a function call string representation.
paul@94 149
    """
paul@94 150
paul@94 151
    op = instruction[0]
paul@94 152
    args = instruction[1:]
paul@94 153
paul@94 154
    if args:
paul@94 155
        a = []
paul@113 156
        for arg in args:
paul@113 157
            if isinstance(arg, tuple):
paul@113 158
                a.append(encode_instruction(arg))
paul@94 159
            else:
paul@113 160
                a.append(arg or "{}")
paul@94 161
        argstr = "(%s)" % ", ".join(a)
paul@94 162
        return "%s%s" % (op, argstr)
paul@94 163
    else:
paul@94 164
        return op
paul@94 165
paul@94 166
paul@94 167
paul@0 168
# Output program encoding.
paul@0 169
paul@113 170
attribute_ops = (
paul@113 171
    "__load_via_class", "__load_via_object",
paul@113 172
    "__store_via_object",
paul@113 173
    )
paul@113 174
paul@113 175
checked_ops = (
paul@113 176
    "__check_and_load_via_class", "__check_and_load_via_object", "__check_and_load_via_any",
paul@113 177
    "__check_and_store_via_class", "__check_and_store_via_object", "__check_and_store_via_any",
paul@113 178
    )
paul@113 179
paul@113 180
typename_ops = (
paul@144 181
    "__test_common_instance", "__test_common_object", "__test_common_type",
paul@113 182
    )
paul@113 183
paul@117 184
encoding_ops = (
paul@117 185
    "__encode_callable",
paul@117 186
    )
paul@117 187
paul@141 188
static_ops = (
paul@141 189
    "__load_static",
paul@141 190
    )
paul@141 191
paul@113 192
def encode_access_instruction(instruction, subs):
paul@113 193
paul@113 194
    """
paul@113 195
    Encode the 'instruction' - a sequence starting with an operation and
paul@113 196
    followed by arguments, each of which may be an instruction sequence or a
paul@113 197
    plain value - to produce a function call string representation.
paul@113 198
paul@113 199
    The 'subs' parameter defines a mapping of substitutions for special values
paul@113 200
    used in instructions.
paul@113 201
    """
paul@113 202
paul@113 203
    op = instruction[0]
paul@113 204
    args = instruction[1:]
paul@113 205
paul@113 206
    if not args:
paul@113 207
        argstr = ""
paul@113 208
paul@113 209
    else:
paul@113 210
        # Encode the arguments.
paul@113 211
paul@113 212
        a = []
paul@113 213
        for arg in args:
paul@113 214
            a.append(encode_access_instruction_arg(arg, subs))
paul@113 215
paul@113 216
        # Modify certain arguments.
paul@113 217
paul@113 218
        # Convert attribute name arguments to position symbols.
paul@113 219
paul@113 220
        if op in attribute_ops:
paul@113 221
            arg = a[1]
paul@113 222
            a[1] = encode_symbol("pos", arg)
paul@113 223
paul@113 224
        # Convert attribute name arguments to position and code symbols.
paul@113 225
paul@113 226
        elif op in checked_ops:
paul@113 227
            arg = a[1]
paul@113 228
            a[1] = encode_symbol("pos", arg)
paul@113 229
            a.insert(2, encode_symbol("code", arg))
paul@113 230
paul@113 231
        # Convert type name arguments to position and code symbols.
paul@113 232
paul@113 233
        elif op in typename_ops:
paul@131 234
            arg = encode_type_attribute(a[1])
paul@113 235
            a[1] = encode_symbol("pos", arg)
paul@113 236
            a.insert(2, encode_symbol("code", arg))
paul@113 237
paul@117 238
        # Replace encoded operations.
paul@117 239
paul@117 240
        elif op in encoding_ops:
paul@117 241
            origin = a[0]
paul@117 242
            kind = a[1]
paul@117 243
            op = "__load_function"
paul@117 244
            a = [kind == "<class>" and encode_instantiator_pointer(origin) or encode_function_pointer(origin)]
paul@117 245
paul@141 246
        # Obtain addresses of static objects.
paul@141 247
paul@141 248
        elif op in static_ops:
paul@141 249
            a[0] = "&%s" % a[0]
paul@141 250
paul@113 251
        argstr = "(%s)" % ", ".join(a)
paul@113 252
paul@113 253
    # Substitute the first element of the instruction, which may not be an
paul@113 254
    # operation at all.
paul@113 255
paul@144 256
    if subs.has_key(op):
paul@144 257
        op = subs[op]
paul@144 258
    elif not args:
paul@144 259
        op = "&%s" % encode_path(op)
paul@144 260
paul@144 261
    return "%s%s" % (op, argstr)
paul@113 262
paul@113 263
def encode_access_instruction_arg(arg, subs):
paul@113 264
paul@113 265
    "Encode 'arg' using 'subs' to define substitutions."
paul@113 266
paul@113 267
    if isinstance(arg, tuple):
paul@113 268
        return encode_access_instruction(arg, subs)
paul@113 269
paul@113 270
    # Special values only need replacing, not encoding.
paul@113 271
paul@113 272
    elif subs.has_key(arg):
paul@113 273
        return subs.get(arg)
paul@113 274
paul@113 275
    # Other values may need encoding.
paul@113 276
paul@113 277
    else:
paul@113 278
        return encode_path(arg)
paul@113 279
paul@126 280
def encode_bound_reference(path):
paul@126 281
paul@126 282
    "Encode 'path' as a bound method name."
paul@126 283
paul@126 284
    return "__bound_%s" % encode_path(path)
paul@126 285
paul@0 286
def encode_function_pointer(path):
paul@0 287
paul@0 288
    "Encode 'path' as a reference to an output program function."
paul@0 289
paul@0 290
    return "__fn_%s" % encode_path(path)
paul@0 291
paul@149 292
def encode_initialiser_pointer(path):
paul@149 293
paul@149 294
    "Encode 'path' as a reference to an initialiser function structure."
paul@149 295
paul@149 296
    return encode_path("%s.__init__" % path)
paul@149 297
paul@0 298
def encode_instantiator_pointer(path):
paul@0 299
paul@0 300
    "Encode 'path' as a reference to an output program instantiator."
paul@0 301
paul@0 302
    return "__new_%s" % encode_path(path)
paul@0 303
paul@136 304
def encode_literal_constant(n):
paul@136 305
paul@136 306
    "Encode a name for the literal constant with the number 'n'."
paul@136 307
paul@136 308
    return "__const%d" % n
paul@136 309
paul@136 310
def encode_literal_constant_member(value):
paul@136 311
paul@136 312
    "Encode the member name for the 'value' in the final program."
paul@136 313
paul@136 314
    return "%svalue" % value.__class__.__name__
paul@136 315
paul@136 316
def encode_literal_constant_value(value):
paul@136 317
paul@136 318
    "Encode the given 'value' in the final program."
paul@136 319
paul@136 320
    if isinstance(value, (int, float)):
paul@136 321
        return str(value)
paul@136 322
    else:
paul@136 323
        return '"%s"' % str(value).replace('"', '\\"')
paul@136 324
paul@136 325
def encode_literal_reference(n):
paul@136 326
paul@136 327
    "Encode a reference to a literal constant with the number 'n'."
paul@136 328
paul@136 329
    return "__constvalue%d" % n
paul@136 330
paul@0 331
def encode_path(path):
paul@0 332
paul@0 333
    "Encode 'path' as an output program object, translating special symbols."
paul@0 334
paul@0 335
    if path in reserved_words:
paul@0 336
        return "__%s" % path
paul@0 337
    else:
paul@0 338
        return path.replace("#", "__").replace("$", "__").replace(".", "_")
paul@0 339
paul@136 340
def encode_predefined_reference(path):
paul@136 341
paul@136 342
    "Encode a reference to a predefined constant value for 'path'."
paul@136 343
paul@136 344
    return "__predefined_%s" % encode_path(path)
paul@136 345
paul@0 346
def encode_symbol(symbol_type, path=None):
paul@0 347
paul@0 348
    "Encode a symbol with the given 'symbol_type' and optional 'path'."
paul@0 349
paul@0 350
    return "__%s%s" % (symbol_type, path and "_%s" % encode_path(path) or "")
paul@0 351
paul@131 352
def encode_type_attribute(path):
paul@131 353
paul@131 354
    "Encode the special type attribute for 'path'."
paul@131 355
paul@131 356
    return "#%s" % path
paul@131 357
paul@56 358
paul@56 359
paul@0 360
# Output language reserved words.
paul@0 361
paul@0 362
reserved_words = [
paul@0 363
    "break", "char", "const", "continue",
paul@0 364
    "default", "double", "else",
paul@0 365
    "float", "for",
paul@0 366
    "if", "int", "long",
paul@0 367
    "NULL",
paul@0 368
    "return", "struct",
paul@0 369
    "typedef",
paul@0 370
    "void", "while",
paul@0 371
    ]
paul@0 372
paul@0 373
# vim: tabstop=4 expandtab shiftwidth=4