Logo Search packages:      
Sourcecode: ragel version File versions

redfsm.cpp

/*
 *  Copyright 2001-2004 Adrian Thurston <adriant@ragel.ca>
 */

/*  This file is part of Ragel.
 *
 *  Ragel 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.
 * 
 *  Ragel 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 Ragel; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

#include "redfsm.h"
#include "fsmgraph.h"
#include "avlmap.h"
#include <iostream>

/* Construct an fsmmachine from a graph. */
RedFsmAp::RedFsmAp( FsmAp *graph, bool complete )
:
      graph(graph),
      complete(complete),
      keyOps(graph->keyOps),
      nextActionId(0),
      nextTransId(0),
      nextStateId(0),
      errState(0),
      errTrans(0),
      firstFinState(0),
      numFinStates(0)
{
      /* Since we reuse the memory allocate for transitions this property must
       * hold. */
      assert( sizeof(RedTransAp) <= sizeof(TransAp) );
      assert( sizeof(RedStateAp) <= sizeof(StateAp) );

      reduceMachine();
}

void RedFsmAp::depthFirstOrdering( RedStateAp *state )
{
      /* Nothing to do if the state is already on the list. */
      if ( state->onStateList )
            return;

      /* Doing depth first, put state on the list. */
      state->onStateList = true;
      stateList.append( state );
      
      /* At this point transitions should only be in ranges. */
      assert( state->outSingle.length() == 0 );
      assert( state->defTrans == 0 );

      /* Recurse on everything ranges. */
      for ( RedTransList::Iter rtel = state->outRange; rtel.lte(); rtel++ ) {
            if ( rtel->value->targ != 0 )
                  depthFirstOrdering( rtel->value->targ );
      }
}

/* Ordering states by transition connections. */
void RedFsmAp::depthFirstOrdering()
{
      /* Init on state list flags. */
      for ( RedStateList::Iter st = stateList; st.lte(); st++ )
            st->onStateList = false;
      
      /* Clear out the state list, we will rebuild it. */
      int stateListLen = stateList.length();
      stateList.abandon();

      /* Add back to the state list from the start state and all other entry
       * points. */
      depthFirstOrdering( startState );
      for ( RedEntryMap::Iter en = entryMap; en.lte(); en++ )
            depthFirstOrdering( en->value );
      
      /* Make sure we put everything back on. */
      assert( stateListLen == stateList.length() );
}

/* Assign state ids by appearance in the state list. */
void RedFsmAp::sequentialStateIds()
{
      nextStateId = 0;
      for ( RedStateList::Iter st = stateList; st.lte(); st++ )
            st->id = nextStateId++;
}

/* Stable sort the states by final state status. */
void RedFsmAp::sortStatesByFinal()
{
      /* Move forward through the list and throw final states onto the end. */
      RedStateAp *state = 0;
      RedStateAp *next = stateList.head;
      RedStateAp *last = stateList.tail;
      while ( state != last ) {
            /* Move forward and load up the next. */
            state = next;
            next = state->next;

            /* Throw to the end? */
            if ( state->isFinal ) {
                  stateList.detach( state );
                  stateList.append( state );
            }
      }
}

/* Assign state ids by final state state status. */
void RedFsmAp::sortStateIdsByFinal()
{
      /* First pass to assign non final ids. */
      nextStateId = 0;
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            if ( ! st->isFinal ) 
                  st->id = nextStateId++;
      }

      /* Second pass to assign final ids. */
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            if ( st->isFinal ) 
                  st->id = nextStateId++;
      }
}

/* Find the final state with the lowest id. */
void RedFsmAp::findFirstFinState()
{
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            if ( st->isFinal && (firstFinState == 0 || st->id < firstFinState->id) )
                  firstFinState = st;
      }
}

void RedFsmAp::assignActionLocs()
{
      int nextLocation = 0;
      for ( ActionTableMap::Iter act = actionMap; act.lte(); act++ ) {
            /* Store the loc, skip over the array and a null terminator. */
            act->location = nextLocation;
            nextLocation += act->key.length() + 1;          
      }
}

