parser.h

/* This is the header file for the Parser module.  It is also included by the
   Cases module. */

/* external function */

extern case_law_specification
Parse_Specification(
      file in_stream,
      file log_stream);

parser.c

/* This is the implementation file for the Parser module. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "shyster.h"
#include "cases.h"
#include "parser.h"
#include "tokenizer.h"

static void
error_exit(
      file stream,
      string message,
      token_details *token)
{
   char full_message[Max_Error_Message_Length];

   if (token != NULL) {
      sprintf(full_message, "%s [%u,%u]", message, token->line_number,
            token->column_number);
      Write_Error_Message_And_Exit(stream, "Parser", full_message);
   } else
      Write_Error_Message_And_Exit(stream, "Parser", message);
}

static void
warning(
      file stream,
      const string message)
{
   Write_Warning_Message(stream, "Parser", message, Top_Level);
}

static void
parse_court_pair(
      file in_stream,
      file log_stream,
      court *court_pointer,
      token_details *token,
      cardinal *count,
      cardinal *rank)

/* Parses a court identifier/string pair, and puts details in the court
   pointed to by court_pointer.  A court identifier has just been read, and
   its details are pointed to by token.  *count is the number of courts
   already parsed.  *rank is the rank of this court.

   EBNF:   hierarchy-block  = court-identifier
                              string { [ "=" ] court-identifier string }.
           court-identifier = identifier.                                   */

{
   court_pointer->identifier = token->details.identifier;

   /* get the next token (it should be a string) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_STRING)
      error_exit(log_stream, "string expected in hierarchy block after identifier",
            token);

   court_pointer->string = token->details.string;

   court_pointer->rank = (*rank)++;

   /* get the next token (it should be an =, another court identifier, or the
      keyword AREA) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token == TK_EQUALS) {

      /* this court, and the next, are of equal rank */

      (*rank)--;

      /* get the next token (it should be another court identifier, or the
         keyword AREA) */

      *token = Get_Token(in_stream, log_stream);
   }
   (*count)++;
   court_pointer->next = NULL;
}

static court *
parse_hierarchy(
      file in_stream,
      file log_stream,
      token_details *token,
      cardinal *count)

/* Parses a hierarchy, and returns a pointer to a list of courts.  The
   keyword HIERARCHY has just been read, and the details of the token that
   followed it are pointed to by token.  Sets *count to the number of courts
   in the hierarchy.

   EBNF:   hierarchy        = hierarchy-header hierarchy-block.
           hierarchy-header = "HIERARCHY".
           hierarchy-block  = court-identifier string
                              { [ "=" ] court-identifier string }.
           court-identifier = identifier.                                   */

{
   cardinal rank = 1;
   court *court_head = NULL,
     *court_pointer = NULL,
     *temp_court_pointer;
   char message[Max_Error_Message_Length];

   while (token->token == TK_IDENTIFIER) {

      if (court_head == NULL) {

         /* allocate memory for this court (the first in the list) */

         if ((court_head = (court *) malloc(sizeof(court))) == NULL)
            error_exit(log_stream, "malloc failed during hierarchy handling",
                  token);

         court_pointer = court_head;

      } else {

         /* go to the end of the list of courts, checking that this court has
            not already been specified */

         for (temp_court_pointer = court_head; temp_court_pointer != NULL;
               temp_court_pointer = temp_court_pointer->next)
            if (!strcmp(token->details.identifier, temp_court_pointer->identifier)) {
               sprintf(message, "%s court already specified",
                     token->details.identifier);
               error_exit(log_stream, message, token);
            }

         /* allocate memory for this court */

         if ((court_pointer->next = (court *) malloc(sizeof(court))) == NULL)
            error_exit(log_stream, "malloc failed during hierarchy handling",
                  token);

         court_pointer = court_pointer->next;
      }
      parse_court_pair(in_stream, log_stream, court_pointer, token, count, &rank);
   }
   return court_head;
}

static void
parse_result_pair(
      file in_stream,
      file log_stream,
      result *result_pointer,
      token_details *token,
      cardinal *count)

/* Parses a result identifier/string pair, and puts details in the result
   pointed to by result_pointer.  A result identifier has just been read, and
   its details are pointed to by token.  *count is the number of results
   already parsed in this area.

   EBNF:   results-block     = result-identifier string
                               result-identifier string
                               { result-identifier string }.
           result-identifier = identifier.                                  */

{
   result_pointer->identifier = token->details.identifier;

   /* get the next token (it should be a string) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_STRING)
      error_exit(log_stream, "string expected in results block after identifier",
            token);

   result_pointer->string = token->details.string;

   result_pointer->case_head = NULL;
   result_pointer->ideal_point_head = NULL;
   result_pointer->centroid_head = NULL;
   result_pointer->hypothetical_list_head = NULL;
   (*count)++;

   /* get the next token (it should be a result identifier, or the keyword
      ATTRIBUTE) */

   *token = Get_Token(in_stream, log_stream);

   result_pointer->next = NULL;
}

static result *
parse_results(
      file in_stream,
      file log_stream,
      token_details *token,
      cardinal *count)

/* Parses results, and returns a pointer to a list of results.  The keyword
   RESULTS has just been read, and the details of the token that followed it
   are pointed to by token.  Sets *count to the number of results in the
   area.

   EBNF:   results           = results-header results-block.
           results-header    = "RESULTS".
           results-block     = result-identifier string
                               result-identifier string
                               { result-identifier string }.
           result-identifier = identifier.                                  */

