Lichen

Annotated encoders.py

789:f5b309580228
2017-03-29 Paul Boddie Incorporate invocation details in the alias output.
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@498 6
Copyright (C) 2016, 2017 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@498 22
from common import first, InstructionSequence
paul@56 23
paul@609 24
paul@609 25
paul@609 26
# Value digest computation.
paul@609 27
paul@609 28
from base64 import b64encode
paul@609 29
from hashlib import sha1
paul@609 30
paul@609 31
def digest(values):
paul@609 32
    m = sha1()
paul@609 33
    for value in values:
paul@609 34
        m.update(str(value))
paul@609 35
    return b64encode(m.digest()).replace("+", "__").replace("/", "_").rstrip("=")
paul@609 36
paul@609 37
paul@609 38
paul@0 39
# Output encoding and decoding for the summary files.
paul@0 40
paul@0 41
def encode_attrnames(attrnames):
paul@0 42
paul@0 43
    "Encode the 'attrnames' representing usage."
paul@0 44
paul@0 45
    return ", ".join(attrnames) or "{}"
paul@0 46
paul@0 47
def encode_constrained(constrained):
paul@0 48
paul@0 49
    "Encode the 'constrained' status for program summaries."
paul@0 50
paul@0 51
    return constrained and "constrained" or "deduced"
paul@0 52
paul@0 53
def encode_usage(usage):
paul@0 54
paul@0 55
    "Encode attribute details from 'usage'."
paul@0 56
paul@0 57
    all_attrnames = []
paul@0 58
    for t in usage:
paul@107 59
        attrname, invocation, assignment = t
paul@107 60
        all_attrnames.append("%s%s" % (attrname, invocation and "!" or assignment and "=" or ""))
paul@0 61
    return ", ".join(all_attrnames) or "{}"
paul@0 62
paul@88 63
def decode_usage(s):
paul@88 64
paul@88 65
    "Decode attribute details from 's'."
paul@88 66
paul@88 67
    all_attrnames = set()
paul@88 68
    for attrname_str in s.split(", "):
paul@107 69
        all_attrnames.add((attrname_str.rstrip("!="), attrname_str.endswith("!"), attrname_str.endswith("=")))
paul@88 70
paul@88 71
    all_attrnames = list(all_attrnames)
paul@88 72
    all_attrnames.sort()
paul@88 73
    return tuple(all_attrnames)
paul@88 74
paul@0 75
def encode_access_location(t):
paul@0 76
paul@0 77
    "Encode the access location 't'."
paul@0 78
paul@0 79
    path, name, attrname, version = t
paul@788 80
    return "%s:%s:%s:%d" % (path, name or "{}", attrname or "{}", version)
paul@0 81
paul@789 82
def encode_alias_location(t, invocation=False):
paul@789 83
paul@789 84
    "Encode the alias location 't'."
paul@789 85
paul@789 86
    path, name, attrname, version = t
paul@789 87
    return "%s:%s:%s:%d%s" % (path, name or "{}", attrname or "{}", version, invocation and "!" or "")
paul@789 88
paul@0 89
def encode_location(t):
paul@0 90
paul@0 91
    "Encode the general location 't' in a concise form."
paul@0 92
paul@0 93
    path, name, attrname, version = t
paul@0 94
    if name is not None and version is not None:
paul@788 95
        return "%s:%s:%d" % (path, name, version)
paul@0 96
    elif name is not None:
paul@788 97
        return "%s:%s" % (path, name)
paul@0 98
    else:
paul@788 99
        return "%s::%s" % (path, attrname)
paul@0 100
paul@0 101
def encode_modifiers(modifiers):
paul@0 102
paul@553 103
    "Encode assignment and invocation details from 'modifiers'."
paul@0 104
paul@0 105
    all_modifiers = []
paul@0 106
    for t in modifiers:
paul@0 107
        all_modifiers.append(encode_modifier_term(t))
paul@0 108
    return "".join(all_modifiers)
paul@0 109
paul@0 110
def encode_modifier_term(t):
paul@0 111
paul@553 112
    "Encode modifier 't' representing an assignment or an invocation."
paul@0 113
paul@117 114
    assignment, invocation = t
