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

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

/* This is the interactive program for morphological and syntactic analysis. */

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

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <setjmp.h>
#include <time.h>
#include <glib.h>
#include "basic.h"
#include "pools.h"
#include "values.h"
#include "symbols.h"
#include "files.h"
#include "rule_type.h"
#include "rules.h"
#include "analysis.h"
#include "input.h"
#include "commands.h"
#include "commands_interactive.h"
#include "options.h"
#include "display.h"
#include "malaga_lib.h"
#include "generation.h"
#include "debugger.h"
#include "breakpoints.h"
#include "cache.h"
#include "transmit.h"
#include "hangul.h"

/* Macros. ==================================================================*/

#define SAFE_STRING(s) ((s) != NULL ? (s) : (string_t) "")
/* Return an empty string if S == NULL. */

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

static char_t *analysis_input; /* Input line for interactive analysis. */

static int_t debug_state_index; /* Index of state to debug or -1. */

static debug_mode_t state_debug_mode; 
/* Debug mode to use for states to debug. */

static string_t analysis_file_name; /* Name of input file for analysis. */

/* Analysis result output. ==================================================*/

static void 
display_result( void )
/* Generate result file and start program to display result. */
{ 
  char_t *input;
  string_t value_string;
  string_t count_string;
  value_t feat;
  int_t result_count;

  if (! analysis_has_nodes()) 
    complain( "No analysis started." );

  /* Print analysis result header. */
  input = new_string_readable( analysis_input, NULL );
  decode_hangul( &input );
  if (use_display) 
  { 
    start_display_process();
    fprintf( display_stream, "result\n" );
    fprintf( display_stream, "%s\n", input );
  } 
  else 
  { 
    if (! analysis_has_results()) 
      printf( "No analyses of %s.\n", input );
    else 
      printf( "Analyses of %s:\n", input );
  }
  free_mem( &input );

  /* Print analysis results. */
  result_count = 0;
  for (feat = first_analysis_result(); 
       feat != NULL; 
       feat = next_analysis_result())
  { 
    result_count++;
    if (use_display) 
    { 
      value_string = value_to_readable( feat, FALSE, -1 );
      fprintf( display_stream, "%d {%s}\n", result_count, value_string );
      free_mem( &value_string );
    } 
    else
    { 
      count_string = int_to_string( result_count );
      value_string = value_to_readable( 
	feat, FALSE, g_utf8_strlen( count_string, -1 ) + 2 );
      printf( "%s: %s\n", count_string, value_string );
      free_mem( &value_string );
      free_mem( &count_string );
    }
  }

  /* Print analysis results footer. */
  if (use_display) 
  { 
    fprintf( display_stream, "end\n" );
    fflush( display_stream );
  }
}

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

static void 
display_tree( void )
/* Display analysis tree. */
{ 
  analysis_node_t *node;
  string_t node_type = NULL, value_string;
  char_t *input;
  char_t *surf_string;

  if (! analysis_has_nodes()) 
    complain( "No analysis started." );
  start_display_process();
  fprintf( display_stream, "tree\n" );

  /* Print sentence that has been analysed. */
  input = new_string_readable( analysis_input, NULL );
  decode_hangul( &input );
  fprintf( display_stream, "%s\n", input );
  free_mem( &input );

  /* Print nodes. */
  for (node = get_first_analysis_node(); 
       node != NULL; 
       node = get_next_analysis_node()) 
  { 
    /* Print node index and type, parent index and rule name. */
    switch (node->type) 
    {
    case INTER_NODE: node_type = "inter"; break;
    case BREAK_NODE: node_type = "break"; break;
    case FINAL_NODE: node_type = "final"; break;
    case UNFINAL_NODE: node_type = "unfinal"; break;
    case PRUNED_NODE: node_type = "pruned"; break;
    }
    fprintf( display_stream, "%d %s %d \"%s\" ", 
             node->index, node_type, node->parent_index, 
             SAFE_STRING( node->rule_name ) );

    /* Print link's surface and feature structure. */
    if (node->link_surf != NULL) 
    { 
      surf_string = new_string_readable( node->link_surf, NULL );
      decode_hangul( &surf_string );
    } 
    else 
      surf_string = new_string( "", NULL );
    value_string = value_to_readable( node->link_feat, FALSE, -1 );
    fprintf( display_stream, "{%s} {%s} ", surf_string, value_string );
    free_mem( &value_string );
    free_mem( &surf_string );

    /* Print result surface and feature structure. */
    if (node->result_surf != NULL) 
    { 
      surf_string = new_string_readable( node->result_surf, NULL );
      decode_hangul( &surf_string );
    } 
    else 
      surf_string = new_string( "", NULL );
    value_string = value_to_readable( node->result_feat, FALSE, -1 );
    fprintf( display_stream, "{%s} {%s} ", surf_string, value_string );
    free_mem( &value_string );
    free_mem( &surf_string );

    /* Print rule set. */
    fprintf( display_stream, "\"%s\"\n", SAFE_STRING( node->rule_set ) );
    free_analysis_node( &node );
  }
  fprintf( display_stream, "end\n" );
  fflush( display_stream );
}

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

