The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* Code provided by S. Cotton, U.Penn
 *
 *    N O T E !!!!  (Rada 06/11/01)
 * For this code to work, it is VERY important that all input files
 * (i.e. key, answer and sensemap) are SORTED 
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#define TRUE 1
#define FALSE 0
#define NOT_FOUND -1

#define COMMENT_DELIMITER "!!"
#define WEIGHT_DELIMITER '/'

#define MINIMAL_SCORING_FLAG "-m"
#define VERBOSITY_FLAG "-v"
#define GRANULARITY_FLAG "-g"

#define COARSE_GRAIN_FLAG "coarse"
#define MIXED_GRAIN_FLAG "mixed"

#define UNASSIGNABLE_SENSE_TAG "U"
#define TYPO_SENSE_TAG "TYPO"
#define PROPER_SENSE_TAG "P"

typedef char boolean;

typedef struct
{FILE * pointer;
 char * name;
 char * line;
 long int length_of_line;} 
i_o_block;

typedef struct
{char ** index;
 char * buffer;
 long int last_entry;
 long int initial_bound [('z' - 'a') + 1] [('z' - 'a') + 1] [2];}
string_table;

void newline (void)

{printf ("\n");
 return;}

void fnewline (FILE * out)

{if (out != NULL) fprintf (out, "\n");
 return;}

boolean strequal (char * first_string, char * second_string)

{int i;
 
 if (first_string == NULL || second_string == NULL) return FALSE;

 for (i = 0; first_string [i] != '\0' && second_string [i] != '\0'; i++)
   if (first_string [i] != second_string [i]) break;

 return (first_string [i] == second_string [i]);}

char * make_buffer_of_size (long int length)

{if (length <= 0) length = 1;
 return (char *) calloc (length, sizeof (char));}

void reclaim (void * space_to_reclaim)

{if (space_to_reclaim != NULL) free (space_to_reclaim);

 return;}

void copystr (char ** target_string, char * source_string)

{int i;

 if (target_string == NULL || source_string == NULL) return;

 reclaim (*target_string);

 i = 0;
 while (source_string [i] != '\0') i++;

 *target_string = (char *) calloc (i + 1, sizeof (char));

 while (i >= 0)
  {*(*target_string + i) = source_string [i];
   i--;}

 return;}

void addstr (char ** target_string, char * source_string)

{char * new_string;
 int i;
 int j;

 if (target_string == NULL || source_string == NULL) return;

 i = 0;
 if (*target_string != NULL)
   while (*(*target_string + i) != '\0') 
     i++;
 
 j = 0;
 while (source_string [j] != '\0') j++;

 new_string = (char *) calloc (i + j + 1, sizeof (char));

 i = 0;

 if (*target_string != NULL)
   while (*(*target_string + i) != '\0')
    {new_string [i] = *(*target_string + i);
     i++;}

 for (j = 0; source_string [j] != '\0'; j++)
  {new_string [i] = source_string [j];
   i++;}

 new_string [i] = '\0';

 reclaim (*target_string);
 *target_string = new_string;

 return;}

boolean has_prefix (char * line, char * prefix)

{int i;

 if (line == NULL || prefix == NULL) return FALSE;

 for (i = 0; prefix [i] != '\0'; i++)
   if (line [i] != prefix [i]) break;

 return (prefix [i] == '\0');}

void strip_carriage_return (char * string_to_be_stripped)

{int length = 0;

 if (string_to_be_stripped == NULL) return;

 while (string_to_be_stripped [length] != '\0') length++;
 length--;
 if (string_to_be_stripped [length] == '\n') string_to_be_stripped [length] = '\0';

 return;}

boolean unreadable_file (char * file_name)

{FILE * file_to_be_tested;

 if (file_name == NULL) return TRUE;

 if ((file_to_be_tested = fopen (file_name, "r")) != NULL)
  {fclose (file_to_be_tested);
   return FALSE;}
 else
  {fclose (file_to_be_tested);
   return TRUE;}}