paul@553 115
    if assignment:
paul@553 116
        return "="
paul@553 117
    elif invocation is not None:
paul@557 118
        arguments, keywords = invocation
paul@557 119
        return "(%d;%s)" % (arguments, ",".join(keywords))
paul@553 120
    else:
paul@553 121
        return "_"
paul@0 122
paul@553 123
def decode_modifiers(s):
paul@553 124
paul@553 125
    "Decode 's' containing modifiers."
paul@553 126
paul@553 127
    i = 0
paul@553 128
    end = len(s)
paul@0 129
paul@553 130
    modifiers = []
paul@0 131
paul@553 132
    while i < end:
paul@553 133
        if s[i] == "=":
paul@553 134
            modifiers.append((True, None))
paul@553 135
            i += 1
paul@553 136
        elif s[i] == "(":
paul@557 137
            j = s.index(";", i)
paul@557 138
            arguments = int(s[i+1:j])
paul@557 139
            i = j
paul@553 140
            j = s.index(")", i)
paul@557 141
            keywords = s[i+1:j]
paul@557 142
            keywords = keywords and keywords.split(",") or []
paul@557 143
            modifiers.append((False, (arguments, keywords)))
paul@553 144
            i = j + 1
paul@553 145
        else:
paul@553 146
            modifiers.append((False, None))
paul@553 147
            i += 1
paul@553 148
paul@553 149
    return modifiers
paul@0 150
paul@56 151
paul@56 152
paul@56 153
# Test generation functions.
paul@56 154
paul@56 155
def get_kinds(all_types):
paul@56 156
paul@56 157
    """ 
paul@56 158
    Return object kind details for 'all_types', being a collection of
paul@56 159
    references for program types.
paul@56 160
    """
paul@56 161
paul@56 162
    return map(lambda ref: ref.get_kind(), all_types)
paul@56 163
paul@237 164
def test_label_for_kind(kind):
paul@56 165
paul@237 166
    "Return the label used for 'kind' in test details."
paul@56 167
paul@237 168
    return kind == "<instance>" and "instance" or "type"
paul@56 169
paul@237 170
def test_label_for_type(ref):
paul@56 171
paul@237 172
    "Return the label used for 'ref' in test details."
paul@56 173
paul@237 174
    return test_label_for_kind(ref.get_kind())
paul@56 175
paul@56 176
paul@56 177
paul@94 178
# Instruction representation encoding.
paul@94 179
paul@94 180
def encode_instruction(instruction):
paul@94 181
paul@94 182
    """
paul@94 183
    Encode the 'instruction' - a sequence starting with an operation and
paul@94 184
    followed by arguments, each of which may be an instruction sequence or a
paul@94 185
    plain value - to produce a function call string representation.
paul@94 186
    """
paul@94 187
paul@94 188
    op = instruction[0]
paul@94 189
    args = instruction[1:]
paul@94 190
paul@94 191
    if args:
paul@94 192
        a = []
paul@113 193
        for arg in args:
paul@113 194
            if isinstance(arg, tuple):
paul@113 195
                a.append(encode_instruction(arg))
paul@94 196
            else:
paul@113 197
                a.append(arg or "{}")
paul@94 198
        argstr = "(%s)" % ", ".join(a)
paul@94 199
        return "%s%s" % (op, argstr)
paul@94 200
    else:
paul@94 201
        return op
paul@94 202
paul@94 203
paul@94 204
paul@0 205
# Output program encoding.
paul@0 206
paul@153 207
attribute_loading_ops = (
paul@153 208
    "__load_via_class", "__load_via_object", "__get_class_and_load",
paul@153 209
    )
paul@153 210
paul@153 211
attribute_ops = attribute_loading_ops + (
paul@113 212
    "__store_via_object",
paul@113 213
    )
paul@113 214
paul@153 215
checked_loading_ops = (
paul@113 216
    "__check_and_load_via_class", "__check_and_load_via_object", "__check_and_load_via_any",
paul@153 217
    )
paul@153 218
paul@153 219
checked_ops = checked_loading_ops + (
paul@113 220
    "__check_and_store_via_class", "__check_and_store_via_object", "__check_and_store_via_any",
paul@113 221
    )