/* Check if we can extend the current range by displacing any ranges
 * ahead to the singles. */
bool RedFsmAp::canExtend( const RedTransList &list, int pos )
{
      /* Get the transition that we want to extend. */
      RedTransAp *extendTrans = list[pos].value;

      /* Look ahead in the transition list. */
      for ( int next = pos + 1; next < list.length(); pos++, next++ ) {
            /* If they are not continuous then cannot extend. */
            long nextKey = list[next].lowKey;
            keyOps->dec( nextKey );
            if ( ! keyOps->eq( list[pos].highKey, nextKey ) )
                  break;

            /* Check for the extenstion property. */
            if ( extendTrans == list[next].value )
                  return true;

            /* If the span of the next element is more than one, then don't keep
             * checking, it won't be moved to single. */
            unsigned long long nextSpan = keyOps->span( list[next].lowKey, list[next].highKey );
            if ( nextSpan > 1 )
                  break;
      }
      return false;
}

/* Move ranges to the singles list. */
void RedFsmAp::moveTransToSingle( RedStateAp *state )
{
      RedTransList &range = state->outRange;
      RedTransList &single = state->outSingle;
      for ( int rpos = 0; rpos < range.length(); ) {
            /* Check if this is a range we can extend. */
            if ( canExtend( range, rpos ) ) {
                  /* Transfer singles over. */
                  while ( range[rpos].value != range[rpos+1].value ) {
                        /* Transfer the range to single. */
                        single.append( range[rpos+1] );
                        range.remove( rpos+1 );
                  }
                  
                  /* Extend. */
                  range[rpos].highKey = range[rpos+1].highKey;
                  range.remove( rpos+1 );
            }
            /* Maybe move it to the singles. */
            else if ( keyOps->span( range[rpos].lowKey, range[rpos].highKey ) == 1 ) {
                  single.append( range[rpos] );
                  range.remove( rpos );
            }
            else {
                  /* Keeping it in the ranges. */
                  rpos += 1;
            }
      }
}

/* Look through ranges and choose suitable single character transitions. */
void RedFsmAp::chooseSingle()
{
      /* Loop the states. */
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            /* Rewrite the transition list taking out the suitable single
             * transtions. */
            moveTransToSingle( st );
      }
}

void RedFsmAp::makeFlat()
{
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            if ( st->outRange.length() == 0 ) {
                  st->lowKey = st->highKey = 0;
                  st->transList = 0;
            }
            else {
                  st->lowKey = st->outRange[0].lowKey;
                  st->highKey = st->outRange[st->outRange.length()-1].highKey;
                  unsigned long long span = keyOps->span( st->lowKey, st->highKey );
                  st->transList = new RedTransAp*[ span ];
                  memset( st->transList, 0, sizeof(RedTransAp*)*span );
                  
                  for ( RedTransList::Iter trans = st->outRange; trans.lte(); trans++ ) {
                        unsigned long long base, trSpan;
                        base = keyOps->span( st->lowKey, trans->lowKey )-1;
                        trSpan = keyOps->span( trans->lowKey, trans->highKey );
                        for ( unsigned long long pos = 0; pos < trSpan; pos++ )
                              st->transList[base+pos] = trans->value;
                  }

                  /* Fill in the gaps with the default transition. */
                  for ( unsigned long long pos = 0; pos < span; pos++ ) {
                        if ( st->transList[pos] == 0 )
                              st->transList[pos] = st->defTrans;
                  }
            }
      }
}


/* A default transition has been picked, move it from the outRange to the
 * default pointer. */
void RedFsmAp::moveToDefault( RedTransAp *defTrans, RedStateAp *state )
{
      /* Rewrite the outRange, omitting any ranges that use 
       * the picked default. */
      RedTransList outRange;
      for ( RedTransList::Iter rtel = state->outRange; rtel.lte(); rtel++ ) {
            /* If it does not take the default, copy it over. */
            if ( rtel->value != defTrans )
                  outRange.append( *rtel );
      }

      /* Save off the range we just created into the state's range. */
      state->outRange.shallowCopy( outRange );
      outRange.abandon();

      /* Store the default. */
      state->defTrans = defTrans;
}