long int length_of_file (char * file_name)

{FILE * file_to_be_measured;
 long int counter = 0;

 if (file_name != NULL)

  {if ((file_to_be_measured = fopen (file_name, "r")) != NULL)
    {while (fgetc (file_to_be_measured) != EOF) counter++;
     counter++;}   

   fclose (file_to_be_measured);}

 return counter + 1;}

long int length_of_longest_line_in (char * file_name)

{FILE * file_to_be_measured;
 char file_character;
 long int max = 0;
 long int i;

 if (file_name != NULL)

  {if ((file_to_be_measured = fopen (file_name, "r")) != NULL)
    {i = 1;
     while ((file_character = fgetc (file_to_be_measured)) != EOF)
      {if (file_character == '\n')
        {if (i > max) max = i;
         i = 1;}
       else
         i++;}
     if (i > max) max = i;}

   fclose (file_to_be_measured);}

 return max + 1;}

boolean open_file (i_o_block * file_to_open, char * mode)

{if (file_to_open != NULL) 
  {file_to_open -> length_of_line = length_of_longest_line_in (file_to_open -> name);
   file_to_open -> line = make_buffer_of_size (file_to_open -> length_of_line);
   if (mode != NULL)
     file_to_open -> pointer = fopen (file_to_open -> name, mode);
   else
     file_to_open -> pointer = NULL;
   return (file_to_open -> pointer != NULL);}

 return FALSE;}
 
void close_file (i_o_block * file_to_close)
 
{if (file_to_close != NULL)
  {fclose (file_to_close -> pointer);
   file_to_close -> pointer = NULL;
   file_to_close -> length_of_line = 0;
   reclaim (file_to_close -> line);
   file_to_close -> line = NULL;
   reclaim (file_to_close -> name);
   file_to_close -> name = NULL;}
 
 return;} 

boolean read_line (i_o_block * file_to_read)

{if (file_to_read == NULL || file_to_read -> line == NULL || file_to_read -> pointer == NULL ||
     fgets (file_to_read -> line, file_to_read -> length_of_line, file_to_read -> pointer) == NULL)
   return FALSE;

 strip_carriage_return (file_to_read -> line);
 return TRUE;}

string_table * make_string_table (void)

{string_table * new;
 int i;
 int j;

 new = (string_table *) malloc (sizeof (string_table));

 new -> index = NULL;
 new -> buffer = NULL;
 new -> last_entry = 0;

 for (i = 0; i < ('z' - 'a') + 1; i++)
   for (j = 0; j < ('z' - 'a') + 1; j++)
    {new -> initial_bound [i] [j] [0] = 0;
     new -> initial_bound [i] [j] [1] = 0;}

 return new;}
 
void reclaim_string_table (string_table * table_to_reclaim)

{if (table_to_reclaim != NULL)

  {reclaim (table_to_reclaim -> index);
   reclaim (table_to_reclaim -> buffer);
   reclaim (table_to_reclaim);}

 return;}

void build_initial_bounds_of_table (string_table * table_to_build)

{char * table_entry;
 int current_first_letter = NOT_FOUND;
 int current_second_letter = NOT_FOUND;
 int i;

 if (table_to_build != NULL)
  {for (i = 0; i < table_to_build -> last_entry; i++)
    {table_entry = table_to_build -> index [i];
     if (table_entry [0] < 'a' || table_entry [0] > 'z' ||
         table_entry [1] < 'a' || table_entry [1] > 'z')
      {if (current_first_letter != NOT_FOUND)
        {table_to_build -> initial_bound [current_first_letter] [current_second_letter] [1] = i;
         current_first_letter = NOT_FOUND;}}
     else
      {if (table_entry [0] - 'a' != current_first_letter ||
           table_entry [1] - 'a' != current_second_letter)
        {if (current_first_letter != NOT_FOUND)
           table_to_build -> initial_bound [current_first_letter] [current_second_letter] [1] = i;
         current_first_letter = table_entry [0] - 'a';
         current_second_letter = table_entry [1] - 'a';
         table_to_build -> initial_bound [current_first_letter] [current_second_letter] [0] = i;}}}
        
   if (current_first_letter != NOT_FOUND)
     table_to_build -> initial_bound [current_first_letter] [current_second_letter] [1] = i;}

 return;}

