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

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

/* This module manages the emission of instructions and keeps track of the 
 * stack index. 
 * It supports constant folding. 
 * It also holds buffers for the compiled code. */

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

#include <stdio.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 "rule_type.h"
#include "files.h"
#include "malaga_files.h"
#include "rule_code.h"

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

code_t code;

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

static instr_t *instr_stack;
/* The instruction stack. The associated constant values are stored in 
 * VALUES_STACK. */

static int_t instr_stack_size; /* The size of INSTR_STACK. */

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

void 
init_rule_code( int_t file_type )
/* Initialise this module. 
 * CODE will contain compilation data for a file of FILE_TYPE.
 * FILE_TYPE may be ALLO_RULE_FILE, MORPHO_RULE_FILE, or SYNTAX_RULE_FILE. */
{
  code.file_type = file_type;
  code.stack_index = 0;
  code.instr_count = 0;
  code.rule_pool = new_pool( sizeof( rule_t ) );
  code.rule_set_pool = new_pool( sizeof( int_t ) );
  code.instr_pool = new_pool( sizeof( instr_t ) );
  code.value_pool = new_pool( sizeof( cell_t ) );
  code.string_pool = new_pool( sizeof( char_t ) );
  code.src_line_pool = new_pool( sizeof( src_line_t ) );
  code.var_pool = new_pool( sizeof( var_t ) );
  code.var_scope_pool = new_pool( sizeof( var_scope_t ) );
  code.constant_pool = new_pool( sizeof( constant_t ) );

  instr_stack_size = 50;
  instr_stack = new_vector( sizeof( instr_t ), instr_stack_size );
  top = 0;
}

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

void 
terminate_rule_code( void )
/* Terminate this module. */
{
  free_pool( &code.rule_pool );
  free_pool( &code.rule_set_pool );
  free_pool( &code.instr_pool );
  free_pool( &code.value_pool );
  free_pool( &code.src_line_pool );
  free_pool( &code.var_pool );
  free_pool( &code.var_scope_pool );
  free_pool( &code.constant_pool );
  free_pool( &code.string_pool );
  free_mem( &instr_stack );
}

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

void 
write_code( string_t file_name )
/* Write CODE to FILE_NAME. */
{ 
  FILE *stream;
  rule_header_t rule_header;

  stream = open_stream( file_name, "wb" );

  /* Set rule file header data. */
  set_header( &rule_header.common_header, RULE_FILE, RULE_CODE_VERSION );
  rule_header.initial_rule_set = code.initial_rule_set;
  rule_header.initial_feat = code.initial_feat;
  rule_header.robust_rule = code.robust_rule;
  rule_header.pruning_rule = code.pruning_rule;
  rule_header.allo_rule = code.allo_rule;
  rule_header.input_filter = code.input_filter;
  rule_header.output_filter = code.output_filter;
  rule_header.rule_count = pool_item_count( code.rule_pool );
  rule_header.rule_sets_size = pool_item_count( code.rule_set_pool );
  rule_header.instr_count = pool_item_count( code.instr_pool );
  rule_header.values_size = pool_item_count( code.value_pool );
  rule_header.src_line_count = pool_item_count( code.src_line_pool );
  rule_header.var_count = pool_item_count( code.var_pool );
  rule_header.var_scope_count = pool_item_count( code.var_scope_pool );
  rule_header.constant_count = pool_item_count( code.constant_pool );
  rule_header.strings_size = pool_item_count( code.string_pool );

  /* Write the header. */
  write_vector( &rule_header, sizeof( rule_header ), 1, stream, file_name ); 

  /* Write the tables. */
  write_pool( code.rule_pool, stream, file_name );
  write_pool( code.rule_set_pool, stream, file_name );
  write_pool( code.instr_pool, stream, file_name );
  write_pool( code.value_pool, stream, file_name );
  write_pool( code.src_line_pool, stream, file_name );
  write_pool( code.var_pool, stream, file_name );
  write_pool( code.var_scope_pool, stream, file_name );
  write_pool( code.constant_pool, stream, file_name );
  write_pool( code.string_pool, stream, file_name );

  close_stream( &stream, file_name );
}

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