{
   result *result_head = NULL,
     *result_pointer = NULL,
     *temp_result_pointer;
   char message[Max_Error_Message_Length];

   while (token->token == TK_IDENTIFIER) {

      if (result_head == NULL) {

         /* allocate memory for this result (the first in the list) */

         if ((result_head = (result *) malloc(sizeof(result))) == NULL)
            error_exit(log_stream, "malloc failed during result handling", token);

         result_pointer = result_head;

      } else {

         /* go to the end of the list of results, checking that this result
            has not already been specified */

         for (temp_result_pointer = result_head; temp_result_pointer != NULL;
               temp_result_pointer = temp_result_pointer->next)
            if (!strcmp(token->details.identifier, temp_result_pointer->identifier)) {
               sprintf(message, "%s result already specified",
                     token->details.identifier);
               error_exit(log_stream, message, token);
            }

         /* allocate memory for this result */

         if ((result_pointer->next = (result *) malloc(sizeof(result))) == NULL)
            error_exit(log_stream, "malloc failed during result handling", token);

         result_pointer = result_pointer->next;
      }
      parse_result_pair(in_stream, log_stream, result_pointer, token, count);
   }
   return result_head;
}

static void
add_to_direction_list(
      file log_stream,
      direction_list_element **list_head,
      result *result_pointer,
      token_details *token)

/* Adds result_pointer to the list of directions pointed to by list_head. */

{
   direction_list_element *list_pointer,
     *last_list_pointer;
   char message[Max_Error_Message_Length];

   if (*list_head == NULL) {

      /* allocate memory for this direction (the first in the list) */

      if ((*list_head =
                  (direction_list_element *) malloc(sizeof(direction_list_element))) ==
            NULL)
         error_exit(log_stream, "malloc failed during result list handling", token);

      list_pointer = *list_head;

   } else {

      /* go to the end of the list of directions, checking that this result
         has not already been specified for this attribute value */

      for (list_pointer = *list_head; list_pointer != NULL;
            list_pointer = list_pointer->next) {
         if (list_pointer->result == result_pointer) {
            sprintf(message,
                  "%s result already specified for this attribute value",
                  token->details.identifier);
            error_exit(log_stream, message, token);
         }
         last_list_pointer = list_pointer;
      }

      /* allocate memory for this direction */

      if ((last_list_pointer->next =
                  (direction_list_element *) malloc(sizeof(direction_list_element))) ==
            NULL)
         error_exit(log_stream, "malloc failed during result list handling", token);

      list_pointer = last_list_pointer->next;
   }
   list_pointer->result = result_pointer;

   list_pointer->next = NULL;
}

static void
add_to_identifier_list(
      file log_stream,
      identifier_list_element **list_head,
      string identifier,
      token_details *token)

/* Adds identifier to the list of identifiers pointed to by list_head. */

{
   identifier_list_element *list_pointer,
     *last_list_pointer;
   char message[Max_Error_Message_Length];

   if (*list_head == NULL) {

      /* allocate memory for this identifier list element (the first in the
         list) */

      if ((*list_head = (identifier_list_element *) malloc(sizeof(identifier_list_element))) ==
            NULL)
         error_exit(log_stream, "malloc failed during identifier list handling",
               token);

      list_pointer = *list_head;

   } else {

      /* go to the end of the list of identifiers, checking that this
         identifier has not already been specified for this attribute value */

      for (list_pointer = *list_head; list_pointer != NULL;
            list_pointer = list_pointer->next) {
         if (!strcmp(list_pointer->identifier, identifier)) {
            sprintf(message,
                  "%s identifier already specified for this attribute value",
                  token->details.identifier);
            error_exit(log_stream, message, token);
         }
         last_list_pointer = list_pointer;
      }

      /* allocate memory for this identifier list element */

      if ((last_list_pointer->next =
                  (identifier_list_element *) malloc(sizeof(identifier_list_element))) ==
            NULL)
         error_exit(log_stream, "malloc failed during identifier list handling",
               token);

      list_pointer = last_list_pointer->next;
   }
   list_pointer->identifier = identifier;

   list_pointer->next = NULL;
}

static attribute *
parse_attributes(
      file in_stream,
      file log_stream,
      result *result_head,
      token_details *token,
      string area_identifier,
      cardinal *count)

/* Parses attributes, and returns a pointer to a list of attributes.  The
   keyword ATTRIBUTE has just been read, and the details of the token that
   followed it are pointed to by token.  *result_head is the head of the list
   of results for this area.  Sets *count to the number of attributes in the
   area.

   EBNF:   attribute          = attribute-header attribute-block.
           attribute-header   = "ATTRIBUTE".
           attribute-block    = local-attribute | external-attribute.
           local-attribute    = "QUESTION" string
                                [ "YES" string { result-identifier } ]
                                [ "NO" string { result-identifier } ]
                                [ "UNKNOWN" string { result-identifier } ]
                                [ "HELP" string ].
           external-attribute = "AREA" area-identifier
                                [ "YES" string { result-identifier }
                                    [ "EXTERNAL" result-identifier
                                      { result-identifier } ] ]
                                [ "NO" string { result-identifier }
                                    [ "EXTERNAL" result-identifier
                                      { result-identifier } ] ]
                                [ "UNKNOWN" string { result-identifier }
                                    [ "EXTERNAL" result-identifier
                                      { result-identifier } ] ].            */

