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

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

/* Debugger functions. */

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

#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "basic.h"
#include "pools.h"
#include "values.h"
#include "symbols.h"
#include "files.h"
#include "input.h"
#include "commands.h"
#include "commands_interactive.h"
#include "options.h"
#include "rule_type.h"
#include "rules.h"
#include "display.h"
#include "breakpoints.h"
#include "value_parser.h"
#include "scanner.h"
#include "debugger.h"

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

bool_t in_debugger;

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

static debug_mode_t debug_mode = RUN_MODE; /* Current debug mode. */

static struct /* Definition of the step properties. */
{ 
  /* Information for NEXT_MODE */
  int_t nested_subrules; /* Depth of subrule nesting when stepping started. */
  int_t path_count; /* Number of inactive paths when stepping started. */

  /* Information for NEXT_MODE and STEP_MODE. */
  int_t line; /* Line where stepping started. */
  string_t file; /* File where stepping started. */

  /* Local breakpoint. */
  int_t break_instr; /* Instr index of local breakpoint. */

  /* Local watchpoint. */
  var_scope_t *var_scope;
  value_t path, value;
} info;

static int_t debug_frame; /* Number of the current frame to be debugged. */

static void (*display_where)( void ); 
/* Pointer to a function that displays where rule execution now stands. */

static command_t **debugger_commands;
/* Commands that can be executed when the debug mode is invoked. */

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

static void
parse_path( void )
/* Stack effects: (nothing) -> NEW_LIST.
 * Parse a PATH (use scanner input) and leave it on the stack.
 * PATH ::= {"." (SYMBOL | NUMBER)} .
 * NEW_LIST is the path converted to a list. */
{
  int_t n;

  n = 0;
  while (next_token == '.') 
  { 
    read_next_token();
    switch (next_token) 
    {
    case TOK_NUMBER:
      if ((int_t) token_number != token_number || token_number == 0.0) 
	complain( "Index must be non-null integer." );
      push_number_value( token_number );
      read_next_token();
      break;
    case TOK_IDENT:
      push_symbol_value( find_symbol( token_name ) );
      read_next_token();
      break;
    default:
      complain( "Index or symbol expected." );
    }
    n++;
  }
  build_list(n);
}

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

static bool_t 
debug_now( rule_sys_t *rule_sys, int_t pos )
/* Test whether debug command loop should be entered now. */
{ 
  int_t line, instr;
  string_t file;
  value_t value;
  volatile bool_t condition;

  switch (debug_mode) 
  {
  case NEXT_MODE:
    if (path_count < info.path_count) 
    { 
      /* Current path is finished. Debug older path. */
      info.nested_subrules = nested_subrules;
      info.path_count = path_count;
    }
    if (nested_subrules <= info.nested_subrules) 
      goto STEP_MODE;
    return FALSE;
  case STEP_MODE: 
    STEP_MODE:
    source_of_instr( rule_sys, pos, &line, &file, NULL );
    condition = ((line != -1 || file != NULL) 
		 && (info.line != line || info.file != file)); 

    /* If this path terminates, let the debugger stop at next source line. */
    instr = OPCODE( rule_sys->instrs[ pos ] );
    if (instr == INS_TERMINATE 
	|| (instr == INS_TERMINATE_IF_NULL && value_stack[ top - 1 ] == NULL))
    {
      info.line = -1;
      info.file = NULL;
    }
    else if (condition)
    {
      info.nested_subrules = nested_subrules;
      info.line = line; 
      info.file = file;
    }
    return condition;
  case FINISH_MODE:
    instr = OPCODE( rule_sys->instrs[ pos ] );
    return ((instr == INS_TERMINATE_IF_NULL && value_stack[ top - 1 ] == NULL)
	    || (instr == INS_RETURN && info.nested_subrules == nested_subrules)
	    || instr == INS_TERMINATE);
  case WALK_MODE:
    return (rule_sys->rules[ executed_rule_number ].first_instr == pos);
  case GO_MODE:
    if (info.break_instr == pos) 
      return TRUE;

    /* Check if watch condition holds. */
    if (info.var_scope == NULL
	|| pos < info.var_scope->first_instr 
	|| pos >= info.var_scope->last_instr) 
    {
      return FALSE;
    }
    condition = FALSE;
    TRY 
    { 
      value = get_value_part( 
	value_stack[ base + info.var_scope->stack_index ], info.path );
      if (value != NULL) 
	condition = values_equal( value, info.value );
    } 
    IF_ERROR 
      RESUME;
    END_TRY;
    if (! condition) 
      return FALSE;
    source_of_instr( rule_sys, pos, &line, NULL, NULL );
    return (line /= -1);
  default:
    return FALSE;
  }
}

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

