Blob Blame History Raw
/* Copyright (C) 1995 Bjoern Beutel. */

/* Description. =============================================================*/

/* This module contains the Malaga rule interpreter. */

/* Includes. ================================================================*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <setjmp.h>
#include <glib.h>
#include "basic.h"
#include "pools.h"
#include "values.h"
#include "symbols.h"
#include "patterns.h"
#include "files.h"
#include "malaga_files.h"
#include "rule_type.h"
#include "hangul.h"
#include "rules.h"

/* Types. ===================================================================*/

typedef struct /* Used to hold the value for a "switch". */
{
  list_node_t *next;
  symbol_t key; 
  value_t value; 
} switch_node_t;

typedef struct 
{ 
  list_node_t *next;
  int_t pc;
  int_t nested_subrules;
  int_t base; 
  int_t bottom;
} path_node_t;

/* Global variables. ========================================================*/

void (*add_end_state)( value_t feat );
void (*add_running_state)( value_t feat, int_t rule_set );
void (*add_allo)( string_t surf, value_t feat );
void (*debug_rule)( bool_t interrupt );
void (*transmit)( void );

rule_sys_t *executed_rule_sys;
int_t executed_rule_number = -1;
int_t pc = -1; /* Current instruction index. */
int_t base; /* Current frame base. */
int_t nested_subrules; /* Current nesting level. */
int_t path_count; /* Current number of alternative paths. */

rule_sys_t *debug_rule_sys;
bool_t rule_successful;

/* Variables. ===============================================================*/

static int_t bottom; /* Index of first stack element used in this branch. */

static list_t path_list; /* List of nodes for alternative paths. */

static list_t switch_list; /* The list of switches. */

static switch_node_t *current_switch; 
/* Needed for "get_first_switch" and "get_next_switch". */

/* Functions. ===============================================================*/

void 
set_switch( symbol_t key, value_t value )
/* Set the switch KEY to VALUE. */
{ 
  switch_node_t *node;
  switch_node_t *pre; /* predecessor node */
  string_t name = get_symbol_name( key );

  pre = NULL;
  FOREACH( node, switch_list )
  {
    if (strcmp_no_case( name, get_symbol_name( node->key ) ) <= 0) 
      break;
    pre = node;
  }
  if (node != NULL && key == node->key) 
    free_mem( &node->value );
  else 
  { 
    node = new_mem( sizeof( switch_node_t ) );
    node->key = key;
    insert_node( &switch_list, (list_node_t *) node, (list_node_t *) pre );
  }
  node->value = new_value( value );
}

/*---------------------------------------------------------------------------*/

value_t 
get_switch( symbol_t key )
/* Return the value of the switch KEY. 
 * Report an error if this switch doesn't exist. */
{ 
  switch_node_t *switch_node;

  FOREACH( switch_node, switch_list ) 
  { 
    if (switch_node->key == key) 
      return switch_node->value;
  }
  complain( "Switch \"%s\" is not defined.", get_symbol_name( key ) );
}

/*---------------------------------------------------------------------------*/

void
get_first_switch( value_t *value, symbol_t *key )
/* Return the first switch in *VALUE and its key in *KEY.
 * If there is no switch, *VALUE is NULL. */
{ 
  current_switch = (switch_node_t *) switch_list.first;
  get_next_switch( value, key );
}

/*---------------------------------------------------------------------------*/

void
get_next_switch( value_t *value, symbol_t *key )
/* Return the value of the next switch in *VALUE and its key in *KEY.
 * If there is no more switch, *VALUE is NULL. */
{ 
  if (current_switch == NULL) 
    *value = NULL;
  else 
  { 
    *key = current_switch->key;
    *value = current_switch->value;
    current_switch = (switch_node_t *) current_switch->next;
  }
}

/*---------------------------------------------------------------------------*/

void 
free_switches( void )
/* Free all switches. */
{ 
  switch_node_t *my_switch;

  FOREACH_FREE( my_switch, switch_list ) 
    free_mem( &my_switch->value );
}