static void 
display_after_analysis( void )
/* Display result in the modes that have been switched on after analysis. */
{ 
  if (auto_result) 
    display_result();
  if (auto_tree && use_display) 
    display_tree();
}

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

static void 
do_result( string_t arguments )
/* Show result of last analysis. */
{ 
  parse_end( &arguments );
  if (! analysis_has_nodes()) 
    complain( "No previous analysis." );
  display_result();
}

static command_t result_command = 
{ 
  "result res", do_result,
  "Show result of last analysis.\n"
  "Usage: result\n"
};

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

static void 
do_tree( string_t arguments )
/* Generate analysis tree file and start program to display tree. */
{ 
  parse_end( &arguments );
  if (! analysis_has_nodes()) 
    complain( "No analysis started." );
  if (! use_display)
    complain( "Can only show a tree when using a display process." );
  display_tree();
}

static command_t tree_command = 
{
  "tree t", do_tree,
  "Display the analysis tree.\n"
  "Usage: tree\n"
  "In debug mode or after a rule execution error, the tree may be "
  "incomplete.\n"
};

/* Analysis functions. ======================================================*/

static void 
analyse_argument( grammar_t grammar, string_t arguments )
/* Analyse ARGUMENTS (or last analysis, if *ARGUMENTS == EOS).
 * Use GRAMMAR (SYNTAX or MORPHOLOGY). */
{ 
  if (*arguments == EOS) 
  { 
    if (analysis_input == NULL) 
      complain( "No previous analysis." );
  } 
  else 
  { 
    free_mem( &analysis_input );
    analysis_input = new_string( arguments, NULL );
    preprocess_input( analysis_input, FALSE );
    encode_hangul( &analysis_input );
  }
  debug_state = NULL;
  analyse( grammar, analysis_input, TRUE, TRUE );
}

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

static void 
analyse_line( grammar_t grammar, string_t arguments )
/* Analyse a word or a sentence in file FILE, line LINE_NO.
 * ARGUMENTS must be of format "FILE LINE_NO".
 * Use GRAMMAR. */
{
  int_t line_number, current_line_number;
  FILE *input_stream;
  char_t *input_line;
  
  input_stream = NULL;
  TRY 
  { 
    /* Read arguments. */
    line_number = parse_cardinal( &arguments );
    if (*arguments != EOS)
    {
      free_mem( &analysis_file_name );
      analysis_file_name = parse_absolute_path( &arguments, NULL );
    }
    parse_end( &arguments );

    if (analysis_file_name == NULL)
      complain( "Missing input file name." );

    /* Read the line from input. */
    input_stream = open_stream( analysis_file_name, "r" );
    current_line_number = 0;
    input_line = NULL;

    TRY
    {
      do 
      { 
	check_user_break();
	free_mem( &input_line );
	current_line_number++;
	input_line = read_line( input_stream );
      } while (current_line_number < line_number && input_line != NULL);
    }
    IF_ERROR
      print_text( error_text, " (line %d)", current_line_number );
    END_TRY;
    if (input_line == NULL) 
    {
      complain( "\"%s\" contains only %d lines.", 
		name_in_path( analysis_file_name ), current_line_number - 1 );
    }
    preprocess_input( input_line, FALSE );
    if (*input_line == EOS) 
      complain( "Line %d is empty.", line_number );
    free_mem( &analysis_input );
    analysis_input = input_line;
    encode_hangul( &analysis_input );
  } 
  FINALLY 
    close_stream( &input_stream, NULL );
  END_TRY;

  debug_state = NULL;
  analyse( grammar, analysis_input, TRUE, TRUE );
}

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

