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

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

/* Program to dump a compiled Malaga rule file with intermediate code. */

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

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <setjmp.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 "patterns.h"
#include "hangul.h"

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

static void 
print_instruction( int_t pos, rule_sys_t *rule_sys )
/* Print the instruction at POS in RULE_SYS. */
{
  int_t opcode, info;
  string_t string;

  opcode = OPCODE( rule_sys->instrs[ pos ] );
  info = INSTR_INFO( rule_sys->instrs[ pos ] );
  switch (opcode) 
  {
  case INS_SYSTEM_ERROR:
    switch (info) 
    {
    case ASSERTION_ERROR: 
      string = "assertion_error"; 
      break;
    case NO_RETURN_ERROR: 
      string = "no_return_error"; 
      break;
    }
    printf( "error               %s", string );
    break;
  case INS_ERROR:
    printf( "error" );
    break;
  case INS_TERMINATE:
    printf( "terminate" );
    break;
  case INS_NOP:
    printf( "nop" );
    break;
  case INS_TERMINATE_IF_NULL:
    printf( "terminate_if_null" );
    break;
  case INS_ADD_END_STATE:
    printf( "add_end_state" );
    break;
  case INS_ADD_STATE:
    string = rule_set_readable( rule_sys, info );
    printf( "add_state           %s", string );
    free_mem( &string );
    break;
  case INS_ADD_ALLO:
    printf( "add_allo" );
    break;
  case INS_ACCEPT:
    printf( "accept" );
    break;
  case INS_PUSH_NULL:
    printf( "push_null           %d", info );
    break;
  case INS_PUSH_VAR:
    printf( "push_var            %d", info );
    break;
  case INS_PUSH_CONST:
    string = value_to_readable( rule_sys->values + info, TRUE, 30 );
    printf( "push_const          %s", string );
    free_mem( &string );
    break;
  case INS_PUSH_SYMBOL:
    printf( "push_symbol         %s", get_symbol_name( info ) );
    break;
  case INS_PUSH_PATTERN_VAR:
    printf( "push_pattern_var    %d", info );
    break;
  case INS_POP:
    printf( "pop                 %d", info );
    break;
  case INS_POP_TO:
    printf( "pop_to              %d", info );
    break;
  case INS_BUILD_LIST:
    printf( "build_list          %d", info );
    break;
  case INS_DECOMPOSE_LIST:
    printf( "decompose_list      %d", info );
    break;
  case INS_BUILD_RECORD:
    printf( "build_record        %d", info );
    break;
  case INS_BUILD_PATH:
    printf( "build_path          %d", info );
    break;
  case INS_DOT_OPERATION:
    printf( "dot_operation" );
    break;
  case INS_PLUS_OPERATION:
    printf( "plus_operation" );
    break;
  case INS_MINUS_OPERATION:
    printf( "minus_operation" );
    break;
  case INS_ASTERISK_OPERATION:
    printf( "asterisk_operation" );
    break;
  case INS_SLASH_OPERATION:
    printf( "slash_operation" );
    break;
  case INS_UNARY_MINUS_OP:
    printf( "unary_minus_op" );
    break;
  case INS_GET_ATTRIBUTE:
    printf( "get_attribute       %s", get_symbol_name( info ) );
    break;
  case INS_REMOVE_ATTRIBUTE:
    printf( "remove_attribute    %s", get_symbol_name( info ) );
    break;
  case INS_STD_FUNCTION:
    switch (info) 
    {
    case FUNC_TO_ATOMS: 
      string = "to_atoms"; 
      break;
    case FUNC_IS_CAPITAL: 
      string = "is_capital"; 
      break;
    case FUNC_GET_LENGTH: 
      string = "get_length"; 
      break;
    case FUNC_TO_MULTI: 
      string = "to_multi"; 
      break;
    case FUNC_TO_SET: 
      string = "to_set"; 
      break;
    case FUNC_GET_SWITCH: 
      string = "get_switch"; 
      break;
    case FUNC_GET_VALUE_STRING: 
      string = "get_value_string"; 
      break;
    case FUNC_GET_VALUE_TYPE: 
      string = "get_value_type"; 
      break;
    case FUNC_TRANSMIT: 
      string = "transmit"; 
      break;
    case FUNC_FLOOR: 
      string = "floor"; 
      break;
    case FUNC_SUBSTRING: 
      string = "substring"; 
      break;
    default: 
      complain( "Internal error." );
    }
    printf( "std_function        %s", string );
    break;
  case INS_MATCH:
    string = new_string_readable( rule_sys->strings + info, NULL );
    printf( "match               %s", string );
    free_mem( &string );
    break;
  case INS_SET_VAR:
    printf( "set_var             %d", info );
    break;
  case INS_PLUS_VAR:
    printf( "plus_var            %d", info );
    break;
  case INS_MINUS_VAR:
    printf( "minus_var           %d", info );
    break;
  case INS_ASTERISK_VAR:
    printf( "asterisk_var        %d", info );
    break;
  case INS_SLASH_VAR:
    printf( "slash_var           %d", info );
    break;
  case INS_SET_VAR_PATH:
    printf( "set_var_path        %d", info );
    break;
  case INS_PLUS_VAR_PATH:
    printf( "plus_var_path       %d", info );
    break;
  case INS_MINUS_VAR_PATH:
    printf( "minus_var_path      %d", info );
    break;
  case INS_ASTERISK_VAR_PATH:
    printf( "asterisk_var_path   %d", info );
    break;
  case INS_SLASH_VAR_PATH:
    printf( "slash_var_path      %d", info );
    break;
  case INS_GET_1ST_ELEMENT:
    printf( "get_1st_element" );
    break;
  case INS_ITERATE:
    printf( "iterate             %d", info );
    break;
  case INS_JUMP:
    printf( "jump                %d", info );
    break;
  case INS_JUMP_IF_EQUAL:
    printf( "jump_if_equal       %d", info );
    break;
  case INS_JUMP_IF_NOT_EQUAL:
    printf( "jump_if_not_equal   %d", info );
    break;
  case INS_JUMP_IF_CONGR:
    printf( "jump_if_congr       %d", info );
    break;
  case INS_JUMP_IF_NOT_CONGR:
    printf( "jump_if_not_congr   %d", info );
    break;
  case INS_JUMP_IF_IN:
    printf( "jump_if_in          %d", info );
    break;
  case INS_JUMP_IF_NOT_IN:
    printf( "jump_if_not_in      %d", info );
    break;
  case INS_JUMP_IF_LESS:
    printf( "jump_if_less        %d", info );
    break;
  case INS_JUMP_IF_NOT_LESS:
    printf( "jump_if_not_less    %d", info );
    break;
  case INS_JUMP_IF_GREATER:
    printf( "jump_if_greater     %d", info );
    break;
  case INS_JUMP_IF_NOT_GREATER:
    printf( "jump_if_not_greater %d", info );
    break;
  case INS_JUMP_IF_NULL:
    printf( "jump_if_null        %d", info );
    break;
  case INS_JUMP_IF_NOT_NULL:
    printf( "jump_if_not_null    %d", info );
    break;
  case INS_JUMP_IF_YES:
    printf( "jump_if_yes         %d", info );
    break;
  case INS_JUMP_IF_NO:
    printf( "jump_if_no          %d", info );
    break;
  case INS_JUMP_NOW:
    printf( "jump_now            %d", info );
    break;
  case INS_JUMP_LATER:
    printf( "jump_later          %d", info );
    break;
  case INS_JUMP_SUBRULE:
    printf( "jump_subrule        %s", 
            rule_sys->strings + rule_sys->rules[ info ].name );
    break;
  case INS_RETURN:
    printf( "return              %d", info );
    break;
  default:
    printf( "ILLEGAL INSTRUCTION %d", opcode );
    break;
  }
}

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