/* Rule execution. ==========================================================*/

static void 
standard_function( int_t function )
/* Stack effect: VALUE -> NEW_VALUE.
 * Perform function FUNCTION on VALUE yielding NEW_VALUE. */
{ 
  char_t *buffer;
  string_t string;
  int_t start, end, len;
  gunichar code;

  switch (function) 
  {
  case FUNC_TO_ATOMS:
    push_value( get_atoms( value_to_symbol( value_stack[ --top ] ) ) );
    break;
  case FUNC_TO_MULTI:
    push_symbol_value( find_multi_symbol( value_stack[ --top ] ) );
    break;
  case FUNC_TO_SET:
    convert_list_to_set();
    break;
  case FUNC_IS_CAPITAL:
    code = g_utf8_get_char( value_to_string( value_stack[ --top ] ) );
    if (g_unichar_tolower( code ) != code) 
      push_symbol_value( YES_SYMBOL );
    else 
      push_symbol_value( NO_SYMBOL );
    break;
  case FUNC_GET_SWITCH:
    push_value( get_switch( value_to_symbol( value_stack[ --top ] ) ) );
    break;
  case FUNC_GET_LENGTH:
    if (get_value_type( value_stack[ top - 1 ] ) == STRING_SYMBOL)
    {
      push_number_value( 
	g_utf8_strlen( value_to_string( value_stack[ --top ] ), -1 ) );
    }
    else 
      push_number_value( get_list_length( value_stack[ --top ] ) );
    break;
  case FUNC_GET_VALUE_TYPE:
    push_symbol_value( get_value_type( value_stack[ --top ] ) );
    break;
  case FUNC_GET_VALUE_STRING:
    buffer = value_to_readable( value_stack[ --top ], TRUE, -1 );
    encode_hangul( &buffer );
    push_string_value( buffer, NULL );
    free_mem( &buffer );
    break;
  case FUNC_TRANSMIT:
    if (transmit == NULL) 
      complain( "No transmit function available." );
    transmit();
    break;
  case FUNC_FLOOR:
    push_number_value( floor( value_to_double( value_stack[ --top ] ) ) );
    break;
  case FUNC_SUBSTRING:
    string = value_to_string( value_stack[ top - 3 ] );
    start = value_to_int( value_stack[ top - 2 ] );
    if (value_stack[ top - 1 ] == NULL) 
      end = start;
    else 
      end = value_to_int( value_stack[ top - 1 ] );
    len = g_utf8_strlen( string, -1 );
    if (start < 0) 
      start += len + 1;
    if (end < 0) 
      end += len + 1;
    if (end < start) 
      push_string_value( "", NULL );
    else if (start <= 0 || end > len)
      complain( "Substring indexes out of bounds." );
    else 
      push_string_value( g_utf8_offset_to_pointer( string, start - 1 ), 
			 g_utf8_offset_to_pointer( string, end ) );
    value_stack[ top - 4 ] = value_stack[ top - 1 ];
    top -= 3;
    break;
  default:
    complain( "Internal error." );
  }
}

/*---------------------------------------------------------------------------*/