bool RedFsmAp::alphabetCovered( RedTransList &outRange )
{
      /* Cannot cover without any out ranges. */
      if ( outRange.length() == 0 )
            return false;

      /* If the first range doesn't start at the the lower bound then the
       * alphabet is not covered. */
      RedTransList::Iter rtel = outRange;
      if ( keyOps->lt( keyOps->lowKey, rtel->lowKey ) )
            return false;

      /* Check that every range is next to the previous one. */
      rtel.increment();
      for ( ; rtel.lte(); rtel++ ) {
            long highKey = rtel[-1].highKey;
            keyOps->inc( highKey );
            if ( !keyOps->eq( highKey, rtel->lowKey ) )
                  return false;
      }

      /* The last must extend to the upper bound. */
      RedTransEl *last = &outRange[outRange.length()-1];
      if ( keyOps->lt( last->highKey, keyOps->highKey ) )
            return false;

      return true;
}

RedTransAp *RedFsmAp::chooseDefaultSpan( RedStateAp *state )
{
      /* Make a set of transitions from the outRange. */
      RedTransSet stateTransSet;
      for ( RedTransList::Iter rtel = state->outRange; rtel.lte(); rtel++ )
            stateTransSet.insert( rtel->value );
      
      /* For each transition in the find how many alphabet characters the
       * transition spans. */
      unsigned long long *span = new unsigned long long[stateTransSet.length()];
      memset( span, 0, sizeof(unsigned long long) * stateTransSet.length() );
      for ( RedTransList::Iter rtel = state->outRange; rtel.lte(); rtel++ ) {
            /* Lookup the transition in the set. */
            RedTransAp **inSet = stateTransSet.find( rtel->value );
            int pos = inSet - stateTransSet.data;
            span[pos] += keyOps->span( rtel->lowKey, rtel->highKey );
      }

      /* Find the max span, choose it for making the default. */
      RedTransAp *maxTrans = 0;
      unsigned long long maxSpan = 0;
      for ( RedTransSet::Iter rtel = stateTransSet; rtel.lte(); rtel++ ) {
            if ( span[rtel.pos()] > maxSpan ) {
                  maxSpan = span[rtel.pos()];
                  maxTrans = *rtel;
            }
      }

      delete[] span;
      return maxTrans;
}

/* Pick default transitions from ranges for the states. */
void RedFsmAp::chooseDefaultSpan()
{
      /* Loop the states. */
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            /* Only pick a default transition if the alphabet is covered. This
             * avoids any transitions in the out range that go to error and avoids
             * the need for an ERR state. */
            if ( alphabetCovered( st->outRange ) ) {
                  /* Pick a default transition by largest span. */
                  RedTransAp *defTrans = chooseDefaultSpan( st );

                  /* Rewrite the transition list taking out the transition we picked
                   * as the default and store the default. */
                  moveToDefault( defTrans, st );
            }
      }
}

RedTransAp *RedFsmAp::chooseDefaultGoto( RedStateAp *state )
{
      /* Make a set of transitions from the outRange. */
      RedTransSet stateTransSet;
      for ( RedTransList::Iter rtel = state->outRange; rtel.lte(); rtel++ ) {
            if ( rtel->value->targ == state->next )
                  return rtel->value;
      }
      return 0;
}

void RedFsmAp::chooseDefaultGoto()
{
      /* Loop the states. */
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            /* Pick a default transition. */
            RedTransAp *defTrans = chooseDefaultGoto( st );
            if ( defTrans == 0 )
                  defTrans = chooseDefaultSpan( st );

            /* Rewrite the transition list taking out the transition we picked
             * as the default and store the default. */
            moveToDefault( defTrans, st );
      }
}