{
   attribute *attribute_pointer;
   result *result_pointer = result_head;
   boolean found = FALSE;
   char message[Max_Error_Message_Length];

   /* allocate memory for this attribute */

   if ((attribute_pointer = (attribute *) malloc(sizeof(attribute))) == NULL)
      error_exit(log_stream, "malloc failed during attribute handling", token);

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_QUESTION))
      attribute_pointer->external_attribute = FALSE;
   else if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_AREA))
      attribute_pointer->external_attribute = TRUE;
   else
      error_exit(log_stream, "keyword QUESTION or AREA expected in attribute",
            token);

   /* get the next token (if the attribute is local, it should be a string;
      if the attribute is external, it should be an area identifier) */

   *token = Get_Token(in_stream, log_stream);

   if (attribute_pointer->external_attribute) {

      if (token->token != TK_IDENTIFIER)
         error_exit(log_stream,
               "identifier expected in attribute after keyword AREA", token);

      if (!strcmp(token->details.string, area_identifier)) {
         sprintf(message, "Recursive external attribute in %s area",
               area_identifier);
         error_exit(log_stream, message, token);
      }
      attribute_pointer->details.external.area_identifier = token->details.string;

   } else {

      if (token->token != TK_STRING)
         error_exit(log_stream,
               "string expected in attribute after keyword QUESTION", token);

      attribute_pointer->details.local.question = token->details.string;
   }

   /* get the next token (it should be the keyword YES, the keyword NO, or
      the keyword UNKNOWN) */

   *token = Get_Token(in_stream, log_stream);

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_YES)) {

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_STRING)
         error_exit(log_stream, "string expected in attribute after keyword YES",
               token);

      attribute_pointer->yes = token->details.string;

      /* get the next token (it should be a result identifier, the keyword
         NO, the keyword UNKNOWN, the keyword HELP, the keyword ATTRIBUTE, or
         the keyword CASE; if the attribute is external the token could also
         be the keyword EXTERNAL) */

      *token = Get_Token(in_stream, log_stream);

      attribute_pointer->yes_direction_head = NULL;

      /* while there are result identifiers to parse ... */

      while (token->token == TK_IDENTIFIER) {

         /* find the result matching the result identifier, and add that
            result to the list of directions for YES for this attribute */

         do {
            if (result_pointer == NULL) {
               sprintf(message, "%s result not found",
                     token->details.identifier);
               error_exit(log_stream, message, token);
            }
            found = !strcmp(token->details.identifier, result_pointer->identifier);
            if (found)
               add_to_direction_list(log_stream, &attribute_pointer->yes_direction_head,
                     result_pointer, token);
            else
               result_pointer = result_pointer->next;
         } while (!found);

         result_pointer = result_head;

         /* get the next token (it should be a result identifier, the keyword
            NO, the keyword UNKNOWN, the keyword HELP, the keyword ATTRIBUTE,
            or the keyword CASE; if the attribute is external the token could
            also be the keyword EXTERNAL) */

         *token = Get_Token(in_stream, log_stream);
      }

      if (attribute_pointer->external_attribute) {

         attribute_pointer->details.external.yes_identifier_head = NULL;

         if ((token->token == TK_KEYWORD) &&
               (token->details.keyword == KW_EXTERNAL)) {

            /* get the next token (it should be a result identifier) */

            *token = Get_Token(in_stream, log_stream);

            while (token->token == TK_IDENTIFIER) {

               /* add the result identifier to the list of external result
                  identifiers for YES for this attribute */

               add_to_identifier_list(log_stream,
                     &attribute_pointer->details.external.yes_identifier_head,
                     token->details.identifier, token);

               /* get the next token (it should be a result identifier, the
                  keyword NO, the keyword UNKNOWN, the keyword HELP, the
                  keyword ATTRIBUTE, or the keyword CASE) */

               *token = Get_Token(in_stream, log_stream);
            }

            if (attribute_pointer->details.external.yes_identifier_head == NULL)
               error_exit(log_stream,
                     "identifier expected in attribute after keyword EXTERNAL",
                     token);
         }
      }
   } else
      attribute_pointer->yes = NULL;

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_NO)) {

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_STRING)
         error_exit(log_stream, "string expected in attribute after keyword NO",
               token);

      attribute_pointer->no = token->details.string;

      /* get the next token (it should be a result identifier, the keyword
         UNKNOWN, the keyword HELP, the keyword ATTRIBUTE, or the keyword
         CASE; if the attribute is external the token could also be the
         keyword EXTERNAL) */

      *token = Get_Token(in_stream, log_stream);

      attribute_pointer->no_direction_head = NULL;

      /* while there are result identifiers to parse ... */

      while (token->token == TK_IDENTIFIER) {

         /* find the result matching the result identifier, and add that
            result to the list of directions for NO for this attribute */

         do {
            if (result_pointer == NULL) {
               sprintf(message, "%s result not found",
                     token->details.identifier);
               error_exit(log_stream, message, token);
            }
            found = !strcmp(token->details.identifier, result_pointer->identifier);
            if (found)
               add_to_direction_list(log_stream, &attribute_pointer->no_direction_head,
                     result_pointer, token);
            else
               result_pointer = result_pointer->next;
         } while (!found);

         result_pointer = result_head;

         /* get the next token (it should be a result identifier, the keyword
            UNKNOWN, the keyword HELP, the keyword ATTRIBUTE, or the keyword
            CASE; if the attribute is external the token could also be the
            keyword EXTERNAL) */

         *token = Get_Token(in_stream, log_stream);
      }

      if (attribute_pointer->external_attribute) {

         attribute_pointer->details.external.no_identifier_head = NULL;

         if ((token->token == TK_KEYWORD) &&
               (token->details.keyword == KW_EXTERNAL)) {

            /* get the next token (it should be a result identifier) */

            *token = Get_Token(in_stream, log_stream);

            while (token->token == TK_IDENTIFIER) {

               /* add the result identifier to the list of external result
                  identifiers for NO for this attribute */

               add_to_identifier_list(log_stream,
                     &attribute_pointer->details.external.no_identifier_head,
                     token->details.identifier, token);

               /* get the next token (it should be a result identifier, the
                  keyword UNKNOWN, the keyword HELP, the keyword ATTRIBUTE,
                  or the keyword CASE) */

               *token = Get_Token(in_stream, log_stream);
            }
            if (attribute_pointer->details.external.no_identifier_head == NULL)
               error_exit(log_stream,
                     "identifier expected in attribute after keyword EXTERNAL",
                     token);
         }
      }
   } else
      attribute_pointer->no = NULL;

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_UNKNOWN)) {

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_STRING)
         error_exit(log_stream,
               "string expected in attribute after keyword UNKNOWN", token);

      attribute_pointer->unknown = token->details.string;

      /* get the next token (it should be a result identifier, the keyword
         HELP, the keyword ATTRIBUTE, or the keyword CASE; if the attribute
         is external the token could also be the keyword EXTERNAL) */

      *token = Get_Token(in_stream, log_stream);

      attribute_pointer->unknown_direction_head = NULL;

      /* while there are result identifiers to parse ... */

      while (token->token == TK_IDENTIFIER) {

         /* find the result matching the result identifier, and add that
            result to the list of directions for UNKNOWN for this attribute */

         do {
            if (result_pointer == NULL) {
               sprintf(message, "%s result not found",
                     token->details.identifier);
               error_exit(log_stream, message, token);
            }
            found = !strcmp(token->details.identifier, result_pointer->identifier);
            if (found)
               add_to_direction_list(log_stream, &attribute_pointer->unknown_direction_head,
                     result_pointer, token);
            else
               result_pointer = result_pointer->next;
         } while (!found);

         result_pointer = result_head;

         /* get the next token (it should be a result identifier, the keyword
            HELP, the keyword ATTRIBUTE, or the keyword CASE; if the
            attribute is external the token could also be the keyword
            EXTERNAL) */

         *token = Get_Token(in_stream, log_stream);
      }

      if (attribute_pointer->external_attribute) {

         attribute_pointer->details.external.unknown_identifier_head = NULL;

         if ((token->token == TK_KEYWORD) &&
               (token->details.keyword == KW_EXTERNAL)) {

            /* get the next token (it should be a result identifier) */

            *token = Get_Token(in_stream, log_stream);

            while (token->token == TK_IDENTIFIER) {

               /* add the result identifier to the list of external result
                  identifiers for UNKNOWN for this attribute */

               add_to_identifier_list(log_stream,
                     &attribute_pointer->details.external.unknown_identifier_head,
                     token->details.identifier, token);

               /* get the next token (it should be a result identifier, the
                  keyword HELP, the keyword ATTRIBUTE, or the keyword CASE) */

               *token = Get_Token(in_stream, log_stream);
            }
            if (attribute_pointer->details.external.unknown_identifier_head == NULL)
               error_exit(log_stream,
                     "identifier expected in attribute after keyword EXTERNAL",
                     token);
         }
      }
   } else
      attribute_pointer->unknown = NULL;

   if ((attribute_pointer->yes == NULL) && (attribute_pointer->no == NULL) &&
         (attribute_pointer->unknown == NULL))
      error_exit(log_stream, "keyword YES, NO or UNKNOWN expected in attribute",
            token);

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_HELP)) {

      if (attribute_pointer->external_attribute)
         error_exit(log_stream, "keyword HELP not allowed in external attribute",
               token);

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_STRING)
         error_exit(log_stream, "string expected in attribute after keyword HELP",
               token);

      attribute_pointer->details.local.help = token->details.string;

      /* get the next token (it should be the keyword ATTRIBUTE, or the
         keyword CASE) */

      *token = Get_Token(in_stream, log_stream);

   } else if (!attribute_pointer->external_attribute)
      attribute_pointer->details.local.help = NULL;

   attribute_pointer->number = ++(*count);
   attribute_pointer->matrix_head = NULL;
   attribute_pointer->weights_head = NULL;
   attribute_pointer->probability_head = NULL;

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_ATTRIBUTE)) {

      /* get the next token (it should be the keyword QUESTION, or the
         keyword AREA) */

      *token = Get_Token(in_stream, log_stream);

      /* parse the next attribute */

      attribute_pointer->next = parse_attributes(in_stream, log_stream, result_head, token,
            area_identifier, count);

   } else
      attribute_pointer->next = NULL;

   return attribute_pointer;
}