void 
execute_rule( rule_sys_t *rule_sys, int_t rule_number )
/* Execute rule RULE_NUMBER in the rule system RULE_SYS.
 * Any parameters must be on the value stack. */
{ 
  static symbol_t nil = NIL_SYMBOL; /* The "nil" symbol. */
  bool_t terminate; /* Shall we terminate the current rule internal path? */
  int_t i, info, new_pc, old_top, old_base, line;
  instr_t instruction;
  symbol_t symbol;
  path_node_t *path;
  string_t file;
          
  /* Initialise the value stack. */
  top = rule_sys->rules[ rule_number ].param_count;
  base = bottom = 0;

  /* Reset nesting and alternative paths. */
  nested_subrules = path_count = 0;
  while (path_list.first != NULL) 
    free_first_node( &path_list );

  /* Copy RULE_SYS and RULE_NUMBER for debugger and error messages. */
  executed_rule_sys = rule_sys;
  executed_rule_number = rule_number;

  pc = rule_sys->rules[ rule_number ].first_instr;
  TRY 
  { 
    rule_successful = FALSE;
    terminate = FALSE;
    while (! terminate) 
    { 
      if (user_break_requested) 
      {
	source_of_instr( rule_sys, pc, NULL, &file, NULL );
	if (file != NULL)
	{
	  if (debug_rule_sys == rule_sys) 
	    debug_rule( TRUE );
	  else 
	    complain( "User break." );
	}
      } 
      else if (debug_rule_sys == rule_sys) 
	debug_rule( FALSE );
      instruction = rule_sys->instrs[ pc ];
      new_pc = pc + 1;
      info = INSTR_INFO( instruction );
      switch (OPCODE( instruction ) ) 
      {
      case INS_SYSTEM_ERROR:
        switch (info) 
	{
        case ASSERTION_ERROR: 
	  complain( "Assertion failed." );
        case NO_RETURN_ERROR: 
	  complain( "Missing return statement." );
        }
      case INS_ERROR:
	complain( "%s", value_to_string( value_stack[ --top ] ) );
      case INS_TERMINATE:
        terminate = TRUE;
        break;
      case INS_NOP:
        break;
      case INS_TERMINATE_IF_NULL:
        if (value_stack[ --top ] == NULL) 
	  terminate = TRUE;
        break;
      case INS_ADD_END_STATE:
        add_end_state( value_stack[ --top ] );
        rule_successful = TRUE;
        break;
      case INS_ADD_STATE:
        add_running_state( value_stack[ --top ], info );
        rule_successful = TRUE;
        break;
      case INS_ADD_ALLO:
        add_allo( value_to_string( value_stack[ top - 2 ] ), 
		  value_stack[ top - 1] );
        top -= 2;
        rule_successful = TRUE;
        break;
      case INS_ACCEPT:
        path_count = 0;
        while (path_list.first != NULL) 
	  free_first_node( &path_list );
        terminate = TRUE;
        break;
      case INS_PUSH_NULL:
        for (i = 0; i < info; i++) 
	  push_value( NULL );
        break;
      case INS_PUSH_VAR:
        push_value( value_stack[ base + info ] );
        break;
      case INS_PUSH_CONST:
        push_value( rule_sys->values + info );
        break;
      case INS_PUSH_SYMBOL:
        push_symbol_value( info );
        break;
      case INS_PUSH_PATTERN_VAR:
        push_string_value( pattern_var[ info ], NULL );
        break;
      case INS_POP:
        top -= info;
        break;
      case INS_POP_TO:
        top = base + info;
        break;
      case INS_BUILD_LIST:
        build_list( info );
        break;
      case INS_BUILD_RECORD:
        build_record( info );
        break;
      case INS_BUILD_PATH:
        build_path( info );
        break;
      case INS_DOT_OPERATION:
        dot_operation();
        if (value_stack[ top - 1 ] == NULL) 
	  value_stack[ top - 1 ] = &nil;
        break;
      case INS_PLUS_OPERATION:
        plus_operation();
        break;
      case INS_MINUS_OPERATION:
        minus_operation();
        break;
      case INS_ASTERISK_OPERATION:
        asterisk_operation();
        break;
      case INS_SLASH_OPERATION:
        slash_operation();
        break;
      case INS_UNARY_MINUS_OP:
        unary_minus_operation();
        break;
      case INS_GET_ATTRIBUTE:
        value_stack[ top - 1 ] = get_attribute( value_stack[ top - 1 ], 
						(symbol_t) info );
        if (value_stack[ top - 1 ] == NULL) 
	  value_stack[ top - 1 ] = &nil;
        break;
      case INS_REMOVE_ATTRIBUTE:
        remove_attribute( (symbol_t) info );
        break;
      case INS_STD_FUNCTION:
        standard_function( info );
        break;
      case INS_MATCH:
        if (match_pattern( value_to_string( value_stack[ --top ] ), 
                           rule_sys->strings + info )) 
	{ 
	  push_symbol_value( YES_SYMBOL ); 
	} 
	else 
	  push_symbol_value( NO_SYMBOL );
        break;
      case INS_SET_VAR:
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_PLUS_VAR:
        insert_value( 1, value_stack[ base + info ] );
        plus_operation();
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_MINUS_VAR:
        insert_value( 1, value_stack[ base + info ]);
        minus_operation();
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_ASTERISK_VAR:
        insert_value( 1, value_stack[ base + info ] );
        asterisk_operation();
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_SLASH_VAR:
        insert_value( 1, value_stack[ base + info ] );
        slash_operation();
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_SET_VAR_PATH:
        insert_value( 2, value_stack[ base + info ] );
        modify_value_part( right_value );
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_PLUS_VAR_PATH:
        insert_value( 2, value_stack[ base + info ] );
        modify_value_part( plus_operation );
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_MINUS_VAR_PATH:
        insert_value( 2, value_stack[ base + info ] );
        modify_value_part( minus_operation );
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_ASTERISK_VAR_PATH:
        insert_value( 2, value_stack[ base + info ] );
        modify_value_part( asterisk_operation );
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_SLASH_VAR_PATH:
        insert_value( 2, value_stack[ base + info ] );
        modify_value_part( slash_operation );
        value_stack[ base + info ] = value_stack[ --top ];
        break;
      case INS_DECOMPOSE_LIST:
        if (info != decompose_list()) 
	  complain( "List doesn't contain %d elements.", info );
        break;
      case INS_GET_1ST_ELEMENT:
        get_first_element();
        break;
      case INS_ITERATE:
        get_next_element( base + info );
        break;
      case INS_JUMP:
        new_pc = info;
        break;
      case INS_JUMP_IF_EQUAL:
	if (values_equal( value_stack[top - 2], value_stack[top - 1] )) 
	  new_pc = info;
        top -= 2;
        break;
      case INS_JUMP_IF_NOT_EQUAL:
        if (! values_equal( value_stack[top - 2], value_stack[top - 1] )) 
	  new_pc = info;
        top -= 2;
        break;
      case INS_JUMP_IF_CONGR:
        if (values_congruent( value_stack[top - 2], value_stack[top - 1] ))
	  new_pc = info;
        top -= 2;
        break;
      case INS_JUMP_IF_NOT_CONGR:
        if (! values_congruent( value_stack[top - 2], value_stack[top - 1] ))
	  new_pc = info;
        top -= 2;
        break;
      case INS_JUMP_IF_IN:
        if (value_in_value( value_stack[top - 2], value_stack[top - 1] )) 
	  new_pc = info;
        top -= 2;
        break;
      case INS_JUMP_IF_NOT_IN:
        if (! value_in_value( value_stack[top - 2], value_stack[top - 1] )) 
	  new_pc = info;
        top -= 2;
        break;
      case INS_JUMP_IF_LESS:
        if (value_to_double( value_stack[top - 2] ) 
            < value_to_double( value_stack[top - 1] )) 
	{ 
	  new_pc = info; 
	}
        top -= 2;
        break;
      case INS_JUMP_IF_NOT_LESS:
        if (! (value_to_double( value_stack[top - 2] )
               < value_to_double( value_stack[top - 1] ))) 
	{ 
	  new_pc = info; 
	}
        top -= 2;
        break;
      case INS_JUMP_IF_GREATER:
        if (value_to_double( value_stack[top - 2] )
            > value_to_double( value_stack[top - 1] )) 
	{ 
	  new_pc = info; 
	}
        top -= 2;
        break;
      case INS_JUMP_IF_NOT_GREATER:
        if (! (value_to_double( value_stack[top - 2] )
               > value_to_double( value_stack[top - 1] ))) 
	{ 
	  new_pc = info; 
	}
        top -= 2;
        break;
      case INS_JUMP_IF_NULL:
        if (value_stack[ --top ] == NULL) 
	  new_pc = info;
        break;
      case INS_JUMP_IF_NOT_NULL:
        if (value_stack[ --top ] != NULL) 
	  new_pc = info;
        break;
      case INS_JUMP_IF_YES:
        symbol = value_to_symbol( value_stack[ --top ] );
        if (symbol != YES_SYMBOL && symbol != NO_SYMBOL) 
	  complain( "Boolean value expected." );
        if (symbol == YES_SYMBOL) 
	  new_pc = info;
        break;
      case INS_JUMP_IF_NO:
        symbol = value_to_symbol( value_stack[ --top ] );
        if (symbol != YES_SYMBOL && symbol != NO_SYMBOL) 
	  complain( "Boolean value expected." );
        if (symbol == NO_SYMBOL) 
	  new_pc = info;
        break;
      case INS_JUMP_NOW:
        old_top = top;
        path = new_node( &path_list, sizeof( path_node_t ), LIST_START );
        path->pc = new_pc;
        path->nested_subrules = nested_subrules;
        path->base = base;
        path->bottom = bottom;
        while (bottom < old_top)
	  push_value( value_stack[ bottom++ ] );
        base += (top - old_top);
        path_count++;
        new_pc = info;
        break;
      case INS_JUMP_LATER:
        old_top = top;
        path = new_node( &path_list, sizeof( path_node_t ), LIST_START );
        path->pc = info;
        path->nested_subrules = nested_subrules;
        path->base = base;
        path->bottom = bottom;
        while (bottom < old_top) 
	  push_value( value_stack[ bottom++ ] );
        base += (top - old_top);
        path_count++;
        break;
      case INS_JUMP_SUBRULE:
        push_number_value( base - bottom );
        push_number_value( new_pc );
        base = top;
        new_pc = rule_sys->rules[ info ].first_instr;
        nested_subrules++;
        break;
      case INS_RETURN:
        old_base = bottom + value_to_int( value_stack[ base - 2 ] );
        new_pc = value_to_int( value_stack[ base - 1 ] );
        value_stack[ base - info - 2 ] = value_stack[ top - 1 ]; /* Result. */
        top = base - (info + 1);
        base = old_base;
        nested_subrules--;
        break;
      default:
        complain( "Internal error." );
        break;
      }
      if (! terminate) 
	pc = new_pc;
      else if (path_list.first != NULL) 
      { 
	/* Load a previously saved rule-internal path and continue. */
        path_count--;
        path = (path_node_t *) path_list.first;
        top = bottom;
        base = path->base;
        bottom = path->bottom;
        pc = path->pc;
        nested_subrules = path->nested_subrules;
        free_first_node( &path_list );
        terminate = FALSE;
      }
    }
  } 
  IF_ERROR 
  { 
    source_of_instr( executed_rule_sys, pc, &line, &file, NULL );
    print_text( error_text, " (\"%s\", line %d)", name_in_path( file ), line );
    if (in_emacs_malaga_mode) 
      printf( "SHOW \"%s\":%d:0\n", file, line );
  } 
  END_TRY;

  pc = -1;
  executed_rule_number = -1;
  executed_rule_sys = NULL;
}