RedTransAp *RedFsmAp::chooseDefaultNumRanges( RedStateAp *state )
{
      /* Make a set of transitions from the outRange. */
      RedTransSet stateTransSet;
      for ( RedTransList::Iter rtel = state->outRange; rtel.lte(); rtel++ )
            stateTransSet.insert( rtel->value );
      
      /* For each transition in the find how many ranges use the transition. */
      int *numRanges = new int[stateTransSet.length()];
      memset( numRanges, 0, sizeof(int) * stateTransSet.length() );
      for ( RedTransList::Iter rtel = state->outRange; rtel.lte(); rtel++ ) {
            /* Lookup the transition in the set. */
            RedTransAp **inSet = stateTransSet.find( rtel->value );
            numRanges[inSet - stateTransSet.data] += 1;
      }

      /* Find the max number of ranges. */
      RedTransAp *maxTrans = 0;
      int maxNumRanges = 0;
      for ( RedTransSet::Iter rtel = stateTransSet; rtel.lte(); rtel++ ) {
            if ( numRanges[rtel.pos()] > maxNumRanges ) {
                  maxNumRanges = numRanges[rtel.pos()];
                  maxTrans = *rtel;
            }
      }

      delete[] numRanges;
      return maxTrans;
}

void RedFsmAp::chooseDefaultNumRanges()
{
      /* Loop the states. */
      for ( RedStateList::Iter st = stateList; st.lte(); st++ ) {
            /* Pick a default transition. */
            RedTransAp *defTrans = chooseDefaultNumRanges( st );

            /* Rewrite the transition list taking out the transition we picked
             * as the default and store the default. */
            moveToDefault( defTrans, st );
      }
}

/* Returns true if there is a previous range, it is continuous with redTel,
 * and if the transitions are the same. */
bool RedFsmAp::doesExtend( RedTransList &destRange, const RedTransEl &redTel )
{
      if ( destRange.length() > 0 ) {
            /* Check if this trans el can extend the previous. */
            RedTransEl *last = &destRange[destRange.length()-1];
            long lowKey = redTel.lowKey;
            keyOps->dec( lowKey );

            if ( keyOps->eq( last->highKey, lowKey ) && last->value == redTel.value )
                  return true;
      }
      return false;
}

RedTransAp *RedFsmAp::getErrorTrans( )
{
      /* If the error trans has not been made aready, make it. */
      if ( errTrans == 0 ) {
            /* This insert should always succeed since no transition created by
             * the user can point to the error state. */
            errTrans = new RedTransAp( getErrorState(), 0, nextTransId++ );
            RedTransAp *inRes = transSet.insert( errTrans );
            assert( inRes != 0 );
      }
      return errTrans;
}

RedStateAp *RedFsmAp::getErrorState()
{
      /* Check if we need to init the error trans. */
      if ( errState == 0 ) {
            errState = new RedStateAp( false, 0 );
            stateList.append( errState );
      }
      return errState;
}


/* Append a reduced trans el to the dest range, merges adjacent ranges that
 * take the same transitions. */
void RedFsmAp::appendToRange( RedTransList &destRange, 
            const RedTransEl &redTel, StateAp *state )
{
      if ( doesExtend( destRange, redTel ) ) {
            /* Take advantage of the fact that the range being appended is
             * continuous with the previous and the transitions are the same. */
            destRange[destRange.length()-1].highKey = redTel.highKey;
      }
      else if ( complete ) {
            /* If the machine is to be complete then we need to fill any gaps with
             * the error transitions. */
            if ( destRange.length() == 0 ) {
                  /* Range is currently empty. */
                  if ( keyOps->lt( keyOps->lowKey, redTel.lowKey ) ) {
                        /* The first range doesn't start at the low end. */
                        long highKey = redTel.lowKey;
                        keyOps->dec( highKey );

                        /* Create the filler with the state's error transition. */
                        RedTransEl newTel( keyOps->lowKey, highKey, getErrorTrans() );
                        destRange.append( newTel );
                  }
            }
            else {
                  /* The range list is not empty, get the the last range. */
                  RedTransEl *last = &destRange[destRange.length()-1];
                  long nextKey = last->highKey;
                  keyOps->inc( nextKey );
                  if ( keyOps->lt( nextKey, redTel.lowKey ) ) {
                        /* There is a gap to fill. Make the high key. */
                        long highKey = redTel.lowKey;
                        keyOps->dec( highKey );

                        /* Create the filler with the state's error transtion. */
                        RedTransEl newTel( nextKey, highKey, getErrorTrans() );
                        destRange.append( newTel );
                  }
            }

            /* Filler taken care of. Append the range. */
            destRange.append( redTel );
      }
      else {
            /* Cannot extend and no filler necessary. Append the range. */
            destRange.append( redTel );
      }
}