void 
set_debug_mode( debug_mode_t new_debug_mode, rule_sys_t *rule_sys )
/* Set debug mode NEW_DEBUG_MODE for RULE_SYS. */
{ 
  if (new_debug_mode == RUN_MODE) 
    debug_rule_sys = NULL;
  else 
    debug_rule_sys = rule_sys;
  debug_mode = new_debug_mode;
  debug_frame = 0;
}

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

debug_mode_t 
get_debug_mode( void )
/* Get the current debug mode. */
{
  return debug_mode;
}

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

static void 
display_variables( void )
{ 
  string_t value_string, var_name;
  int_t i, first_var, last_var, pc_index, base_index;
  value_t value;

  if (use_display) 
  { 
    start_display_process();
    fprintf( display_stream, "variables\n" );
  }
  get_frame_info( debug_frame, &pc_index, &base_index, &first_var, &last_var );
  for (i = first_var; i < last_var; i++) 
  { 
    var_name = variable_at_index( executed_rule_sys, i - base_index, 
				  pc_index );
    value = value_stack[i];
    if (var_name != NULL && value != NULL) 
    { 
      if (use_display) 
      { 
	value_string = value_to_readable( value, FALSE, -1 );
        fprintf( display_stream, "\"$%s\" {%s}\n", var_name, value_string );
        free_mem( &value_string );
      } 
      else 
      { 
	value_string = value_to_readable( value, FALSE, 
					  g_utf8_strlen( var_name, -1 ) + 4 );
        printf( "$%s = %s\n", var_name, value_string );
        free_mem( &value_string );
      }
    }
  }
  if (use_display) 
  { 
    fprintf( display_stream, "end\n" );
    fflush( display_stream );
  }
}

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

static void 
debugger_debug_rule( bool_t interrupt )
/* Called from "execute_rule" before instruction at PC is executed. 
 * If INTERRUPT == TRUE, we have been called by an interrupt. */
{ 
  int_t breakpoint_number, old_top, line;
  rule_t *rule;
  string_t file_name;

  in_debugger = TRUE;
  old_top = top;
  breakpoint_number = at_breakpoint( executed_rule_sys, pc );
  if (interrupt || breakpoint_number != 0 
      || debug_now( executed_rule_sys, pc )) 
  { 
    rule = executed_rule_sys->rules + executed_rule_number;
    if (rule->first_instr == pc) 
      display_where();
    else 
    { 
      source_of_instr( executed_rule_sys, pc, &line, &file_name, NULL );
      if (in_emacs_malaga_mode) 
	printf( "SHOW \"%s\":%d:0\n", file_name, line );
      else 
	printf( "At \"%s\", line %d.\n", name_in_path( file_name ), line );
    }
    if (interrupt) 
      printf( "User interrupt.\n" );
    if (breakpoint_number != 0) 
      printf( "Hit breakpoint %d.\n", breakpoint_number );
    if (auto_variables) 
      display_variables();
    command_loop( "debug", debugger_commands );
    if (leave_program) 
      set_debug_mode( RUN_MODE, NULL );
  }
  top = old_top;
  in_debugger = FALSE;
}

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

void 
init_debugger( void (*my_display_where)( void ), 
               command_t *my_debugger_commands[] )
/* Initialise the debugger module.
 * The function MY_DISPLAY_WHERE is a callback to display where rule 
 * execution currently stands at.
 * MY_DEBUGGER_COMMANDS is an array of commands that can be invoked
 * in debug mode. */
{
  display_where = my_display_where;
  debugger_commands = my_debugger_commands;
  debug_rule = debugger_debug_rule;
}

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

