#include "config.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#include <orc/orc.h>
#include <orc/orcprogram.h>
#include <orc/orcdebug.h>
#include <orc/orcinternal.h>
#include <orc/orcutils-private.h>

/**
 * SECTION:orcprogram
 * @title: OrcProgram
 * @short_description: Creating and manipulating Orc programs
 */


/**
 * orc_program_new:
 * 
 * Create a new OrcProgram.  The program should be freed using
 * @orc_program_free().
 *
 * Returns: a pointer to an OrcProgram structure
 */
OrcProgram *
orc_program_new (void)
{
  OrcProgram *p;

  /* FIXME: there's no need to do this. If we guard every API
   * by having orc_init called before, we'll never end
   */
  orc_init ();

  p = orc_malloc(sizeof(OrcProgram));
  memset (p, 0, sizeof(OrcProgram));

  p->name = orc_malloc (40);
  sprintf(p->name, "func_%p", p);

  return p;
}

/**
 * orc_program_new_dss:
 * @size1: size of destination array members
 * @size2: size of first source array members
 * @size3: size of second source array members
 * 
 * Create a new OrcProgram, with a destination named "d1" and
 * two sources named "s1" and "s2".
 *
 * Returns: a pointer to an OrcProgram structure
 */
OrcProgram *
orc_program_new_dss (int size1, int size2, int size3)
{
  OrcProgram *p;

  p = orc_program_new ();

  orc_program_add_destination (p, size1, "d1");
  orc_program_add_source (p, size2, "s1");
  orc_program_add_source (p, size3, "s2");

  return p;
}

/**
 * orc_program_new_ds:
 * @size1: size of destination array members
 * @size2: size of source array members
 * 
 * Create a new OrcProgram, with a destination named "d1" and
 * one source named "s1".
 *
 * Returns: a pointer to an OrcProgram structure
 */
OrcProgram *
orc_program_new_ds (int size1, int size2)
{
  OrcProgram *p;

  p = orc_program_new ();

  orc_program_add_destination (p, size1, "d1");
  orc_program_add_source (p, size2, "s1");

  return p;
}

/**
 * orc_program_new_ass:
 * @size1: size of destination array members
 * @size2: size of first source array members
 * @size3: size of second source array members
 * 
 * Create a new OrcProgram, with an accumulator named "a1" and
 * two source named "s1" and "s2".
 *
 * Returns: a pointer to an OrcProgram structure
 */
OrcProgram *
orc_program_new_ass (int size1, int size2, int size3)
{
  OrcProgram *p;

  p = orc_program_new ();

  orc_program_add_accumulator (p, size1, "a1");
  orc_program_add_source (p, size2, "s1");
  orc_program_add_source (p, size3, "s2");

  return p;
}

/**
 * orc_program_new_as:
 * @size1: size of destination array members
 * @size2: size of source array members
 * 
 * Create a new OrcProgram, with an accumulator named "a1" and
 * one source named "s1".
 *
 * Returns: a pointer to an OrcProgram structure
 */
OrcProgram *
orc_program_new_as (int size1, int size2)
{
  OrcProgram *p;

  p = orc_program_new ();

  orc_program_add_accumulator (p, size1, "a1");
  orc_program_add_source (p, size2, "s1");

  return p;
}

OrcProgram *
orc_program_new_from_static_bytecode (const orc_uint8 *bytecode)
{
  OrcProgram *p;

  p = orc_program_new ();
  orc_bytecode_parse_function (p, bytecode);

  return p;
}

/**
 * orc_program_free:
 * @program: a pointer to an OrcProgram structure
 *
 * Frees an OrcProgram.
 */