/*---------------------------------------------------------------------------*/

rule_sys_t *
read_rule_sys( string_t file_name )
/* Read rule system from file FILE_NAME.
 * A symbol file must have already been loaded. */
{ 
  FILE *stream;
  rule_header_t header;
  rule_sys_t *rule_sys;

  stream = open_stream( file_name, "rb" );
  read_vector( &header, sizeof( header ), 1, stream, file_name );
  check_header( &header.common_header, file_name, 
                RULE_FILE, MIN_RULE_CODE_VERSION, RULE_CODE_VERSION );

  rule_sys = new_mem( sizeof( rule_sys_t ) );
  rule_sys->initial_rule_set = header.initial_rule_set;
  rule_sys->initial_feat = header.initial_feat;
  rule_sys->robust_rule = header.robust_rule;
  rule_sys->allo_rule = header.allo_rule;
  rule_sys->pruning_rule = header.pruning_rule;
  rule_sys->input_filter = header.input_filter;
  rule_sys->output_filter = header.output_filter;
  rule_sys->rule_count = header.rule_count;
  rule_sys->rules = read_new_vector( sizeof( rule_t ), header.rule_count,
                                     stream, file_name );
  rule_sys->rule_sets_size = header.rule_sets_size;
  rule_sys->rule_sets = read_new_vector( sizeof( int_t ), 
					 header.rule_sets_size,
                                         stream, file_name );
  rule_sys->instr_count = header.instr_count;
  rule_sys->instrs = read_new_vector( sizeof( instr_t ), header.instr_count, 
                                      stream, file_name );
  rule_sys->values_size = header.values_size;
  rule_sys->values = read_new_vector( sizeof( cell_t ), header.values_size, 
                                      stream, file_name );
  rule_sys->src_line_count = header.src_line_count;
  rule_sys->src_lines = read_new_vector( sizeof( src_line_t ), 
                                         header.src_line_count, 
                                         stream, file_name );
  rule_sys->var_count = header.var_count;
  rule_sys->vars = read_new_vector( sizeof( var_t ), header.var_count,
                                    stream, file_name );
  rule_sys->var_scope_count = header.var_scope_count;
  rule_sys->var_scopes = read_new_vector( sizeof( var_scope_t ), 
                                          header.var_scope_count,
                                          stream, file_name );
  rule_sys->constant_count = header.constant_count;
  rule_sys->constants = read_new_vector( sizeof( constant_t ), 
					 header.constant_count, 
					 stream, file_name );
  rule_sys->strings_size = header.strings_size;
  rule_sys->strings = read_new_vector( sizeof( char_t ), header.strings_size, 
                                       stream, file_name );

  close_stream( &stream, file_name );
  return rule_sys;
}