void 
terminate_debugger( void )
/* Terminate the debugger module. */
{
  free_mem( &info.path );
  free_mem( &info.value );
  debug_rule = NULL;
}

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

static void 
step_or_next( debug_mode_t mode, string_t argument )
/* Execute "step" or "next" command (see MODE) with ARGUMENT. */
{ 
  int_t line;
  string_t file;

  assert_in_debug_mode();
  parse_end( &argument );
  source_of_instr( executed_rule_sys, pc, &line, &file, NULL );
  info.path_count = path_count;
  info.nested_subrules = nested_subrules;
  info.line = line;
  info.file = file;
  set_debug_mode( mode, executed_rule_sys );
}

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

static void 
do_run( string_t arguments )
/* Continue rule execution in non-debugging mode. */
{ 
  assert_in_debug_mode();
  parse_end( &arguments );
  set_debug_mode( RUN_MODE, NULL );
  leave_command_loop = TRUE;
}

command_t run_command = 
{ 
  "run r", do_run,
  "Return to non-debug mode and complete rule execution.\n"
  "Usage: run\n"
  "\"run\" can only be used in debug mode.\n"
};

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

static void 
do_continue( string_t arguments )
/* Continue rule execution until condition is reached. */
{ 
  rule_sys_t *rule_sys;
  string_t var_name;

  var_name = NULL;
  assert_in_debug_mode();
  info.break_instr = -1;
  info.var_scope = NULL;
  free_mem( &info.path );
  free_mem( &info.value );
  if (*arguments == '$') 
  { 
    set_scanner_input( arguments );
    TRY 
    { 
      test_token( TOK_VARIABLE );
      var_name = new_string( token_name, NULL );
      read_next_token();
      parse_path();
      info.var_scope = get_var_scope_by_name( executed_rule_sys, 
					      var_name, pc );
      if (info.var_scope == NULL) 
	complain( "\"$%s\" is not defined here.", var_name );
      info.path = new_value( value_stack[ --top ] );
      parse_token( '=' );
      parse_a_value();
      info.value = new_value( value_stack[ --top ] );
      test_token( EOF );
    } 
    FINALLY 
    { 
      set_scanner_input( NULL );
      free_mem( &var_name );
    }
    END_TRY;
  } 
  else if (*arguments != EOS) 
  { 
    get_breakpoint( arguments, &rule_sys, &info.break_instr );
    if (rule_sys != executed_rule_sys) 
      complain( "This rule system is not being debugged." );
  }
  set_debug_mode( GO_MODE, executed_rule_sys );
  leave_command_loop = TRUE;
}

command_t continue_command = 
{ 
  "continue c go g", do_continue,
  "Execute rules until a breakpoint is hit or analysis is completed.\n"
  "Usage:\n"
  "continue -- Continue until analysis is completed.\n"
  "continue BREAKPOINT -- Continue until BREAKPOINT is met.\n"
  "continue VAR_PATH = VALUE -- Continue until VAR_PATH = VALUE.\n"
  "\"continue\" can only be used in debug mode.\n"
};

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

void 
assert_not_in_debug_mode( void )
/* Make sure we are NOT in debug mode. */
{ 
  if (in_debugger) 
    complain( "In debug mode." );
}

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

void 
assert_in_debug_mode( void )
/* Make sure we ARE in debug mode. */
{ 
  if (! in_debugger) 
    complain( "Not in debug mode." );
}

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

static void 
do_walk( string_t arguments )
/* Continue rule execution until next rule or breakpoint is met 
 * or end is reached. */
{ 
  assert_in_debug_mode();
  parse_end( &arguments );
  set_debug_mode( WALK_MODE, executed_rule_sys );
  leave_command_loop = TRUE;
}

command_t walk_command = 
{ 
  "walk w", do_walk,
  "Walk to the next rule or the next state.\n"
  "Usage: walk\n"
  "\"walk\" can only be used in debug mode.\n"
};

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

static void 
do_step( string_t argument )
/* Continue until control reaches a different source line. */
{ 
  step_or_next( STEP_MODE, argument );
  leave_command_loop = TRUE;
}