static void 
do_ma( string_t arguments )
/* Analyse ARGUMENTS morphologically. */
{
  assert_not_in_debug_mode();
  set_debug_mode( RUN_MODE, NULL );
  analyse_argument( MORPHOLOGY, arguments );
  display_after_analysis();
}

static command_t ma_command = 
{ 
  "ma", do_ma,
  "Analyse the argument morphologically.\n"
  "Usage:\n"
  "  ma INPUT -- Analyse INPUT.\n"
  "  ma -- Re-analyse last input.\n"
  "\"ma\" can't be used in debug mode.\n"
};

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

static void 
do_sa( string_t arguments )
/* Analyse ARGUMENTS syntactically. */
{
  assert_not_in_debug_mode();
  set_debug_mode( RUN_MODE, NULL );
  analyse_argument( SYNTAX, arguments );
  display_after_analysis();
}

static command_t sa_command = 
{
  "sa", do_sa,
  "Analyse the argument syntactically.\n"
  "Usage:\n"
  "  sa INPUT -- Analyse INPUT.\n"
  "  sa -- Re-analyse last input.\n"
  "\"sa\" can't be used in debug mode.\n"
};

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

static void 
do_ma_line( string_t arguments )
/* Analyse ARGUMENTS morphologically. */
{ 
  assert_not_in_debug_mode();
  set_debug_mode( RUN_MODE, NULL );
  analyse_line( MORPHOLOGY, arguments );
  display_after_analysis();
}

static command_t ma_line_command = 
{
  "ma-line mal", do_ma_line,
  "Analyse a line in a file morphologically.\n"
  "Usage:\n"
  "  ma-line LINE FILE -- Analyse LINE in FILE.\n"
  "If FILE is omitted, the previous file name is used.\n"
  "\"ma-line\" can't be used in debug mode.\n"
};

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

static void 
do_sa_line( string_t arguments )
/* Analyse ARGUMENTS syntactically. */
{ 
  assert_not_in_debug_mode();
  set_debug_mode( RUN_MODE, NULL );
  analyse_line( SYNTAX, arguments );
  display_after_analysis();
}

static command_t sa_line_command = 
{
  "sa-line sal", do_sa_line,
  "Analyse a line in a file syntactically.\n"
  "Usage:\n"
  "  sa-line LINE [FILE] -- Analyse LINE in FILE.\n"
  "If FILE is omitted, the previous file name is used.\n"
  "\"sa-line\" can't be used in debug mode.\n"
};

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

static void analyse_input( grammar_t grammar, string_t input )
{
  value_t value;
  int_t i;
  string_t string, count;
  char_t *input_readable;

  analysis_input = new_string( input, NULL );
  input_readable = new_string_readable( input, NULL );
  preprocess_input( analysis_input, FALSE );
  encode_hangul( &analysis_input );
  analyse( grammar, analysis_input, TRUE, TRUE );

  if (analysis_has_results()) 
  {
    printf( "Results for %s:\n", input_readable );
    i = 0;
    for (value = first_analysis_result(); 
	 value != NULL;  
	 value = next_analysis_result()) 
    {
      i++;
      count = int_to_string( i );
      string = value_to_readable( value, FALSE, 
				  g_utf8_strlen( count, -1 ) + 2) ;
      printf( "\n%s: %s\n", count, string );
      free_mem( &string );
      free_mem( &count );
    }
  }
  else 
    printf( "No results for %s.\n", input_readable );
  free( input_readable );
}

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

static void 
display_where( void )
/* Print rule name, left and right surface. */
{ 
  char_t *surf;
  string_t file, rule;
  int_t line;

  source_of_instr( executed_rule_sys, pc, &line, &file, &rule );
  printf( "At \"%s\", line %d, rule \"%s\".\n", 
	  name_in_path( file ), line, rule );

  /* Print state number and state's surface. */
  surf = get_surface( STATE_SURFACE );
  decode_hangul( &surf );
  if (current_state != -1) 
    printf( "State: %d, surf: %s", current_state, surf );
  else
    printf( "Surf: %s", surf );
  free_mem( &surf );

  /* Print link's surface. */
  surf = get_surface( LINK_SURFACE );
  if (surf != NULL)
  {
    decode_hangul( &surf );
    printf( ", link: %s", surf );
    free_mem( &surf );
  }

  printf( ".\n" );

  if (in_emacs_malaga_mode) 
    printf( "SHOW \"%s\":%d:0\n", file, line );
}

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

