/* ,file-id archive://[lord]/455/rx/unfa.c/1998-05-18
 */

/*	Copyright (C) 1997 Tom Lord
 * 
 * This program is provided to you under the terms of the Liberty Software
 * License.  You are NOT permitted to redistribute, modify, or use it
 * except in very specific ways described by that license.
 *
 * This software comes with NO WARRANTY.
 * 
 * You should have received a copy of the Liberty Software License
 * along with this software; see the file =LICENSE.  If not, write to
 * the Tom Lord, 1810 Francisco St. #2, Berkeley CA, 94703, USA.  
 */





#include "vu/bitset.h"
#include "vu/hashtab.h"
#include "vu/dstr.h"
#include "vu/str.h"
#include "rexp.h"
#include "nfa.h"
#include "super.h"
#include "unfa.h"




struct rx_cached_rexp
{
  struct rx_unfa unfa;
  struct rx_cached_rexp * next;
  struct rx_cached_rexp * prev;
  struct hashtab_item * hash_item;
};

static int delay = 64;
static int delayed = 0;
static struct hashtab table = { 0 };
static struct rx_cached_rexp * free_queue = 0;



static int
unfa_equal (void * va, void * vb)
{
  return rx_exp_equal ((struct rx_exp_node *)va, (struct rx_exp_node *)vb);
}

struct hashtab_rules unfa_rules = { unfa_equal, 0, 0, 0, 0 };

static struct rx_cached_rexp *
canonical_unfa (struct hashtab * table, struct rx_exp_node * rexp, int cset_size)
{
  struct hashtab_item * it;

  it = hashtab_store (table, rx_exp_hash (rexp), rexp, &unfa_rules);
  if (it->binding == 0)
    {
      struct rx_cached_rexp * cr;

      if (it->data == (void *)rexp)
	rx_save_rexp (rexp);
      
      cr = (struct rx_cached_rexp *)xmalloc (sizeof (*cr));
      memset0 ((char *)cr, sizeof (*cr));
      it->binding = (void *)cr;
      cr->unfa.nfa = 0;
      cr->unfa.exp = rexp;
      cr->hash_item = it;
      rx_save_rexp (rexp);
    }
  return (struct rx_cached_rexp *)it->binding;
}

static struct rx_nfa *
rx_unfa_to_nfa (struct rx_cached_rexp * cr, struct rx_exp_node * exp, int cset_size)
{
  struct rx_nfa * new_rx;

  if (cr->unfa.nfa)
    return cr->unfa.nfa;

  new_rx = rx_nfa_xalloc (cset_size);

  {
    struct rx_nfa_state * start;
    struct rx_nfa_state * end;
    start = end = 0;
    rx_build_nfa (new_rx, exp, &start, &end);
    end->state_label = 1;
    rx_set_start_state (new_rx, start);
    {
      struct rx_nfa_state * s;
      int x;
      x = 0;
      for (s = new_rx->nfa_states; s; s = s->next)
	s->id = x++;
    }
  }
  cr->unfa.nfa = new_rx;
  return new_rx;
}


/* rx_unfa
 *
 * Return the regexp=>NFA cache entry for a given expression,
 * constructing a new NFA if necessary.
 */

struct rx_unfa *
rx_unfa (struct rx_exp_node * exp, int cset_size)
{
  struct rx_cached_rexp * cr;

  if (exp && exp->cr)
    cr = exp->cr;
  else
    {
      cr = canonical_unfa (&table, exp, cset_size);
      if (exp)
	exp->cr = cr;
    }

  if (cr->next)
    {
      if (free_queue == cr)
	{
	  free_queue = cr->next;
	  if (free_queue == cr)
	    free_queue = 0;
	}
      cr->next->prev = cr->prev;
      cr->prev->next = cr->next;
      cr->next = 0;
      cr->prev = 0;
      --delayed;
    }
  ++cr->unfa.refs;
  cr->unfa.cset_size = cset_size;
  rx_unfa_to_nfa (cr, exp, cset_size);
  return &cr->unfa;
}

/* rx_save_rexp
 *
 * Increment the reference count for a regexp=>NFA cache entry.
 */
void
rx_save_unfa (struct rx_unfa * unfa)
{
  ++(unfa->refs);
}

/* rx_free_unfa
 *
 * Decrement the reference count for a regexp=>NFA cache entry.
 */
void
rx_free_unfa (struct rx_unfa * unfa)
{
  struct rx_cached_rexp * cr;

  cr = (struct rx_cached_rexp *)unfa;
  if (!cr)
    return;

  if (!--cr->unfa.refs)
    {
      if (!free_queue)
	{
	  free_queue = cr;
	  cr->next = cr->prev = cr;
	}
      else
	{
	  cr->next = free_queue;
	  cr->prev = free_queue->prev;
	  cr->next->prev = cr;
	  cr->prev->next = cr;
	}

      ++delayed;
      while (delayed > delay)
	{
	  struct rx_cached_rexp * it;
	  it = free_queue;
	  free_queue = it->next;
	  if (!--delayed)
	    free_queue = 0;
	  it->prev->next = it->next;
	  it->next->prev = it->prev;
	  if (it->unfa.exp)
	    it->unfa.exp->cr = 0;
	  rx_free_rexp ((struct rx_exp_node *)it->hash_item->data);
	  hashtab_delete (it->hash_item, &unfa_rules);
	  rx_free_nfa (it->unfa.nfa);
	  rx_free_rexp (it->unfa.exp);
	  free (it);
	  if (it == cr)
	    break;
	}
    }
  else
    return;
}

