/*  This file is part of the program psim.

    Copyright (C) 1994-1997, Andrew Cagney <cagney@highland.com.au>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
    */


#include "misc.h"
#include "lf.h"
#include "table.h"
#include "filter.h"
#include "igen.h"
#include "ld-insn.h"

static insn_word_entry *
parse_insn_word (line_ref *line,
		 char *string,
		 int word_nr)
{
  char *chp;
  insn_word_entry *word = ZALLOC (insn_word_entry);

  /* create a leading sentinal */
  word->first = ZALLOC (insn_field_entry);
  word->first->first = -1;
  word->first->last = -1;
  word->first->width = 0;

  /* and a trailing sentinal */
  word->last = ZALLOC (insn_field_entry);
  word->last->first = options.insn_bit_size;
  word->last->last = options.insn_bit_size;
  word->last->width = 0;

  /* link them together */
  word->first->next = word->last;
  word->last->prev = word->first;

  /* now work through the formats */
  chp = skip_spaces (string);

  while (*chp != '\0') {
    char *start_pos;
    int strlen_pos;
    char *start_val;
    int strlen_val;
    insn_field_entry *new_field;

    /* create / link in the new field */
    new_field = ZALLOC (insn_field_entry);
    new_field->next = word->last;
    new_field->prev = word->last->prev;
    new_field->next->prev = new_field;
    new_field->prev->next = new_field;
    new_field->word_nr = word_nr;

    /* break out the first field (if present) */
    start_pos = chp;
    chp = skip_to_separator (chp, ".,!");
    strlen_pos = back_spaces (start_pos, chp) - start_pos;

    /* break out the second field (if present) */
    if (*chp != '.')
      {
	/* assume what was specified was the value (and not the start
           position).  Assume the value length implicitly specifies
           the number of bits */
	start_val = start_pos;
	strlen_val = strlen_pos;
	start_pos = "";
	strlen_pos = 0;
      }
    else
      {
	chp++; /* skip `.' */
	chp = skip_spaces (chp);
	start_val = chp;
	if (*chp == '/' || *chp == '*')
	  {
	    do
	      {
		chp++;
	      }
	    while (*chp == '/' || *chp == '*');
	  }
	else if (isalpha(*start_val))
	  {
	    do
	      {
		chp++;
	      }
	    while (isalnum(*chp) || *chp == '_');
	  }
	else if (isdigit(*start_val))
	  {
	    do {
	      chp++;
	    }
	    while (isalnum(*chp));
	  }
	strlen_val = chp - start_val;
	chp = skip_spaces (chp);
      }
    if (strlen_val == 0)
      error (line, "Empty value field\n");
    
    /* break out any conditional fields - { "!" <value> } */
    while (*chp == '!')
      {
	char *start;
	int len;
	insn_field_exclusion *new_exclusion = ZALLOC (insn_field_exclusion);
	insn_field_exclusion **last;
	
	/* what type of conditional field */
	chp++; 
	chp = skip_spaces (chp);
	/* the value */
	start = chp;
	chp = skip_digits (chp);
	len = chp - start;
	if (len == 0)
	  error (line, "Missing or invalid conditional value\n");
	/* fill in the entry */
	new_exclusion->string = NZALLOC (char, len + 1);
	strncpy (new_exclusion->string, start, len);
	new_exclusion->value = a2i (new_exclusion->string);
	/* insert it */
	last = &new_field->exclusions;
	while (*last != NULL)
	  last = &(*last)->next;
	*last = new_exclusion;
	chp = skip_spaces (chp);
      }

    /* NOW verify that the field ws finished */
    if (*chp == ',')
      {
	chp = skip_spaces (chp + 1);
	if (*chp == '\0')
	  error (line, "empty field\n");
      }
    else if (*chp != '\0')
      {
	error (line, "Missing field separator");
      }

    /* copy the value */
    new_field->val_string = NZALLOC (char, strlen_val+1);
    strncpy (new_field->val_string, start_val, strlen_val);
    if (isdigit (new_field->val_string[0]))
      {
	if (strlen_pos == 0)
	  {
	    /* when the length/pos field is omited, an integer field
               is always binary */
	    unsigned64 val = 0;
	    int i;
	    for (i = 0; i < strlen_val; i++)
	      {
		if (new_field->val_string[i] != '0'
		    && new_field->val_string[i] != '1')
		  error (line, "invalid binary field %s\n",
			 new_field->val_string);
		val = (val << 1) + (new_field->val_string[i] == '1');
	      }
	    new_field->val_int = val;
	    new_field->type = insn_field_int;
	  }
	else
	  {
	    new_field->val_int = a2i (new_field->val_string);
	    new_field->type = insn_field_int;
	  }
      }
    else if (new_field->val_string[0] == '/')
      {
	new_field->type = insn_field_reserved;
      }
    else if (new_field->val_string[0] == '*')
      {
	new_field->type = insn_field_wild;
      }
    else
      {
	new_field->type = insn_field_string;
	if (filter_is_member (word->field_names, new_field->val_string))
	  error (line, "Field name %s is duplicated\n", new_field->val_string);
	filter_parse (&word->field_names, new_field->val_string);
      }
    if (new_field->type != insn_field_string
	&& new_field->exclusions != NULL)
      error (line, "Exclusions only apply to name fields\n");

    /* the copy the position */
    new_field->pos_string = NZALLOC (char, strlen_pos + 1);
    strncpy (new_field->pos_string, start_pos, strlen_pos);
    if (strlen_pos == 0)
      {
	new_field->first = new_field->prev->last + 1;
	if (new_field->first == 0 /* first field */
	    && *chp == '\0' /* no further fields */
	    && new_field->type == insn_field_string)
	  {
	    /* A single string without any position, assume that it
               represents the entire instruction word */
	    new_field->width = options.insn_bit_size;
	  }
	else
	  {
	    /* No explicit width/position, assume value implicitly
	       supplies the width */
	    new_field->width = strlen_val;
	  }
	new_field->last = new_field->first + new_field->width - 1;
	if (new_field->last >= options.insn_bit_size)
	  error (line, "Bit position %d exceed instruction bit size (%d)\n",
		 new_field->last, options.insn_bit_size);
      }
    else if (options.insn_specifying_widths)
      {
	new_field->first = new_field->prev->last + 1;
	new_field->width = a2i(new_field->pos_string);
	new_field->last = new_field->first + new_field->width - 1;
	if (new_field->last >= options.insn_bit_size)
	  error (line, "Bit position %d exceed instruction bit size (%d)\n",
		 new_field->last, options.insn_bit_size);
      }
    else
      {
	new_field->first = target_a2i(options.hi_bit_nr,
				      new_field->pos_string);
	new_field->last = new_field->next->first - 1; /* guess */
	new_field->width = new_field->last - new_field->first + 1; /* guess */
	new_field->prev->last = new_field->first - 1; /*fix*/
	new_field->prev->width = new_field->first - new_field->prev->first; /*fix*/
      }
  }

  /* fiddle first/last so that the sentinals disapear */
  ASSERT(word->first->last < 0);
  ASSERT(word->last->first >= options.insn_bit_size);
  word->first = word->first->next;
  word->last = word->last->prev;

  /* check that the last field goes all the way to the last bit */
  if (word->last->last != options.insn_bit_size - 1)
    {
      options.warning (line, "Instruction format is not %d bits wide\n",
		       options.insn_bit_size);
      word->last->last = options.insn_bit_size - 1;
    }

  /* now go over this again, pointing each bit position at a field
     record */
  {
    insn_field_entry *field;
    for (field = word->first;
	 field->last < options.insn_bit_size;
	 field = field->next)
      {
	int i;
	for (i = field->first; i <= field->last; i++)
	  {
	    word->bit[i] = ZALLOC (insn_bit_entry);
	    word->bit[i]->field = field;
	    switch (field->type)
	      {
	      case insn_field_int:
		word->bit[i]->mask = 1;
		word->bit[i]->value = ((field->val_int
					& ((insn_uint)1 << (field->last - i)))
				       != 0);
	      case insn_field_reserved:
	      case insn_field_wild:
	      case insn_field_string:
		break;
	      }
	  }
      }
  }

  return word;
}


