/* Copyright (C) 1995 Gerald Schueller. */
/* Copyright (C) 1995 Bjoern Beutel. */

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

/* In this file, the administration of breakpoints is managed. */

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

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
#include <glib.h>
#include "basic.h"
#include "pools.h"
#include "values.h"
#include "files.h"
#include "rule_type.h"
#include "rules.h"
#include "input.h"
#include "commands.h"
#include "breakpoints.h"

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

typedef struct /* Definition of a breakpoint. */
{ 
  list_node_t *next;
  int_t number; /* Breakpoint number. */
  rule_sys_t *rule_sys; /* Rule system of breakpoint. */
  int_t instr; /* Instruction to break at. */
} breakpoint_t;

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

static int_t breakpoint_count; /* Number of breakpoints so far. */
static list_t breakpoints; /* List of breakpoints. */

static int_t rule_system_count; /* Number of loaded rule systems. */
static rule_sys_name_t *rule_systems; /* Name for each rule system. */

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

static int_t 
instruction_at( rule_sys_t *rule_sys, string_t file, int_t line )
/* Return the index of the first instruction at RULE_SYS, FILE, LINE
 * or -1 if there is no code there. */
{ 
  src_line_t *src_line;
  
  for (src_line = rule_sys->src_lines; 
       src_line < rule_sys->src_lines + rule_sys->src_line_count; 
       src_line++) 
  { 
    if (src_line->file != -1 
        && strcmp( rule_sys->strings + src_line->file, file ) == 0 
        && src_line->line >= line) 
    { 
      return src_line->instr; 
    }
  }
  return -1;
}

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

static int_t 
find_rule_by_name( rule_sys_t *rule_sys, string_t rule_name )
/* Find the first line of rule RULE_NAME in RULE_SYS.
 * Return the first instruction of this rule. */
{ 
  int_t i;
  
  for (i = 0; i < rule_sys->rule_count; i++) 
  { 
    if (strcmp_no_case( rule_sys->strings + rule_sys->rules[i].name,
                        rule_name ) == 0) 
    { 
      return rule_sys->rules[i].first_instr; 
    }
  }
  return -1;
}

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

static string_t 
complete_file_name( rule_sys_t *rule_sys, string_t file_name )
/* Return FILE_NAME completed if it is in RULE_SYS, else return NULL. */
{ 
  src_line_t *src_line;
  
  for (src_line = rule_sys->src_lines;
       src_line < rule_sys->src_lines + rule_sys->src_line_count; 
       src_line++) 
  { 
    if (src_line->file != -1 
        && strcmp( name_in_path( rule_sys->strings + src_line->file ),
		   file_name ) == 0) 
    { 
      return rule_sys->strings + src_line->file; 
    }
  }
  return NULL;
}

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

static void 
parse_breakpoint( string_t arguments, 
                  rule_sys_t **rule_sys, 
                  string_t *file, 
                  int_t *line )
/* Parse a breakpoint specification; ARGUMENTS must be
 * "" (nothing) or
 * "LINE_NUMBER" or
 * "[RULE_SYS_NAME] [FILE LINE_NUMBER | RULE_NAME]".
 * Set RULE_SYS, FILE and LINE according to it. */
{ 
  int_t first_instr;
  string_t argument;
  int_t i;
      
  if (pc == -1) 
  { 
    *rule_sys = NULL; 
    *file = NULL; 
    *line = -1; 
  } 
  else 
  { 
    *rule_sys = executed_rule_sys;
    source_of_instr( executed_rule_sys, pc, line, file, NULL );
  }
  if (*arguments >= '0' && *arguments <= '9') 
    *line = parse_cardinal( &arguments );
  else if (*arguments != EOS) /* Read file name or rule name. */  
  { 
    argument = parse_word( &arguments );

    /* See if ARGUMENT is a rule system name. */
    for (i = 0; i < rule_system_count; i++) 
    { 
      if (rule_systems[i].rule_sys != NULL
	  && strcmp_no_case( argument, rule_systems[i].name ) == 0) 
      { 
	*rule_sys = rule_systems[i].rule_sys;
        free_mem( &argument );
        argument = parse_word( &arguments );
        break;
      }
    } 

    /* If a line number follows, we have a file name. */
    if (*arguments >= '0' && *arguments <= '9') 
    { 
      *line = parse_cardinal( &arguments );
      if (*rule_sys != NULL) 
	*file = complete_file_name( *rule_sys, argument );
      else 
      { 
	for (i = 0; i < rule_system_count; i++) 
        { 
	  if (rule_systems[i].rule_sys != NULL) 
	  { 
	    *rule_sys = rule_systems[i].rule_sys;
            *file = complete_file_name( *rule_sys, argument );
            if (*file != NULL) 
	      break;
          }
        }
      }
      if (*file == NULL) 
	complain( "No source file \"%s\".", argument );
    } 
    else /* ARGUMENT should be a rule name. */
    { 
      if (*rule_sys != NULL) 
	first_instr = find_rule_by_name( *rule_sys, argument );
      else 
      { 
	first_instr = -1; /* Prevent a "not initialized" warning. */
        for (i = 0; i < rule_system_count; i++) 
	{ 
	  if (rule_systems[i].rule_sys != NULL) 
	  { 
	    *rule_sys = rule_systems[i].rule_sys;
            first_instr = find_rule_by_name( *rule_sys, argument );
            if (first_instr != -1) 
	      break;
          }
        }
      }
      if (first_instr == -1) 
	complain( "Rule \"%s\" is unknown.", argument );
      
      /* Find the corresponding source line. */
      source_of_instr( *rule_sys, first_instr, line, file, NULL );
    }
    free_mem( &argument );
  }
  parse_end( &arguments );
  
  if (*file == NULL) 
    complain( "Missing file name." );
  if (*line == -1) 
    complain( "Missing line number." );
}

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