/*---------------------------------------------------------------------------*/

void 
free_rule_sys( rule_sys_t **rule_sys )
/* Free all memory used by *RULE_SYS. */
{ 
  if (*rule_sys != NULL) 
  { 
    free_mem( &(*rule_sys)->rules );
    free_mem( &(*rule_sys)->rule_sets );
    free_mem( &(*rule_sys)->instrs );
    free_mem( &(*rule_sys)->values );
    free_mem( &(*rule_sys)->src_lines );
    free_mem( &(*rule_sys)->vars );
    free_mem( &(*rule_sys)->var_scopes );
    free_mem( &(*rule_sys)->constants );
    free_mem( &(*rule_sys)->strings );
    free_mem( rule_sys );
  }
}

/* Debug support functions. =================================================*/

void 
source_of_instr( rule_sys_t *rule_sys, int_t instr_index, 
                 int_t *line, string_t *file_name, string_t *rule_name )
/* Set *LINE, *FILE_NAME and *RULE_NAME to appropriate values
 * for the statement that has generated the instruction at INSTR_INDEX. */
{ 
  int_t lower, upper, middle;
  src_line_t *src_line;
  int_t rule_number, i, first_instr;
  rule_t *rule;
    
  if (rule_sys->src_line_count == 0 
      || rule_sys->src_lines[0].instr > instr_index) 
  { 
    if (line != NULL) 
      *line = -1;
    if (file_name != NULL) 
      *file_name = NULL;
    if (rule_name != NULL) 
      *rule_name = NULL;
    return;
  }
  /* Find the last SRC_LINE entry with INSTR <= INSTR_INDEX. */
  lower = 0;
  upper = rule_sys->src_line_count - 1;
  while (lower < upper) 
  { 
    middle = (lower + upper + 1) / 2;
    src_line = rule_sys->src_lines + middle;
    if (src_line->instr <= instr_index) 
      lower = middle;
    else 
      upper = middle - 1;
  }
  src_line = rule_sys->src_lines + lower;
  if (line != NULL)  
    *line = src_line->line;
  if (file_name != NULL) 
  { 
    if (src_line->file != -1) 
      *file_name = rule_sys->strings + src_line->file;
    else 
      *file_name = NULL;
  }

  /* Find the rule of the statement */
  if (rule_name != NULL) 
  { 
    rule_number = 0;
    first_instr = -1;
    for (i = 0; i < rule_sys->rule_count; i++) 
    { 
      rule = rule_sys->rules + i;
      if (rule->first_instr <= instr_index && rule->first_instr > first_instr)
      { 
	rule_number = i;
        first_instr = rule->first_instr;
      }
    }
    *rule_name = rule_sys->strings + rule_sys->rules[ rule_number ].name;
  }
}