void
orc_program_free (OrcProgram *program)
{
  int i;
  for(i=0;i<ORC_N_VARIABLES;i++){
    if (program->vars[i].name) {
      free (program->vars[i].name);
      program->vars[i].name = NULL;
    }
    if (program->vars[i].type_name) {
      free (program->vars[i].type_name);
      program->vars[i].type_name = NULL;
    }
  }
  if (program->asm_code) {
    free (program->asm_code);
    program->asm_code = NULL;
  }
  if (program->orccode) {
    orc_code_free (program->orccode);
    program->orccode = NULL;
  }
  if (program->init_function) {
    free (program->init_function);
    program->init_function = NULL;
  }
  if (program->backup_name) {
    free (program->backup_name);
    program->backup_name = NULL;
  }
  if (program->name) {
    free (program->name);
    program->name = NULL;
  }
  if (program->error_msg) {
    free (program->error_msg);
    program->error_msg = NULL;
  }
  free (program);
}

/**
 * orc_program_set_name:
 * @program: a pointer to an OrcProgram structure
 * @name: string to set the name to
 *
 * Sets the name of the program.  The string is copied.
 */
void
orc_program_set_name (OrcProgram *program, const char *name)
{
  if (program->name) {
    free (program->name);
  }
  program->name = strdup (name);
}

/**
 * orc_program_set_line:
 * @program: a pointer to an OrcProgram structure
 * @name: define where we are in the source
 *
 * Sets the current line of the program.
 */
void
orc_program_set_line (OrcProgram *program, unsigned int line)
{
  program->current_line = line;
}

/**
 * orc_program_set_2d:
 * @program: a pointer to an OrcProgram structure
 *
 * Sets a flag on the program indicating that arrays are two
 * dimensional.  This causes the compiler to generate code for
 * an OrcExec2D executor.
 */
void
orc_program_set_2d (OrcProgram *program)
{
  program->is_2d = TRUE;
}

void orc_program_set_constant_n (OrcProgram *program, int n)
{
  program->constant_n = n;
}

void orc_program_set_n_multiple (OrcProgram *program, int n)
{
  program->n_multiple = n;
}

void orc_program_set_n_minimum (OrcProgram *program, int n)
{
  program->n_minimum = n;
}

void orc_program_set_n_maximum (OrcProgram *program, int n)
{
  program->n_maximum = n;
}

void orc_program_set_constant_m (OrcProgram *program, int m)
{
  program->constant_m = m;
}

/**
 * orc_program_set_backup_function:
 * @program: a pointer to an OrcProgram structure
 * @func: a function that performs the operations in the program
 *
 * Normally, if a program cannot be compiled for a particular CPU,
 * Orc will emulate the function, which is typically very slow.  This
 * function allows the developer to provide a function that is called
 * instead of resorting to emulation.
 */
void
orc_program_set_backup_function (OrcProgram *program, OrcExecutorFunc func)
{
  program->backup_func = func;
  if (program->code_exec == NULL) {
    program->code_exec = func;
  }
}

/**
 * orc_program_set_backup_name:
 * @program: a pointer to an OrcProgram structure
 * @name: a function name that performs the operations in the program
 */
void
orc_program_set_backup_name (OrcProgram *program, const char *name)
{
  if (program->backup_name)
    free (program->backup_name);
  program->backup_name = strdup (name);
}

/**
 * orc_program_get_name:
 * @program: a pointer to an OrcProgram structure
 *
 * Gets the name of the program.  The string is valid until the name
 * is changed or the program is freed.
 *
 * Returns: a character string
 */
const char *
orc_program_get_name (OrcProgram *program)
{
  return program->name;
}

/**
 * orc_program_add_temporary:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data values
 * @name: name of variable
 *
 * Creates a new variable holding temporary values.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_temporary (OrcProgram *program, int size, const char *name)
{
  int i = ORC_VAR_T1 + program->n_temp_vars;

  if (program->n_temp_vars >= ORC_MAX_TEMP_VARS) {
    orc_program_set_error (program, "too many temporary variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_TEMP;
  program->vars[i].size = size;
  program->vars[i].name = strdup(name);
  program->n_temp_vars++;

  return i;
}

/**
 * orc_program_dup_temporary:
 * @program: a pointer to an OrcProgram structure
 * @var: variable to duplicate
 * @j: index
 *
 * Internal function.
 *
 * Returns: the index of the new variable
 */