static void 
malaga_debug_state( int_t state, bool_t enter )
/* Callback function for "analyse". 
 * This is called with ENTER == TRUE when successor rules for
 * state with analysis node INDEX will be executed.
 * It is called with ENTER == FALSE when successor rules for state with 
 * analysis node INDEX have been executed. */
{ 
  if (state != debug_state_index) 
    return;
  if (enter)
    set_debug_mode( state_debug_mode, rule_system[ top_grammar ] );
  else
  {
    state_debug_mode = get_debug_mode();
    set_debug_mode( RUN_MODE, NULL );
  }
}

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

static void 
check_interactive_analysis( void )
{
  if (analysis_input == NULL || analysis_input != last_analysis_input) 
    complain( "No interactive analysis." );
}

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

static void 
do_debug_state( string_t arguments )
/* Analyse the last argument again and stop before executing the rules for a
 * state whose tree node index is specified in ARGUMENTS. */
{ 
  assert_not_in_debug_mode();
  check_interactive_analysis();

  debug_state_index = parse_int( &arguments );
  parse_end( &arguments );

  if (debug_state_index < 0 || debug_state_index >= state_count) 
    complain( "State not found." );

  state_debug_mode = WALK_MODE;
  debug_state = malaga_debug_state;
  /* Debug mode is set by "malaga_debug_state" before and after 
   * rule application. */

  analyse( top_grammar, analysis_input, TRUE, TRUE       );
}

static command_t debug_state_command = 
{ 
  "debug-state debug-node dn", do_debug_state,
  "Re-analyse the last analysis input.\n"
  "Execute successor rule for given state in debug mode.\n"
  "Usage: debug-state STATE_INDEX\n"
  "Analysis is restarted for last input and switches to debug mode\n"
  "when executing successor rules for state STATE_INDEX.\n"
  "\"debug-state\" can't be used in debug mode.\n"
};

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

static void 
do_debug_ma( string_t arguments )
/* Analyse ARGUMENTS morphologically.
 * Execute morphology combination rules in debug mode. */
{ 
  assert_not_in_debug_mode();
  set_debug_mode( WALK_MODE, rule_system[ MORPHOLOGY ] );
  analyse_argument( MORPHOLOGY, arguments );
}

static command_t debug_ma_command = 
{ 
  "debug-ma dma debug-mor ma-debug mad", do_debug_ma,
  "Analyse morphologically. "
  "Execute morphology combination rules in debug mode.\n"
  "Usage:\n"
  "  debug-ma INPUT -- Analyse INPUT.\n"
  "  debug-ma -- Re-analyse the last analysis argument.\n"
  "Rule execution stops at the first statement.\n"
  "\"debug-ma\" can't be used in debug mode.\n"
};

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

static void 
do_debug_sa( string_t arguments )
/* Analyse ARGUMENTS syntactically.
 * Execute syntax combination rules in debug mode. */
{ 
  assert_not_in_debug_mode();
  set_debug_mode( WALK_MODE, rule_system[ SYNTAX ] );
  analyse_argument( SYNTAX, arguments );
}

static command_t debug_sa_command = 
{ 
  "debug-sa dsa debug-syn sa-debug sad", do_debug_sa,
  "Analyse syntactically. Execute syntax combination rules in debug mode.\n"
  "Usage:\n"
  "  debug-sa INPUT -- Analyse INPUT.\n"
  "  debug-sa -- Re-analyse the last analysis argument.\n"
  "Rule execution stops at the first statement.\n"
  "\"debug-sa\" can't be used in debug mode.\n"
};

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

static void 
do_debug_ma_line( string_t arguments )
/* Analyse a word in file FILE, line LINE_NO.
 * ARGUMENTS must be of format "FILE LINE_NO".
 * Execute morphology combination rules in debug mode. */
{ 
  assert_not_in_debug_mode();
  set_debug_mode( WALK_MODE, rule_system[ MORPHOLOGY ] );
  analyse_line( MORPHOLOGY, arguments );
}