/*---------------------------------------------------------------------------*/

string_t 
rule_set_readable( rule_sys_t *rule_sys, int_t rule_set )
/* Return RULE_SET in RULE_SYS as a readable string.
 * The string must be freed after use. */
{ 
  text_t *text;
  bool_t name_has_been_printed;
  int_t *rule;
      
  text = new_text();
  if (rule_set == -1) 
    add_to_text( text, "(end state)" );
  else 
  { 
    add_to_text( text, "rules " );
    rule = rule_sys->rule_sets + rule_set;
    while (TRUE) 
    { 
      name_has_been_printed = FALSE;
      while (*rule >= 0) 
      { 
	if (name_has_been_printed) 
	  add_to_text( text, ", " );
	else 
	  name_has_been_printed = TRUE;
        add_to_text( text, 
		     rule_sys->strings + rule_sys->rules[ *rule++ ].name );
      }
      if (*rule == -1) 
	break;
      add_to_text( text, " else " );
      rule++;
    }
  }
  return text_to_string( &text );
}

/*---------------------------------------------------------------------------*/

static int_t 
first_variable_index( int_t instr_index )
/* Return the stack index of the first variable that is visible
 * when PC is at INSTR_INDEX in RULE_SYS. */
{ 
  rule_t *rule, *rule2;
  int_t i, first_instr;

  /* Find the rule/subrule we're in. */
  rule = NULL;
  first_instr = -1;
  for (i = 0; i < executed_rule_sys->rule_count; i++) 
  { 
    rule2 = executed_rule_sys->rules + i;
    if (rule2->first_instr <= instr_index && rule2->first_instr > first_instr)
    { 
      rule = rule2;
      first_instr = rule->first_instr;
    }
  }

  if (rule->type == SUBRULE) 
    return - (2 + rule->param_count);
  else 
    return 0;
}

