Lichen

Annotated encoders.py

118:900d641f42d6
2016-10-20 Paul Boddie Added some more support for generating invocation code, distinguishing between static invocation targets that are identified and whose functions can be obtained directly and other kinds of targets whose functions must be obtained via the special attribute.
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@113 181
    "__test_common_instance",
paul@113 182
    )
paul@113 183
paul@117 184
encoding_ops = (
paul@117 185
    "__encode_callable",
paul@117 186
    )
paul@117 187
paul@113 188
def encode_access_instruction(instruction, subs):
paul@113 189
paul@113 190
    """
paul@113 191
    Encode the 'instruction' - a sequence starting with an operation and
paul@113 192
    followed by arguments, each of which may be an instruction sequence or a
paul@113 193
    plain value - to produce a function call string representation.
paul@113 194
paul@113 195
    The 'subs' parameter defines a mapping of substitutions for special values
paul@113 196
    used in instructions.
paul@113 197
    """
paul@113 198
paul@113 199
    op = instruction[0]
paul@113 200
    args = instruction[1:]
paul@113 201
paul@113 202
    if not args:
paul@113 203
        argstr = ""
paul@113 204
paul@113 205
    else:
paul@113 206
        # Encode the arguments.
paul@113 207
paul@113 208
        a = []
paul@113 209
        for arg in args:
paul@113 210
            a.append(encode_access_instruction_arg(arg, subs))
paul@113 211
paul@113 212
        # Modify certain arguments.
paul@113 213
paul@113 214
        # Convert attribute name arguments to position symbols.
paul@113 215
paul@113 216
        if op in attribute_ops:
paul@113 217
            arg = a[1]
paul@113 218
            a[1] = encode_symbol("pos", arg)
paul@113 219
paul@113 220
        # Convert attribute name arguments to position and code symbols.
paul@113 221
paul@113 222
        elif op in checked_ops:
paul@113 223
            arg = a[1]
paul@113 224
            a[1] = encode_symbol("pos", arg)
paul@113 225
            a.insert(2, encode_symbol("code", arg))
paul@113 226
paul@113 227
        # Convert type name arguments to position and code symbols.
paul@113 228
paul@113 229
        elif op in typename_ops:
paul@113 230
            arg = "#" % a[1]
paul@113 231
            a[1] = encode_symbol("pos", arg)
paul@113 232
            a.insert(2, encode_symbol("code", arg))
paul@113 233
paul@117 234
        # Replace encoded operations.
paul@117 235
paul@117 236
        elif op in encoding_ops:
paul@117 237
            origin = a[0]
paul@117 238
            kind = a[1]
paul@117 239
            op = "__load_function"
paul@117 240
            a = [kind == "<class>" and encode_instantiator_pointer(origin) or encode_function_pointer(origin)]
paul@117 241
paul@113 242
        argstr = "(%s)" % ", ".join(a)
paul@113 243
paul@113 244
    # Substitute the first element of the instruction, which may not be an
paul@113 245
    # operation at all.
paul@113 246
paul@113 247
    return "%s%s" % (subs.get(op, op), argstr)
paul@113 248
paul@113 249
def encode_access_instruction_arg(arg, subs):
paul@113 250
paul@113 251
    "Encode 'arg' using 'subs' to define substitutions."
paul@113 252
paul@113 253
    if isinstance(arg, tuple):
paul@113 254
        return encode_access_instruction(arg, subs)
paul@113 255
paul@113 256
    # Special values only need replacing, not encoding.
paul@113 257
paul@113 258
    elif subs.has_key(arg):
paul@113 259
        return subs.get(arg)
paul@113 260
paul@113 261
    # Other values may need encoding.
paul@113 262
paul@113 263
    else:
paul@113 264
        return encode_path(arg)
paul@113 265
paul@0 266
def encode_function_pointer(path):
paul@0 267
paul@0 268
    "Encode 'path' as a reference to an output program function."
paul@0 269
paul@0 270
    return "__fn_%s" % encode_path(path)
paul@0 271
paul@0 272
def encode_instantiator_pointer(path):
paul@0 273
paul@0 274
    "Encode 'path' as a reference to an output program instantiator."
paul@0 275
paul@0 276
    return "__new_%s" % encode_path(path)
paul@0 277
paul@0 278
def encode_path(path):
paul@0 279
paul@0 280
    "Encode 'path' as an output program object, translating special symbols."
paul@0 281
paul@0 282
    if path in reserved_words:
paul@0 283
        return "__%s" % path
paul@0 284
    else:
paul@0 285
        return path.replace("#", "__").replace("$", "__").replace(".", "_")
paul@0 286
paul@0 287
def encode_symbol(symbol_type, path=None):
paul@0 288
paul@0 289
    "Encode a symbol with the given 'symbol_type' and optional 'path'."
paul@0 290
paul@0 291
    return "__%s%s" % (symbol_type, path and "_%s" % encode_path(path) or "")
paul@0 292
paul@56 293
paul@56 294
paul@0 295
# Output language reserved words.
paul@0 296
paul@0 297
reserved_words = [
paul@0 298
    "break", "char", "const", "continue",
paul@0 299
    "default", "double", "else",
paul@0 300
    "float", "for",
paul@0 301
    "if", "int", "long",
paul@0 302
    "NULL",
paul@0 303
    "return", "struct",
paul@0 304
    "typedef",
paul@0 305
    "void", "while",
paul@0 306
    ]
paul@0 307
paul@0 308
# vim: tabstop=4 expandtab shiftwidth=4