static void
parse_insn_words (insn_entry *insn,
		  char *formats)
{
  insn_word_entry **last_word = &insn->words;
  char *chp;

  /* now work through the formats */
  insn->nr_words = 0;
  chp = formats;

  while (1)
    {
      char *start_pos;
      char *end_pos;
      int strlen_pos;
      char *format;
      insn_word_entry *new_word;

      /* skip leading spaces */
      chp = skip_spaces (chp);

      /* break out the format */
      start_pos = chp;
      chp = skip_to_separator (chp, "+");
      end_pos = back_spaces (start_pos, chp);
      strlen_pos = end_pos - start_pos;

      /* check that something was there */
      if (strlen_pos == 0)
	error (insn->line, "missing or empty instruction format\n");

      /* parse the field */
      format = NZALLOC (char, strlen_pos + 1);
      strncpy (format, start_pos, strlen_pos);
      new_word = parse_insn_word (insn->line, format, insn->nr_words);
      insn->nr_words++;
      if (filter_is_common (insn->field_names, new_word->field_names))
	error (insn->line, "Field name duplicated between two words\n");
      filter_add (&insn->field_names, new_word->field_names);

      /* insert it */
      *last_word = new_word;
      last_word = &new_word->next;

      /* last format? */
      if (*chp == '\0')
	break;
      ASSERT (*chp == '+');
      chp++;
    }

  /* now create a quick access array of the same structure */
  {
    int i;
    insn_word_entry *word;
    insn->word = NZALLOC (insn_word_entry *, insn->nr_words + 1);
    for (i = 0, word = insn->words;
	 i < insn->nr_words;
	 i++, word = word->next)
      insn->word[i] = word;
  }
}

typedef enum {
  unknown_record = 0,
  insn_record, /* default */
  code_record,
  cache_record,
  compute_record,
  scratch_record,
  option_record,
  string_function_record,
  function_record,
  internal_record,
  define_record,
  include_record,
  model_processor_record,
  model_macro_record,
  model_data_record,
  model_static_record,
  model_function_record,
  model_internal_record,
} insn_record_type;

static const name_map insn_type_map[] = {
  { "option", option_record },
  { "cache", cache_record },
  { "compute", compute_record },
  { "scratch", scratch_record },
  { "define", define_record },
  { "include", include_record },
  { "%s", string_function_record },
  { "function", function_record },
  { "internal", internal_record },
  { "model", model_processor_record },
  { "model-macro", model_macro_record },
  { "model-data", model_data_record },
  { "model-static", model_static_record },
  { "model-internal", model_internal_record },
  { "model-function", model_function_record },
  { NULL, insn_record },
};


static int
record_is_old (table_entry *entry)
{
  if (entry->nr_fields > record_type_field
      && strlen (entry->field[record_type_field]) == 0)
    return 1;
  return 0;
}

static insn_record_type
record_type (table_entry *entry)
{
  switch (entry->type)
    {
    case table_code_entry:
      return code_record;

    case table_colon_entry:
      if (record_is_old (entry))
	{
	  /* old-format? */
	  if (entry->nr_fields > old_record_type_field)
	    {
	      int i = name2i (entry->field[old_record_type_field],
			      insn_type_map);
	      return i;
	    }
	  else
	    {
	      return unknown_record;
	    }
	}
      else if (entry->nr_fields > record_type_field
	       && entry->field[0][0] == '\0')
	{
	  /* new-format? */
	  int i = name2i (entry->field[record_type_field],
			  insn_type_map);
	  return i;
	}
      else
	return insn_record; /* default */
    }
  return unknown_record;
}

static int
record_prefix_is (table_entry *entry,
		  char ch,
		  int nr_fields)
{
  if (entry->type != table_colon_entry)
    return 0;
  if (entry->nr_fields < nr_fields)
    return 0;
  if (entry->field[0][0] != ch && ch != '\0')
    return 0;
  return 1;
}