/*---------------------------------------------------------------------------*/

int_t 
get_frame_count( void )
/* Get the number of frames in the current path. */
{ 
  int_t frame, count;

  /* Count the number of frames. */
  count = 1;
  for (frame = base; 
       frame > bottom; 
       frame = bottom + value_to_int( value_stack[ frame - 2 ] ))
  {
    count++;
  }
  return count;
}

/*---------------------------------------------------------------------------*/

void
get_frame_info( int_t frame, 
		int_t *pc_index, 
		int_t *base_index,
		int_t *first_var_index,
		int_t *last_var_index )
/* Return *PC_INDEX, *BASE_INDEX, *FIRST_VAR_INDEX and *LAST_VAR_INDEX
 * of the frame no. FRAME. Any result pointer may be NULL. 
 * Frame no. 0 is the current frame, 
 * frame no. "get_frame_count() - 1" is the outermost one. */
{ 
  int_t first_var_idx, last_var_idx, base_idx, pc_idx;

  last_var_idx = top;
  pc_idx = pc;
  base_idx = base;
  first_var_idx = base_idx + first_variable_index( pc_idx );
  
  /* Find the right frame. */
  for (; frame > 0; frame--)
  {
    last_var_idx = first_var_idx;
    pc_idx = value_to_int( value_stack[ base_idx - 1 ] );
    base_idx = bottom + value_to_int( value_stack[ base_idx - 2 ] );
    first_var_idx = base_idx + first_variable_index( pc_idx );
  }

  if (pc_index != NULL) 
    *pc_index = pc_idx;
  if (base_index != NULL) 
    *base_index = base_idx;
  if (first_var_index != NULL) 
    *first_var_index = first_var_idx;
  if (last_var_index != NULL) 
    *last_var_index = last_var_idx;
}