long int search_string_table (char * word, string_table * table_to_search)

{long int high_bound;
 long int low_bound;
 long int new_middle;
 long int i;
 int j;

if (word == NULL || table_to_search == NULL || table_to_search -> index == NULL)
   return NOT_FOUND;

 if (word [0] >= 'a' && word [0] <= 'z' &&
     word [1] >= 'a' && word [1] <= 'z')
  {high_bound = table_to_search -> initial_bound [word [0] - 'a'] [word [1] - 'a'] [1];
   if (high_bound == 0) return NOT_FOUND;
   low_bound = table_to_search -> initial_bound [word [0] - 'a'] [word [1] - 'a'] [0];}
 else
  {high_bound = table_to_search -> last_entry;
   low_bound = 0;}

 new_middle = (high_bound + low_bound) / 2;
  
 do
  {i = new_middle;
  
  for (j = 0; word [j] != '\0'; j++)
     {
       if (word [j] < table_to_search -> index [i] [j])
      {high_bound = i;
       break;}
     if (word [j] > table_to_search -> index [i] [j])
      {low_bound = i;
       break;}}
   if (word [j] == '\0')
    {if (table_to_search -> index [i] [j] == '\0' ||
         table_to_search -> index [i] [j] == ' ')
       return i;
     else
       high_bound = i;}
   new_middle = (high_bound + low_bound) / 2;}
 while (i != new_middle);

 return NOT_FOUND;}


string_table * unsorted_string_table_for (char * file_name)

{string_table * new;
 FILE * words;
 long int beginning_of_word;
 long int characters_seen;
 char file_character;

 new = make_string_table ();

 characters_seen = 0;

 if (file_name != NULL)
  {if ((words = fopen (file_name, "r")) != NULL)
     while ((file_character = fgetc (words)) != EOF)
      {if (file_character == '\n') 
        (new -> last_entry)++;
       characters_seen++;}
   fclose (words);}

 new -> buffer = make_buffer_of_size (characters_seen + 1);
 new -> buffer [0] = '\0';

 new -> index = (char **) calloc ((new -> last_entry) + 1, sizeof (char *));
 new -> index [0] = new -> buffer;

 new -> last_entry = 0;
 characters_seen = 0;
 beginning_of_word = 0;

 if (file_name != NULL)
  {if ((words = fopen (file_name, "r")) != NULL)
    while ((new -> buffer [characters_seen] = fgetc (words)) != EOF)
      {if (new -> buffer [characters_seen] == '\n')
        {new -> buffer [characters_seen] = '\0';
         new -> index [new -> last_entry] = &(new -> buffer [beginning_of_word]);
         beginning_of_word = characters_seen + 1;
        (new -> last_entry)++;}
       characters_seen++;}
   fclose (words);}

 return new;}

string_table * string_table_for (char * file_name)

{string_table * new;

 new = unsorted_string_table_for (file_name);

 build_initial_bounds_of_table (new);

 return new;}

char ** tokenize (char * line)

{char ** tokens;
 int last_token = 0;
 int i;

 if (line != NULL) 
   tokens = (char **) calloc (strlen (line) + 1, sizeof (char *));
 else
   tokens = (char **) calloc (1, sizeof (char *));

 tokens [last_token] = NULL;

 if (line != NULL)
   for (i = 0; line [i] != '\0'; i++)
    {if (line [i] == ' ' || line [i] == '\n' || line [i] == '\t')
      {line [i] = '\0';    
       if (tokens [last_token] != NULL) 
        {last_token++;
         tokens [last_token] = NULL;}}

     else
     if (tokens [last_token] == NULL)
       tokens [last_token] = &(line [i]);}

 if (tokens [last_token] != NULL) 
  {last_token++;
   tokens [last_token] = NULL;}

 return tokens;}