paul@113 222
paul@113 223
typename_ops = (
paul@144 224
    "__test_common_instance", "__test_common_object", "__test_common_type",
paul@113 225
    )
paul@113 226
paul@385 227
type_ops = (
paul@385 228
    "__test_specific_instance", "__test_specific_object", "__test_specific_type",
paul@385 229
    )
paul@385 230
paul@141 231
static_ops = (
paul@595 232
    "__load_static_ignore", "__load_static_replace", "__load_static_test", "<test_context_static>",
paul@141 233
    )
paul@141 234
paul@591 235
context_values = (
paul@591 236
    "<context>",
paul@591 237
    )
paul@591 238
paul@591 239
context_ops = (
paul@601 240
    "<context>", "<set_context>", "<test_context_revert>", "<test_context_static>",
paul@591 241
    )
paul@591 242
paul@602 243
context_op_functions = (
paul@602 244
    "<test_context_revert>", "<test_context_static>",
paul@602 245
    )
paul@602 246
paul@757 247
reference_acting_ops = attribute_ops + checked_ops + type_ops + typename_ops
paul@153 248
attribute_producing_ops = attribute_loading_ops + checked_loading_ops
paul@153 249
paul@757 250
attribute_producing_variables = (
paul@757 251
    "<accessor>", "<context>", "<name>", "<private_context>", "<target_accessor>"
paul@757 252
    )
paul@757 253
paul@591 254
def encode_access_instruction(instruction, subs, context_index):
paul@113 255
paul@113 256
    """
paul@113 257
    Encode the 'instruction' - a sequence starting with an operation and
paul@113 258
    followed by arguments, each of which may be an instruction sequence or a
paul@113 259
    plain value - to produce a function call string representation.
paul@113 260
paul@113 261
    The 'subs' parameter defines a mapping of substitutions for special values
paul@113 262
    used in instructions.
paul@482 263
paul@591 264
    The 'context_index' parameter defines the position in local context storage
paul@591 265
    for the referenced context or affected by a context operation.
paul@591 266
paul@482 267
    Return both the encoded instruction and a collection of substituted names.
paul@113 268
    """
paul@113 269
paul@113 270
    op = instruction[0]
paul@113 271
    args = instruction[1:]
paul@482 272
    substituted = set()
paul@113 273
paul@591 274
    # Encode the arguments.
paul@113 275
paul@591 276
    a = []
paul@591 277
    if args:
paul@153 278
        converting_op = op
paul@113 279
        for arg in args:
paul@591 280
            s, _substituted = encode_access_instruction_arg(arg, subs, converting_op, context_index)
paul@482 281
            substituted.update(_substituted)
paul@482 282
            a.append(s)
paul@153 283
            converting_op = None
paul@113 284
paul@591 285
    # Modify certain arguments.
paul@113 286
paul@624 287
    # Convert type name arguments.
paul@113 288
paul@624 289
    if op in typename_ops:
paul@624 290
        a[1] = encode_path(encode_type_attribute(args[1]))
paul@113 291
paul@591 292
    # Obtain addresses of type arguments.
paul@591 293
paul@591 294
    elif op in type_ops:
paul@591 295
        a[1] = "&%s" % a[1]
paul@113 296
paul@591 297
    # Obtain addresses of static objects.
paul@591 298
paul@591 299
    elif op in static_ops:
paul@591 300
        a[-1] = "&%s" % a[-1]
paul@385 301
paul@591 302
    # Add context storage information to certain operations.
paul@385 303
paul@595 304
    if op in context_ops:
paul@591 305
        a.insert(0, context_index)
paul@141 306
paul@602 307
    # Add the local context array to certain operations.
paul@602 308
paul@602 309
    if op in context_op_functions:
paul@602 310
        a.append("__tmp_contexts")
paul@602 311
paul@591 312
    # Define any argument string.
paul@141 313
paul@591 314
    if a:
paul@491 315
        argstr = "(%s)" % ", ".join(map(str, a))
paul@591 316
    else:
paul@591 317
        argstr = ""
paul@113 318
paul@113 319
    # Substitute the first element of the instruction, which may not be an
paul@113 320
    # operation at all.