static table_entry *
parse_model_data_record (insn_table *isa,
			 table *file,
			 table_entry *record,
			 int nr_fields,
			 model_data **list)
{
  table_entry *model_record = record;
  table_entry *code_record = NULL;
  model_data *new_data;
  if (record->nr_fields < nr_fields)
    error (record->line, "Incorrect number of fields\n");
  record = table_read (file);
  if (record->type == table_code_entry)
    {
      code_record = record;
      record = table_read (file);
    }
  /* create the new data record */
  new_data = ZALLOC (model_data);
  new_data->line = model_record->line;
  filter_parse (&new_data->flags,
		model_record->field[record_filter_flags_field]);
  new_data->entry = model_record;
  new_data->code = code_record;
  /* append it if not filtered out */
  if (!is_filtered_out (options.flags_filter,
			model_record->field[record_filter_flags_field])
      && !is_filtered_out (options.model_filter,
			   model_record->field[record_filter_models_field]))
    {
      while (*list != NULL)
	list = &(*list)->next;
      *list = new_data;
    }
  return record;
}


typedef enum {
  insn_bit_size_option = 1,
  insn_specifying_widths_option,
  hi_bit_nr_option,
  flags_filter_option,
  model_filter_option,
  multi_sim_option,
  format_names_option,
  gen_delayed_branch,
  unknown_option,
} option_names;

static const name_map option_map[] = {
  { "insn-bit-size", insn_bit_size_option },
  { "insn-specifying-widths", insn_specifying_widths_option },
  { "hi-bit-nr", hi_bit_nr_option },
  { "flags-filter", flags_filter_option },
  { "model-filter", model_filter_option },
  { "multi-sim", multi_sim_option },
  { "format-names", format_names_option },
  { "gen-delayed-branch", gen_delayed_branch },
  { NULL, unknown_option },
};

static table_entry *
parse_include_record (table *file,
		      table_entry *record)
{
  /* parse the include record */
  if (record->nr_fields < nr_include_fields)
    error (record->line, "Incorrect nr fields for include record\n");
  /* process it */
  if (!is_filtered_out (options.flags_filter,
			record->field[record_filter_flags_field])
      && !is_filtered_out (options.model_filter,
			   record->field[record_filter_models_field]))
    {
      table_push (file, record->line, options.include,
		  record->field[include_filename_field]);
    }  
  /* nb: can't read next record until after the file has been pushed */
  record = table_read (file);
  return record;
}


static table_entry *
parse_option_record (table *file,
		     table_entry *record)
{
  table_entry *option_record;
  /* parse the option record */
  option_record = record;
  if (record->nr_fields < nr_option_fields)
    error (record->line, "Incorrect nr of fields for option record\n");
  record = table_read (file);
  /* process it */
  if (!is_filtered_out (options.flags_filter,
			option_record->field[record_filter_flags_field])
      && !is_filtered_out (options.model_filter,
			   option_record->field[record_filter_models_field]))
    {
      char *name = option_record->field[option_name_field];
      option_names option = name2i (name, option_map);
      char *value = option_record->field[option_value_field];
      switch (option)
	{
	case insn_bit_size_option:
	  {
	    options.insn_bit_size = a2i (value);
	    if (options.insn_bit_size < 0
		|| options.insn_bit_size > max_insn_bit_size)
	      error (option_record->line, "Instruction bit size out of range\n");
	    if (options.hi_bit_nr != options.insn_bit_size - 1
		&& options.hi_bit_nr != 0)
	      error (option_record->line, "insn-bit-size / hi-bit-nr conflict\n");
	    break;
	  }
	case insn_specifying_widths_option:
	  {
	    options.insn_specifying_widths = a2i (value);
	    break;
	  }
	case hi_bit_nr_option:
	  {
	    options.hi_bit_nr = a2i (value);
	    if (options.hi_bit_nr != 0
		&& options.hi_bit_nr != options.insn_bit_size - 1)
	      error (option_record->line, "hi-bit-nr / insn-bit-size conflict\n");
	    break;
	  }
	case flags_filter_option:
	  {
	    filter_parse (&options.flags_filter, value);
	    break;
	  }
	case model_filter_option:
	  {
	    filter_parse (&options.model_filter, value);
	    break;
	  }
	case multi_sim_option:
	  {
	    options.gen.multi_sim = a2i (value);
	    break;
	  }
	case format_names_option:
	  {
	    filter_parse (&options.format_name_filter, value);
	    break;
	  }
	case gen_delayed_branch:
	  {
	    options.gen.delayed_branch = a2i (value);
	    break;
	  }
	case unknown_option:
	  {
	    error (option_record->line, "Unknown option - %s\n", name);
	    break;
	  }
	}
    }  
  return record;
}


static table_entry *
parse_function_record (table *file,
		       table_entry *record,
		       function_entry **list,
		       function_entry **list_entry,
		       int is_internal)
{
  function_entry *new_function;
  new_function = ZALLOC (function_entry);
  new_function->line = record->line;
  new_function->is_internal = is_internal;
  /* parse the function header */
  if (record_is_old (record))
    {
      if (record->nr_fields < nr_old_function_fields)
	error (record->line, "Missing fields from (old) function record\n");
      new_function->type = record->field[old_function_typedef_field];
      new_function->type = record->field[old_function_typedef_field];
      if (record->nr_fields > old_function_param_field)
	new_function->param = record->field[old_function_param_field];
      new_function->name = record->field[old_function_name_field];
    }
  else
    {
      if (record->nr_fields < nr_function_fields)
	error (record->line, "Missing fields from function record\n");
      filter_parse (&new_function->flags,
		    record->field[record_filter_flags_field]);
      filter_parse (&new_function->models,
		    record->field[record_filter_models_field]);
      new_function->type = record->field[function_typedef_field];
      new_function->param = record->field[function_param_field];
      new_function->name = record->field[function_name_field];
    }
  record = table_read (file);
  /* parse the function body */
  if (record->type == table_code_entry)
    {
      new_function->code = record;
      record = table_read (file);
    }
  /* insert it */
  if (!filter_is_subset (options.flags_filter, new_function->flags))
    {
      notify (new_function->line, "Discarding function entry - filter flags\n");
    }
  else if (!filter_is_subset (options.model_filter, new_function->models))
    {
      notify (new_function->line, "Discarding function entry - filter models\n");
    }
  else
    {
      while (*list != NULL)
	list = &(*list)->next;
      *list = new_function;
      if (list_entry != NULL)
	*list_entry = new_function;
    }
  /* done */
  return record;
}