char * coarse_sense_tag_for (char * tag, string_table * sense_table)

{char * coarse_tag;
 long int sense_index;
 int i;
 
 coarse_tag = tag;

 sense_index = search_string_table (tag, sense_table);
 if (sense_index != NOT_FOUND)
  {coarse_tag = sense_table -> index [sense_index];
   for (i = 0; sense_table -> index [sense_index] [i] != '\0'; i++)
     if (sense_table -> index [sense_index] [i] == ' ')
       coarse_tag = &(sense_table -> index [sense_index] [i + 1]);}

 return coarse_tag;}

float subsumption_probability_of_tags (char * subsumer, char * tag, string_table * sense_table)

{float probability;
 long int sense_index;
 int i;
 int j;
 
 sense_index = search_string_table (tag, sense_table);

 if (sense_index != NOT_FOUND && subsumer != NULL)
  {probability = 1.0;
   i = 0;
   for (j = 0; sense_table -> index [sense_index] [j] != '\0'; j++)
     if (j == 0 || sense_table -> index [sense_index] [j - 1] == ' ')
      {i++;
       if (i % 2 == 0)
         probability *= atof (&(sense_table -> index [sense_index] [j]));
       else
       if (has_prefix (&(sense_table -> index [sense_index] [j]), subsumer) &&
           (sense_table -> index [sense_index] [j + strlen (subsumer)] == ' ' ||
            sense_table -> index [sense_index] [j + strlen (subsumer)] == '\0'))
         return 1.0 / probability;}}

 return 0.0;}

int main (int argc, char * argv [])