static boolean
is_more_important(
      kase *x,
      kase *y)

/* Returns TRUE, iff case x is more important than case y: i.e. case x was a
   decision of a more important court, or of an equally important court at a
   later date (if neither case x nor case y has a court then the more recent
   of the two is the more important). */

{
   if (x->court_string == NULL)
      if (y->court_string == NULL)

         /* neither case x nor case y has a court, so return TRUE if case x
            is a more recent decision than case y */

         return x->year > y->year;

      else

         /* case y has a court and case x doesn't, so case y is assumed to be
            more important than case x */

         return FALSE;

   else if (y->court_string == NULL)

      /* case x has a court and case y doesn't, so case x is assumed to be
         more important than case y */

      return TRUE;

   else {

      /* both case x and case y have a court */

      if (x->court_rank < y->court_rank)

         /* case x was a decision of a more important court then was case y */

         return TRUE;

      else if (x->court_rank == y->court_rank)

         /* case x and case y were decisions of an equally important court,
            so return TRUE if case x is a more recent decision than case y */

         return x->year > y->year;

      else

         /* case x was a decision of a less important court than was case y */

         return FALSE;
   }
}

static void
rank_cases(
      kase **case_head)

/* Reorders the list of cases pointed to by *case_head so that the cases are
   listed in order of their importance (more important cases first). */

{
   boolean changed;
   kase *case_pointer,
     *previous_case_pointer,
     *next_case_pointer,
     *temp_case_pointer;

   do {
      changed = FALSE;
      previous_case_pointer = NULL;
      case_pointer = *case_head;

      /* while there are still cases in the list ... */

      while (case_pointer != NULL) {

         next_case_pointer = case_pointer->next;

         if (next_case_pointer != NULL) {

            if (is_more_important(next_case_pointer, case_pointer)) {

               /* swap this case and the next */

               if (previous_case_pointer == NULL)

                  /* case_pointer points to the first case in the list */

                  *case_head = next_case_pointer;

               else {

                  /* case_pointer points to a case which is not the first in
                     the list */

                  previous_case_pointer->next = next_case_pointer;
               }

               case_pointer->next = next_case_pointer->next;
               next_case_pointer->next = case_pointer;
               temp_case_pointer = case_pointer;
               case_pointer = next_case_pointer;
               next_case_pointer = temp_case_pointer;

               changed = TRUE;
            }
         }
         previous_case_pointer = case_pointer;
         case_pointer = next_case_pointer;
      }
   } while (changed);
}

static void
number_cases(
      result *result_head)

/* Assigns a number to each case for each result in the list pointed to by
   result_head. */