static command_t debug_ma_line_command = 
{ 
  "debug-ma-line dmal", do_debug_ma_line,
  "Analyse a line in a file morphologically.\n"
  "Execute morphology combination rules in debug mode.\n"
  "Usage:\n"
  "  debug-ma-line LINE [FILE] -- Analyse LINE in FILE.\n"
  "If FILE is omitted, the previous file name is used.\n"
  "Rule execution stops at the first statement.\n"
  "\"debug-ma-line\" can't be used in debug mode.\n"
};

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

static void 
do_debug_sa_line( string_t arguments )
/* Analyse a sentence in file FILE, line LINE_NO.
 * ARGUMENTS must be of format "FILE LINE_NO".
 * Execute syntax combination rules in debug mode. */
{ 
  assert_not_in_debug_mode();
  set_debug_mode( WALK_MODE, rule_system[ SYNTAX ] );
  analyse_line( SYNTAX, arguments );
}

static command_t debug_sa_line_command = 
{ 
  "debug-sa-line dsal", do_debug_sa_line,
  "Analyse a line in a file syntactically.\n"
  "Execute syntax combination rules in debug mode.\n"
  "Usage:\n"
  "  debug-sa-line LINE [FILE]  -- Analyse LINE in FILE.\n"
  "If FILE is omitted, the previous file name is used.\n"
  "Rule execution stops at the first statement.\n"
  "\"debug-sa-line\" can't be used in debug mode.\n"
};

/* File analysis. ===========================================================*/

static void 
write_output( string_t input, int_t line_number, string_t error_message,
              FILE *output )
/* Write the result of the last analysis on OUTPUT. */
{ 
  string_t line_number_string, state_count_string, buffer;
  string_t value_string, result_number_string;
  char_t *input_string;
  int_t result_count;
  value_t feat;

  input_string = new_string_readable( input, NULL );
  decode_hangul( &input_string );
  line_number_string = int_to_string( line_number );
  state_count_string = int_to_string( state_count );
  if (error_message != NULL) 
  { 
    /* Print error result. */
    if (*error_format != EOS) 
    { 
      buffer = replace_arguments( error_format, "slne",
				  input_string, line_number_string, 
                                  state_count_string, error_message );
      fprintf( output, "%s\n", buffer );
      free_mem( &buffer );
    }
  } 
  else if (! analysis_has_results()) 
  { 
    /* Print unknown result. */
    if (*unknown_format != EOS) 
    { 
      buffer = replace_arguments( unknown_format, "sln", 
                                  input_string, line_number_string,
                                  state_count_string );
      fprintf( output, "%s\n", buffer );
      free_mem( &buffer );
    }
  } 
  else 
  { 
    if (result_as_list)
    {
      result_count = 0;
      for (feat = first_analysis_result();
	   feat != NULL;
	   feat = next_analysis_result())
      {
	result_count++;
	push_value( feat );
      }
      build_list( result_count );
      value_string = value_to_readable( value_stack[ --top], FALSE, -1 );
      buffer = replace_arguments( result_format, "slrfn",
				  input_string, line_number_string,
				  "0", value_string, state_count_string );
      fprintf( output, "%s\n", buffer );
      free_mem( &buffer );
      free_mem( &value_string );
    }
    else
    {
      /* Print real results. */
      result_count = 0;
      for (feat = first_analysis_result(); 
	   feat != NULL; 
	   feat = next_analysis_result())
      { 
	result_count++;
	if (*result_format != EOS) 
	{ 
	  result_number_string = int_to_string( result_count );
	  value_string = value_to_readable( feat, FALSE, -1 );
	  buffer = replace_arguments( result_format, "slrfn", 
				      input_string, line_number_string, 
				      result_number_string, value_string,
				      state_count_string );
	  fprintf( output, "%s\n", buffer );
	  free_mem( &buffer );
	  free_mem( &value_string );
	  free_mem( &result_number_string );
	}
      }
    }
  }
  free_mem( &input_string );
  free_mem( &line_number_string );
  free_mem( &state_count_string );
  if (ferror( output )) 
    complain( "Can't write result: %s.", strerror( errno ) );
}

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

static void 
analyse_stream( grammar_t grammar, 
		FILE *input, 
		FILE *output, 
		FILE *statistics,
		bool_t expect_quotes )