{

  boolean invalid_argument_seen = FALSE;
  string_table * answer;
  string_table * key;
  string_table * sense_table;
  int sense_table_arg = 0;
  char *** key_tag;
  char ** answer_tag;
  float * answer_weight;
  long int answer_index;
  long int key_index;
  long int sense_index;
  char * sense_granularity = NULL;
  char * coarse_sense_tag;
  boolean minimal_scoring_desired = FALSE;
  boolean line_by_line_trace_desired = FALSE;
  float total_weight;
  float instances_attempted;
  float instances;
  float score;
  float subscore;
  float global_score;
  int i;
  int j;
  int k;

 if (argc < 3)
  {fnewline (stderr);   
   fprintf (stderr, "This scorer requires two command-line arguments in the");
   fnewline (stderr);   
   fprintf (stderr, "following order:");
   fnewline (stderr);   
   fnewline (stderr);
   fprintf (stderr, "   ANSWER FILE NAME (name of a file containing formatted answers)");
   fnewline (stderr);
   fnewline (stderr);   
   fprintf (stderr, "   KEY FILE NAME (name of an answer-key file)");
   fnewline (stderr);
   fnewline (stderr);   
   fprintf (stderr, "Optionally, the following may be appended:");
   fnewline (stderr);
   fnewline (stderr);
   fprintf (stderr, "   SENSE FILE NAME (name of a file containing sense-map information)");
   fnewline (stderr);
   fprintf (stderr, "     - without this file, only fine-grained scoring is available,\n");
   fprintf (stderr, "       and illformed sense tags will lower precision (rather than recall)");
   fnewline (stderr);   
   fnewline (stderr);
   fprintf (stderr, "   %s", GRANULARITY_FLAG);
   fprintf (stderr, " (specifies granularity: ");
   fprintf (stderr, "\"%s\" or \"%s\"; \"fine\" is default)", COARSE_GRAIN_FLAG, MIXED_GRAIN_FLAG);
   fnewline (stderr);   
   fnewline (stderr);
   fprintf (stderr, "   %s", MINIMAL_SCORING_FLAG);
   fprintf (stderr, " (causes exclusion of instances tagged with multiple tags in key)");
   fnewline (stderr);   
   fnewline (stderr);
   fprintf (stderr, "   %s", VERBOSITY_FLAG);
   fprintf (stderr, " (causes line-by-line scoring calculations to be printed)");
   fnewline (stderr);   
   fnewline (stderr);
   invalid_argument_seen = TRUE;}

 for (i = 1; i <= 2 && i < argc; i++)
   if (unreadable_file (argv [i])) {
     fnewline (stderr);
     fprintf (stderr, "Unable to read \"%s\".", argv [i]);
     fnewline (stderr);
     fnewline (stderr);
     invalid_argument_seen = TRUE;
   }

 for (i = 3; i < argc; i++) {
   if (argv[i][0] != '-') {
     if (unreadable_file (argv [i])) {
       fnewline (stderr);
       fprintf (stderr, "Unable to read \"%s\".", argv [i]);
       fnewline (stderr);
       fnewline (stderr);
       invalid_argument_seen = TRUE;
     } else {
       sense_table_arg = i;
     }
   }
   if (strequal (argv [i], MINIMAL_SCORING_FLAG)) minimal_scoring_desired = TRUE;
   if (strequal (argv [i], VERBOSITY_FLAG)) line_by_line_trace_desired = TRUE;
   if (strequal (argv [i], GRANULARITY_FLAG) && i + 1 < argc &&
       (strequal (argv [i + 1], COARSE_GRAIN_FLAG) || strequal (argv [i + 1], MIXED_GRAIN_FLAG)))
     sense_granularity = argv [++i];
 }

 if (sense_table_arg == 0) {
   /* force fine-grained scoring */
   if (sense_granularity) {
     fprintf(stderr,"Fine-grained scoring will be used since no sense map was given\n");
     sense_granularity = NULL;
   }
 }

 if (!(invalid_argument_seen)) {

   global_score = 0.0;
   instances = 0.0;
   instances_attempted = 0.0;
   
   if (sense_table_arg > 0) {
     sense_table = string_table_for (argv [sense_table_arg]);
   } else {
     sense_table = NULL;
   }

   key = string_table_for (argv [2]);
   key_tag = (char ***) calloc (key -> last_entry + 1, sizeof (char ***));

   for (key_index = 0; key_index < key -> last_entry; key_index++) {

     /* Re-format the key's first two fields, which specify the test instance to be answered */
     
     for (i = 0; key -> index [key_index] [i] != '\0'; i++)
       if (key -> index [key_index] [i] == ' ') {
	 key -> index [key_index] [i] = '_';
         while (key -> index [key_index] [i] != '\0' &&
		key -> index [key_index] [i] != ' ')
	   {i++;}
	 break;
       }
	 
     if (key -> index [key_index] [i] == ' ') {

       key_tag [key_index] = tokenize (&(key -> index [key_index] [i]));

       /* Convert correct answers to appropriate granularity, collapsing duplicates if they arise */

       if (strequal (sense_granularity, COARSE_GRAIN_FLAG))
	 for (i = 0; key_tag [key_index] [i] != NULL; i++) {
	   coarse_sense_tag = coarse_sense_tag_for (key_tag [key_index] [i], sense_table);
	   for (j = 0; j < i; j++)
	     if (strequal (coarse_sense_tag, key_tag [key_index] [j])) break;
	   if (j < i) {
	     for (j = i; key_tag [key_index] [j] != NULL; j++)
	       key_tag [key_index] [j] = key_tag [key_index] [j + 1];
	     i--;
	   } 
	   else
             if (!(strequal (coarse_sense_tag, key_tag [key_index] [i])))
               key_tag [key_index] [i] = coarse_sense_tag;
	 }
       
       /* If filter criteria for this instance aren't matched, mark the key to exclude it from scoring */
       
       if (minimal_scoring_desired && key_tag [key_index] [0] != NULL && key_tag [key_index] [1] != NULL)
	 key_tag [key_index] [0] = NULL;
       
       if (key_tag [key_index] [0] != NULL) instances += 1.0;

       /*break;*/
     }
   }

   answer = string_table_for (argv [1]);

   for (answer_index = 0; answer_index < answer -> last_entry; answer_index++)

     /* Strip off any comments in answers */

    {for (i = 0; answer -> index [answer_index] [i] != '\0'; i++)
       if (has_prefix (&(answer -> index [answer_index] [i]), COMMENT_DELIMITER))
        {answer -> index [answer_index] [i] = '\0';
         break;}
     
     /* Re-format the answer's first two fields, which specify the test instance which is being answered */

     for (i = 0; answer -> index [answer_index] [i] != '\0'; i++)
       if (answer -> index [answer_index] [i] == ' ')
        {answer -> index [answer_index] [i] = '_';
         while (answer -> index [answer_index] [i] != '\0')
          {if (answer -> index [answer_index] [i] == ' ') 
            {answer -> index [answer_index] [i] = '\0';
             i++;
             break;}
           i++;}
         break;}

     /* Parse the raw answer, separating out sense tags and their associated weights, if any */

     answer_tag = tokenize (&(answer -> index [answer_index] [i]));

     i = 0; 
     while (answer_tag [i] != NULL) i++;
     answer_weight = (float *) calloc (i + 1, sizeof (float));

     total_weight = 0.0;
     invalid_argument_seen = FALSE;
     for (i = 0; answer_tag [i] != NULL; i++)

      {for (j = 0; answer_tag [i] [j] != '\0'; j++)
         if (answer_tag [i] [j] == WEIGHT_DELIMITER) break;

       if (answer_tag [i] [j] != WEIGHT_DELIMITER)
         invalid_argument_seen = TRUE;
       else
        {answer_tag [i] [j] = '\0';
         answer_weight [i] = atof (&(answer_tag [i] [j + 1]));
         total_weight += answer_weight [i];}

       /* Discard any answer which contains an unknown sense tag, or a sense tag with a suffix */
       /* But don't check if we don't have a sense table */

       /*if (sense_table) {
	 sense_index = search_string_table (answer_tag [i], sense_table);
	 if (sense_index == NOT_FOUND &&
	     !(strequal (answer_tag [i], UNASSIGNABLE_SENSE_TAG)) &&
	     !(strequal (answer_tag [i], TYPO_SENSE_TAG)) &&
	     !(strequal (answer_tag [i], PROPER_SENSE_TAG))) {
	   answer_tag [0] = NULL;
	   break;
	 }} */
      }

     /* If not all sense tags have weights, set the weights for all of them to be equal */

     if (invalid_argument_seen)
      {for (i = 0; answer_tag [i] != NULL; i++) answer_weight [i] = 1.0;
       total_weight = 1.0 * i;}

     /* Convert answer tags to appropriate granularity, collapsing duplicates and merging their weights */

     if (strequal (sense_granularity, COARSE_GRAIN_FLAG))
       for (i = 0; answer_tag [i] != NULL; i++)
        {coarse_sense_tag = coarse_sense_tag_for (answer_tag [i], sense_table);
	for (j = 0; j < i; j++)
           if (strequal (coarse_sense_tag, answer_tag [j])) break;
         if (j < i)
          {answer_weight [j] += answer_weight [i];
           for (j = i; answer_tag [j] != NULL; j++)
            {answer_tag [j] = answer_tag [j + 1];
             answer_weight [j] = answer_weight [j + 1];}
           i--;} 
         else
         if (!(strequal (coarse_sense_tag, answer_tag [i])))
           answer_tag [i] = coarse_sense_tag;}

     /* If weights don't form a probability distribution on sense tags (plus implicit no-guess tag), normalize them */

     if (total_weight > 1.0) 
      {for (i = 0; answer_tag [i] != NULL; i++) answer_weight [i] /= total_weight;
       total_weight = 1.0;}

     /* Look up the correct answer for this test instance in the key */

     key_index = search_string_table (answer -> index [answer_index], key);
     
     /* If there is such a test instance, and it hasn't been scored already or excluded by filter criteria */

     if (key_index != NOT_FOUND && answer_tag [0] != NULL && key_tag [key_index] [0] != NULL)

      {instances_attempted += total_weight;

       score = 0.0;

       /* Check each answer sense tag against the correct sense tags in the key */

       for (i = 0; answer_tag [i] != NULL; i++)

        {subscore = 0.0;

         for (j = 0; key_tag [key_index] [j] != NULL; j++)

           /* If a tag in the key is equal to the answer tag, give full credit */

          {if (strequal (answer_tag [i], key_tag [key_index] [j]))
             subscore += 1.0;
           else
	     
	     /* If correct tag subsumes the answer, give full credit under mixed-grain scoring */

           if (strequal (sense_granularity, MIXED_GRAIN_FLAG) &&
               subsumption_probability_of_tags (key_tag [key_index] [j], answer_tag [i], sense_table) > 0.0)
             subscore += 1.0;
           else

           /* If answer subsumes correct tag, give partial credit under mixed-grain scoring */

           if (strequal (sense_granularity, MIXED_GRAIN_FLAG))
             subscore += subsumption_probability_of_tags (answer_tag [i], key_tag [key_index] [j], sense_table);}
  
	 
         if (subscore > 1.0) subscore = 1.0;

         score += subscore * answer_weight [i];}

       if (score > 1.0) score = 1.0;

       global_score += score;

       if (line_by_line_trace_desired)
        {printf ("score for \"%s\": %.3f", key -> index [key_index], score);
         newline ();
         printf (" key   =");
         for (i = 0; key_tag [key_index] [i] != NULL; i++) 
           printf (" %s", key_tag [key_index] [i]);
         newline ();
         printf (" guess =");
         for (i = 0; answer_tag [i] != NULL; i++) 
           printf (" %s%c%.3f", answer_tag [i], WEIGHT_DELIMITER, answer_weight [i]);
         newline ();
         newline ();}

       /* Reset the key for this test instance so a second answer to this instance won't be counted redundantly */

       key_tag [key_index] [0] = NULL;}
   
     else

      {fprintf (stderr, "Unable to score answer for \"%s\" (line %ld):", answer -> index [answer_index], answer_index);
       if (key_index == NOT_FOUND)
         fprintf (stderr, " Does not exist.");
       else
       if (answer_tag [0] == NULL)
         fprintf (stderr, " Invalid answer.");
       else
         fprintf (stderr, " Excluded or already scored.");
       fnewline (stderr);

       if (line_by_line_trace_desired)
        {printf ("Unable to score answer for \"%s\" (line %ld):", answer -> index [answer_index], answer_index);
         if (key_index == NOT_FOUND)
           printf (" Does not exist.");
         else
         if (answer_tag [0] == NULL)
           printf (" Invalid answer.");
         else
           printf (" Excluded or already scored.");
         newline ();
         newline ();}}

     reclaim (answer_tag);
     reclaim (answer_weight);}

   newline ();
   if (strequal (sense_granularity, COARSE_GRAIN_FLAG))
     printf ("Coarse-grained");
   else
   if (strequal (sense_granularity, MIXED_GRAIN_FLAG))
     printf ("Mixed-grained");
   else
     printf ("Fine-grained");
   if (minimal_scoring_desired) printf (" minimal");
   printf (" score for \"%s\" using key \"%s\":", argv [1], argv [2]);
   newline ();
   printf (" precision: %.3f", global_score / instances_attempted);
   printf (" (%.2f correct of %.2f attempted)", global_score, instances_attempted);
   newline ();
   printf (" recall: %.3f", global_score / instances);
   printf (" (%.2f correct of %.2f in total)", global_score, instances);
   newline ();
   printf (" attempted: %.2f %%", 100.0 * instances_attempted / instances);
   printf (" (%.2f attempted of %.2f in total)", instances_attempted, instances);
   newline ();
   newline ();

   reclaim_string_table (answer);

   for (key_index = 0; key_index < key -> last_entry; key_index++) reclaim (key_tag [key_index]);
   reclaim (key_tag);
   reclaim_string_table (key);

   reclaim_string_table (sense_table);
 }
}