{
   result *result_pointer;
   kase *case_pointer;
   cardinal count = 1;

   /* for every result ... */

   for (result_pointer = result_head; result_pointer != NULL; result_pointer =
         result_pointer->next)

      /* for every case ... */

      for (case_pointer = result_pointer->case_head; case_pointer != NULL;
            case_pointer = case_pointer->next)
         case_pointer->number = count++;
}

static void
cross_link(
      result *result_head,
      attribute *attribute_head)

/* Links attribute values by attribute (they are already linked by case). */

{
   result *result_pointer;
   kase *case_pointer;
   attribute *attribute_pointer;
   matrix_element *case_matrix_pointer,
     *attribute_matrix_pointer;

   /* for every result ... */

   for (result_pointer = result_head; result_pointer != NULL; result_pointer =
         result_pointer->next)

      /* for every case ... */

      for (case_pointer = result_pointer->case_head; case_pointer != NULL;
            case_pointer = case_pointer->next) {

         attribute_pointer = attribute_head;
         case_matrix_pointer = case_pointer->matrix_head;

         if (attribute_pointer->matrix_head == NULL)

            /* this is the first attribute value for this (or any other)
               attribute, so each attribute value for this case becomes the
               head of the appropriate attribute's list */

            while ((attribute_pointer != NULL) && (case_matrix_pointer != NULL)) {
               attribute_pointer->matrix_head = case_matrix_pointer;
               case_matrix_pointer = case_matrix_pointer->case_next;
               attribute_pointer = attribute_pointer->next;
            }

         else

            /* this is not the first attribute value for this attribute, so
               add each attribute value for this case to the end of the
               appropriate attribute's list */

            while (attribute_pointer != NULL) {

               for (attribute_matrix_pointer = attribute_pointer->matrix_head;
                     attribute_matrix_pointer->attribute_next != NULL;
                     attribute_matrix_pointer =
                     attribute_matrix_pointer->attribute_next);

               attribute_matrix_pointer->attribute_next = case_matrix_pointer;
               case_matrix_pointer = case_matrix_pointer->case_next;
               attribute_pointer = attribute_pointer->next;
            }
      }
}

static void
check_for_identical_cases(
      file log_stream,
      area *area_pointer)

/* Checks every case in the area_pointer area against every other case in
   that area and warns if two cases are identical, or identical but for
   UNKNOWN values. */

{
   result *result_pointer_X,
     *result_pointer_Y;
   kase *case_pointer_X,
     *case_pointer_Y;
   matrix_element *matrix_pointer_X,
     *matrix_pointer_Y;
   boolean identical,
      possibly_identical;
   char message[Max_Error_Message_Length];

   /* for every result ... */

   for (result_pointer_X = area_pointer->result_head; result_pointer_X != NULL;
         result_pointer_X = result_pointer_X->next)

      /* for every case X ... */

      for (case_pointer_X = result_pointer_X->case_head; case_pointer_X != NULL;
            case_pointer_X = case_pointer_X->next) {

         case_pointer_Y = case_pointer_X;
         result_pointer_Y = result_pointer_X;

         /* for every case Y (i.e. every case after case X) ... */

         while (case_pointer_Y != NULL) {

            if (case_pointer_X != case_pointer_Y) {

               /* X and Y are not the same case */

               identical = TRUE;
               possibly_identical = TRUE;

               matrix_pointer_X = case_pointer_X->matrix_head;
               matrix_pointer_Y = case_pointer_Y->matrix_head;

               while (possibly_identical &&
                     (matrix_pointer_X != NULL) && (matrix_pointer_Y != NULL)) {

                  /* look for differences between case X and case Y */

                  if (matrix_pointer_X->attribute_value !=
                        matrix_pointer_Y->attribute_value) {

                     identical = FALSE;

                     if ((matrix_pointer_X->attribute_value != UNKNOWN) &&
                           (matrix_pointer_Y->attribute_value != UNKNOWN))
                        possibly_identical = FALSE;
                  }
                  matrix_pointer_X = matrix_pointer_X->case_next;
                  matrix_pointer_Y = matrix_pointer_Y->case_next;
               }
               if (identical) {

                  sprintf(message,
                        "C%u and C%u in %s area have "
                        "identical attribute vectors",
                        case_pointer_X->number, case_pointer_Y->number,
                        area_pointer->identifier);

                  if (result_pointer_X != result_pointer_Y)
                     sprintf(message, "%s and different results", message);

                  warning(log_stream, message);

               } else if (possibly_identical) {

                  sprintf(message,
                        "C%u and C%u in %s area have "
                        "identical attribute values (except for unknowns)",
                        case_pointer_X->number, case_pointer_Y->number,
                        area_pointer->identifier);

                  if (result_pointer_X != result_pointer_Y)
                     sprintf(message, "%s and different results", message);

                  warning(log_stream, message);
               }
            }
            case_pointer_Y = case_pointer_Y->next;

            while ((case_pointer_Y == NULL) && (result_pointer_Y != NULL)) {

               /* the next case Y is not of this result, so move to the next
                  result */

               result_pointer_Y = result_pointer_Y->next;
               if (result_pointer_Y != NULL)
                  case_pointer_Y = result_pointer_Y->case_head;
            }
         }
      }
}

static void
parse_case(
      file in_stream,
      file log_stream,
      court *court_pointer,
      area *area_pointer,
      token_details *token,
      cardinal *count)

/* Parses a case, and adds it to the list of cases for the appropriate result
   in the area pointed to by area_pointer.  The keyword CASE has just been
   read, and the details of the token that followed it are pointed to by
   token.  Increments *count (the number of cases already parsed in this
   area).

   EBNF:   case        = case-header case-block.
           case-header = "CASE" string [ string ].
           case-block  = "CITATION" string
                         "YEAR" year
                         [ "COURT" court-identifier ]
                         "FACTS" attribute-vector
                         "RESULT" result-identifier
                         [ "SUMMARY" string ].                              */