void RedFsmAp::finishRange( RedTransList &destRange, StateAp *state )
{
      /* If building a complete machine we may need filler on the end. */
      if ( complete ) {
            /* Check if there are any ranges already. */
            if ( destRange.length() == 0 ) {
                  /* Fill with the whole alphabet. */
                  /* Add the range on the lower and upper bound. */
                  RedTransEl newTel( keyOps->lowKey, keyOps->highKey, getErrorTrans() );
                  destRange.append( newTel );
            }
            else {
                  /* Get the last and check for a gap on the end. */
                  RedTransEl *last = &destRange[destRange.length()-1];
                  if ( keyOps->lt( last->highKey, keyOps->highKey ) ) {
                        /* Make the high key. */
                        long lowKey = last->highKey;
                        keyOps->inc( lowKey );

                        /* Create the new range with the error trans and append it. */
                        RedTransEl newTel( lowKey, keyOps->highKey, getErrorTrans() );
                        destRange.append( newTel );
                  }
            }
      }
}

/* Move the default transition for the state to the range. Assumes that the
 * state has a default transition. */
void RedFsmAp::defaultToRange( RedTransList &destRange, StateAp *state )
{
      /* Grab a pointer to the default in the new type. */
      RedTransAp *defTrans = (RedTransAp*)state->outDefault;

      /* If there is only are no ranges the task is simple. */
      if ( state->outList.length() == 0 ) {
            /* If there is a default, add a full upper .. lower range. */
            if ( defTrans != 0 ) {
                  /* Add the range on the lower and upper bound. */
                  RedTransEl newTel( keyOps->lowKey, keyOps->highKey, defTrans );
                  appendToRange( destRange, newTel, state );
            }
      }
      else {
            /* Check for a gap at the beginning. */
            NextRedTrans tel( state->outList.head );
            if ( defTrans != 0 && keyOps->lt( keyOps->lowKey, tel.lowKey ) ) {
                  /* Make the high key and append. */
                  long highKey = tel.lowKey;
                  keyOps->dec( highKey );

                  RedTransEl newTel( keyOps->lowKey, highKey, defTrans );
                  appendToRange( destRange, newTel, state );
            }

            /* Reduce the transition. If it reduced to anything then add it. */
            tel.redTrans = reduceTrans( tel.trans );
            if ( tel.redTrans != 0 ) {
                  appendToRange( destRange, RedTransEl( tel.lowKey, tel.highKey, 
                              tel.redTrans ), state );
            }

            /* Keep the last high end. */
            long lastHigh = tel.highKey;

            /* Loop each source range. */
            for ( tel.increment(); tel.trans != 0; tel.increment() ) {
                  if ( defTrans != 0 ) {
                        /* Make the next key following the last range. */
                        long nextKey = lastHigh;
                        keyOps->inc( nextKey );

                        /* Check for a gap from last up to here. */
                        if ( keyOps->lt( nextKey, tel.lowKey ) ) {
                              /* Make the high end of the range that fills the gap. */
                              long highKey = tel.lowKey;
                              keyOps->dec( highKey );

                              /* Append the new range. */
                              RedTransEl newTel( nextKey, highKey, defTrans );
                              appendToRange( destRange, newTel, state );
                        }
                  }

                  /* Reduce the transition. If it reduced to anything then add it. */
                  tel.redTrans = reduceTrans( tel.trans );
                  if ( tel.redTrans != 0 ) {
                        appendToRange( destRange, RedTransEl( tel.lowKey, tel.highKey, 
                                    tel.redTrans ), state );
                  }

                  /* Keep the last high end. */
                  lastHigh = tel.highKey;
            }

            /* Now check for a gap on the end to fill. */
            if ( defTrans != 0 && keyOps->lt( lastHigh, keyOps->highKey ) ) {
                  /* Get a copy of the default. */
                  keyOps->inc( lastHigh );
                  RedTransEl newTel( lastHigh, keyOps->highKey, defTrans );
                  appendToRange( destRange, newTel, state );
            }
      }

      /* If we are making a complete list then we may need to fill a gap. */
      finishRange( destRange, state );
}