paul@113 321
paul@144 322
    if subs.has_key(op):
paul@482 323
        substituted.add(op)
paul@498 324
paul@498 325
        # Break accessor initialisation into initialisation and value-yielding
paul@498 326
        # parts:
paul@498 327
paul@498 328
        if op == "<set_accessor>" and isinstance(a[0], InstructionSequence):
paul@498 329
            ops = []
paul@498 330
            ops += a[0].get_init_instructions()
paul@498 331
            ops.append("%s(%s)" % (subs[op], a[0].get_value_instruction()))
paul@498 332
            return ", ".join(map(str, ops)), substituted
paul@498 333
paul@144 334
        op = subs[op]
paul@498 335
paul@144 336
    elif not args:
paul@144 337
        op = "&%s" % encode_path(op)
paul@144 338
paul@482 339
    return "%s%s" % (op, argstr), substituted
paul@113 340
paul@591 341
def encode_access_instruction_arg(arg, subs, op, context_index):
paul@113 342
paul@482 343
    """
paul@591 344
    Encode 'arg' using 'subs' to define substitutions, 'op' to indicate the
paul@591 345
    operation to which the argument belongs, and 'context_index' to indicate any
paul@591 346
    affected context storage.
paul@591 347
paul@591 348
    Return a tuple containing the encoded form of 'arg' along with a collection
paul@591 349
    of any substituted values.
paul@482 350
    """
paul@113 351
paul@113 352
    if isinstance(arg, tuple):
paul@591 353
        encoded, substituted = encode_access_instruction(arg, subs, context_index)
paul@757 354
        return attribute_to_reference(op, arg[0], encoded, substituted)
paul@113 355
paul@113 356
    # Special values only need replacing, not encoding.
paul@113 357
paul@113 358
    elif subs.has_key(arg):
paul@591 359
paul@591 360
        # Handle values modified by storage details.
paul@591 361
paul@591 362
        if arg in context_values:
paul@757 363
            encoded = "%s(%s)" % (subs.get(arg), context_index)
paul@591 364
        else:
paul@757 365
            encoded = subs.get(arg)
paul@757 366
paul@757 367
        substituted = set([arg])
paul@757 368
        return attribute_to_reference(op, arg, encoded, substituted)
paul@113 369
paul@258 370
    # Convert static references to the appropriate type.
paul@258 371
paul@757 372
    elif op and op in reference_acting_ops and \
paul@757 373
         arg not in attribute_producing_variables:
paul@757 374
paul@482 375
        return "&%s" % encode_path(arg), set()
paul@258 376
paul@113 377
    # Other values may need encoding.
paul@113 378
paul@113 379
    else:
paul@482 380
        return encode_path(arg), set()
paul@113 381
paul@757 382
def attribute_to_reference(op, arg, encoded, substituted):
paul@757 383
paul@757 384
    # Convert attribute results to references where required.
paul@757 385
paul@757 386
    if op and op in reference_acting_ops and (
paul@757 387
       arg in attribute_producing_ops or
paul@757 388
       arg in attribute_producing_variables):
paul@757 389
paul@757 390
        return "__VALUE(%s)" % encoded, substituted
paul@757 391
    else:
paul@757 392
        return encoded, substituted
paul@757 393
paul@0 394
def encode_function_pointer(path):
paul@0 395
paul@0 396
    "Encode 'path' as a reference to an output program function."
paul@0 397
paul@0 398
    return "__fn_%s" % encode_path(path)
paul@0 399
paul@0 400
def encode_instantiator_pointer(path):
paul@0 401
paul@0 402
    "Encode 'path' as a reference to an output program instantiator."
paul@0 403
paul@0 404
    return "__new_%s" % encode_path(path)
paul@0 405
paul@491 406
def encode_instructions(instructions):
paul@491 407
paul@491 408
    "Encode 'instructions' as a sequence."
paul@491 409
paul@491 410
    if len(instructions) == 1:
paul@491 411
        return instructions[0]
paul@491 412
    else:
paul@491 413
        return "(\n%s\n)" % ",\n".join(instructions)
paul@491 414
paul@136 415
def encode_literal_constant(n):
paul@136 416
paul@136 417
    "Encode a name for the literal constant with the number 'n'."