/* Analyse words or sentences in INPUT, write result to OUTPUT.
 * Use GRAMMAR for analysis.
 * If EXPECT_QUOTES == TRUE, expect quoted input lines and remove the quotes.
 * Write statistic information to STATISTICS. */
{ 
  volatile int_t analyses, recognised, results; /* Statistic information. */
  volatile int_t combi_recognised, robust_recognised, errors, line_number;
  volatile string_t item, error_message;
  time_t start_time, stop_time;
  char_t *input_line;
  bool_t old_in_emacs_malaga_mode;
  value_t feat;
  double time_diff;

  item = (grammar == MORPHOLOGY ? "wordform" : "sentence");
  set_debug_mode( RUN_MODE, NULL );
  debug_state = NULL;
  robust_recognised = recognised = results = analyses = errors = 0;
  combi_recognised = line_number = 0;
  cache_hits = cache_accesses = 0;
  time( &start_time );
  input_line = NULL;
  TRY 
  { 
    while (TRUE) 
    { 
      check_user_break();
      free_mem( &input_line );
      input_line = read_line( input );
      if (input_line == NULL) 
	break;
      line_number++;
      preprocess_input( input_line, expect_quotes );
      if (*input_line != EOS) 
      { 
	/* Analyse a non-empty line. */
	old_in_emacs_malaga_mode = in_emacs_malaga_mode;
        in_emacs_malaga_mode = FALSE;
        error_message = NULL;
        TRY 
	{ 
	  analyses++;
          encode_hangul( &input_line );
          analyse( grammar, input_line, FALSE, TRUE );
          if (recognised_by_combi_rules) 
	    combi_recognised++;
          if (recognised_by_robust_rule) 
	    robust_recognised++;
          if (analysis_has_results()) 
	    recognised++;
          for (feat = first_analysis_result(); 
	       feat != NULL; 
               feat = next_analysis_result()) 
	  { 
	    results++; 
	  }
        }
	IF_ERROR 
	{ 
	  error_message = error_text->buffer;
          errors++;
          RESUME;
        } 
	FINALLY 
	  in_emacs_malaga_mode = old_in_emacs_malaga_mode;
	END_TRY;
        write_output( input_line, line_number, error_message, output );
	fflush( output );
      }
    }
  } 
  IF_ERROR 
  { 
    printf( "%s (line %d)\n", error_text->buffer, line_number );
    RESUME;
  } 
  FINALLY 
    free_mem( &input_line );
  END_TRY;
  time( &stop_time );
  if (analyses == 0) 
    fprintf( statistics, "No %ss analysed.\n", item );
  else 
  { 
    time_diff = difftime( stop_time, start_time );
    fprintf( statistics, "Analysed %ss:        %d\n", item, analyses );
    fprintf( statistics, "Recognised:                %d (%.2f%%)\n", 
             recognised, (100.0 * recognised) / analyses );
    if (combi_recognised > 0) 
    { 
      fprintf( statistics, "Recognised by combi rules: %d (%.2f%%)\n", 
               combi_recognised, (100.0 * combi_recognised) / analyses );
    }
    if (robust_recognised > 0) 
    { 
      fprintf( statistics, "Recognised by robust rule: %d (%.2f%%)\n",
               robust_recognised, (100.0 * robust_recognised) / analyses );
    }
    if (errors > 0) 
    { 
      fprintf( statistics, "Error-creating %ss:  %d (%.2f%%)\n", 
               item, errors, (100.0 * errors) / analyses );
    }
    if (results > 0) 
    { 
      fprintf( statistics, "Results per %s:      %.4G\n",
               item, ((double) results / (double) recognised) );
    }
    if (time_diff > 0) 
    { 
      fprintf( statistics, "Analysis run time:         %d sec\n", 
               (int_t) time_diff );
      fprintf( statistics, "Avg. %ss per second: %d\n", 
               item, (int_t) (analyses / time_diff) );
    }
    if (cache_accesses > 0) 
    { 
      fprintf( statistics, "Cache accesses:            %d\n", 
               cache_accesses );
      fprintf( statistics, "Cache hits:                %d (%.2f%%)\n",
               cache_hits, (100.0 * cache_hits) / cache_accesses );
    }
  }
}

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