static void
parse_insn_model_record (table *file,
			 table_entry *record,
			 insn_entry *insn,
			 model_table *model)
{
  insn_model_entry **last_insn_model;
  insn_model_entry *new_insn_model = ZALLOC (insn_model_entry);
  /* parse it */
  new_insn_model->line = record->line;
  if (record->nr_fields > insn_model_unit_data_field)
    new_insn_model->unit_data = record->field[insn_model_unit_data_field];
  new_insn_model->insn = insn;
  /* parse the model names, verify that all were defined */
  new_insn_model->names = NULL;
  filter_parse (&new_insn_model->names,
		record->field[insn_model_name_field] + 1 /*skip `*'*/);
  if (new_insn_model->names == NULL)
    {
      /* No processor names - a generic model entry, enter it into all
	 the non-empty fields */
      int index;
      for (index = 0; index < model->nr_models; index++)
	if (insn->model[index] == 0)
	  {
	    insn->model[index] = new_insn_model;
	  }
      /* also add the complete processor set to this processor's set */
      filter_add (&insn->processors, model->processors);
    }
  else
    {
      /* Find the corresponding master model record for each name so
         that they can be linked in. */
      int index;
      char *name = "";
      while (1)
	{
	  name = filter_next (new_insn_model->names, name);
	  if (name == NULL) break;
	  index = filter_is_member (model->processors, name) - 1;
	  if (index < 0)
	    {
	      error (new_insn_model->line,
		     "machine model `%s' undefined\n", name);
	    }
	  /* store it in the corresponding model array entry */
	  if (insn->model[index] != NULL
	      && insn->model[index]->names != NULL)
	    {
	      warning (new_insn_model->line,
		       "machine model `%s' previously defined\n", name);
	      error (insn->model[index]->line, "earlier definition\n");
	    }
	  insn->model[index] = new_insn_model;
	  /* also add the name to the instructions processor set as an
	     alternative lookup mechanism */
	  filter_parse (&insn->processors, name);
	}
    }
#if 0
  /* for some reason record the max length of any
     function unit field */
  int len = strlen (insn_model_ptr->field[insn_model_fields]);
  if (model->max_model_fields_len < len)
    model->max_model_fields_len = len;
#endif
  /* link it in */
  last_insn_model = &insn->models;
  while ((*last_insn_model) != NULL)
    last_insn_model = &(*last_insn_model)->next;
  *last_insn_model = new_insn_model;
}


static void
parse_insn_mnemonic_record (table *file,
			    table_entry *record,
			    insn_entry *insn)
{
  insn_mnemonic_entry **last_insn_mnemonic;
  insn_mnemonic_entry *new_insn_mnemonic = ZALLOC (insn_mnemonic_entry);
  /* parse it */
  new_insn_mnemonic->line = record->line;
  ASSERT (record->nr_fields > insn_mnemonic_format_field);
  new_insn_mnemonic->format = record->field[insn_mnemonic_format_field];
  ASSERT (new_insn_mnemonic->format[0] == '"');
  if (new_insn_mnemonic->format[strlen (new_insn_mnemonic->format) - 1] != '"')
    error (new_insn_mnemonic->line, "Missing closing double quote in mnemonic field\n");
  if (record->nr_fields > insn_mnemonic_condition_field)
    new_insn_mnemonic->condition = record->field[insn_mnemonic_condition_field];
  new_insn_mnemonic->insn = insn;
  /* insert it */
  last_insn_mnemonic = &insn->mnemonics;
  while ((*last_insn_mnemonic) != NULL)
    last_insn_mnemonic = &(*last_insn_mnemonic)->next;
  insn->nr_mnemonics++;
  *last_insn_mnemonic = new_insn_mnemonic;
}


