/* 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);
/* 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.