{
   result *result_pointer = area_pointer->result_head;
   kase *case_pointer,
     *temp_case_pointer;
   attribute *attribute_pointer;
   matrix_element *matrix_pointer;
   boolean found = FALSE;
   char message[Max_Error_Message_Length];

   if (token->token != TK_STRING)
      error_exit(log_stream, "string expected in case after keyword CASE", token);

   /* allocate memory for this case */

   if ((case_pointer = (kase *) malloc(sizeof(kase))) == NULL)
      error_exit(log_stream, "malloc failed during case handling", token);

   case_pointer->name = token->details.string;

   /* get the next token (it should be a string or the keyword CITATION) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token == TK_STRING) {

      case_pointer->short_name = token->details.string;

      /* get the next token (it should be the keyword CITATION) */

      *token = Get_Token(in_stream, log_stream);

   } else
      case_pointer->short_name = case_pointer->name;

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_CITATION))
      error_exit(log_stream, "keyword CITATION expected in case", token);

   /* get the next token (it should be a string) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_STRING)
      error_exit(log_stream, "string expected in case after keyword CITATION",
            token);

   case_pointer->citation = token->details.string;

   /* get the next token (it should be the keyword YEAR) */

   *token = Get_Token(in_stream, log_stream);

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_YEAR))
      error_exit(log_stream, "keyword YEAR expected in case", token);

   /* get the next token (it should be a year) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_YEAR)
      error_exit(log_stream, "year expected in case after keyword YEAR", token);

   case_pointer->year = token->details.year;

   /* get the next token (it should be the keyword COURT, or the keyword
      FACTS) */

   *token = Get_Token(in_stream, log_stream);

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_COURT)) {

      /* get the next token (it should be a court identifier) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_IDENTIFIER)
         error_exit(log_stream, "identifier expected in case after keyword COURT",
               token);

      /* find the court identifier in the list of courts, and link the case
         to that court */

      do {
         if (court_pointer == NULL) {
            sprintf(message, "%s court not found", token->details.identifier);
            error_exit(log_stream, message, token);
         }
         found = !strcmp(token->details.identifier, court_pointer->identifier);
         if (found) {
            case_pointer->court_string = court_pointer->string;

            case_pointer->court_rank = court_pointer->rank;
         } else
            court_pointer = court_pointer->next;
      } while (!found);

      /* get the next token (it should be the keyword FACTS) */

      *token = Get_Token(in_stream, log_stream);

   } else

      /* no court specified */

      case_pointer->court_string = NULL;

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_FACTS))
      error_exit(log_stream, "keyword FACTS expected in case", token);

   /* get the next token (it should be an attribute vector) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_ATTRIBUTE_VECTOR)
      error_exit(log_stream,
            "attribute vector expected in case after keyword FACTS", token);

   case_pointer->matrix_head = token->details.matrix_head;

   /* check that there are as many values in the attribute vector as there
      are attributes */

   matrix_pointer = case_pointer->matrix_head;
   for (attribute_pointer = area_pointer->attribute_head;
         (attribute_pointer != NULL) && (matrix_pointer != NULL);
         attribute_pointer = attribute_pointer->next)
      matrix_pointer = matrix_pointer->case_next;
   if (attribute_pointer != NULL)
      error_exit(log_stream, "too few values in attribute vector", token);
   if (matrix_pointer != NULL)
      error_exit(log_stream, "too many values in attribute vector", token);

   /* get the next token (it should be the keyword RESULT) */

   *token = Get_Token(in_stream, log_stream);

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_RESULT))
      error_exit(log_stream, "keyword RESULT expected in case", token);

   /* get the next token (it should be a result identifier) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_IDENTIFIER)
      error_exit(log_stream, "identifier expected in case after keyword RESULT",
            token);

   /* find the result identifier in the list of results, and add the case to
      the list of cases for that result */

   do {
      if (result_pointer == NULL) {
         sprintf(message, "%s result not found", token->details.identifier);
         error_exit(log_stream, message, token);
      }
      found = !strcmp(token->details.identifier, result_pointer->identifier);
      if (found) {
         if (result_pointer->case_head == NULL)
            result_pointer->case_head = case_pointer;
         else {
            for (temp_case_pointer = result_pointer->case_head;
                  temp_case_pointer->next != NULL;
                  temp_case_pointer = temp_case_pointer->next);
            temp_case_pointer->next = case_pointer;
         }
      } else
         result_pointer = result_pointer->next;
   } while (!found);

   (*count)++;

   case_pointer->summary = NULL;

   /* get the next token (it should be the keyword SUMMARY, the keyword CASE,
      the keyword IDEAL, the keyword AREA, or the end of the file) */

   *token = Get_Token(in_stream, log_stream);

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_SUMMARY)) {

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_STRING)
         error_exit(log_stream, "string expected in case after keyword SUMMARY",
               token);

      case_pointer->summary = token->details.string;

      /* get the next token (it should be the keyword CASE, the keyword
         IDEAL, the keyword AREA, or the end of the file) */

      *token = Get_Token(in_stream, log_stream);
   }
   case_pointer->next = NULL;
}

static vector_element *
vector_from_matrix(
      file log_stream,
      matrix_element *matrix_pointer,
      token_details *token)

/* Returns a pointer to a new list of vector elements whose values correspond
   to those in the list of matrix elements pointed to by matrix_pointer
   (linked by case).  Frees the memory taken up by the list of matrix
   elements. */

{
   vector_element *vector_head,
     *vector_pointer;
   matrix_element *next_matrix_pointer;

   /* allocate memory for the first vector element in the list */

   if ((vector_head = (vector_element *) malloc(sizeof(vector_element))) == NULL)
      error_exit(log_stream, "malloc failed during matrix/vector conversion", token);

   vector_pointer = vector_head;

   /* while there are still matrix elements in the list ... */

   while (matrix_pointer != NULL) {

      /* copy the attribute value into the vector element */

      vector_pointer->attribute_value = matrix_pointer->attribute_value;
      next_matrix_pointer = matrix_pointer->case_next;

      /* free the memory taken up by the matrix element */

      free(matrix_pointer);
      if (next_matrix_pointer == NULL)
         vector_pointer->next = NULL;
      else {

         /* allocate memory for the next vector element */

         if ((vector_pointer->next = (vector_element *) malloc(sizeof(vector_element))) ==
               NULL)
            error_exit(log_stream, "malloc failed during matrix/vector conversion",
                  token);

         vector_pointer = vector_pointer->next;
      }
      matrix_pointer = next_matrix_pointer;
   }
   return vector_head;
}

