/* 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. =============================================================*/