insn_table *
load_insn_table (char *file_name,
		 cache_entry *cache)
{
  table *file = table_open (file_name);
  table_entry *record = table_read (file);

  insn_table *isa = ZALLOC (insn_table);
  model_table *model = ZALLOC (model_table);
  
  isa->model = model;
  isa->caches = cache;

  while (record != NULL)
    {

      switch (record_type (record))
	{

	case include_record:
	  {
	    record = parse_include_record (file, record);
	    break;
	  }

	case option_record:
	  {
	    if (isa->insns != NULL)
	      error (record->line, "Option after first instruction\n");
	    record = parse_option_record (file, record);
	    break;
	  }
	
	case string_function_record:
	  {
	    function_entry *function = NULL;
	    record = parse_function_record (file, record,
					    &isa->functions,
					    &function,
					    0/*is-internal*/);
	    /* convert a string function record into an internal function */
	    if (function != NULL)
	      {
		char *name = NZALLOC (char,
				      (strlen ("str_")
				       + strlen (function->name)
				       + 1));
		strcat (name, "str_");
		strcat (name, function->name);
		function->name = name;
		function->type = "const char *";
	      }
	    break;
	  }
	
	case function_record: /* function record */
	  {
	    record = parse_function_record (file, record,
					    &isa->functions,
					    NULL,
					    0/*is-internal*/);
	    break;
	  }

	case internal_record:
	  {
	    /* only insert it into the function list if it is unknown */
	    function_entry *function = NULL;
	    record = parse_function_record (file, record,
					    &isa->functions,
					    &function,
					    1/*is-internal*/);
	    /* check what was inserted to see if a pseudo-instruction
               entry also needs to be created */
	    if (function != NULL)
	      {
		insn_entry **insn = NULL;
		if (strcmp (function->name, "illegal") == 0)
		  {
		    /* illegal function save it away */
		    if (isa->illegal_insn != NULL)
		      {
			warning (function->line,
				 "Multiple illegal instruction definitions\n");
			error (isa->illegal_insn->line,
			       "Location of first illegal instruction\n");
		      }
		    else
		      insn = &isa->illegal_insn;
		  }
		if (insn != NULL)
		  {
		    *insn = ZALLOC (insn_entry);
		    (*insn)->line = function->line;
		    (*insn)->name = function->name;
		    (*insn)->code = function->code;
		  }
	      }
	    break;
	  }
	  
	case scratch_record: /* cache macro records */
	case cache_record:
	case compute_record:
	  {
	    cache_entry *new_cache;
	    /* parse the cache record */
	    if (record->nr_fields < nr_cache_fields)
	      error (record->line,
		     "Incorrect nr of fields for scratch/cache/compute record\n");
	    /* create it */
	    new_cache = ZALLOC (cache_entry);
	    new_cache->line = record->line;
	    filter_parse (&new_cache->flags,
			  record->field[record_filter_flags_field]);
	    filter_parse (&new_cache->models,
			  record->field[record_filter_models_field]);
	    new_cache->type = record->field[cache_typedef_field];
	    new_cache->name = record->field[cache_name_field];
	    filter_parse (&new_cache->original_fields,
			  record->field[cache_original_fields_field]);
	    new_cache->expression = record->field[cache_expression_field];
	    /* insert it but only if not filtered out */
	    if (!filter_is_subset (options.flags_filter, new_cache->flags))
	      {
		notify (new_cache->line, "Discarding cache entry %s - filter flags\n",
			new_cache->name);
	      }
	    else if (is_filtered_out (options.model_filter,
				      record->field[record_filter_models_field]))
	      {
		notify (new_cache->line, "Discarding cache entry %s - filter models\n",
			new_cache->name);
	      }
	    else
	      {
		cache_entry **last;
		last = &isa->caches;
		while (*last != NULL)
		  last = &(*last)->next;
		*last = new_cache;
	      }
	    /* advance things */
	    record = table_read (file);
	    break;
	  }
	
	/* model records */
	case model_processor_record:
	  {
	    model_entry *new_model;
	    /* parse the model */
	    if (record->nr_fields < nr_model_processor_fields)
	      error (record->line, "Incorrect nr of fields for model record\n");
	    if (isa->insns != NULL)
	      error (record->line, "Model appears after first instruction\n");
	    new_model = ZALLOC (model_entry);
	    filter_parse (&new_model->flags,
			  record->field[record_filter_flags_field]);
	    new_model->line = record->line;
	    new_model->name = record->field[model_name_field];
	    new_model->full_name = record->field[model_full_name_field];
	    new_model->unit_data = record->field[model_unit_data_field];
	    /* only insert it if not filtered out */
	    if (!filter_is_subset (options.flags_filter, new_model->flags))
	      {
		notify (new_model->line, "Discarding processor model %s - filter flags\n",
			new_model->name);
	      }
	    else if (is_filtered_out (options.model_filter,
				      record->field[record_filter_models_field]))
	      {
		notify (new_model->line, "Discarding processor model %s - filter models\n",
			new_model->name);
	      }
	    else if (filter_is_member (model->processors, new_model->name))
	      {
		error (new_model->line, "Duplicate processor model %s\n",
		       new_model->name);
	      }
	    else
	      {
		model_entry **last;
		last = &model->models;
		while (*last != NULL)
		  last = &(*last)->next;
		*last = new_model;
		/* count it */
		model->nr_models ++;
		filter_parse (&model->processors, new_model->name);
	      }
	    /* advance things */
	    record = table_read (file);
	  }
	  break;
	  
	case model_macro_record:
	  record = parse_model_data_record (isa, file, record,
					    nr_model_macro_fields,
					    &model->macros);
	  break;
	  
	case model_data_record:
	  record = parse_model_data_record (isa, file, record,
					    nr_model_data_fields,
					    &model->data);
	  break;
	  
	case model_static_record:
	  record = parse_function_record (file, record,
					  &model->statics,
					  NULL,
					  0/*is internal*/);
	  break;
	  
	case model_internal_record:
	  record = parse_function_record (file, record,
					  &model->internals,
					  NULL,
					  1/*is internal*/);
	  break;
	  
	case model_function_record:
	  record = parse_function_record (file, record,
					  &model->functions,
					  NULL,
					  0/*is internal*/);
	  break;
	  
	case insn_record: /* instruction records */
	  {
	    insn_entry *new_insn;
	    char *format;
	    /* parse the instruction */
	    if (record->nr_fields < nr_insn_fields)
	      error (record->line, "Incorrect nr of fields for insn record\n");
	    new_insn = ZALLOC (insn_entry);
	    new_insn->line = record->line;
	    filter_parse (&new_insn->flags,
			  record->field[record_filter_flags_field]);
	    /* save the format field.  Can't parse it until after the
               filter-out checks.  Could be filtered out because the
               format is invalid */
	    format = record->field[insn_word_field];
	    new_insn->format_name = record->field[insn_format_name_field];
	    if (options.format_name_filter != NULL
		&& !filter_is_member (options.format_name_filter,
				      new_insn->format_name))
	      error (new_insn->line, "Unreconized instruction format name `%s'\n",
		     new_insn->format_name);
	    filter_parse (&new_insn->options,
			  record->field[insn_options_field]);
	    new_insn->name = record->field[insn_name_field];
	    record = table_read (file);
	    /* Parse any model/assember records */
	    new_insn->nr_models = model->nr_models;
	    new_insn->model = NZALLOC (insn_model_entry*, model->nr_models + 1);
	    while (record != NULL)
	      {
		if (record_prefix_is (record, '*', nr_insn_model_fields))
		  parse_insn_model_record (file, record, new_insn, model);
		else if (record_prefix_is (record, '"', nr_insn_mnemonic_fields))
		  parse_insn_mnemonic_record (file, record, new_insn);
		else
		  break;
		/* advance */
		record = table_read (file);
	      }
	    /* Parse the code record */
	    if (record != NULL && record->type == table_code_entry)
	      {
		new_insn->code = record;
		record = table_read (file);
	      }
	    /* insert it */
	    if (!filter_is_subset (options.flags_filter, new_insn->flags))
	      {
		if (options.warn.discard)
		  notify (new_insn->line,
			  "Discarding instruction %s (flags-filter)\n",
			  new_insn->name);
	      }
	    else if (new_insn->processors != NULL
		     && options.model_filter != NULL
		     && !filter_is_common (options.model_filter,
					   new_insn->processors))
	      {
		/* only discard an instruction based in the processor
                   model when both the instruction and the options are
                   nonempty */
		if (options.warn.discard)
		  notify (new_insn->line,
			  "Discarding instruction %s (processor-model)\n",
			  new_insn->name);
	      }
	    else
	      {
		insn_entry **last;
		/* finish the parsing */
		parse_insn_words (new_insn, format);
		/* append it */
		last = &isa->insns;
		while (*last)
		  last = &(*last)->next;
		*last = new_insn;
		/* update global isa counters */
		isa->nr_insns ++;
		if (isa->max_nr_words < new_insn->nr_words)
		  isa->max_nr_words = new_insn->nr_words;
		filter_add (&isa->flags, new_insn->flags);
		filter_add (&isa->options, new_insn->options);
	      }
	    break;
	  }
      
	case unknown_record:
	case define_record:
	case code_record:
	  error (record->line, "Unknown or unexpected entry\n");


	}
    }
  return isa;
}