static void
parse_ideal_point(
      file in_stream,
      file log_stream,
      result *result_pointer,
      attribute *attribute_pointer,
      token_details *token,
      cardinal *count)

/* Parses an ideal point, and makes it the ideal point for the appropriate
   result in the list of results pointed to by result_pointer.  The keyword
   IDEAL has just been read, and the details of the token that followed it
   are pointed to by token.  *attribute_pointer is the head of the list of
   attributes for this area.  Increments count (the number of ideal points
   already parsed in this area).

   EBNF:   ideal-point        = ideal-point-header ideal-point-block.
           ideal-point-header = "IDEAL".
           ideal-point-block  = "FACTS" attribute-vector
                                "RESULT" result-identifier.                 */

{
   vector_element *temp_vector_head;
   matrix_element *matrix_pointer;
   boolean found = FALSE;
   char message[Max_Error_Message_Length];

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_FACTS))
      error_exit(log_stream, "keyword FACTS expected in ideal point", token);

   /* get the next token (it should be an attribute vector) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_ATTRIBUTE_VECTOR)
      error_exit(log_stream,
            "attribute vector expected in ideal point "
            "after keyword FACTS", token);

   /* check that there are as many values in the attribute vector as there
      are attributes */

   matrix_pointer = token->details.matrix_head;
   for (;
         (attribute_pointer != NULL) && (matrix_pointer != NULL);
         attribute_pointer = attribute_pointer->next)
      matrix_pointer = matrix_pointer->case_next;
   if (attribute_pointer != NULL)
      error_exit(log_stream, "too few values in attribute vector", token);
   if (matrix_pointer != NULL)
      error_exit(log_stream, "too many values in attribute vector", token);

   /* convert the attribute vector from a list of matrix elements into a list
      of vector elements */

   temp_vector_head = vector_from_matrix(log_stream, token->details.matrix_head, token);

   /* get the next token (it should be the keyword RESULT) */

   *token = Get_Token(in_stream, log_stream);

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_RESULT))
      error_exit(log_stream, "keyword RESULT expected in ideal point", token);

   /* get the next token (it should be a result identifier) */

   *token = Get_Token(in_stream, log_stream);

   if (token->token != TK_IDENTIFIER)
      error_exit(log_stream,
            "identifier expected in ideal point after keyword RESULT", token);

   /* find the result identifier in the list of results, and link that result
      to the ideal point */

   do {
      if (result_pointer == NULL) {
         sprintf(message, "%s result not found", token->details.identifier);
         error_exit(log_stream, message, token);
      }
      found = !strcmp(token->details.identifier, result_pointer->identifier);
      if (found) {
         if (result_pointer->ideal_point_head == NULL)
            result_pointer->ideal_point_head = temp_vector_head;
         else {
            sprintf(message,
                  "ideal point for %s result already specified",
                  token->details.identifier);
            error_exit(log_stream, message, token);
         }
      } else
         result_pointer = result_pointer->next;
   } while (!found);

   (*count)++;

   /* get the next token (it should be the keyword IDEAL, the keyword AREA,
      or the end of the file) */

   *token = Get_Token(in_stream, log_stream);
}

static void
parse_area_block(
      file in_stream,
      file log_stream,
      area *area_pointer,
      token_details *token,
      court *court_head)

/* Parses an area block, and puts details in the area pointed to by
   area_pointer.  The keyword AREA and an area identifier have just been
   read; the identifier's details are pointed to by token.

   EBNF:   area            = area-header area-block.
           area-header     = "AREA" area-identifier.
           area-block      = [ opening ] [ closing ]
                             results
                             attribute { attribute }
                             case { case }
                             { ideal-point }.
           area-identifier = identifier.
           opening         = "OPENING" string.
           closing         = "CLOSING" string.                              */

