Search
lxdream.org :: lxdream/src/tools/gendec.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/tools/gendec.c
changeset 1298:d0eb2307b847
prev1296:30ecee61f811
author nkeynes
date Wed Feb 04 08:38:23 2015 +1000 (5 years ago)
permissions -rw-r--r--
last change Fix assorted compile warnings reported by Clang
file annotate diff log raw
nkeynes@359
     1
/**
nkeynes@561
     2
 * $Id$
nkeynes@359
     3
 * 
nkeynes@359
     4
 * Parse the instruction and action files and generate an appropriate
nkeynes@359
     5
 * instruction decoder.
nkeynes@359
     6
 *
nkeynes@359
     7
 * Copyright (c) 2005 Nathan Keynes.
nkeynes@359
     8
 *
nkeynes@359
     9
 * This program is free software; you can redistribute it and/or modify
nkeynes@359
    10
 * it under the terms of the GNU General Public License as published by
nkeynes@359
    11
 * the Free Software Foundation; either version 2 of the License, or
nkeynes@359
    12
 * (at your option) any later version.
nkeynes@359
    13
 *
nkeynes@359
    14
 * This program is distributed in the hope that it will be useful,
nkeynes@359
    15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
nkeynes@359
    16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
nkeynes@359
    17
 * GNU General Public License for more details.
nkeynes@359
    18
 */
nkeynes@359
    19
nkeynes@359
    20
#include <stdio.h>
nkeynes@359
    21
#include <stdlib.h>
nkeynes@359
    22
#include <string.h>
nkeynes@359
    23
#include <getopt.h>
nkeynes@359
    24
#include <errno.h>
nkeynes@359
    25
#include <ctype.h>
nkeynes@1296
    26
#include <glib.h>
nkeynes@359
    27
#include <assert.h>
nkeynes@359
    28
#include "tools/gendec.h"
nkeynes@359
    29
nkeynes@359
    30
#define DEFAULT_OUT_EXT ".c"
nkeynes@359
    31
nkeynes@359
    32
const char *ins_filename = NULL;
nkeynes@359
    33
const char *act_filename = NULL;
nkeynes@359
    34
const char *out_filename = NULL;
nkeynes@359
    35
nkeynes@359
    36
#define GEN_SOURCE 1
nkeynes@359
    37
#define GEN_TEMPLATE 2
nkeynes@359
    38
nkeynes@359
    39
FILE *ins_file, *act_file, *out_file;
nkeynes@359
    40
nkeynes@1084
    41
char *option_list = "tmho:w";
nkeynes@359
    42
int gen_mode = GEN_SOURCE;
nkeynes@1084
    43
int emit_warnings = 0;
nkeynes@1084
    44
nkeynes@1084
    45
struct option longopts[] = { 
nkeynes@1084
    46
    { "help", no_argument, NULL, 'h' },
nkeynes@1084
    47
    { "output", required_argument, NULL, 'o' },
nkeynes@1084
    48
    { "template", no_argument, NULL, 't' },
nkeynes@1084
    49
    { "warnings", no_argument, NULL, 'w' },
nkeynes@1084
    50
    { NULL, 0, 0, 0 } };
nkeynes@359
    51
nkeynes@969
    52
static void usage() {
nkeynes@1084
    53
    printf( "Usage: gendec [options] <instruction-file> <action-file> [ -o <output-file> ]\n" );
nkeynes@1084
    54
    printf( "Options:\n" );
nkeynes@1084
    55
    printf( "  -h, --help         Print this help message\n" );
nkeynes@1084
    56
    printf( "  -o, --output=FILE  Generate output to the given file\n" );
nkeynes@1084
    57
    printf( "  -t, --template     Generate a template skeleton instead of an instruction matcher\n" );
nkeynes@1084
    58
    printf( "  -w, --warnings     Emit warnings when unmatched instructions are found\n" );
nkeynes@1084
    59
}
nkeynes@1084
    60
nkeynes@1084
    61
/**
nkeynes@1084
    62
 * Check that rules are provided for all actions
nkeynes@1084
    63
 */
nkeynes@1084
    64