paul@136 418
paul@609 419
    return "__const%s" % n
paul@136 420
paul@378 421
def encode_literal_constant_size(value):
paul@378 422
paul@378 423
    "Encode a size for the literal constant with the given 'value'."
paul@378 424
paul@378 425
    if isinstance(value, basestring):
paul@378 426
        return len(value)
paul@378 427
    else:
paul@378 428
        return 0
paul@378 429
paul@136 430
def encode_literal_constant_member(value):
paul@136 431
paul@136 432
    "Encode the member name for the 'value' in the final program."
paul@136 433
paul@136 434
    return "%svalue" % value.__class__.__name__
paul@136 435
paul@136 436
def encode_literal_constant_value(value):
paul@136 437
paul@136 438
    "Encode the given 'value' in the final program."
paul@136 439
paul@136 440
    if isinstance(value, (int, float)):
paul@136 441
        return str(value)
paul@136 442
    else:
paul@451 443
        l = []
paul@451 444
paul@451 445
        # Encode characters including non-ASCII ones.
paul@451 446
paul@451 447
        for c in str(value):
paul@451 448
            if c == '"': l.append('\\"')
paul@451 449
            elif c == '\n': l.append('\\n')
paul@451 450
            elif c == '\t': l.append('\\t')
paul@451 451
            elif c == '\r': l.append('\\r')
paul@512 452
            elif c == '\\': l.append('\\\\')
paul@451 453
            elif 0x20 <= ord(c) < 0x80: l.append(c)
paul@451 454
            else: l.append("\\x%02x" % ord(c))
paul@451 455
paul@451 456
        return '"%s"' % "".join(l)
paul@136 457
paul@283 458
def encode_literal_data_initialiser(style):
paul@283 459
paul@283 460
    """
paul@283 461
    Encode a reference to a function populating the data for a literal having
paul@283 462
    the given 'style' ("mapping" or "sequence").
paul@283 463
    """
paul@283 464
paul@283 465
    return "__newdata_%s" % style
paul@283 466
paul@159 467
def encode_literal_instantiator(path):
paul@159 468
paul@159 469
    """
paul@159 470
    Encode a reference to an instantiator for a literal having the given 'path'.
paul@159 471
    """
paul@159 472
paul@159 473
    return "__newliteral_%s" % encode_path(path)
paul@159 474
paul@136 475
def encode_literal_reference(n):
paul@136 476
paul@136 477
    "Encode a reference to a literal constant with the number 'n'."
paul@136 478
paul@609 479
    return "__constvalue%s" % n
paul@136 480
paul@512 481
paul@512 482
paul@340 483
# Track all encoded paths, detecting and avoiding conflicts.
paul@340 484
paul@340 485
all_encoded_paths = {}
paul@340 486
paul@0 487
def encode_path(path):
paul@0 488
paul@0 489
    "Encode 'path' as an output program object, translating special symbols."
paul@0 490
paul@0 491
    if path in reserved_words:
paul@0 492
        return "__%s" % path
paul@0 493
    else:
paul@340 494
        part_encoded = path.replace("#", "__").replace("$", "__")
paul@349 495
paul@349 496
        if "." not in path:
paul@349 497
            return part_encoded
paul@349 498
paul@340 499
        encoded = part_encoded.replace(".", "_")
paul@340 500
paul@340 501
        # Test for a conflict with the encoding of a different path, re-encoding
paul@340 502
        # if necessary.
paul@340 503
paul@340 504
        previous = all_encoded_paths.get(encoded)
paul@340 505
        replacement = "_"
paul@340 506
paul@340 507
        while previous:
paul@340 508
            if path == previous:
paul@340 509
                return encoded
paul@340 510
            replacement += "_"
paul@340 511
            encoded = part_encoded.replace(".", replacement)
paul@340 512
            previous = all_encoded_paths.get(encoded)
paul@340 513
paul@340 514
        # Store any new or re-encoded path.
paul@340 515
paul@340 516
        all_encoded_paths[encoded] = path
paul@340 517
        return encoded
paul@0 518
paul@623 519
def encode_code(name):
paul@623 520
paul@623 521
    "Encode 'name' as an attribute code indicator."