void
print_insn_words (lf *file,
		  insn_entry *insn)
{
  insn_word_entry *word = insn->words;
  if (word != NULL)
    {
      while (1)
	{
	  insn_field_entry *field = word->first;
	  while (1)
	    {
	      if (options.insn_specifying_widths)
		lf_printf (file, "%d.", field->width);
	      else
		lf_printf (file, "%d.", i2target (options.hi_bit_nr, field->first));
	      switch (field->type)
		{
		case insn_field_int:
		  lf_printf (file, "0x%lx", (long) field->val_int);
		  break;
		case insn_field_reserved:
		  lf_printf (file, "/");
		  break;
		case insn_field_wild:
		  lf_printf (file, "*");
		  break;
		case insn_field_string:
		  lf_printf (file, "%s", field->val_string);
		  break;
		}
	      if (field == word->last)
		break;
	      field = field->next;
	      lf_printf (file, ",");
	    }
	  word = word->next;
	  if (word == NULL)
	    break;
	  lf_printf (file, "+");
	}
    }
}



void
function_entry_traverse (lf *file,
			 function_entry *functions,
			 function_entry_handler *handler,
			 void *data)
{
  function_entry *function;
  for (function = functions; function != NULL; function = function->next)
    {
      handler (file, function, data);
    }
}

void
insn_table_traverse_insn (lf *file,
			  insn_table *isa,
			  insn_entry_handler *handler,
			  void *data)
{
  insn_entry *insn;
  for (insn = isa->insns; insn != NULL; insn = insn->next)
    {
      handler (file, isa, insn, data);
    }
}


static void
dump_function_entry (lf *file,
		     char *prefix,
		     function_entry *entry,
		     char *suffix)
{
  lf_printf (file, "%s(function_entry *) 0x%lx", prefix, (long) entry);
  if (entry != NULL)
    {
      dump_line_ref (file, "\n(line ", entry->line, ")");
      dump_filter (file, "\n(flags ", entry->flags, ")");
      lf_printf (file, "\n(type \"%s\")", entry->type);
      lf_printf (file, "\n(name \"%s\")", entry->name);
      lf_printf (file, "\n(param \"%s\")", entry->param);
      dump_table_entry (file, "\n(code ", entry->code, ")");
      lf_printf (file, "\n(is_internal %d)", entry->is_internal);
      lf_printf (file, "\n(next 0x%lx)", (long) entry->next);
    }
  lf_printf (file, "%s", suffix);
}

static void
dump_function_entries (lf *file,
		       char *prefix,
		       function_entry *entry,
		       char *suffix)
{
  lf_printf (file, "%s", prefix);
  lf_indent (file, +1);
  while (entry != NULL)
    {
      dump_function_entry (file, "\n(", entry, ")");
      entry = entry->next;
    }
  lf_indent (file, -1);
  lf_printf (file, "%s", suffix);
}

static char *
cache_entry_type_to_str (cache_entry_type type)
{
  switch (type)
    {
    case scratch_value: return "scratch";
    case cache_value: return "cache";
    case compute_value: return "compute";
    }
  ERROR ("Bad switch");
  return 0;
}

static void
dump_cache_entry (lf *file,
		  char *prefix,
		  cache_entry *entry,
		  char *suffix)
{
  lf_printf (file, "%s(cache_entry *) 0x%lx", prefix, (long) entry);
  if (entry != NULL)
    {
      dump_line_ref (file, "\n(line ", entry->line, ")");
      dump_filter (file, "\n(flags ", entry->flags, ")");
      lf_printf (file, "\n(entry_type \"%s\")", cache_entry_type_to_str (entry->entry_type));
      lf_printf (file, "\n(name \"%s\")", entry->name);
      dump_filter (file, "\n(original_fields ", entry->original_fields, ")");
      lf_printf (file, "\n(type \"%s\")", entry->type);
      lf_printf (file, "\n(expression \"%s\")", entry->expression);
      lf_printf (file, "\n(next 0x%lx)", (long) entry->next);
    }
  lf_printf (file, "%s", suffix);
}

void
dump_cache_entries (lf *file,
		    char *prefix,
		    cache_entry *entry,
		    char *suffix)
{
  lf_printf (file, "%s", prefix);
  lf_indent (file, +1);
  while (entry != NULL)
    {
      dump_cache_entry (file, "\n(", entry, ")");
      entry = entry->next;
    }
  lf_indent (file, -1);
  lf_printf (file, "%s", suffix);
}