static void check_actions( struct ruleset *rules, const actiontoken_t token )
nkeynes@1084
    65
{
nkeynes@1084
    66
    int i;
nkeynes@1084
    67
    int warnings = 0;
nkeynes@1084
    68
    for( i=0; i<rules->rule_count; i++ ) {
nkeynes@1084
    69
        if( token->actions[i].text == NULL ) {
nkeynes@1084
    70
            if( warnings == 0 ) {
nkeynes@1084
    71
                fprintf( stderr, "In action block starting at line %d of file %s:\n",
nkeynes@1084
    72
                         token->lineno, token->filename );
nkeynes@1084
    73
            }
nkeynes@1084
    74
            fprintf( stderr, "Warning: No action matches rule %d %s\n", i, rules->rules[i]->format );
nkeynes@1084
    75
            warnings++;
nkeynes@1084
    76
        } else {
nkeynes@1084
    77
            const char *s = token->actions[i].text;
nkeynes@1084
    78
            while( *s ) {
nkeynes@1084
    79
                if( !isspace(*s) )
nkeynes@1084
    80
                    break;
nkeynes@1084
    81
                s++;
nkeynes@1084
    82
            }
nkeynes@1084
    83
            if( !*s ) {
nkeynes@1084
    84
                if( warnings == 0 ) {
nkeynes@1084
    85
                    fprintf( stderr, "In action block starting at line %d of file %s:\n",
nkeynes@1084
    86
                         token->lineno, token->filename );
nkeynes@1084
    87
                }
nkeynes@1084
    88
                fprintf( stderr, "Warning: Empty action for rule %d %s at line %d\n", i, rules->rules[i]->format,
nkeynes@1084
    89
                    token->actions[i].lineno );
nkeynes@1084
    90
                warnings++;
nkeynes@1084
    91
            }
nkeynes@1084
    92
        }
nkeynes@1084
    93
    }
nkeynes@359
    94
}
nkeynes@359
    95
nkeynes@359
    96
/**
nkeynes@359
    97
 * Find a mask that can be used to split up the given rules
nkeynes@359
    98
 */
nkeynes@969
    99
static uint32_t find_mask( struct ruleset *rules, int ruleidx[], int rule_count, 
nkeynes@736
   100
                    uint32_t input_mask )
nkeynes@359
   101
{
nkeynes@359
   102
    int i;
nkeynes@359
   103
    uint32_t mask = rules->rules[ruleidx[0]]->mask;
nkeynes@359
   104
nkeynes@359
   105
    for( i=1; i<rule_count; i++ ) {
nkeynes@736
   106
        mask = mask & rules->rules[ruleidx[i]]->mask;
nkeynes@359
   107
    }
nkeynes@359
   108
nkeynes@359
   109
    assert( (mask & input_mask) == input_mask ); /* input_mask should always be included in the mask */
nkeynes@359
   110
nkeynes@359
   111
    return mask & (~input_mask); /* but we don't want to see the input mask again */
nkeynes@359
   112
}
nkeynes@359
   113
nkeynes@969
   114
static int get_option_count_for_mask( uint32_t mask ) {
nkeynes@359
   115
    int count = 0;
nkeynes@359
   116
nkeynes@359
   117
    while( mask ) {
nkeynes@736
   118
        if( mask&1 ) 
nkeynes@736
   119
            count++;
nkeynes@736
   120
        mask >>= 1;
nkeynes@359
   121
    }
nkeynes@359
   122
    return 1<<count;
nkeynes@359
   123
}
nkeynes@359
   124
nkeynes@359
   125
int get_bitshift_for_mask( uint32_t mask ) {
nkeynes@359
   126
    int shift = 0;
nkeynes@359
   127
    while( mask && !(mask&1) ) {
nkeynes@736
   128
        shift++;
nkeynes@736
   129
        mask >>= 1;
nkeynes@359
   130
    }
nkeynes@359
   131
    return shift;
nkeynes@359
   132
}
nkeynes@359
   133
nkeynes@969
   134
static void get_option_values_for_mask( uint32_t *options, 
nkeynes@736
   135
                                 uint32_t mask ) 
nkeynes@359
   136
{
nkeynes@359
   137
    /* This could be a lot smarter. But it's not */
nkeynes@359
   138
    int i;
nkeynes@359
   139
    *options = 0;
nkeynes@359
   140
    for( i=1; i<=mask; i++ ) {
nkeynes@736
   141
        if( (i & mask) > *options ) {
nkeynes@736
   142
            options++;
nkeynes@736
   143
            *options = (i&mask);
nkeynes@736
   144
        }
nkeynes@359
   145
    }
nkeynes@359
   146
}
nkeynes@359
   147