int_t 
at_breakpoint( rule_sys_t *rule_sys, int_t instr )
/* Return breakpoint number if INSTR in RULE_SYS hits a 
 * breakpoint; return 0 else. */
{ 
  breakpoint_t *bp;
  
  FOREACH( bp, breakpoints ) 
  { 
    if (bp->rule_sys == rule_sys && bp->instr == instr) 
      return bp->number;
  }
  return 0;
}

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

static void 
delete_all_breakpoints( void )
/* Run through breakpoint list and free all breakpoints. */
{ 
  while (breakpoints.first != NULL) 
    free_first_node( &breakpoints );
}

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

static void 
do_delete( string_t argument )
/* Remove a breakpoint. */
{ 
  string_t word;
  int_t break_num;
  breakpoint_t *breakpoint;

  if (*argument >= '0' && *argument <= '9')
  { 
    while (*argument != EOS) 
    { 
      break_num = parse_cardinal( &argument );

      /* Delete breakpoint with BREAK_NUM. */
      FOREACH( breakpoint, breakpoints ) 
      { 
	if (breakpoint->number == break_num) 
	  break;
      }
      if (breakpoint == NULL) 
	complain( "No breakpoint %d.", break_num );
      free_node( &breakpoints, (list_node_t *) breakpoint );
    }
  }
  else
  { 
    word = parse_word( &argument );
    if (strcmp_no_case( word, "all" ) != 0) 
      complain( "\"all\" or breakpoint numbers expected, not \"%s\".", word );
    delete_all_breakpoints();
  } 
  parse_end( &argument );
}

command_t delete_command = 
{ 
  "delete d", do_delete,
  "Delete breakpoints.\n"
  "Usage:\n"
  "  delete NUMBER ... -- Delete specified breakpoints.\n"
  "  delete all -- Delete all breakpoints.\n"
};

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

void 
get_breakpoint( string_t argument, 
		rule_sys_t **rule_sys_p, int_t *instr_p )
/* Parse a breakpoint in ARGUMENT and set the remaining arguments. */
{ 
  string_t file;
  int_t line;

  /* Parse breakpoint ARGUMENT. */
  parse_breakpoint( argument, rule_sys_p, &file, &line );

  /* Find first instruction of this breakpoint */
  *instr_p = instruction_at( *rule_sys_p, file, line );
  if (*instr_p == -1) 
    complain( "No code at file \"%s\", line %d.", name_in_path( file ), line );
}

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

static void 
do_break( string_t argument )
/* Define a breakpoint. */
{ 
  rule_sys_t *rule_sys;
  string_t file, rule;
  int_t line, instr;
  breakpoint_t *breakpoint;

  /* Parse breakpoint ARGUMENT. */
  get_breakpoint( argument, &rule_sys, &instr );

  /* Check if other breakpoints exist at this position. */
  FOREACH( breakpoint, breakpoints ) 
  { 
    if (breakpoint->rule_sys == rule_sys && breakpoint->instr == instr) 
      complain( "Breakpoint %d already set here.", breakpoint->number );
  }

  /* Add breakpoint. */
  breakpoint = new_node( &breakpoints, sizeof( breakpoint_t ), LIST_END );
  breakpoint->number = ++breakpoint_count;
  breakpoint->rule_sys = rule_sys;
  breakpoint->instr = instr;

  /* Print source position of the instruction. */
  source_of_instr( rule_sys, instr, &line, &file, &rule );
  printf( "Breakpoint %d in file \"%s\", line %d, rule \"%s\".\n",
          breakpoint->number, name_in_path( file ), line, rule );
}

command_t break_command = 
{ 
  "break b", do_break,
  "Set a breakpoint at the specified position.\n"
  "Usage:\n"
  "  break RULE -- Set a breakpoint at the beginning of RULE.\n"
  "  break FILE LINE -- Set a breakpoint at LINE in FILE.\n"
  "  break LINE -- Set a breakpoint at LINE in the current rule file.\n"
  "  break -- Set a breakpoint at the current line in the current rule file.\n"
  "The first two forms may begin with a rule system specification.\n"
  "The last two forms can only be used in debug mode or after a rule error.\n"
  "You can't set two breakpoints at the same position.\n"
};

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

static void 
do_list( string_t argument )
/* List breakpoints. */
{ 
  breakpoint_t *breakpoint;
  string_t file, rule;
  int_t line;

  parse_end( &argument );

  if (breakpoints.first == NULL) 
    printf( "No breakpoints.\n" );
  FOREACH( breakpoint, breakpoints )
  { 
    source_of_instr( breakpoint->rule_sys, breakpoint->instr, 
		     &line, &file, &rule );
    printf( "Breakpoint %d in file \"%s\", line %d, rule \"%s\".\n", 
            breakpoint->number, name_in_path( file ), line, rule );
  }
}

command_t list_command = 
{ 
  "list l", do_list,
  "List all breakpoints.\n"
  "Usage: list\n"
};

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

void 
init_breakpoints( int_t rule_sys_count, rule_sys_name_t rule_sys[] )
/* Initialise this module. 
 * Pass the number of rule systems in RULE_SYS_COUNT
 * and their names in RULE_SYS. */
{ 
  rule_system_count = rule_sys_count;
  rule_systems = rule_sys;
  clear_list( &breakpoints );
}

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

void 
terminate_breakpoints( void )
/* Terminate this module. */
{ 
  delete_all_breakpoints(); 
}

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