paul@623 522
paul@623 523
    return "__ATTRCODE(%s)" % encode_path(name)
paul@623 524
paul@623 525
def encode_pcode(name):
paul@623 526
paul@623 527
    "Encode 'name' as an parameter code indicator."
paul@623 528
paul@623 529
    return "__PARAMCODE(%s)" % encode_path(name)
paul@623 530
paul@623 531
def encode_pos(name):
paul@623 532
paul@623 533
    "Encode 'name' as an attribute position indicator."
paul@623 534
paul@623 535
    return "__ATTRPOS(%s)" % encode_path(name)
paul@623 536
paul@623 537
def encode_ppos(name):
paul@623 538
paul@623 539
    "Encode 'name' as an parameter position indicator."
paul@623 540
paul@623 541
    return "__PARAMPOS(%s)" % encode_path(name)
paul@623 542
paul@136 543
def encode_predefined_reference(path):
paul@136 544
paul@136 545
    "Encode a reference to a predefined constant value for 'path'."
paul@136 546
paul@136 547
    return "__predefined_%s" % encode_path(path)
paul@136 548
paul@150 549
def encode_size(kind, path=None):
paul@150 550
paul@150 551
    """
paul@150 552
    Encode a structure size reference for the given 'kind' of structure, with
paul@150 553
    'path' indicating a specific structure name.
paul@150 554
    """
paul@150 555
paul@150 556
    return "__%ssize%s" % (structure_size_prefixes.get(kind, kind), path and "_%s" % encode_path(path) or "")
paul@150 557
paul@0 558
def encode_symbol(symbol_type, path=None):
paul@0 559
paul@0 560
    "Encode a symbol with the given 'symbol_type' and optional 'path'."
paul@0 561
paul@0 562
    return "__%s%s" % (symbol_type, path and "_%s" % encode_path(path) or "")
paul@0 563
paul@150 564
def encode_tablename(kind, path):
paul@150 565
paul@150 566
    """
paul@150 567
    Encode a table reference for the given 'kind' of table structure, indicating
paul@150 568
    a 'path' for the specific object concerned.
paul@150 569
    """
paul@150 570
paul@150 571
    return "__%sTable_%s" % (table_name_prefixes[kind], encode_path(path))
paul@150 572
paul@131 573
def encode_type_attribute(path):
paul@131 574
paul@131 575
    "Encode the special type attribute for 'path'."
paul@131 576
paul@131 577
    return "#%s" % path
paul@131 578
paul@318 579
def decode_type_attribute(s):
paul@318 580
paul@318 581
    "Decode the special type attribute 's'."
paul@318 582
paul@318 583
    return s[1:]
paul@318 584
paul@318 585
def is_type_attribute(s):
paul@318 586
paul@318 587
    "Return whether 's' is a type attribute name."
paul@318 588
paul@318 589
    return s.startswith("#")
paul@318 590
paul@56 591
paul@56 592
paul@150 593
# A mapping from kinds to structure size reference prefixes.
paul@150 594
paul@150 595
structure_size_prefixes = {
paul@150 596
    "<class>" : "c",
paul@150 597
    "<module>" : "m",
paul@150 598
    "<instance>" : "i"
paul@150 599
    }
paul@150 600
paul@150 601
# A mapping from kinds to table name prefixes.
paul@150 602
paul@150 603
table_name_prefixes = {
paul@150 604
    "<class>" : "Class",
paul@150 605
    "<function>" : "Function",
paul@150 606
    "<module>" : "Module",
paul@150 607
    "<instance>" : "Instance"
paul@150 608
    }
paul@150 609
paul@150 610
paul@150 611
paul@0 612
# Output language reserved words.
paul@0 613
paul@0 614
reserved_words = [
paul@0 615
    "break", "char", "const", "continue",
paul@0 616
    "default", "double", "else",
paul@0 617
    "float", "for",
paul@0 618
    "if", "int", "long",
paul@0 619
    "NULL",
paul@0 620
    "return", "struct",
paul@0 621
    "typedef",
paul@0 622
    "void", "while",
paul@0 623
    ]
paul@0 624
paul@0 625
# vim: tabstop=4 expandtab shiftwidth=4