nkeynes@979
   148
static void fprint_indent( const char *action, int depth, FILE *f )
nkeynes@359
   149
{
nkeynes@359
   150
    int spaces = 0, needed = depth*8, i;
nkeynes@979
   151
    const char *text = action;
nkeynes@359
   152
nkeynes@359
   153
    /* Determine number of spaces in first line of input */
nkeynes@359
   154
    for( i=0; isspace(action[i]); i++ ) {
nkeynes@736
   155
        if( action[i] == '\n' ) {
nkeynes@736
   156
            spaces = 0;
nkeynes@736
   157
            text = &action[i+1];
nkeynes@736
   158
        } else {
nkeynes@736
   159
            spaces++;
nkeynes@736
   160
        }
nkeynes@359
   161
    }
nkeynes@359
   162
nkeynes@359
   163
    needed -= spaces;
nkeynes@359
   164
    fprintf( f, "%*c", needed, ' ' );
nkeynes@359
   165
    for( i=0; text[i] != '\0'; i++ ) {
nkeynes@736
   166
        fputc( text[i], f );
nkeynes@736
   167
        if( text[i] == '\n' && text[i+1] != '\0' ) {
nkeynes@736
   168
            fprintf( f, "%*c", needed, ' ' );
nkeynes@736
   169
        }
nkeynes@359
   170
    }
nkeynes@359
   171
    if( text[i-1] != '\n' ) {
nkeynes@736
   172
        fprintf( f, "\n" );
nkeynes@359
   173
    }
nkeynes@359
   174
}
nkeynes@359
   175
nkeynes@979
   176
static void fprint_action( struct rule *rule, const struct action *action, int depth, FILE *f ) 
nkeynes@359
   177
{
nkeynes@359
   178
    int i;
nkeynes@1298
   179
    if( action == NULL || action->text == NULL ) {
nkeynes@736
   180
        fprintf( f, "%*cUNIMP(ir); /* %s */\n", depth*8, ' ', rule->format );
nkeynes@359
   181
    } else {
nkeynes@736
   182
        fprintf( f, "%*c{ /* %s */", depth*8, ' ', rule->format );
nkeynes@736
   183
        if( rule->operand_count != 0 ) {
nkeynes@736
   184
            fprintf( f, "\n%*c", depth*8, ' ' );
nkeynes@736
   185
            for( i=0; i<rule->operand_count; i++ ) {
nkeynes@736
   186
                if( rule->operands[i].is_signed ) {
nkeynes@736
   187
                    fprintf( f, "int32_t %s = SIGNEXT%d", rule->operands[i].name, rule->operands[i].bit_count );
nkeynes@736
   188
                } else {
nkeynes@736
   189
                    fprintf( f, "uint32_t %s = ", rule->operands[i].name );
nkeynes@736
   190
                }
nkeynes@736
   191
                if( rule->operands[i].bit_shift == 0 ) {
nkeynes@736
   192
                    fprintf( f, "(ir&0x%X)", (1<<(rule->operands[i].bit_count))-1 );
nkeynes@736
   193
                } else {
nkeynes@736
   194
                    fprintf( f, "((ir>>%d)&0x%X)", rule->operands[i].bit_shift,
nkeynes@736
   195
                            (1<<(rule->operands[i].bit_count))-1 );
nkeynes@736
   196
                }
nkeynes@736
   197
                if( rule->operands[i].left_shift != 0 ) {
nkeynes@736
   198
                    fprintf( f, "<<%d", rule->operands[i].left_shift );
nkeynes@736
   199
                }
nkeynes@736
   200
                fprintf( f, "; " );
nkeynes@736
   201
            }
nkeynes@736
   202
        }
nkeynes@736
   203
        fputs( "\n", f );
nkeynes@979
   204
        if( action->text && action->text[0] != '\0' ) {
nkeynes@979
   205
            fprintf( f, "#line %d \"%s\"\n", action->lineno, action->filename );
nkeynes@979
   206
            fprint_indent( action->text, depth, f );
nkeynes@736
   207
        }
nkeynes@736
   208
        fprintf( f, "%*c}\n", depth*8, ' ' );
nkeynes@359
   209
    }
nkeynes@359
   210
}
nkeynes@359
   211