static void 
analyse_file( string_t arguments, grammar_t grammar )
/* Open the file with name in ARGUMENTS, which must contain a word list
 * or sentence list, analyse all its lines according to GRAMMAR,
 * and write the results to a file with extension ".out". */
{ 
  string_t result_file_name;
  FILE *input_stream, *output_stream;

  input_stream = output_stream = NULL;
  result_file_name = NULL;
  TRY 
  { 
    if (*arguments != EOS)
    {
      free_mem( &analysis_file_name );
      analysis_file_name = parse_absolute_path( &arguments, NULL );
    }
    if (*analysis_file_name == NULL)
      complain( "Missing input file name." );

    if (*arguments != EOS) 
      result_file_name = parse_absolute_path( &arguments, NULL );
    else 
      result_file_name = concat_strings( analysis_file_name, ".out", NULL );
    parse_end( &arguments );
    input_stream = open_stream( analysis_file_name, "r" );
    output_stream = open_stream( result_file_name, "w" );
    analyse_stream( grammar, input_stream, output_stream, stdout, FALSE );
  } 
  FINALLY 
  { 
    close_stream( &input_stream, analysis_file_name );
    close_stream( &output_stream, result_file_name );
    free_mem( &result_file_name );
  } 
  END_TRY;
}

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

static void 
do_ma_file( string_t arguments )
/* Analyse file in ARGUMENTS morphologically. */
{
  assert_not_in_debug_mode();
  analyse_file( arguments, MORPHOLOGY );
}

static command_t ma_file_command = 
{ 
  "ma-file maf", do_ma_file,
  "Analyse a word list file.\n"
  "Usage: ma-file [INPUT_FILE [OUTPUT_FILE]]\n"
  "INPUT_FILE must contain one word form on each line.\n"
  "The results are written to \"OUTPUT_FILE\".\n"
  "If INPUT_FILE is missing, the previous file name is used.\n"
  "If OUTPUT_FILE is missing, they are written to \"INPUT_FILE.out\".\n"
  "\"ma-file\" can't be used in debug mode.\n"
};

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

static void 
do_sa_file( string_t arguments )
/* Analyse file in ARGUMENTS syntactically. */
{ 
  assert_not_in_debug_mode();
  if (rule_system[ SYNTAX ] == NULL) 
    complain( "Syntax rule file not loaded." );
  analyse_file( arguments, SYNTAX );
}

static command_t sa_file_command = 
{ 
  "sa-file saf", do_sa_file,
  "Analyse a sentence list file.\n"
  "Usage: sa-file [INPUT_FILE [OUTPUT_FILE]]\n"
  "INPUT_FILE must contain one sentence on each line.\n"
  "The results are written to \"OUTPUT_FILE\".\n"
  "If INPUT_FILE is missing, the previous file name is used.\n"
  "If OUTPUT_FILE is missing, they are written to \"INPUT_FILE.out\".\n"
  "\"sa-file\" can't be used in debug mode.\n"
};

/*===========================================================================*/

static void 
do_clear_cache( string_t arguments )
/* Clear the wordform analysis cache. */
{ 
  parse_end( &arguments );
  clear_cache();
}

static command_t clear_cache_command = 
{ 
  "clear-cache", do_clear_cache,
  "Clear the wordform analysis cache.\n"
  "Usage: clear-cache\n"
};

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

static void 
do_info( string_t arguments )
/* Show information about morphology and syntax. */
{ 
  parse_end( &arguments );
  printf( "%s", grammar_info->buffer );
}

static command_t info_command = 
{ 
  "info", do_info,
  "Show information about current grammar.\n"
  "Usage: info\n"
};

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

/* The commands that can be called interactively, in alphabetical order. */
static command_t *malaga_commands[] = 
{ 
  &backtrace_command, &break_command, &clear_cache_command, &continue_command,
  &debug_ma_command, &debug_ma_line_command, &debug_sa_command,
  &debug_sa_line_command, &debug_state_command, &delete_command, &down_command,
  &finish_command, &frame_command, &get_command, &help_command, &info_command, 
  &list_command, &ma_command, &ma_file_command, &ma_line_command, &mg_command, 
  &next_command, &print_command, &quit_command, &result_command, &run_command, 
  &sa_command, &sa_file_command, &sa_line_command, &set_command, &sg_command, 
  &step_command, &transmit_command, &tree_command, &up_command, 
  &variables_command, &walk_command, &where_command, 
  NULL
};

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