command_t step_command = 
{ 
  "step s", do_step,
  "Step to the next source line.\n"
  "Also stop when current path has terminated.\n"
  "Usage: step\n"
  "\"step\" can only be used in debug mode.\n"
};

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

static void 
do_next( string_t argument )
/* Continue until control reaches a different source line, but jump over
 * subrules. */
{ 
  step_or_next( NEXT_MODE, argument );
  leave_command_loop = TRUE;
}

command_t next_command = 
{ 
  "next n", do_next,
  "Step to the next source line, but jump over subrules.\n"
  "Also stop when current path has terminated.\n"
  "Usage: next\n"
  "\"next\" can only be used in debug mode.\n"
};

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

static void 
do_finish( string_t argument )
/* Continue until path ends or returns. */
{ 
  assert_in_debug_mode();
  parse_end( &argument );
  set_debug_mode( FINISH_MODE, executed_rule_sys );
  info.nested_subrules = nested_subrules;
  leave_command_loop = TRUE;
}

command_t finish_command = 
{ 
  "finish fi", do_finish,
  "Go until current path terminates or returns from current subrule.\n"
  "Usage: finish\n"
  "\"finish\" can only be used in debug mode.\n"
};

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

static void
display_frame( void )
/* Display source location and variables for current frame. */
{
  int_t line, pc_index;
  string_t file_name;

  get_frame_info( debug_frame, &pc_index, NULL, NULL, NULL );
  source_of_instr( executed_rule_sys, pc_index, &line, &file_name, NULL );
  if (in_emacs_malaga_mode) 
    printf( "SHOW \"%s\":%d:0\n", file_name, line );
  else 
    printf( "At \"%s\", line %d.\n", name_in_path( file_name ), line );
  if (auto_variables) 
    display_variables();
}

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

static void
do_frame( string_t arguments )
/* Select a new debug frame. */
{
  int_t frame, frame_count;

  if (pc == -1) 
    complain( "No rule executed." );
  frame = parse_cardinal( &arguments );
  parse_end( &arguments );
  frame_count = get_frame_count();
  if (frame < 1 || frame > frame_count) 
    complain( "Frame %d doesn't exist.", frame );
  debug_frame = frame_count - frame;
  display_frame();
}

command_t frame_command = 
{ 
  "frame f", do_frame,
  "Select a new frame for debugging.\n"
  "Usage: frame NUMBER\n"
  "You can list the frames using \"backtrace\".\n"
  "\"frame\" can only be used in debug mode or after a rule execution error.\n"
};

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

static void
do_up( string_t arguments )
/* Go to calling frame. */
{
  if (pc == -1) 
    complain( "No rule executed." );
  parse_end( &arguments );
  if (debug_frame + 1 >= get_frame_count()) 
    complain( "Already at outermost frame." );
  debug_frame++;
  display_frame();
}

command_t up_command = 
{ 
  "up", do_up,
  "Select frame that called current frame for debugging.\n"
  "Usage: up\n"
  "\"up\" can only be used in debug mode or after a rule execution error.\n"
};

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

static void
do_down( string_t arguments )
/* Go to called frame. */
{
  if (pc == -1) 
    complain( "No rule executed." );
  parse_end( &arguments );
  if (debug_frame <= 0) 
    complain( "Already at innermost frame." );
  debug_frame--;
  display_frame();
}

command_t down_command = 
{ 
  "down", do_down,
  "Select frame that has been called by current frame for debugging.\n"
  "Usage: down\n"
  "\"down\" can only be used in debug mode or after a rule execution error.\n"
};

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

static void 
do_backtrace( string_t arguments )
/* Print all active subrules and rules in reverse order. */
{ 
  string_t file, rule;
  int_t frame, frame_count, line, pc_index;
  
  if (pc == -1) 
    complain( "No rule executed." );
  parse_end( &arguments );

  frame_count = get_frame_count();
  for (frame = 0; frame < frame_count; frame++)
  { 
    get_frame_info( frame, &pc_index, NULL, NULL, NULL );
    source_of_instr( executed_rule_sys, pc_index, &line, &file, &rule );
    printf( "%c%d: \"%s\", line %d, rule \"%s\"\n",
	    (frame == debug_frame) ? '*' : ' ', frame_count - frame, 
	    name_in_path( file ), line, rule );
  }
}