nkeynes@979
   212
static void split_and_generate( struct ruleset *rules, const struct action *actions, 
nkeynes@736
   213
                         int ruleidx[], int rule_count, int input_mask, 
nkeynes@736
   214
                         int depth, FILE *f ) {
nkeynes@359
   215
    uint32_t mask;
nkeynes@359
   216
    int i,j;
nkeynes@359
   217
nkeynes@359
   218
    if( rule_count == 0 ) {
nkeynes@736
   219
        fprintf( f, "%*cUNDEF(ir);\n", depth*8, ' ' );
nkeynes@359
   220
    } else if( rule_count == 1 ) {
nkeynes@979
   221
        fprint_action( rules->rules[ruleidx[0]], &actions[ruleidx[0]], depth, f );
nkeynes@359
   222
    } else {
nkeynes@359
   223
nkeynes@736
   224
        mask = find_mask(rules, ruleidx, rule_count, input_mask);
nkeynes@736
   225
        if( mask == 0 ) { /* No matching mask? */
nkeynes@736
   226
            fprintf( stderr, "Error: unable to find a valid bitmask (%d rules, %08X input mask)\n", rule_count, input_mask );
nkeynes@736
   227
            dump_rulesubset( rules, ruleidx, rule_count, stderr );
nkeynes@736
   228
            return;
nkeynes@736
   229
        }
nkeynes@736
   230
nkeynes@736
   231
        /* break up the rules into sub-sets, and process each sub-set.
nkeynes@736
   232
         * NB: We could do this in one pass at the cost of more complex
nkeynes@736
   233
         * data structures. For now though, this keeps it simple
nkeynes@736
   234
         */
nkeynes@736
   235
        int option_count = get_option_count_for_mask( mask );
nkeynes@736
   236
        uint32_t options[option_count];
nkeynes@736
   237
        int subruleidx[rule_count];
nkeynes@736
   238
        int subrule_count;
nkeynes@736
   239
        int mask_shift = get_bitshift_for_mask( mask );
nkeynes@736
   240
        int has_empty_options = 0;
nkeynes@736
   241
        get_option_values_for_mask( options, mask );
nkeynes@736
   242
nkeynes@736
   243
        if( mask_shift == 0 ) {
nkeynes@736
   244
            fprintf( f, "%*cswitch( ir&0x%X ) {\n", depth*8, ' ', mask );
nkeynes@736
   245
        } else {
nkeynes@736
   246
            fprintf( f, "%*cswitch( (ir&0x%X) >> %d ) {\n", depth*8, ' ',
nkeynes@736
   247
                    mask, mask_shift);
nkeynes@736
   248
        }
nkeynes@736
   249
        for( i=0; i<option_count; i++ ) {
nkeynes@736
   250
            subrule_count = 0;
nkeynes@736
   251
            for( j=0; j<rule_count; j++ ) {
nkeynes@736
   252
                int match = rules->rules[ruleidx[j]]->bits & mask;
nkeynes@736
   253
                if( match == options[i] ) {
nkeynes@736
   254
                    subruleidx[subrule_count++] = ruleidx[j];
nkeynes@736
   255
                }
nkeynes@736
   256
            }
nkeynes@736
   257
            if( subrule_count == 0 ) {
nkeynes@736
   258
                has_empty_options = 1;
nkeynes@736
   259
            } else {
nkeynes@736
   260
                fprintf( f, "%*ccase 0x%X:\n", depth*8+4, ' ', options[i]>>mask_shift );
nkeynes@736
   261
                split_and_generate( rules, actions, subruleidx, subrule_count,
nkeynes@736
   262
                                    mask|input_mask, depth+1, f );
nkeynes@736
   263
                fprintf( f, "%*cbreak;\n", depth*8+8, ' ' );
nkeynes@736
   264
            }
nkeynes@736
   265
        }
nkeynes@736
   266
        if( has_empty_options ) {
nkeynes@824
   267
            fprintf( f, "%*cdefault:\n%*cUNDEF(ir);\n%*cbreak;\n",
nkeynes@736
   268
                    depth*8+4, ' ', depth*8+8, ' ', depth*8 + 8, ' ' );
nkeynes@736
   269
        }
nkeynes@736
   270
        fprintf( f, "%*c}\n", depth*8, ' ' );
nkeynes@359
   271
    }
nkeynes@359
   272
}
nkeynes@359
   273