{
   cardinal number_of_cases = 0,
      number_of_ideal_points = 0;
   result *result_pointer;

   area_pointer->identifier = token->details.identifier;

   Indent(log_stream, 1);
   fprintf(log_stream, "%s area:\n\n", area_pointer->identifier);

   /* get the next token (it should be the keyword OPENING, the keyword
      CLOSING, or the keyword RESULTS) */

   *token = Get_Token(in_stream, log_stream);

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_OPENING)) {

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_STRING)
         error_exit(log_stream, "string expected after keyword OPENING", token);

      area_pointer->opening = token->details.string;

      /* get the next token (it should be the keyword CLOSING, or the keyword
         RESULTS) */

      *token = Get_Token(in_stream, log_stream);
   } else
      area_pointer->opening = NULL;

   if ((token->token == TK_KEYWORD) && (token->details.keyword == KW_CLOSING)) {

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      if (token->token != TK_STRING)
         error_exit(log_stream, "string expected after keyword CLOSING", token);

      area_pointer->closing = token->details.string;

      /* get the next token (it should be the keyword RESULTS) */

      *token = Get_Token(in_stream, log_stream);
   } else
      area_pointer->closing = NULL;

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_RESULTS))
      error_exit(log_stream, "keyword RESULTS expected in results header", token);

   /* get the next token (it should be a result identifier) */

   *token = Get_Token(in_stream, log_stream);

   area_pointer->number_of_results = 0;
   area_pointer->result_head = parse_results(in_stream, log_stream, token,
         &area_pointer->number_of_results);

   switch (area_pointer->number_of_results) {
      case 0:
         error_exit(log_stream,
               "no results (at least two are required)", NULL);
         break;
      case 1:
         error_exit(log_stream,
               "only one result (at least two are required)", NULL);
         break;
      default:
         Indent(log_stream, 2);
         fprintf(log_stream,
               "%u results\n", area_pointer->number_of_results);
         break;
   }

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_ATTRIBUTE))
      error_exit(log_stream, "keyword ATTRIBUTE expected in attribute header",
            token);

   /* get the next token (it should be the keyword QUESTION, or the keyword
      AREA) */

   *token = Get_Token(in_stream, log_stream);

   area_pointer->number_of_attributes = 0;
   area_pointer->attribute_head = parse_attributes(in_stream, log_stream,
         area_pointer->result_head, token, area_pointer->identifier,
         &area_pointer->number_of_attributes);

   Indent(log_stream, 2);
   fprintf(log_stream, "%u attribute%s\n", area_pointer->number_of_attributes,
         area_pointer->number_of_attributes == 1 ? Empty_String : "s");

   if ((token->token != TK_KEYWORD) || (token->details.keyword != KW_CASE))
      error_exit(log_stream, "keyword CASE expected in case header", token);

   while ((token->token == TK_KEYWORD) && (token->details.keyword == KW_CASE)) {

      /* get the next token (it should be a string) */

      *token = Get_Token(in_stream, log_stream);

      parse_case(in_stream, log_stream, court_head, area_pointer, token, &number_of_cases);
   }
   Indent(log_stream, 2);
   fprintf(log_stream, "%u case%s\n", number_of_cases,
         number_of_cases == 1 ? Empty_String : "s");

   for (result_pointer = area_pointer->result_head; result_pointer != NULL;
         result_pointer = result_pointer->next)
      rank_cases(&result_pointer->case_head);

   number_cases(area_pointer->result_head);

   cross_link(area_pointer->result_head, area_pointer->attribute_head);

   while ((token->token == TK_KEYWORD) &&
         (token->details.keyword == KW_IDEAL)) {

      /* get the next token (it should be the keyword FACTS) */

      *token = Get_Token(in_stream, log_stream);

      parse_ideal_point(in_stream, log_stream, area_pointer->result_head,
            area_pointer->attribute_head, token, &number_of_ideal_points);
   }

   if (number_of_ideal_points != 0) {
      Indent(log_stream, 2);
      fprintf(log_stream, "%u ideal point%s\n", number_of_ideal_points,
            number_of_ideal_points == 1 ? Empty_String : "s");
   }
   fprintf(log_stream, "\n");
   area_pointer->correlation_coefficients = FALSE;
   area_pointer->next = NULL;

   check_for_identical_cases(log_stream, area_pointer);
}

static area *
parse_areas(
      file in_stream,
      file log_stream,
      court *court_head)

/* Parses areas, and returns a pointer to a list of areas.  The keyword AREA
   has just been read. */

{
   area *area_head = NULL,
     *area_pointer = NULL,
     *temp_area_pointer;
   token_details token;
   char message[Max_Error_Message_Length];

   do {

      /* get the next token (it should be an area identifier) */

      token = Get_Token(in_stream, log_stream);

      if (token.token != TK_IDENTIFIER)
         error_exit(log_stream,
               "identifier expected in area header after keyword AREA", &token);

      if (area_head == NULL) {

         /* allocate memory for this area (the first in the list) */

         if ((area_head = (area *) malloc(sizeof(area))) == NULL)
            error_exit(log_stream, "malloc failed during area handling", &token);

         area_pointer = area_head;

      } else {

         /* go to the end of the list of areas, checking that an area with
            this identifier has not already been specified */

         for (temp_area_pointer = area_head; temp_area_pointer != NULL;
               temp_area_pointer = temp_area_pointer->next)
            if (!strcmp(token.details.identifier, temp_area_pointer->identifier)) {
               sprintf(message, "%s area already specified",
                     token.details.identifier);
               error_exit(log_stream, message, &token);
            }

         /* allocate memory for this area */

         if ((area_pointer->next = (area *) malloc(sizeof(area))) == NULL)
            error_exit(log_stream, "malloc failed during area handling", &token);

         area_pointer = area_pointer->next;
      }
      parse_area_block(in_stream, log_stream, area_pointer, &token, court_head);

   } while ((token.token == TK_KEYWORD) && (token.details.keyword == KW_AREA));

   if (token.token != TK_EOF)
      error_exit(log_stream, "end of file expected", &token);

   return area_head;
}

extern case_law_specification
Parse_Specification(
      file in_stream,
      file log_stream)

/* Parses the specification file in_stream, and returns a case law
   specification.

   EBNF:   specification = [ hierarchy ]
                           area { area }.                                   */

{
   case_law_specification case_law;
   token_details token;
   cardinal number_of_courts = 0;

   /* get the first token */

   token = Get_Token(in_stream, log_stream);

   if ((token.token == TK_KEYWORD) && (token.details.keyword == KW_HIERARCHY)) {

      /* get the next token (it should be a court identifier) */

      token = Get_Token(in_stream, log_stream);

      case_law.court_head = parse_hierarchy(in_stream, log_stream, &token,
            &number_of_courts);

      if (case_law.court_head == NULL)
         error_exit(log_stream,
               "identifier expected in hierarchy block "
               "after keyword HIERARCHY", &token);
      else {
         Indent(log_stream, 1);
         fprintf(log_stream, "%u court%s in the hierarchy.\n\n",
               number_of_courts, number_of_courts == 1 ? Empty_String : "s");
      }

   } else
      case_law.court_head = NULL;

   if ((token.token != TK_KEYWORD) || (token.details.keyword != KW_AREA))
      error_exit(log_stream, "keyword AREA expected in area header", &token);

   case_law.area_head = parse_areas(in_stream, log_stream, case_law.court_head);

   return case_law;
}

Other SHYSTER modules: Shyster, Statutes, Cases, Tokenizer, Dumper, Checker, Scales, Adjuster, Consultant, Odometer and Reporter.