command_t backtrace_command = 
{ 
  "backtrace bt trace", do_backtrace,
  "Print the active subrules in reverse order.\n"
  "Usage: backtrace\n"
  "\"backtrace\" can only be used in debug mode or after an execution error.\n"
};

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

static void 
do_print( string_t argument )
/* Print content of variables. */
{ 
  string_t value_string, path, name;
  value_t value;
  var_scope_t *var_scope;
  int_t base_idx, pc_idx;
  volatile bool_t is_constant = FALSE;

  if (pc == -1) 
    complain( "No rule executed." );
  if (use_display)
  {
    start_display_process();
    fprintf( display_stream, "expressions\n" );
  }
  TRY 
  {
    while (*argument != EOS) 
    { 
      get_frame_info( debug_frame, &pc_idx, &base_idx, NULL, NULL );
      path = parse_word( &argument );
      set_scanner_input( path );
      TRY 
      { 
	if (next_token != TOK_VARIABLE && next_token != TOK_CONSTANT)
	{
	  complain( "variable or constant expected, not %s", 
		    token_as_text( next_token ) );
	}
	is_constant = (next_token == TOK_CONSTANT);
	name = new_string( token_name, NULL );
	read_next_token();
	parse_path();
	test_token( EOF );
      } 
      FINALLY 
	set_scanner_input( NULL );
      END_TRY;

      /* Get variable or constant value. */
      value = NULL;
      if (is_constant)
      {
	value = get_constant( executed_rule_sys, name );
	if (value == NULL) 
	  printf( "@%s is not defined.\n", name );
      }
      else
      {
	var_scope = get_var_scope_by_name( executed_rule_sys, name, pc_idx );
	if (var_scope == NULL) 
	  printf( "$%s is not defined.\n", name );
	else 
	  value = value_stack[ base_idx + var_scope->stack_index ];
      }

      if (value != NULL)
      { 
	value = get_value_part( value, value_stack[ --top ] );
	if (value == NULL) 
	  printf( "%s does not exist.\n", path );
	else if (use_display) 
	{
	  value_string = value_to_readable( value, FALSE, -1 );
	  fprintf( display_stream, "\"%s\" {%s}\n", path, value_string );
	  free_mem( &value_string );
	}
	else
	{
	  value_string = value_to_readable( value, FALSE, 
					    g_utf8_strlen( path, -1 ) + 3 );
	  printf( "%s = %s\n", path, value_string );
	  free_mem( &value_string );
	}
      }
      free_mem( &path );
      free_mem( &name );
    }
  } 
  FINALLY
  {
    if (use_display)
    {
      fprintf( display_stream, "end\n") ;
      fflush( display_stream );
    }
  }
  END_TRY;
}

command_t print_command = 
{
  "print p", do_print,
  "Print the values of Malaga expressions.\n"
  "Usage:\n"
  "  print EXPRESSION ... -- Print the values of the specified expressions.\n"
  "An expression is a variable or constant with an optional attribute path.\n"
  "\"print\" can only be used in debug mode or after a rule execution error.\n"
};

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

static void 
do_variables( string_t arguments )
/* Generate variables file and start program to display variables. */
{ 
  if (pc == -1) 
    complain( "No rule executed." );
  parse_end( &arguments );
  display_variables();
}

command_t variables_command = 
{ 
  "variables v", do_variables,
  "Show variables of current frame.\n"
  "Usage: variables\n"
  "\"variables\" can only be used in debug mode or after a rule execution "
  "error.\n"
};

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

static void 
do_where( string_t arguments )
/* Print where rule execution currently stands at. */
{ 
  if (pc == -1) 
    complain( "No rule executed." );
  parse_end( &arguments );
  display_where();
}

command_t where_command = 
{ 
  "where rule", do_where,
  "Show where rule execution currently stands at.\n"
  "Usage: where\n"
  "\"where\" can only be used in debug mode or after a rule execution error.\n"
};

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