static void 
set_stack_index( int_t opcode, int_t info )
/* Set the stack index according to the given instruction. */
{ 
  /* The stack index is not always correct, because "parse_choose" 
   * and "parse_foreach" in "rule_parser.c" patch an INS_PUSH_NULL that they 
   * have previously emitted. They will also correct the stack index. */

  switch (opcode) 
  {
  case INS_SYSTEM_ERROR:
  case INS_TERMINATE:
  case INS_NOP:
  case INS_UNARY_MINUS_OP:
  case INS_GET_ATTRIBUTE:
  case INS_REMOVE_ATTRIBUTE:
  case INS_MATCH:
  case INS_GET_1ST_ELEMENT:
  case INS_ITERATE:
  case INS_JUMP:
  case INS_JUMP_NOW:
  case INS_JUMP_LATER:
    /* These instructions leave the stack size unchanged. */
    return;
  case INS_PUSH_VAR:
  case INS_PUSH_CONST:
  case INS_PUSH_SYMBOL:
  case INS_PUSH_PATTERN_VAR:
  case INS_JUMP_SUBRULE: /* This has to decrement the STACK_INDEX by the number
			  * of parameters manually. */
    code.stack_index++;
    return;
  case INS_PUSH_NULL:
    code.stack_index += info;
    return;
  case INS_ERROR:
  case INS_ADD_END_STATE:
  case INS_ADD_STATE:
  case INS_TERMINATE_IF_NULL:
  case INS_DOT_OPERATION:
  case INS_PLUS_OPERATION:
  case INS_MINUS_OPERATION:
  case INS_ASTERISK_OPERATION:
  case INS_SLASH_OPERATION:
  case INS_SET_VAR:
  case INS_PLUS_VAR:
  case INS_MINUS_VAR:
  case INS_ASTERISK_VAR:
  case INS_SLASH_VAR:
  case INS_JUMP_IF_NULL:
  case INS_JUMP_IF_NOT_NULL:
  case INS_JUMP_IF_YES:
  case INS_JUMP_IF_NO:
  case INS_ACCEPT: /* No instruction after this instruction is executed. */
  case INS_RETURN: /* No instruction after this instruction is executed. */
    code.stack_index--;
    return;
  case INS_ADD_ALLO:
  case INS_SET_VAR_PATH:
  case INS_PLUS_VAR_PATH:
  case INS_MINUS_VAR_PATH:
  case INS_ASTERISK_VAR_PATH:
  case INS_SLASH_VAR_PATH:
  case INS_JUMP_IF_EQUAL:
  case INS_JUMP_IF_NOT_EQUAL:
  case INS_JUMP_IF_CONGR:
  case INS_JUMP_IF_NOT_CONGR:
  case INS_JUMP_IF_IN:
  case INS_JUMP_IF_NOT_IN:
  case INS_JUMP_IF_LESS:
  case INS_JUMP_IF_NOT_LESS:
  case INS_JUMP_IF_GREATER:
  case INS_JUMP_IF_NOT_GREATER:
    code.stack_index -= 2;
    return;
  case INS_POP:
    code.stack_index -= info;
    return;
  case INS_POP_TO:
    code.stack_index = info;
    return;
  case INS_BUILD_LIST:
  case INS_BUILD_PATH:
    code.stack_index -= (info - 1);
    return;
  case INS_DECOMPOSE_LIST:
    code.stack_index += (info - 1);
    return;
  case INS_BUILD_RECORD:
    code.stack_index -= (2*info - 1);
    return;
  case INS_STD_FUNCTION:
    switch (info)
    {
    case FUNC_TO_ATOMS:
    case FUNC_IS_CAPITAL:
    case FUNC_GET_LENGTH:
    case FUNC_TO_MULTI:
    case FUNC_TO_SET:
    case FUNC_GET_SWITCH:
    case FUNC_GET_VALUE_TYPE:
    case FUNC_GET_VALUE_STRING:
    case FUNC_TRANSMIT:
    case FUNC_FLOOR:
      /* These functions leave the stack size unchanged. */
      return;
    case FUNC_SUBSTRING:
      code.stack_index -= 2;
      return;
    }
  }
  complain( "Internal error." );
}

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

static instr_t *
emit_instr_local( int_t opcode, int_t info )
/* Emit an instruction to the instruction pool. Do not flush the buffer!
 * Return the address of the instruction in the pool. */
{
  instr_t instr;
  instr_t *instr_p;

  if (info < INSTR_INFO_MIN || info > INSTR_INFO_MAX) 
    complain( "Internal error." );

  /* Create the next instruction. */
  instr = INSTR( opcode, info );

  /* Generate instruction. */
  instr_p = (instr_t *) copy_to_pool( code.instr_pool, &instr, 1, NULL );
  set_stack_index( opcode, info );
  code.instr_count = pool_item_count( code.instr_pool );

  return instr_p;
}

/* Functions that support constant folding. =================================*/

static void 
put_instr( int_t opcode, int_t info )
/* Put the instruction (OPCODE, INFO) at INSTR_STACK[ TOP - 1 ]. */
{
  if (top > instr_stack_size) 
  {
    instr_stack_size = renew_vector( &instr_stack, 
				     sizeof( u_int_t ), 2 * top );
  }
  instr_stack[ top - 1 ] = INSTR( opcode, info );
}

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

void 
buffer_instr( int_t opcode, int_t info )
/* Buffer the instructions BUILD_LIST, BUILD_RECORD, PUSH_SYMBOL,
 * and PUSH_CONST for constant folding. */