int 
main( int argc, char *argv[] )
/* The main function of "malaga". */
{ 
  enum {INTERACTIVE_MODE, MORPHOLOGY_MODE, SYNTAX_MODE} malaga_mode;
  int_t i;
  string_t project_file, input;
  rule_sys_name_t rule_systems[2]; /* Rule systems for debugger. */
  grammar_t grammar; /* Grammar for batch mode. */
  bool_t expect_quotes;

  expect_quotes = FALSE;
  malaga_mode = INTERACTIVE_MODE;
  input = NULL;
  init_basic( "malaga" );

  /* Parse arguments. */
  if (argc == 2) 
  { 
    if (strcmp_no_case( argv[1], "--version" ) == 0
	|| strcmp_no_case( argv[1], "-version" ) == 0
	|| strcmp_no_case( argv[1], "-v" ) == 0) 
    { 
      program_message();
      exit(0);
    } 
    else if (strcmp_no_case( argv[1], "--help" ) == 0
	     || strcmp_no_case( argv[1], "-help" ) == 0
	     || strcmp_no_case( argv[1], "-h" ) == 0) 
    { 
      printf( "Analyse words and/or sentences according to a Malaga grammar.\n"
	      "\n"
	      "Usage:\n"
	      "malaga PROJECT-FILE               "
	      "-- Start interactive malaga.\n"
	      "malaga PROJECT-FILE -m[orphology] "
	      "-- Run as a morphology filter.\n"
	      "malaga PROJECT-FILE -s[yntax]     "
	      "-- Run as a syntax filter.\n"
	      "malaga -v[ersion]                 "
	      "-- Print version information.\n"
	      "malaga -h[elp]                    "
	      "-- Print this help.\n\n"
	      "Option \"-i[nput] STRING\" makes malaga analyse STRING.\n"
	      "Option \"-q[uoted]\" expects quoted lines in filter mode.\n"
	      "PROJECT_FILE must end on \".pro\".\n" );
      exit(0);
    }
  }
  project_file = NULL;
  for (i = 1; i < argc; i++) 
  { 
    if (has_extension( argv[i], "pro" )) 
      set_file_name( &project_file, argv[i] );
    else if (strcmp_no_case( argv[i], "-morphology" ) == 0
	     || strcmp_no_case( argv[i], "-m" ) == 0) 
    { 
      malaga_mode = MORPHOLOGY_MODE; 
    } 
    else if (strcmp_no_case( argv[i], "-syntax" ) == 0
	     || strcmp_no_case( argv[i], "-s" ) == 0) 
    { 
      malaga_mode = SYNTAX_MODE; 
    } 
    else if (strcmp_no_case( argv[i], "-input" ) == 0
	     || strcmp_no_case( argv[i], "-i" ) == 0)
    {
      if (argv[ ++i ] == NULL) 
	complain( "Missing string after \"-input\"." );
      if (input != NULL) 
	complain( "Redundant \"-input\"." );
      input = argv[i];
      if (! g_utf8_validate( input, -1, NULL ))
	complain( "Illegal UTF-8 character." );
    }
    else if (strcmp_no_case( argv[i], "-quoted" ) == 0
	     || strcmp_no_case( argv[i], "-q" ) == 0)
    {
      expect_quotes = TRUE;
    }
    else 
      complain( "Illegal argument \"%s\".", argv[i] );
  }
  if (project_file == NULL) 
    complain( "Missing project file name." );
  init_malaga( project_file );
  if (malaga_mode == INTERACTIVE_MODE) 
  { 
    if (input != NULL) 
      complain( "Need \"-morphology\" or \"-syntax\"." );
    init_debugger( display_where, malaga_commands );
    rule_systems[0].rule_sys = rule_system[ MORPHOLOGY ];
    rule_systems[0].name = "mor";
    rule_systems[1].rule_sys = rule_system[ SYNTAX ];
    rule_systems[1].name = "syn";
    init_breakpoints( 2, rule_systems );
    init_generation();
    program_message();
    command_loop( program_name, malaga_commands );
    terminate_generation();
    terminate_breakpoints();
    terminate_debugger();
  } 
  else
  { 
    grammar = (malaga_mode == MORPHOLOGY_MODE) ? MORPHOLOGY : SYNTAX;
    if (rule_system[ grammar ] == NULL) 
      complain( "Rule file not loaded." );
    if (input != NULL) 
      analyse_input( grammar, input );
    else 
      analyse_stream( grammar, stdin, stdout, stderr, expect_quotes );
  }
  stop_display_process();
  terminate_malaga();
  free_mem( &analysis_input );
  free_mem( &analysis_file_name );
  free_mem( &project_file );
  terminate_basic();
  return 0;
}

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