nkeynes@969
   274
static int generate_decoder( struct ruleset *rules, actionfile_t af, FILE *out )
nkeynes@359
   275
{
nkeynes@359
   276
    int ruleidx[rules->rule_count];
nkeynes@359
   277
    int i;
nkeynes@359
   278
nkeynes@359
   279
    for( i=0; i<rules->rule_count; i++ ) {
nkeynes@736
   280
        ruleidx[i] = i;
nkeynes@359
   281
    }
nkeynes@359
   282
nkeynes@948
   283
    actiontoken_t token = action_file_next(af);
nkeynes@948
   284
    while( token->symbol != END ) {
nkeynes@948
   285
        if( token->symbol == TEXT ) {
nkeynes@979
   286
            fprintf( out, "#line %d \"%s\"\n", token->lineno, token->filename );
nkeynes@948
   287
            fputs( token->text, out );
nkeynes@948
   288
        } else if( token->symbol == ERROR ) {
nkeynes@948
   289
            fprintf( stderr, "Error parsing action file" );
nkeynes@948
   290
            return -1;
nkeynes@948
   291
        } else {
nkeynes@1084
   292
            if( emit_warnings ) {
nkeynes@1084
   293
                check_actions( rules, token );
nkeynes@1084
   294
            }
nkeynes@1298
   295
            fprintf( out, "#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-variable\"\n" );
nkeynes@948
   296
            split_and_generate( rules, token->actions, ruleidx, rules->rule_count, 0, 1, out );
nkeynes@1298
   297
            fprintf( out, "#pragma clang diagnostic pop\n" );
nkeynes@948
   298
        }
nkeynes@948
   299
        token = action_file_next(af);
nkeynes@948
   300
    }
nkeynes@359
   301
    return 0;
nkeynes@359
   302
}
nkeynes@359
   303
nkeynes@969
   304
static int generate_template( struct ruleset *rules, actionfile_t af, FILE *out )
nkeynes@359
   305
{
nkeynes@359
   306
    int i;
nkeynes@948
   307
    
nkeynes@948
   308
    actiontoken_t token = action_file_next(af);
nkeynes@948
   309
    while( token->symbol != END ) {
nkeynes@948
   310
        if( token->symbol == TEXT ) {
nkeynes@948
   311
            fputs( token->text, out );
nkeynes@948
   312
        } else if( token->symbol == ERROR ) {
nkeynes@948
   313
            fprintf( stderr, "Error parsing action file" );
nkeynes@948
   314
            return -1;
nkeynes@948
   315
        } else {
nkeynes@948
   316
            fputs( "%%\n", out );
nkeynes@948
   317
            for( i=0; i<rules->rule_count; i++ ) {
nkeynes@948
   318
                fprintf( out, "%s {: %s :}\n", rules->rules[i]->format,
nkeynes@979
   319
                        token->actions[i].text == NULL ? "" : token->actions[i].text );
nkeynes@948
   320
            }
nkeynes@948
   321
            fputs( "%%\n", out );
nkeynes@948
   322
        }
nkeynes@948
   323
        token = action_file_next(af);
nkeynes@359
   324
    }
nkeynes@736
   325
nkeynes@359
   326
    return 0;
nkeynes@359
   327
}
nkeynes@420
   328
nkeynes@420
   329
nkeynes@420
   330
