/* Copyright (C) 1995 Bjoern Beutel. */
/* Description. =============================================================*/
/* This module compiles Malaga rule files. */
/* Includes. ================================================================*/
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <setjmp.h>
#include <glib.h>
#include "basic.h"
#include "pools.h"
#include "values.h"
#include "scanner.h"
#include "rule_type.h"
#include "rule_code.h"
#include "rule_parser.h"
#include "rule_symbols.h"
#include "files.h"
#include "rule_compiler.h"
/* Types. ===================================================================*/
typedef struct /* A rule's first instruction. */
{
int_t number; /* The rule number. */
int_t first_instr; /* The index of the first instruction. */
} rule_instr_t;
typedef struct /* A rule reference, part of a list. */
{
list_node_t *next;
int_t rule_number; /* The actual rule number. */
} reference_t;
/* Functions. ===============================================================*/
static string_t
rule_name( int_t rule_index )
/* Return the rule name of rule RULE_INDEX. */
{
rule_t *rule;
rule = pool_item( code.rule_pool, rule_index );
return pool_item( code.string_pool, rule->name );
}
/*---------------------------------------------------------------------------*/
static void
mark_reachable( bool_t reachable[], list_t *references, int_t rule )
/* Mark RULE as reachable in the vector REACHABLE.
* If it has not been reachable before, add it to list NEW_RULES. */
{
reference_t *reference;
if (reachable[ rule ])
return;
reachable[ rule ] = TRUE;
reference = new_node( references, sizeof( reference_t ), LIST_END );
reference->rule_number = rule;
}
/*---------------------------------------------------------------------------*/
static void
add_reference( list_t *references, int_t rule )
/* Add a reference to RULE to *REFERENCES if it is not already there. */
{
reference_t *reference;
/* Check if a reference to RULE already exists in <*references>. */
FOREACH( reference, *references )
{
if (reference->rule_number == rule)
return;
}
/* Add a new reference to the front of the list. */
reference = new_node( references, sizeof( reference_t ), LIST_END );
reference->rule_number = rule;
}
/*---------------------------------------------------------------------------*/
static void
check_all_rules_reachable( int_t rule_count, list_t references[] )
/* Check if every rule can be reached.
* REFERENCES[I] is the reference list for rule I,
* i.e., the list of all rules that may be called from rule I. */
{
bool_t *reachable;
int_t *rule_set;
list_t new_rules;
reference_t *reference;
string_t name;
int_t i;
/* Create a vector initialised with FALSE. */
reachable = new_vector( sizeof( bool_t ), rule_count );
clear_list( &new_rules );
if (code.pruning_rule != -1)
mark_reachable( reachable, &new_rules, code.pruning_rule );
if (code.robust_rule != -1)
mark_reachable( reachable, &new_rules, code.robust_rule );
if (code.allo_rule != -1)
mark_reachable( reachable, &new_rules, code.allo_rule );
if (code.input_filter != -1)
mark_reachable( reachable, &new_rules, code.input_filter );
if (code.output_filter != -1)
mark_reachable( reachable, &new_rules, code.output_filter );
/* Mark the initial rules in the REACHABLE vector. */
if (code.initial_rule_set != -1)
{
for (rule_set = pool_item( code.rule_set_pool, code.initial_rule_set );
*rule_set != -1;
rule_set++)
{
if (*rule_set >= 0)
mark_reachable( reachable, &new_rules, *rule_set );
}
}
/* Mark all rules that may be called by other rules. */
while (new_rules.first != NULL)
{
i = ((reference_t *) new_rules.first)->rule_number;
free_first_node( &new_rules );
FOREACH( reference, references[i] )
mark_reachable( reachable, &new_rules, reference->rule_number );
}
/* Check if any rule is not reachable. */
for (i = 0; i < rule_count; i++)
{
if (! reachable[i])
{
name = rule_name( i );
fprintf( stderr,
"Warning: Rule \"%s\" can't be reached. (\"%s\", line %d)\n",
name, name_in_path( get_rule_file( name ) ),
get_rule_line( name ) );
}
}
free_mem( &reachable );
}
/*---------------------------------------------------------------------------*/
static void
check_no_circular_calls( int_t rule_count, list_t calls[] )
/* Check if there are no circular call chains in the rules.
* CALLS[I] is the list of subrules that may be called by rule I. */
{
bool_t *reachable;
list_t new_rules;
int_t n, i;
reference_t *called;
string_t name;
reachable = new_vector( sizeof( bool_t ), rule_count );
clear_list( &new_rules );
for (n = 0; n < rule_count; n++)
{
/* Mark all rules as unreachable. */
for (i = 0; i < rule_count; i++)
reachable[i] = FALSE;
/* Mark all rules called from RULE as reachable. */
FOREACH( called, calls[n] )
mark_reachable( reachable, &new_rules, called->rule_number );
/* Mark all rules that are called from rules already marked. */
while (new_rules.first != NULL)
{
i = ((reference_t *) new_rules.first)->rule_number;
free_first_node( &new_rules );
FOREACH( called, calls[i] )
mark_reachable( reachable, &new_rules, called->rule_number );
}
if (reachable[n])
{
name = rule_name( n );
fprintf( stderr,
"Warning: Rule \"%s\" is recursive. (\"%s\", line %d)\n", name,
name_in_path( get_rule_file( name ) ), get_rule_line( name ) );
}
}
free_mem( &reachable );
}
/*---------------------------------------------------------------------------*/
static int
compare_first_instr( const void *key1, const void *key2 )
/* Returns (-1/0/1) when the first instruction KEY1 is
* (lower than/same as/higher than) the first instruction KEY2. */
{
rule_instr_t *rule_1, *rule_2;
rule_1 = (rule_instr_t *) key1;
rule_2 = (rule_instr_t *) key2;
if (rule_1->first_instr < rule_2->first_instr)
return -1;
else if (rule_1->first_instr > rule_2->first_instr)
return 1;
else
return 0;
}
/*---------------------------------------------------------------------------*/
static void
check_rule_calls( void )
/* Report a warning if the rules in CODE contain a circular call chain.
* Report a warning if a rule can't be reached. */
{
int_t rule_count, i, rule_index, rule_index2;
rule_instr_t *rules;
list_t *references, *calls; /* Vectors for references and calls. */
reference_t *reference;
rule_t *rule;
instr_t instr;
int_t *rule_set;
/* Initialise RULES and sort them for FIRST_INSTR. */
rule_count = pool_item_count( code.rule_pool );
rules = new_vector( sizeof( rule_instr_t ), rule_count );
for (i = 0; i < rule_count; i++)
{
rule = pool_item( code.rule_pool, i );
rules[i].number = i;
rules[i].first_instr = rule->first_instr;
}
qsort( rules, rule_count, sizeof( rule_instr_t ), compare_first_instr );
/* Allocate a vector of rule references and a vector of calls.
* The structs are NULL-initialised, so we don't need to use "clear_list". */
references = new_vector( sizeof( list_t ), rule_count );
calls = new_vector( sizeof( list_t ), rule_count );
/* Fill references. */
rule_index = 0;
for (i = 0; i < pool_item_count( code.instr_pool ); i++)
{
/* Increment RULE if instruction belongs to next rule. */
while (rule_index + 1 < rule_count
&& rules[ rule_index + 1 ].first_instr <= i)
{
rule_index++;
}
rule_index2 = rules[ rule_index ].number;
instr = *((instr_t *) pool_item( code.instr_pool, i ));
/* Check each instruction: is it a add_state instruction? */
if (OPCODE( instr ) == INS_ADD_STATE && INSTR_INFO( instr ) != -1)
{
/* Examine the rule set of this result statement. */
for (rule_set = pool_item( code.rule_set_pool, INSTR_INFO( instr ) );
*rule_set != -1;
rule_set++)
{
if (*rule_set >= 0)
add_reference( &references[ rule_index2 ], *rule_set );
}
}
else if (OPCODE( instr ) == INS_JUMP_SUBRULE)
{
add_reference( &references[ rule_index2 ], INSTR_INFO( instr ) );
add_reference( &calls[ rule_index2 ], INSTR_INFO( instr ) );
}
}
check_all_rules_reachable( rule_count, references );
check_no_circular_calls( rule_count, calls );
free_mem( &rules );
for (i = 0; i < rule_count; i++)
{
FOREACH_FREE( reference, references[i] )
; /* empty */
FOREACH_FREE( reference, calls[i] )
; /* empty */
}
free_mem( &references );
free_mem( &calls );
}
/*---------------------------------------------------------------------------*/
void
compile_rule_file( string_t source_file_name, string_t object_file_name,
int_t file_type )
/* Compile file SOURCE_FILE_NAME.
* Save compiled data in a file OBJECT_FILE_NAME. */
{
string_t file_name;
int_t file_name_index;
init_rule_code( file_type );
enter_function( "atoms", FUNC_TO_ATOMS );
enter_function( "capital", FUNC_IS_CAPITAL );
enter_function( "length", FUNC_GET_LENGTH );
enter_function( "multi", FUNC_TO_MULTI );
enter_function( "set", FUNC_TO_SET );
enter_function( "switch", FUNC_GET_SWITCH );
enter_function( "value_type", FUNC_GET_VALUE_TYPE );
enter_function( "value_string", FUNC_GET_VALUE_STRING );
enter_function( "transmit", FUNC_TRANSMIT );
enter_function( "floor", FUNC_FLOOR );
enter_function( "substring", FUNC_SUBSTRING );
/* Copy file name into string pool. */
file_name = copy_string_to_pool( code.string_pool, source_file_name,
&file_name_index );
/* Parse the rule file (and all included files). */
begin_include( file_name );
TRY
parse_rule_file();
IF_ERROR
{
print_text( error_text, " (\"%s\", line %d, column %d)",
name_in_path( current_file_name() ), current_line_number(),
current_column() );
if (in_emacs_malaga_mode)
{
printf( "SHOW \"%s\":%d:%d\n", current_file_name(),
current_line_number(), current_column() );
}
}
FINALLY
end_includes();
END_TRY;
check_rules_defined(); /* Check if all used rules are also defined. */
check_rule_calls(); /* Check for recursive and/or unreachable rules. */
dump_variables_and_constants();
write_code( object_file_name );
free_symbols();
terminate_rule_code();
}
/* End of file. =============================================================*/