static void 
print_rules( string_t source_name, rule_sys_t *rule_sys )
/* Print all rules of SOURCE_NAME. */
{
  string_t next_source_name;
  int_t instr, next_instr;
  int_t source_line, next_source_line;
  FILE *stream;
  int_t c;

  if (source_name != NULL) 
    stream = open_stream( source_name, "r" );
  else 
    stream = NULL;

  source_line = 1;
  next_instr = next_source_line = 0;
  for (instr = 0; instr < rule_sys->instr_count; instr++) 
  { 
    if (stream != NULL) 
    { 
      if (instr >= next_instr) /* NEXT_INSTR has a new source location. */
      { 
	for (next_instr = instr; 
	     next_instr < rule_sys->instr_count; 
	     next_instr++) 
	{ 
	  source_of_instr( rule_sys, next_instr, 
			   &next_source_line, &next_source_name, NULL );
	  if (next_source_name != NULL 
	      && strcmp( name_in_path( next_source_name ), 
			 name_in_path( source_name ) ) == 0
	      && next_source_line > source_line) 
	  { 
	    break; 
	  }
	}
      }
      while (source_line < next_source_line) 
      { 
	c = getc( stream );
        if (c == EOF) 
	  break;
        if (c == '\n') 
	  source_line++;
        putchar( c );
      }
    }
    
    printf( "|%6d:  ", instr );
    print_instruction( instr, rule_sys );
    printf( "\n" );
  }

  if (stream != NULL) 
  { 
    /* Print remainder of source file. */
    while ((c = getc( stream )) != EOF) 
      putchar( c );
    close_stream( &stream, source_name );
  }
}