{
  switch (opcode) 
  {
  case INS_PUSH_SYMBOL:
    push_symbol_value( info );
    put_instr( INS_PUSH_SYMBOL, info );
    break;
  case INS_PUSH_CONST:
    push_value( pool_item( code.value_pool, info ) );
    put_instr( INS_PUSH_CONST, info );
    break;
  case INS_BUILD_LIST:
    if (top >= info) 
    { 
      /* Execute the operation in the buffer. */
      build_list( info );
      put_instr( INS_PUSH_CONST, -1 );
    } 
    else 
      emit_instr( opcode, info );
    break;
  case INS_BUILD_RECORD:
    if (top >= 2 * info) 
    { 
      /* Execute the operation in the buffer. */
      build_record( info );
      put_instr( INS_PUSH_CONST, -1 );
    } 
    else 
      emit_instr( opcode, info );
    break;
  case INS_BUILD_PATH:
    if (top >= info) 
    { 
      /* Execute the operation in the buffer. */
      build_path( info );
      put_instr( INS_PUSH_CONST, -1 );
    } 
    else 
      emit_instr( opcode, info );
    break;
  case INS_DOT_OPERATION:
  case INS_PLUS_OPERATION:
  case INS_MINUS_OPERATION:
  case INS_ASTERISK_OPERATION:
  case INS_SLASH_OPERATION:
    if (top >= 2) 
    { 
      /* Execute the operation in the buffer. */
      switch (opcode) 
      {
      case INS_DOT_OPERATION: 
        dot_operation();
	if (value_stack[ top - 1 ] == NULL) 
	  complain( "Component doesn't exist." );
        break;
      case INS_PLUS_OPERATION: 
	plus_operation(); 
	break;
      case INS_MINUS_OPERATION: 
	minus_operation(); 
	break;
      case INS_ASTERISK_OPERATION: 
	asterisk_operation(); 
	break;
      case INS_SLASH_OPERATION: 
	slash_operation(); 
	break;
      default: 
	complain( "Internal error." );
      }
      put_instr( INS_PUSH_CONST, -1 );
    } 
    else 
      emit_instr( opcode, info );
    break;
  case INS_UNARY_MINUS_OP:
  case INS_GET_ATTRIBUTE:
  case INS_REMOVE_ATTRIBUTE:
    if (top >= 1) 
    { 
      /* Execute the operation in the buffer. */
      switch (opcode) 
      {
      case INS_UNARY_MINUS_OP: 
	unary_minus_operation(); 
	break;
      case INS_GET_ATTRIBUTE:
        push_value( get_attribute( value_stack[ --top ], info ) );
        if (value_stack[ top - 1 ] == NULL) 
	{
	  complain( "No attribute \"%s\" in record.", 
		    get_symbol_name( info ) );
	}
        break;
      case INS_REMOVE_ATTRIBUTE: 
	remove_attribute( info ); 
	break;
      default: 
	complain( "Internal error." );
      }
      put_instr( INS_PUSH_CONST, -1 );
    } 
    else 
      emit_instr( opcode, info );
    break;
  default:
    emit_instr( opcode, info );
    break;
  }
}

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

void 
buffer_push_number_instr( double number )
/* Buffer the instruction PUSH_CONST with NUMBER converted to a value. */
{
  push_number_value( number );
  put_instr( INS_PUSH_CONST, -1 );
}

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

void 
buffer_push_string_instr( string_t string, string_t string_end )
/* Buffer the instruction PUSH_CONST with STRING converted to a value.
 * STRING_END points to the string end if STRING_END != NULL. */
{
  push_string_value( string, string_end );
  put_instr( INS_PUSH_CONST, -1 );
}

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

void 
flush_buffer( void )
/* Emit the instructions that are still in the buffer. */
{
  int_t i, value_index;
      
  for (i = 0; i < top; i++) 
  { 
    switch (OPCODE( instr_stack[i] )) 
    {
    case INS_PUSH_CONST:
      if (INSTR_INFO( instr_stack[i] ) == -1) 
	copy_value_to_pool( code.value_pool, value_stack[i], &value_index );
      else 
	value_index = INSTR_INFO( instr_stack[i] );
      emit_instr_local( INS_PUSH_CONST, value_index );
      break;
    case INS_PUSH_SYMBOL: 
      emit_instr_local( INS_PUSH_SYMBOL, INSTR_INFO( instr_stack[i] ) );
      break;
    }
  }
  top = 0;
}

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

value_t 
get_buffer_top_value( void )
/* Test if the buffer contains a value and return the top value. */
{
  if (top == 0) 
    complain( "Internal error." );
  return value_stack[ top - 1 ];
}

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

value_t 
pop_buffer_top_value( void )
/* Pop the top value in the buffer. */
{
  if (top == 0) 
    complain( "Internal error." );
  return value_stack[ --top ];
}

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

instr_t *
emit_instr( int_t opcode, int_t info )
/* Emit an instruction to the instruction pool (flushes buffer before)
 * and return the address of the instruction in the pool. */
{
  flush_buffer();
  return emit_instr_local( opcode, info );
}

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