/*---------------------------------------------------------------------------*/

static var_scope_t *
get_var_scope( rule_sys_t *rule_sys, var_t *var, int_t instr_index )
/* Return the stack index of variable VAR at INSTR_INDEX.
 * Return -1 if it is not currently defined. */
{ 
  int_t lower, upper, middle;
  var_scope_t *var_scope;

  /* Find last scope whose FIRST_INSTR is not higher than INSTR_INDEX. */ 
  lower = var->first_scope;
  upper = lower + var->scope_count - 1;

  while (lower < upper) 
  { 
    middle = (lower + upper + 1) / 2;
    var_scope = rule_sys->var_scopes + middle;
    if (var_scope->first_instr <= instr_index) 
      lower = middle;
    else 
      upper = middle - 1;
  }

  /* LOWER is the index of the highest line
   * with an instruction index not more than INSTR_INDEX. */
  if (lower == upper) /* We found a scope. */
  { 
    var_scope = rule_sys->var_scopes + lower;
    if (instr_index >= var_scope->first_instr 
        && instr_index <= var_scope->last_instr) 
    { 
      return var_scope; 
    }
  }
  
  return NULL;
}

/*---------------------------------------------------------------------------*/

string_t 
variable_at_index( rule_sys_t *rule_sys, int_t stack_index, int_t instr_index )
/* Return the name of the variable that is defined at STACK_INDEX
 * when instruction INSTR_INDEX is executed or NULL if there is none. */
{ 
  int_t i;
  var_t *var;
  var_scope_t *var_scope;

  /* There is never a variable at stack index -2 or -1. */
  if (stack_index == -2 || stack_index == -1) 
    return NULL;

  /* For each variable name, test if it is the right one. */
  for (i = 0; i < rule_sys->var_count; i++)  
  { 
    var = rule_sys->vars + i;
    var_scope = get_var_scope( rule_sys, var, instr_index );
    if (var_scope != NULL && var_scope->stack_index == stack_index) 
      return rule_sys->strings + var->name;
  }

  return NULL;
}

/*---------------------------------------------------------------------------*/

var_scope_t *
get_var_scope_by_name( rule_sys_t *rule_sys, 
		       string_t var_name, 
		       int_t instr_index )
/* Return the scope of variable VAR_NAME at INSTR_INDEX. */
{ 
  int_t lower, upper, middle, result;
  var_t *var;

  /* Search for the right variable name (binary search). */
  lower = 0;
  upper = rule_sys->var_count - 1;
  while (lower <= upper) 
  { 
    middle = (lower + upper) / 2;
    var = rule_sys->vars + middle;
    result = strcmp_no_case( var_name, rule_sys->strings + var->name );
    if (result < 0) 
      upper = middle - 1;
    else if (result > 0) 
      lower = middle + 1;
    else 
      return get_var_scope( rule_sys, var, instr_index );
  }
  return NULL;
}

/*---------------------------------------------------------------------------*/

value_t
get_constant( rule_sys_t *rule_sys, string_t name )
/* Return the value of constant NAME in RULE_SYS.
 * Return NULL if there is no such constant. */
{ 
  int_t lower, upper, middle, result;
  constant_t *constant;

  /* Search for the right variable name (binary search). */
  lower = 0;
  upper = rule_sys->constant_count - 1;
  while (lower <= upper) 
  { 
    middle = (lower + upper) / 2;
    constant = rule_sys->constants + middle;
    result = strcmp_no_case( name, rule_sys->strings + constant->name );
    if (result < 0) 
      upper = middle - 1;
    else if (result > 0) 
      lower = middle + 1;
    else 
      return rule_sys->values + constant->value;
  }
  return NULL;
}

/* End of file. =============================================================*/