/* Top level functions. =====================================================*/

int 
main( int argc, char *argv[] )
/* The main function of "maldump". */
{
  rule_sys_t *rule_sys;
  string_t rule_file, symbol_file, source_file;
  int_t i;

  init_basic( "maldump" );

  /* 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( "Dump a compiled rule file of a Malaga grammar.\n\n"
	      "Usage:\n"
	      "maldump SYM_FILE RULE_FILE "
	      "-- Disassemble RULE_FILE on stdout.\n"
	      "maldump -v[ersion]         "
	      "-- Print version information.\n"
	      "maldump -h[elp]            "
	      "-- Print this help.\n\n"
	      "SYM_FILE may end on \".sym\" or \".esym\".\n"
	      "RULE_FILE may end on \".all\", \"mor\" or \"syn\".\n"
	      "Option \"-s[ource] SOURCE_FILE\" gives "
	      "a source file for RULE_FILE.\n" );
      exit(0);
    }
  }

  rule_file = symbol_file = source_file = NULL;
  for (i = 1; i < argc; i++) 
  { 
    if (has_extension( argv[i], "all" )
	|| has_extension( argv[i], "mor" )
	|| has_extension( argv[i], "syn" ))
    {
      set_binary_file_name( &rule_file, argv[i] );
    }
    else if (has_extension( argv[i], "sym" )
	     || has_extension( argv[i], "esym" ))
    { 
      set_binary_file_name( &symbol_file, argv[i] ); 
    }
    else if (strcmp_no_case( argv[i], "-source" ) == 0
	     || strcmp_no_case( argv[i], "-s" ) == 0)
    {
      if (argv[ ++i ] == NULL) 
	complain( "Missing file name after \"-source\"." );
      set_file_name( &source_file, argv[i] );
    } 
    else 
      complain( "Illegal argument \"%s\".", argv[i] );
  }
  
  if (rule_file == NULL) 
    complain( "Missing rule file name." );
  if (symbol_file == NULL) 
    complain( "Missing symbol file name." );
  init_values();

  init_symbols( symbol_file );
  rule_sys = read_rule_sys( rule_file );
  init_hangul();

  print_rules( source_file, rule_sys );
    
  terminate_hangul();
  free_rule_sys( &rule_sys );
  terminate_symbols();
  free_mem( &symbol_file );
  free_mem( &rule_file );
  free_mem( &source_file );

  terminate_values();
  terminate_patterns();
  terminate_basic();
  return 0;
}

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