int
orc_program_dup_temporary (OrcProgram *program, int var, int j)
{
  int i = ORC_VAR_T1 + program->n_temp_vars;

  if (program->n_temp_vars >= ORC_MAX_TEMP_VARS) {
    orc_program_set_error (program, "too many temporary variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_TEMP;
  program->vars[i].size = program->vars[var].size;
  program->vars[i].name = orc_malloc (strlen(program->vars[var].name) + 10);
  sprintf(program->vars[i].name, "%s.dup%d", program->vars[var].name, j);
  program->n_temp_vars++;

  return i;
}

/**
 * orc_program_add_source_full:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data values
 * @name: name of variable
 * @type_name: name of type, or NULL
 * @alignment: alignment in bytes, or 0
 *
 * Creates a new variable representing a source array.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_source_full (OrcProgram *program, int size, const char *name,
    const char *type_name, int alignment)
{
  int i = ORC_VAR_S1 + program->n_src_vars;

  if (program->n_src_vars >= ORC_MAX_SRC_VARS) {
    orc_program_set_error (program, "too many source variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_SRC;
  program->vars[i].size = size;
  if (alignment == 0) alignment = size;
  program->vars[i].alignment = alignment;
  program->vars[i].name = strdup(name);
  if (type_name) {
    program->vars[i].type_name = strdup(type_name);
  }
  program->n_src_vars++;

  return i;
}

/**
 * orc_program_add_source:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data values
 * @name: name of variable
 *
 * Creates a new variable representing a source array.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_source (OrcProgram *program, int size, const char *name)
{
  return orc_program_add_source_full (program, size, name, NULL, 0);
}

/**
 * orc_program_add_destination_full:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data values
 * @name: name of variable
 *
 * Creates a new variable representing a destination array.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_destination_full (OrcProgram *program, int size, const char *name,
    const char *type_name, int alignment)
{
  int i = ORC_VAR_D1 + program->n_dest_vars;

  if (program->n_dest_vars >= ORC_MAX_DEST_VARS) {
    orc_program_set_error (program, "too many destination variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_DEST;
  program->vars[i].size = size;
  if (alignment == 0) alignment = size;
  program->vars[i].alignment = alignment;
  program->vars[i].name = strdup(name);
  if (type_name) {
    program->vars[i].type_name = strdup(type_name);
  }
  program->n_dest_vars++;

  return i;
}

/**
 * orc_program_add_destination:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data values
 * @name: name of variable
 *
 * Creates a new variable representing a destination array.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_destination (OrcProgram *program, int size, const char *name)
{
  return orc_program_add_destination_full (program, size, name, NULL, 0);
}

/**
 * orc_program_add_constant:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data value
 * @value: the value
 * @name: name of variable
 *
 * Creates a new variable representing a constant value.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_constant (OrcProgram *program, int size, int value, const char *name)
{
  int i;
  
  i = ORC_VAR_C1 + program->n_const_vars;

  if (program->n_const_vars >= ORC_MAX_CONST_VARS) {
    orc_program_set_error (program, "too many constants allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_CONST;
  program->vars[i].size = size;
  program->vars[i].value.i = value;
  program->vars[i].name = strdup(name);
  program->n_const_vars++;

  return i;
}

int
orc_program_add_constant_int64 (OrcProgram *program, int size,
    orc_int64 value, const char *name)
{
  int i;
  
  i = ORC_VAR_C1 + program->n_const_vars;

  if (program->n_const_vars >= ORC_MAX_CONST_VARS) {
    orc_program_set_error (program, "too many constants allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_CONST;
  program->vars[i].size = size;
  program->vars[i].value.i = value;
  program->vars[i].name = strdup(name);
  program->n_const_vars++;

  return i;
}

int
orc_program_add_constant_float (OrcProgram *program, int size,
    float value, const char *name)
{
  orc_union32 u;
  u.f = value;
  return orc_program_add_constant (program, size, u.i, name);
}

int
orc_program_add_constant_double (OrcProgram *program, int size,
    double value, const char *name)
{
  orc_union64 u;
  u.f = value;
  return orc_program_add_constant_int64 (program, size, u.i, name);
}

int
orc_program_add_constant_str (OrcProgram *program, int size,
    const char *value, const char *name)
{
  int i;
  char *end;
  orc_int64 val_i;
  double val_d;
  int j;

  i = ORC_VAR_C1 + program->n_const_vars;

  if (program->n_const_vars >= ORC_MAX_CONST_VARS) {
    orc_program_set_error (program, "too many constants allocated");
    return 0;
  }

  val_i = _strtoll (value, &end, 0);
  if (end[0] == 0) {
    program->vars[i].value.i = val_i;
    if (size == 0)
      size = 4;
  } else if ((end[0] == 'l' || end[0] == 'L') && end[1] == 0) {
    program->vars[i].value.i = val_i;
    if (size == 0)
      size = 8;
  } else {
    val_d = strtod (value, &end);

    if (end[0] == 0) {
      orc_union32 u;
      u.f = val_d;
      program->vars[i].value.i = u.i;
      if (size == 0)
        size = 4;
    } else if ((end[0] == 'l' || end[0] == 'L') && end[1] == 0) {
      program->vars[i].value.f = val_d;
      if (size == 0)
        size = 8;
    } else {
      return -1;
    }
  }

  for(j=0;j<program->n_const_vars;j++){
    if (program->vars[ORC_VAR_C1 + j].value.i == program->vars[i].value.i &&
        program->vars[ORC_VAR_C1 + j].size == size) {
      return ORC_VAR_C1 + j;
    }
  }

  program->vars[i].vartype = ORC_VAR_TYPE_CONST;
  program->vars[i].size = size;
  program->vars[i].name = strdup(name);
  program->n_const_vars++;

  return i;
}

/**
 * orc_program_add_parameter:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data value
 * @name: name of variable
 *
 * Creates a new variable representing a scalar parameter.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_parameter (OrcProgram *program, int size, const char *name)
{
  int i = ORC_VAR_P1 + program->n_param_vars;

  if (program->n_param_vars >= ORC_MAX_PARAM_VARS) {
    orc_program_set_error (program, "too many parameter variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_PARAM;
  program->vars[i].param_type = ORC_PARAM_TYPE_INT;
  program->vars[i].size = size;
  program->vars[i].name = strdup(name);
  program->n_param_vars++;

  return i;
}

/**
 * orc_program_add_parameter_float:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data value
 * @name: name of variable
 *
 * Creates a new variable representing a scalar parameter.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_parameter_float (OrcProgram *program, int size, const char *name)
{
  int i = ORC_VAR_P1 + program->n_param_vars;

  if (program->n_param_vars >= ORC_MAX_PARAM_VARS) {
    orc_program_set_error (program, "too many parameter variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_PARAM;
  program->vars[i].param_type = ORC_PARAM_TYPE_FLOAT;
  program->vars[i].size = size;
  program->vars[i].name = strdup(name);
  program->n_param_vars++;

  return i;
}

int
orc_program_add_parameter_double (OrcProgram *program, int size,
    const char *name)
{
  int i = ORC_VAR_P1 + program->n_param_vars;

  if (program->n_param_vars >= ORC_MAX_PARAM_VARS) {
    orc_program_set_error (program, "too many parameter variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_PARAM;
  program->vars[i].param_type = ORC_PARAM_TYPE_DOUBLE;
  program->vars[i].size = size;
  program->vars[i].name = strdup(name);
  program->n_param_vars++;

  return i;
}

int
orc_program_add_parameter_int64 (OrcProgram *program, int size,
    const char *name)
{
  int i = ORC_VAR_P1 + program->n_param_vars;

  if (program->n_param_vars >= ORC_MAX_PARAM_VARS) {
    orc_program_set_error (program, "too many parameter variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_PARAM;
  program->vars[i].param_type = ORC_PARAM_TYPE_INT64;
  program->vars[i].size = size;
  program->vars[i].name = strdup(name);
  program->n_param_vars++;

  return i;
}

/**
 * orc_program_add_accumulator:
 * @program: a pointer to an OrcProgram structure
 * @size: size of data value
 * @name: name of variable
 *
 * Creates a new variable representing an accumulator.
 *
 * Returns: the index of the new variable
 */
int
orc_program_add_accumulator (OrcProgram *program, int size, const char *name)
{
  int i = ORC_VAR_A1 + program->n_accum_vars;

  if (program->n_accum_vars >= ORC_MAX_ACCUM_VARS) {
    orc_program_set_error (program, "too many accumulator variables allocated");
    return 0;
  }

  program->vars[i].vartype = ORC_VAR_TYPE_ACCUMULATOR;
  program->vars[i].size = size;
  program->vars[i].name = strdup(name);
  program->n_accum_vars++;

  return i;
}

void
orc_program_set_type_name (OrcProgram *program, int var, const char *type_name)
{
  program->vars[var].type_name = strdup(type_name);
}

void
orc_program_set_var_alignment (OrcProgram *program, int var, int alignment)
{
  program->vars[var].alignment = alignment;
}

void
orc_program_set_sampling_type (OrcProgram *program, int var,
    int sampling_type)
{
  /* This doesn't do anything yet */
}

/**
 * orc_program_append_ds:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 * @arg0: index of first variable
 * @arg1: index of second variable
 *
 * Appends an instruction to the program, with arguments @arg0 and
 * @arg1.  The instruction must take 2 operands.
 */
void
orc_program_append_ds (OrcProgram *program, const char *name, int arg0,
    int arg1)
{
  OrcInstruction *insn;

  insn = program->insns + program->n_insns;

  insn->opcode = orc_opcode_find_by_name (name);
  if (!insn->opcode) {
    ORC_ERROR ("unknown opcode: %s", name);
    orc_program_set_error (program, "unknown opcode");
    return;
  }
  insn->dest_args[0] = arg0;
  insn->src_args[0] = arg1;
  
  program->n_insns++;
}

/**
 * orc_program_append_ds:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 * @arg0: index of first variable
 * @arg1: index of second variable
 * @arg2: index of second variable
 *
 * Appends an instruction to the program, with arguments @arg0,
 * @arg1, and @arg2.  The instruction must take 3 operands.
 */
void
orc_program_append (OrcProgram *program, const char *name, int arg0,
    int arg1, int arg2)
{
  OrcInstruction *insn;

  insn = program->insns + program->n_insns;

  insn->opcode = orc_opcode_find_by_name (name);
  if (!insn->opcode) {
    ORC_ERROR ("unknown opcode: %s", name);
    orc_program_set_error (program, "unknown opcode");
    return;
  }
  insn->dest_args[0] = arg0;
  insn->src_args[0] = arg1;
  insn->src_args[1] = arg2;
  
  program->n_insns++;
}

/**
 * orc_program_append_ds_2:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 * @arg0: index of first variable
 * @arg1: index of second variable
 * @arg2: index of third variable
 * @arg3: index of fourth variable
 *
 * Appends an instruction to the program, with arguments @arg0,
 * @arg1, @arg2, and @arg3.
 */
void
orc_program_append_2 (OrcProgram *program, const char *name, unsigned int flags,
    int arg0, int arg1, int arg2, int arg3)
{
  OrcInstruction *insn;
  int args[4];
  int i;

  insn = program->insns + program->n_insns;

  insn->opcode = orc_opcode_find_by_name (name);
  if (!insn->opcode) {
    ORC_ERROR ("unknown opcode: %s", name);
    orc_program_set_error (program, "unknown opcode");
    return;
  }
  if (insn->opcode->dest_size[1] != 0 && insn->opcode->src_size[2] != 0) {
    ORC_ERROR ("opcode has too many dest/src parameters: %s", name);
    orc_program_set_error (program, "opcode has too many dest/src parameters");
    return;
  }

  insn->flags = flags;
  args[0] = arg0;
  args[1] = arg1;
  args[2] = arg2;
  args[3] = arg3;
  insn->flags = flags;
  i = 0;
  insn->dest_args[0] = args[i++];
  if (insn->opcode->dest_size[1] != 0) {
    insn->dest_args[1] = args[i++];
  }
  if (insn->opcode->src_size[0] != 0) {
    insn->src_args[0] = args[i++];
  }
  if (insn->opcode->src_size[1] != 0) {
    insn->src_args[1] = args[i++];
  }
  if (insn->opcode->src_size[2] != 0) {
    insn->src_args[2] = args[i++];
  }

  program->n_insns++;
}

/**
 * orc_program_find_var_by_name:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 *
 * Finds the variable with the name @name.  If no variable with the
 * given name exists in the program, -1 is returned.
 *
 * Returns: the index of the variable
 */
int
orc_program_find_var_by_name (OrcProgram *program, const char *name)
{
  int i;

  if (name == NULL) return -1;

  for(i=0;i<ORC_N_VARIABLES;i++){
    if (program->vars[i].name && strcmp (program->vars[i].name, name) == 0) {
      return i;
    }
  }

  return -1;
}

/**
 * orc_program_append_str:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 * @arg0: name of first variable
 * @arg1: name of second variable
 * @arg2: name of third variable
 *
 * Appends an instruction to the program, with arguments @arg0,
 * @arg1, and @arg2.  The instruction must take 3 operands.
 */
void
orc_program_append_str (OrcProgram *program, const char *name,
    const char *arg1, const char *arg2, const char *arg3)
{
  const char *args[3] = { arg1, arg2, arg3 };
  int argc;

  if (arg3)
    argc = 3;
  else if (arg2)
    argc = 2;
  else if (arg1)
    argc = 1;
  else
    argc = 0;

  orc_program_append_str_n (program, name, 0, argc, args);
}

/**
 * orc_program_append_str_2:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 * @flags: flags
 * @arg0: name of first variable
 * @arg1: name of second variable
 * @arg2: name of third variable
 * @arg3: name of fourth variable
 *
 * Appends an instruction to the program, with arguments @arg0,
 * @arg1, @arg2, and @arg3.
 */
void
orc_program_append_str_2 (OrcProgram *program, const char *name,
    unsigned int flags, const char *arg1, const char *arg2, const char *arg3,
    const char *arg4)
{
  const char *args[4] = { arg1, arg2, arg3, arg4 };
  int argc;

  if (arg4)
    argc = 4;
  else if (arg3)
    argc = 3;
  else if (arg2)
    argc = 2;
  else if (arg1)
    argc = 1;
  else
    argc = 0;

  orc_program_append_str_n (program, name, flags, argc, args);
}

/**
 * orc_program_append_str_n:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 * @flags: flags
 * @argc: number of variableds
 * @argv: array of variables
 *
 * Appends an instruction to the program, with any number of
 * arguments. Only 1 <= number <= 6 is supported.
 *
 * Returns: status code.
 * 0: succesfull
 * <0: malformed opcode (unknown or bad arguments)
 * >0: unknown argument. Then the return value equals to the nth
 * argument. Beware: this is index, not offset (the first is 1).
 */
int
orc_program_append_str_n (OrcProgram *program, const char *name,
    unsigned int flags, int argc, const char **argv)
{
  OrcInstruction *insn;
  int args[6];
  int i;
  int expected_args = 0;

  insn = program->insns + program->n_insns;

  insn->line = program->current_line;
  insn->opcode = orc_opcode_find_by_name (name);
  if (!insn->opcode) {
    ORC_ERROR ("unknown opcode: %s", name);
    orc_program_set_error (program, "unknown opcode");
    return -1;
  }

  /* dest_size[0] */
  expected_args++;
  if (insn->opcode->dest_size[1] != 0)
    expected_args++;
  if (insn->opcode->src_size[0] != 0)
    expected_args++;
  if (insn->opcode->src_size[1] != 0)
    expected_args++;
  if (insn->opcode->src_size[2] != 0)
    expected_args++;
  if (insn->opcode->src_size[3] != 0)
    expected_args++;

  if (argc < expected_args) {
    ORC_ERROR ("not the correct number of arguments provided for opcode: %s expects %d but got %d", name, expected_args, argc);
    orc_program_set_error (program, "not the correct number of arguments provided for opcode");
    return -1;
  }

  if (argc != expected_args) {
    ORC_WARNING ("not the correct number of arguments provided for opcode: %s expects %d but got %d", name, expected_args, argc);
  };

  for (i=0;i<expected_args;i++){
    args[i] = orc_program_find_var_by_name (program, argv[i]);
    if (args[i] == -1) {
      ORC_ERROR ("bad operand \"%s\" in position %d for opcode: %s at line %d",
                 argv[i], i+1, name, insn->line);
      orc_program_set_error (program, "bad operand");
      return i+1;
    }
  }

  insn->flags = flags;

  i = 0;
  insn->dest_args[0] = args[i++];
  if (insn->opcode->dest_size[1] != 0)
    insn->dest_args[1] = args[i++];
  if (insn->opcode->src_size[0] != 0)
    insn->src_args[0] = args[i++];
  if (insn->opcode->src_size[1] != 0)
    insn->src_args[1] = args[i++];
  if (insn->opcode->src_size[2] != 0)
    insn->src_args[2] = args[i++];
  if (insn->opcode->src_size[3] != 0)
    insn->src_args[3] = args[i++];

  program->n_insns++;
  return 0;
}

/**
 * orc_program_append_ds_str:
 * @program: a pointer to an OrcProgram structure
 * @name: name of instruction
 * @arg0: name of first variable
 * @arg1: name of second variable
 *
 * Appends an instruction to the program, with arguments @arg0 and
 * @arg2.  The instruction must take 2 operands.
 */
void
orc_program_append_ds_str (OrcProgram *program, const char *name,
    const char *arg1, const char *arg2)
{
  OrcInstruction *insn;

  insn = program->insns + program->n_insns;

  insn->opcode = orc_opcode_find_by_name (name);
  if (!insn->opcode) {
    ORC_ERROR ("unknown opcode: %s", name);
    orc_program_set_error (program, "unknown opcode");
    return;
  }
  insn->dest_args[0] = orc_program_find_var_by_name (program, arg1);
  insn->src_args[0] = orc_program_find_var_by_name (program, arg2);
  
  program->n_insns++;
}

void
orc_program_append_dds_str (OrcProgram *program, const char *name,
    const char *arg1, const char *arg2, const char *arg3)
{
  OrcInstruction *insn;

  insn = program->insns + program->n_insns;

  insn->opcode = orc_opcode_find_by_name (name);
  if (!insn->opcode) {
    ORC_ERROR ("unknown opcode: %s", name);
    orc_program_set_error (program, "unknown opcode");
    return;
  }
  insn->dest_args[0] = orc_program_find_var_by_name (program, arg1);
  insn->dest_args[1] = orc_program_find_var_by_name (program, arg2);
  insn->src_args[0] = orc_program_find_var_by_name (program, arg3);
  
  program->n_insns++;
}

/**
 * orc_program_get_asm_code:
 * @program: a pointer to an OrcProgram structure
 *
 * Returns a character string containing the assembly code created
 * by compiling the program.  This string is valid until the program
 * is compiled again or the program is freed.
 * 
 * Returns: a character string
 */
const char *
orc_program_get_asm_code (OrcProgram *program)
{
  return program->asm_code;
}

/**
 * orc_program_get_error:
 * @program: a pointer to an OrcProgram structure
 *
 * Returns a character string containing the error message from
 * compilation.  This string is valid until the program
 * is compiled again, the program is freed, or another error
 * is set.
 * 
 * Returns: a character string
 */
const char *
orc_program_get_error (OrcProgram *program)
{
  if (program->error_msg) return program->error_msg;
  return "";
}

/**
 * orc_program_set_error:
 * @program: a pointer to an OrcProgram structure
 * @error: an error string
 *
 * Stores the error in the program. This string is duplicated.
 * If an error has already been set, this new error is ignored.
 * An error will stay till the next call to _reset, if any.
 */
void
orc_program_set_error (OrcProgram *program, const char *error)
{
  if (!program->error_msg && error) {
    program->error_msg = strdup (error);
  }
}

/**
 * orc_program_get_max_array_size:
 * @program: a pointer to an OrcProgram structure
 *
 * Returns the size of the largest array used in the program.
 * 
 * Returns: the number of bytes
 */
int
orc_program_get_max_array_size (OrcProgram *program)
{
  int i;
  int max;

  max = 0;
  for(i=0;i<ORC_N_VARIABLES;i++){
    if (program->vars[i].size) {
      if (program->vars[i].vartype == ORC_VAR_TYPE_SRC ||
          program->vars[i].vartype == ORC_VAR_TYPE_DEST) {
        max = MAX(max, program->vars[i].size);
      }
    }
  }

  return max;
}

/**
 * orc_program_get_max_accumulator_size:
 * @program: a pointer to an OrcProgram structure
 *
 * Returns the size of the largest array used in the program.
 * 
 * Returns: the number of bytes
 */
int
orc_program_get_max_accumulator_size (OrcProgram *program)
{
  int i;
  int max;

  max = 0;
  for(i=0;i<ORC_N_VARIABLES;i++){
    if (program->vars[i].size) {
      if (program->vars[i].vartype == ORC_VAR_TYPE_ACCUMULATOR) {
        max = MAX(max, program->vars[i].size);
      }
    }
  }

  return max;
}

void
orc_program_reset (OrcProgram *program)
{
  if (program->orccode) {
    orc_code_free (program->orccode);
    program->orccode = NULL;
  }
  if (program->asm_code) {
    free(program->asm_code);
    program->asm_code = NULL;
  }
  if (program->error_msg) {
    free(program->error_msg);
    program->error_msg = NULL;
  }
}

OrcCode *
orc_program_take_code (OrcProgram *program)
{
  OrcCode *code = program->orccode;
  program->orccode = NULL;
  return code;
}

/**
 * orc_program_compile:
 * @program: the OrcProgram to compile
 *
 * Compiles an Orc program for the current CPU.  If successful,
 * executable code for the program was generated and can be
 * executed.
 *
 * The return value indicates various levels of success or failure.
 * Success can be determined by checking for a true value of the
 * macro ORC_COMPILE_RESULT_IS_SUCCESSFUL() on the return value.  This
 * indicates that executable code was generated.  If the macro
 * ORC_COMPILE_RESULT_IS_FATAL() on the return value evaluates to
 * true, then there was a syntactical error in the program.  If the
 * result is neither successful nor fatal, the program can still be
 * emulated.
 *
 * Returns: an OrcCompileResult
 */
OrcCompileResult
orc_program_compile (OrcProgram *program)
{
  return orc_program_compile_for_target (program, orc_target_get_default ());
}

/**
 * orc_program_compile_for_target:
 * @program: the OrcProgram to compile
 *
 * Compiles an Orc program for the given target, using the
 * default target flags for that target.
 *
 * Returns: an OrcCompileResult
 */
OrcCompileResult
orc_program_compile_for_target (OrcProgram *program, OrcTarget *target)
{
  unsigned int flags;

  if (target) {
    flags = target->get_default_flags ();
  } else {
    flags = 0;
  }

  return orc_program_compile_full (program, target, flags);
}

/**
 * orc_program_compile_full:
 * @program: the OrcProgram to compile
 *
 * Compiles an Orc program for the given target, using the
 * given target flags.
 *
 * Returns: an OrcCompileResult
 */
OrcCompileResult
orc_program_compile_full (OrcProgram *program, OrcTarget *target,
    unsigned int flags)
{
  OrcCompiler *compiler;

  compiler = orc_malloc (sizeof(OrcCompiler));
  memset (compiler, 0, sizeof(OrcCompiler));
  return orc_compiler_compile_program (compiler, program, target, flags);
}