static void
dump_model_data (lf *file,
		 char *prefix,
		 model_data *entry,
		 char *suffix)
{
  lf_printf (file, "%s(model_data *) 0x%lx", prefix, (long) entry);
  if (entry != NULL)
    {
      lf_indent (file, +1);
      dump_line_ref (file, "\n(line ", entry->line, ")");
      dump_filter (file, "\n(flags ", entry->flags, ")");
      dump_table_entry (file, "\n(entry ", entry->entry, ")");
      dump_table_entry (file, "\n(code ", entry->code, ")");
      lf_printf (file, "\n(next 0x%lx)", (long) entry->next);
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", prefix);
}

static void
dump_model_datas (lf *file,
		  char *prefix,
		  model_data *entry,
		  char *suffix)
{
  lf_printf (file, "%s", prefix);
  lf_indent (file, +1);
  while (entry != NULL)
    {
      dump_model_data (file, "\n(", entry, ")");
      entry = entry->next;
    }
  lf_indent (file, -1);
  lf_printf (file, "%s", suffix);
}

static void
dump_model_entry (lf *file,
		  char *prefix,
		  model_entry *entry,
		  char *suffix)
{
  lf_printf (file, "%s(model_entry *) 0x%lx", prefix, (long) entry);
  if (entry != NULL)
    {
      lf_indent (file, +1);
      dump_line_ref (file, "\n(line ", entry->line, ")");
      dump_filter (file, "\n(flags ", entry->flags, ")");
      lf_printf (file, "\n(name \"%s\")", entry->name);
      lf_printf (file, "\n(full_name \"%s\")", entry->full_name);
      lf_printf (file, "\n(unit_data \"%s\")", entry->unit_data);
      lf_printf (file, "\n(next 0x%lx)", (long) entry->next);
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", prefix);
}

static void
dump_model_entries (lf *file,
		    char *prefix,
		    model_entry *entry,
		    char *suffix)
{
  lf_printf (file, "%s", prefix);
  lf_indent (file, +1);
  while (entry != NULL)
    {
      dump_model_entry (file, "\n(", entry, ")");
      entry = entry->next;
    }
  lf_indent (file, -1);
  lf_printf (file, "%s", suffix);
}


static void
dump_model_table (lf *file,
		  char *prefix,
		  model_table *entry,
		  char *suffix)
{
  lf_printf (file, "%s(model_table *) 0x%lx", prefix, (long) entry);
  if (entry != NULL)
    {
      lf_indent (file, +1);
      dump_filter (file, "\n(processors ", entry->processors, ")");
      lf_printf (file, "\n(nr_models %d)", entry->nr_models);
      dump_model_entries (file, "\n(models ", entry->models, ")");
      dump_model_datas (file, "\n(macros ", entry->macros, ")");
      dump_model_datas (file, "\n(data ", entry->data, ")");
      dump_function_entries (file, "\n(statics ", entry->statics, ")");
      dump_function_entries (file, "\n(internals ", entry->functions, ")");
      dump_function_entries (file, "\n(functions ", entry->functions, ")");
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}


static char *
insn_field_type_to_str (insn_field_type type)
{
  switch (type)
    {
    case insn_field_int: return "int";
    case insn_field_reserved: return "reserved";
    case insn_field_wild: return "wild";
    case insn_field_string: return "string";
    }
  ERROR ("bad switch");
  return 0;
}

void
dump_insn_field (lf *file,
		 char *prefix,
		 insn_field_entry *field,
		 char *suffix)
{
  char *sep = " ";
  lf_printf (file, "%s(insn_field_entry *) 0x%lx", prefix, (long) field);
  if (field != NULL)
    {
      lf_indent (file, +1);
      lf_printf (file, "%s(first %d)", sep, field->first);
      lf_printf (file, "%s(last %d)", sep, field->last);
      lf_printf (file, "%s(width %d)", sep, field->width);
      lf_printf (file, "%s(type %s)", sep, insn_field_type_to_str (field->type));
      switch (field->type)
	{
	case insn_field_int:
	  lf_printf (file, "%s(val 0x%lx)", sep, (long) field->val_int);
	  break;
	case insn_field_reserved:
	  /* nothing output */
	  break;
	case insn_field_wild:
	  /* nothing output */
	  break;
	case insn_field_string:
	  lf_printf (file, "%s(val \"%s\")", sep, field->val_string);
	  break;
	}
      lf_printf (file, "%s(next 0x%lx)", sep, (long) field->next);
      lf_printf (file, "%s(prev 0x%lx)", sep, (long) field->prev);
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}

void
dump_insn_word_entry (lf *file,
		      char *prefix,
		      insn_word_entry *word,
		      char *suffix)
{
  lf_printf (file, "%s(insn_word_entry *) 0x%lx", prefix, (long) word);
  if (word != NULL)
    {
      int i;
      insn_field_entry *field;
      lf_indent (file, +1);
      lf_printf (file, "\n(first 0x%lx)", (long) word->first);
      lf_printf (file, "\n(last 0x%lx)", (long) word->last);
      lf_printf (file, "\n(bit");
      for (i = 0; i < options.insn_bit_size; i++)
	lf_printf (file, "\n ((value %d) (mask %d) (field 0x%lx))",
		   word->bit[i]->value, word->bit[i]->mask, (long) word->bit[i]->field);
      lf_printf (file, ")");
      for (field = word->first; field != NULL; field = field->next)
	dump_insn_field (file, "\n(", field, ")");
      dump_filter (file, "\n(field_names ", word->field_names, ")");
      lf_printf (file, "\n(next 0x%lx)", (long) word->next);
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}

static void
dump_insn_word_entries (lf *file,
			char *prefix,
			insn_word_entry *word,
			char *suffix)
{
  lf_printf (file, "%s", prefix);
  while (word != NULL)
    {
      dump_insn_word_entry (file, "\n(", word, ")");
      word = word->next;
    }
  lf_printf (file, "%s", suffix);
}

static void
dump_insn_model_entry (lf *file,
		       char *prefix,
		       insn_model_entry *model,
		       char *suffix)
{
  lf_printf (file, "%s(insn_model_entry *) 0x%lx", prefix, (long) model);
  if (model != NULL)
    {
      lf_indent (file, +1);
      dump_line_ref (file, "\n(line ", model->line, ")");
      dump_filter (file, "\n(names ", model->names, ")");
      lf_printf (file, "\n(full_name \"%s\")", model->full_name);
      lf_printf (file, "\n(unit_data \"%s\")", model->unit_data);
      lf_printf (file, "\n(insn (insn_entry *) 0x%lx)", (long) model->insn);
      lf_printf (file, "\n(next (insn_model_entry *) 0x%lx)",
		 (long) model->next);
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}

static void
dump_insn_model_entries (lf *file,
			 char *prefix,
			 insn_model_entry *model,
			 char *suffix)
{
  lf_printf (file, "%s", prefix);
  while (model != NULL)
    {
      dump_insn_model_entry (file, "\n", model, "");
      model = model->next;
    }
  lf_printf (file, "%s", suffix);
}


static void
dump_insn_mnemonic_entry (lf *file,
			  char *prefix,
			  insn_mnemonic_entry *mnemonic,
			  char *suffix)
{
  lf_printf (file, "%s(insn_mnemonic_entry *) 0x%lx", prefix, (long) mnemonic);
  if (mnemonic != NULL)
    {
      lf_indent (file, +1);
      dump_line_ref (file, "\n(line ", mnemonic->line, ")");
      lf_printf (file, "\n(format \"%s\")", mnemonic->format);
      lf_printf (file, "\n(condition \"%s\")", mnemonic->condition);
      lf_printf (file, "\n(insn (insn_entry *) 0x%lx)",
		 (long) mnemonic->insn);
      lf_printf (file, "\n(next (insn_mnemonic_entry *) 0x%lx)",
		 (long) mnemonic->next);
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}

static void
dump_insn_mnemonic_entries (lf *file,
			    char *prefix,
			    insn_mnemonic_entry *mnemonic,
			    char *suffix)
{
  lf_printf (file, "%s", prefix);
  while (mnemonic != NULL)
    {
      dump_insn_mnemonic_entry (file, "\n", mnemonic, "");
      mnemonic = mnemonic->next;
    }
  lf_printf (file, "%s", suffix);
}

void
dump_insn_entry (lf *file,
		 char *prefix,
		 insn_entry *entry,
		 char *suffix)
{
  lf_printf (file, "%s(insn_entry *) 0x%lx", prefix, (long) entry);
  if (entry != NULL)
    {
      int i;
      lf_indent (file, +1);
      dump_line_ref (file, "\n(line ", entry->line, ")");
      dump_filter (file, "\n(flags ", entry->flags, ")");
      lf_printf (file, "\n(nr_words %d)", entry->nr_words);
      dump_insn_word_entries (file, "\n(words ", entry->words, ")");
      lf_printf (file, "\n(word");
      for (i = 0; i < entry->nr_models; i++)
	lf_printf (file, " 0x%lx", (long) entry->word[i]);
      lf_printf (file, ")");
      dump_filter (file, "\n(field_names ", entry->field_names, ")");
      lf_printf (file, "\n(format_name \"%s\")", entry->format_name);
      dump_filter (file, "\n(options ", entry->options, ")");
      lf_printf (file, "\n(name \"%s\")", entry->name);
      lf_printf (file, "\n(nr_models %d)", entry->nr_models);
      dump_insn_model_entries (file, "\n(models ", entry->models, ")");
      lf_printf (file, "\n(model");
      for (i = 0; i < entry->nr_models; i++)
	lf_printf (file, " 0x%lx", (long) entry->model[i]);
      lf_printf (file, ")");
      dump_filter (file, "\n(processors ", entry->processors, ")");
      dump_insn_mnemonic_entries (file, "\n(mnemonics ", entry->mnemonics, ")");
      dump_table_entry (file, "\n(code ", entry->code, ")");
      lf_printf (file, "\n(next 0x%lx)", (long) entry->next);
      lf_indent (file, -1);
  }
  lf_printf (file, "%s", suffix);
}

static void
dump_insn_entries (lf *file,
		   char *prefix,
		   insn_entry *entry,
		   char *suffix)
{
  lf_printf (file, "%s", prefix);
  lf_indent (file, +1);
  while (entry != NULL)
    {
      dump_insn_entry (file, "\n(", entry, ")");
      entry = entry->next;
    }
  lf_indent (file, -1);
  lf_printf (file, "%s", suffix);
}



void
dump_insn_table (lf *file,
		 char *prefix,
		 insn_table *isa,
		 char *suffix)
{
  lf_printf (file, "%s(insn_table *) 0x%lx", prefix, (long) isa);
  if (isa != NULL)
    {
      lf_indent (file, +1);
      dump_cache_entries (file, "\n(caches ", isa->caches, ")");
      lf_printf (file, "\n(nr_insns %d)", isa->nr_insns);
      lf_printf (file, "\n(max_nr_words %d)", isa->max_nr_words);
      dump_insn_entries (file, "\n(insns ", isa->insns, ")");
      dump_function_entries (file, "\n(functions ", isa->functions, ")");
      dump_insn_entry (file, "\n(illegal_insn ", isa->illegal_insn, ")");
      dump_model_table (file, "\n(model ", isa->model, ")");
      dump_filter (file, "\n(flags ", isa->flags, ")");
      dump_filter (file, "\n(options ", isa->options, ")");
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}

#ifdef MAIN

igen_options options;

int
main (int argc, char **argv)
{
  insn_table *isa;
  lf *l;

  INIT_OPTIONS (options);

  if (argc == 3)
    filter_parse (&options.flags_filter, argv[2]);
  else if (argc != 2)
    error (NULL, "Usage: insn <insn-table> [ <filter-in> ]\n");

  isa = load_insn_table (argv[1], NULL);
  l = lf_open ("-", "stdout", lf_omit_references, lf_is_text, "tmp-ld-insn");
  dump_insn_table (l, "(isa ", isa, ")\n");

  return 0;
}

#endif