int main( int argc, char *argv[] )
nkeynes@420
   331
{
nkeynes@420
   332
    int opt;
nkeynes@420
   333
nkeynes@420
   334
    /* Parse the command line */
nkeynes@420
   335
    while( (opt = getopt_long( argc, argv, option_list, longopts, NULL )) != -1 ) {
nkeynes@420
   336
        switch( opt ) {
nkeynes@736
   337
        case 't':
nkeynes@736
   338
            gen_mode = GEN_TEMPLATE;
nkeynes@736
   339
            break;
nkeynes@736
   340
        case 'o':
nkeynes@736
   341
            out_filename = optarg;
nkeynes@736
   342
            break;
nkeynes@1084
   343
        case 'w':
nkeynes@1084
   344
            emit_warnings = 1;
nkeynes@1084
   345
            break;
nkeynes@736
   346
        case 'h':
nkeynes@736
   347
            usage();
nkeynes@736
   348
            exit(0);
nkeynes@736
   349
        }
nkeynes@420
   350
    }
nkeynes@420
   351
    if( optind < argc ) {
nkeynes@736
   352
        ins_filename = argv[optind++];
nkeynes@420
   353
    }
nkeynes@420
   354
    if( optind < argc ) {
nkeynes@736
   355
        act_filename = argv[optind++];
nkeynes@420
   356
    }
nkeynes@420
   357
nkeynes@420
   358
    if( optind < argc || ins_filename == NULL || act_filename == NULL ) {
nkeynes@736
   359
        usage();
nkeynes@736
   360
        exit(1);
nkeynes@420
   361
    }
nkeynes@736
   362
nkeynes@420
   363
    if( out_filename == NULL ) {
nkeynes@736
   364
        if( gen_mode == GEN_TEMPLATE ) {
nkeynes@736
   365
            out_filename = act_filename;
nkeynes@736
   366
        } else {
nkeynes@736
   367
            char tmp[strlen(act_filename)+1];
nkeynes@736
   368
            strcpy( tmp, act_filename);
nkeynes@736
   369
            char *c = strrchr( tmp, '.' );
nkeynes@736
   370
            if( c != NULL ) {
nkeynes@736
   371
                *c = '\0';
nkeynes@736
   372
            }
nkeynes@736
   373
            out_filename = g_strconcat( tmp, DEFAULT_OUT_EXT, NULL );
nkeynes@736
   374
        }
nkeynes@420
   375
    }
nkeynes@420
   376
nkeynes@420
   377
    /* Open the files */
nkeynes@420
   378
    ins_file = fopen( ins_filename, "ro" );
nkeynes@420
   379
    if( ins_file == NULL ) {
nkeynes@420
   380
        fprintf( stderr, "Unable to open '%s' for reading (%s)\n", ins_filename, strerror(errno) );
nkeynes@736
   381
        exit(2);
nkeynes@420
   382
    }
nkeynes@420
   383
nkeynes@420
   384
    /* Parse the input */
nkeynes@420
   385
    struct ruleset *rules = parse_ruleset_file( ins_file );
nkeynes@420
   386
    fclose( ins_file );
nkeynes@420
   387
    if( rules == NULL ) {
nkeynes@736
   388
        exit(5);
nkeynes@420
   389
    }
nkeynes@948
   390
    
nkeynes@948
   391
    actionfile_t af = action_file_open( act_filename, rules );
nkeynes@948
   392
    if( af == NULL ) {
nkeynes@948
   393
        fprintf( stderr, "Unable to open '%s' for reading (%s)\n", act_filename, strerror(errno) );
nkeynes@948
   394
        exit(3);
nkeynes@420
   395
    }
nkeynes@420
   396
nkeynes@948
   397
nkeynes@948
   398
    /* Open the output file */
nkeynes@420
   399
    out_file = fopen( out_filename, "wo" );
nkeynes@420
   400
    if( out_file == NULL ) {
nkeynes@420
   401
        fprintf( stderr, "Unable to open '%s' for writing (%s)\n", out_filename, strerror(errno) );
nkeynes@736
   402
        exit(4);
nkeynes@420
   403
    }
nkeynes@736
   404
nkeynes@420
   405
    switch( gen_mode ) {
nkeynes@420
   406
    case GEN_SOURCE:
nkeynes@948
   407
        if( generate_decoder( rules, af, out_file ) != 0 ) {
nkeynes@736
   408
            exit(7);
nkeynes@736
   409
        }
nkeynes@736
   410
        break;
nkeynes@420
   411
    case GEN_TEMPLATE:
nkeynes@948
   412
        if( generate_template( rules, af, out_file ) != 0 ) {
nkeynes@736
   413
            exit(7);
nkeynes@736
   414
        }
nkeynes@736
   415
        break;
nkeynes@420
   416
    }
nkeynes@948
   417
    
nkeynes@948
   418
    action_file_close(af);
nkeynes@420
   419
    fclose( out_file );
nkeynes@420
   420
    return 0;
nkeynes@420
   421
}
.