RedTransAp *RedFsmAp::reduceTrans( TransAp *trans )
{
      /* Reduce the action. Try to insert the action table. Don't care what
       * happens, just store the dict item in the trans. */
      RedAction *action = 0;
      if ( trans->actionTable.length() > 0 ) {
            if ( actionMap.insert( trans->actionTable, &action ) )
                  action->actListId = nextActionId++;
      }
      
      /* Get the target, if the target is null, use the error state. */
      RedStateAp *targ = (RedStateAp*)trans->toState;
      if ( targ == 0 ) {
            /* No target, if there is no action then this trans is not very
             * interesting and reduces to nothing. */
            if ( action == 0 ) {
                  delete trans;
                  return 0;
            }

            /* Target is error state with an action. */
            if ( complete )
                  targ = getErrorState();
      }

      /* Create a reduced trans and look for it in the transiton set. */
      RedTransAp redTrans( targ, action, 0 );
      RedTransAp *inDict = transSet.find( &redTrans );
      if ( inDict != 0 ) {
            /* A reduced trans is alredy there, use it and delete the trans. */
            delete trans;
      }
      else {
            /* Convert the space allocated for the TransAp to a RedTransAp. */
            trans->~TransAp();
            inDict = new(trans) RedTransAp( targ, action, nextTransId++ );
            transSet.insert( inDict );
      }

      return inDict;
}

void RedFsmAp::reduceMachine()
{
      /* Loop all states, loop all actions within the state. */
      while ( graph->stateList.length() > 0 ) {
            /* Pop the first state off. */
            StateAp *state = graph->stateList.detachFirst();

            /* Reduce the default transition, which may be used by the default to
             * range conversion. */
            if ( state->outDefault != 0 )
                  state->outDefault = (TransAp*) reduceTrans( state->outDefault );

            /* Make the out range list. */
            RedTransList outRange;
            defaultToRange( outRange, state );

            /* Reduce out actions. */
            RedAction *eofAction = 0;
            if ( state->eofActionTable.length() > 0 ) {
                  if ( actionMap.insert( state->eofActionTable, &eofAction ) )
                        eofAction->actListId = nextActionId++;
            }

            /* Clear the state's in lists. */
            state->inRange.head = 0;
            state->inDefault.head = 0;

            /* Final state status. */
            bool isFinal = state->isFinState();

            /* Transfer the context set into a local var. */
            ContextSet contextSet;
            contextSet.shallowCopy( state->contextSet );
            state->contextSet.abandon();

            /* Convert the state to a RedStateAp. */
            state->~StateAp();
            RedStateAp *redState = new(state) RedStateAp( isFinal, eofAction );

            /* Save the outlist. */
            redState->outRange.shallowCopy( outRange );
            outRange.abandon();

            /* Save the context set. */
            redState->contextSet.shallowCopy( contextSet );
            contextSet.abandon();

            /* Put the reduced state on the state list. */
            stateList.append( redState );
      }

      /* If an error state was made and the graph is to be complete then we
       * need to give it a default transition. */
      if ( errState != 0 && complete ) {
            /* Error state needs a transition to itself. */
            RedTransEl newTel( keyOps->lowKey, keyOps->highKey, getErrorTrans() );
            errState->outRange.append( newTel );
      }

      /* Get the start state and the entry points. */
      startState = (RedStateAp*)graph->startState;
      for ( EntryMap::Iter en = graph->entryPoints; en.lte(); en++ ) {
            /* Get the entry point, all inserts should succeed. */
            entryList.append( RedEntryListEl( en->key, (RedStateAp*)en->value ) );
            RedEntryMapEl *insRes = entryMap.insert( en->key, (RedStateAp*)en->value );
            assert( insRes != 0 );
      }

      /* Find locations of actions. */
      assignActionLocs();
}

Generated by  Doxygen 1.6.0   Back to index