Advertisement
Guest User

Untitled

a guest
Jun 26th, 2016
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 18.71 KB | None | 0 0
  1. /***************************************************************************
  2.  * Definição da classe Automato.                                           *
  3.  * Última alteração: 12/11/2004 às 22:06                                   *
  4.  * Autor: Raphael Araújo e Silva - khaotix_@hotmail.com                    *
  5.  *                                                                         *
  6.  * ATENÇÃO: VOCÊ TEM A COMPLETA PERMISSÃO PARA ALTERAÇÃO E REDISTRIBUIÇÃO  *
  7.  *          DO CÓDIGO NESTE E EM QUALQUER ARQUIVO ACOMPANHANTE DESDE QUE O *
  8.  *      AUTOR ORIGINAL SEJA CITADO. O USO DO CÓDIGO CONTIDO NESTE      *
  9.  *      ARQUIVO É POR SUA CONTA E RISCO, O AUTOR NAO SE RESPONSABILIZA *
  10.  *          RÁ POR QUALQUER DANO QUE ESTE VENHA A CAUSAR A COMPUTADORES DE *
  11.  *          TERCEIROS.                                             *
  12.  ***************************************************************************/
  13. #include "automato.h"
  14. //***************************************************************************
  15. int extrairAlfabeto(string elems,vector<string> &alf)
  16. {
  17.  //Função que extrai um alfabeto a partir de uma expressão regular
  18.  //Na forma: [az:09:A:B:C:#]
  19.  
  20.  /* Descrição da máquina:
  21.    
  22.     Q.C = qualquer caractere da tabela ascii
  23.     estado_final: 4
  24.  
  25.    { estado_atual: 0  char_entrada: '[' estado destino: 1 }
  26.    { estado_atual: 1  char_entrada: Q.C estado destino: 2 }
  27.    { estado_atual: 2  char_entrada: ':' estado destino: 1 }
  28.    { estado_atual: 2  char_entrada: Q.C estado destino: 3 }
  29.    { estado_atual: 2  char_entrada: ']' estado destino: 4 }
  30.    { estado_atual: 3  char_entrada: ':' estado destino: 1 }
  31.    { estado_atual: 3  char_entrada: ']' estado destino: 4 }  */
  32.  if(elems.length()<=2) //A expressão deve ter pelo menos 2 bytes de comp. para ser analisada
  33.   return(ERRO_EXP_REG_INVAL);
  34.  else
  35.  {
  36.   int idx=0,tam,st=1;
  37.   char chr;
  38.   string str_aux;
  39.   bool fim=false;
  40.  
  41.   alf.clear(); //Limpa o vetor que guarda o alfabeto
  42.   tam=elems.size();
  43.  
  44.   if(elems[0]=='[') //Ponto de partida de máquina
  45.   {
  46.    do
  47.    {
  48.     chr=elems[++idx]; //Obtém um caractere da string de entrada
  49.     if((st==2 || st==3) && (chr==':' || chr==']')) //Indica o final de um intervalo
  50.     {
  51.      alf.push_back(str_aux); //Insere o que foi retirado até aqui no vetor que guarda o alfabeto
  52.      st=1; //Volta para o estado 1
  53.      str_aux="";
  54.      if(chr==']') fim=true; //Finaliza a máquina
  55.     }
  56.     else if(st==1 || st==2)
  57.     { str_aux+=chr; st++; }
  58.     else break;
  59.    }
  60.    while(idx<tam && !fim);
  61.   }
  62.  
  63.   if(!fim)
  64.   {
  65.    alf.clear(); //Limpa o vetor que guarda o alfabeto
  66.    return(idx); //Retorna o índice do caractere inválido
  67.   }
  68.   else if(idx<tam-1)
  69.   {
  70.     alf.clear(); //Limpa o vetor que guarda o alfabeto
  71.    return(ERRO_STR_NAO_TERM);
  72.   }
  73.   else return(EXP_VALIDA); //Retorna um valor q indica que o alfabeto foi extraido sem erros
  74.  }
  75. }
  76. //***************************************************************************
  77. bool intervaloValido(string interv,vector<string> &alf)
  78. {
  79.  int tam,tam_alf;
  80.  
  81.  tam=interv.length();
  82.  tam_alf=alf.size();
  83.  
  84.  if(tam_alf>0 && tam>0)
  85.  {
  86.   bool valido=false;
  87.   vector<string>::iterator itr,itr_end;
  88.   string alfabeto;
  89.   unsigned char ext1,ext2,_ext1,_ext2;
  90.  
  91.   itr=alf.begin();
  92.   itr_end=alf.end();
  93.  
  94.   if(tam==2) //caso o intervalo contenha duas letras
  95.   { ext1=interv[0]; ext2=interv[1]; } //Os extremos serão o primeiro e o segundo caractere do intervalo
  96.   else
  97.   { ext1=interv[0]; ext2=interv[0]; } //Os extremos será apenas o primeiro cractere do intervalo
  98.  
  99.   while(itr!=itr_end && !valido)
  100.   {
  101.    alfabeto=(*(itr++)); //Obtém um elemento do alfabeto
  102.    tam_alf=alfabeto.length();
  103.    
  104.    if(tam_alf==2)
  105.    { _ext1=alfabeto[0]; _ext2=alfabeto[1]; }
  106.    else
  107.    { _ext1=alfabeto[0]; _ext2=alfabeto[0]; }
  108.  
  109.    valido=(ext1>=_ext1 && ext2<=_ext2);
  110.   }
  111.  
  112.   return(valido);
  113.  }
  114.  else return(false);
  115. }
  116. //***********************************************************************************
  117. ExpRegular::ExpRegular(void){ flag=FLAG_INDEF; }
  118. //-----------------------------------------------------------------------------------
  119. ExpRegular::~ExpRegular(void)
  120. {
  121.  //Limpa as listas que constituem a expressão regular
  122.  elems.clear();
  123.  flag=FLAG_INDEF;
  124. }
  125. //-----------------------------------------------------------------------------------
  126. int ExpRegular::obterFlag(void){ return(flag); }
  127. //-----------------------------------------------------------------------------------
  128. vector<string> &ExpRegular::obterElementos(void){ return(elems); }
  129. //-----------------------------------------------------------------------------------
  130. ExpRegular &ExpRegular::operator = (ExpRegular &exp)
  131. {
  132.  vector<string>::iterator itr,itr_end;
  133.  
  134.  elems.clear();
  135.  itr=exp.elems.begin();
  136.  itr_end=exp.elems.end();
  137.  
  138.  //Copia os elementos da expressão regular do parametro para
  139.  //o objeto 'this'
  140.  while(itr!=itr_end)
  141.   elems.push_back(*(itr++));
  142.  
  143.  flag=exp.flag;
  144.  
  145.  return(*this);
  146. }
  147. //-----------------------------------------------------------------------------------
  148. int ExpRegular::extrairElementos(string exp,vector<string> &alf)
  149. {
  150.  int tam;
  151.  
  152.  tam=exp.length();
  153.  if(tam<3)  //Expressão regular inválida (menos de dois caracteres)
  154.   return(ERRO_EXP_REG_INVAL);
  155.  else if(alf.size()==0) //Alfabeto vazio (indefinido)
  156.   return(ERRO_ALF_INDEFINIDO);
  157.  else
  158.  {
  159.   int idx=0,st=1;
  160.   char chr;
  161.   string str_aux;
  162.   bool fim=false,erro=false,alf_definido;
  163.  
  164.   alf_definido=(alf.size()>0); //Vefifica se há um alfabeto definido, se naõ, o alfabeto padrão será a tabela ascii
  165.   flag=FLAG_INDEF;
  166.   elems.clear(); //Limpa a lista de elementos atual
  167.   tam=exp.size();  
  168.   chr=exp[0];
  169.    
  170.   switch(chr)
  171.   {
  172.    //---------------------------
  173.    case '%': //Retira as expressões %qc, %qo
  174.    {
  175.     idx++;
  176.     if(exp[idx]=='q')
  177.     {
  178.      idx++;
  179.      if(exp[idx]=='c') flag=FLAG_QC_ALF;
  180.      else if(exp[idx]=='o') flag=FLAG_QO_ALF;
  181.      else erro=true;
  182.     }
  183.     else erro=true;
  184.    }
  185.    break;
  186.    //---------------------------
  187.    case '[': //Retira as expressões []
  188.    {  
  189.     flag=FLAG_ALF_DEF;
  190.     do
  191.     {
  192.      chr=exp[++idx]; //Obtém um caractere da string de entrada
  193.      
  194.      if((st==2 || st==3) && (chr==':' || chr==']')) //Indica o final de um intervalo
  195.      {
  196.       if(alf_definido)
  197.        erro=!intervaloValido(str_aux,alf); //Verifica se a string aux contém caracteres inválidos (fora do alfabeto)
  198.    
  199.       if(!erro)
  200.       {
  201.        elems.push_back(str_aux); //Insere o que foi retirado até aqui no vetor que guarda o alfabeto
  202.        str_aux="";  st=1; //Volta para o estado 1
  203.        if(chr==']') fim=true; //Finaliza a máquina
  204.       }
  205.      }
  206.      else if(st==1 || st==2){ str_aux+=chr; st++; }
  207.      else break;
  208.     }
  209.     while(idx<tam && !fim && !erro);
  210.     if(!fim) erro=true;
  211.    }
  212.    break;
  213.    //---------------------------
  214.    default: erro=true; break;
  215.   }
  216.  
  217.   if(erro){ flag=FLAG_INDEF; return(idx); }
  218.   else if(idx<tam-1) return(ERRO_STR_NAO_TERM); //Retorna um erro indicando que a análise terminou, com sucesso, porém no meio da string
  219.   else return(EXP_VALIDA); //Sem erro
  220.  }
  221. }
  222. //***********************************************************************************
  223. Automato::Automato(void)
  224. {
  225.  extrairAlfabeto("[ ÿ]",alfabeto); //O alfabeto padrão é toda tabela ascii (do caractere 32 ao 255)
  226.  buf_entrada=buf_saida="";
  227.  alfabeto_str="[ ÿ]";  
  228.  est_inicial=est_atual=-1;
  229.  idx_atual=status=-1;  
  230. }
  231. //------------------------------------------------------------------------------------
  232. Automato::~Automato(void){ limparConfig(); }
  233. //------------------------------------------------------------------------------------
  234. void Automato::reiniciarAutomato(void)
  235. {
  236.  status=AFD_OCIOSO; //Somente com esse status que o automato é capaz de analisar a string
  237.  idx_atual=0;
  238.  est_atual=est_inicial; //Reinicia o estado atual do automato
  239.  buf_saida="";
  240. }
  241. //------------------------------------------------------------------------------------
  242. void Automato::limparConfig(void)
  243. {
  244.  Config *cfg;
  245.  deque<Config *>::iterator itr,itr_end;
  246.  
  247.  est_inicial=est_atual=-1;
  248.  idx_atual=status=-1;  
  249.  estados.clear();
  250.  alfabeto.clear();
  251.  
  252.  itr=configs.begin();
  253.  itr_end=configs.end();
  254.  while(itr!=itr_end) //Destói cada configuração do automato separadamente
  255.  {
  256.   cfg=*(itr++); //Obtem uma configuração
  257.   delete(cfg); //Destrói
  258.  }
  259.  configs.clear(); //Limpa a lista de configuraçoes
  260.  
  261.  buf_entrada=buf_saida=alfabeto_str="";
  262. }
  263. //------------------------------------------------------------------------------------
  264. int Automato::definirAlfabeto(string exp_reg)
  265. {
  266.  int res;
  267.  
  268.  res=extrairAlfabeto(exp_reg,alfabeto); //Tenta extrair o alfabeto a partir da exp. regular passada
  269.  if(res==EXP_VALIDA)
  270.  {
  271.   alfabeto_str=exp_reg;
  272.   reiniciarAutomato();
  273.  }
  274.  return(res);
  275. }
  276. //------------------------------------------------------------------------------------
  277. const string &Automato::obterAlfabeto(void)
  278. { return(alfabeto_str); }
  279. //------------------------------------------------------------------------------------
  280. int Automato::inserirEstado(int tipo_est)
  281. {
  282.  //Verifica se o tipo do estado é valido
  283.  if(tipo_est>=EST_COMUM && tipo_est<=EST_FINAL_SAIDA)
  284.  {
  285.   estados.push_back(tipo_est);
  286.   reiniciarAutomato();
  287.   return(estados.size()-1);
  288.  }
  289.  else return(ERRO_EST_INVALIDO); //Erro!
  290. }
  291. //------------------------------------------------------------------------------------
  292. int Automato::inserirEstados(const int *vet_est,unsigned num_est)
  293. {
  294.  unsigned idx;
  295.  int res=0;
  296.  
  297.  for(idx=0;idx<num_est && res!=ERRO_EST_INVALIDO;idx++)
  298.  {
  299.   //Verifica se todos os estados no vetor são válidos
  300.   res=(vet_est[idx]>=EST_COMUM && vet_est[idx]<=EST_FINAL_SAIDA ?
  301.        SEM_ERRO : ERRO_EST_INVALIDO);
  302.  }
  303.  
  304.  if(res==SEM_ERRO) //Caso sejam válidos
  305.  {
  306.   for(idx=0;idx<num_est;idx++) //Insere todos no vetor de estados
  307.    estados.push_back(vet_est[idx]);
  308.   reiniciarAutomato();
  309.  }
  310.  
  311.  return(res);
  312. }
  313. //------------------------------------------------------------------------------------
  314. int Automato::definirEstadoInicial(int idx_est)
  315. {
  316.  //Verfica se o estado passado existe na lista de estados
  317.  if(idx_est>=0 && idx_est<static_cast<int>(estados.size()))
  318.  {
  319.   est_inicial=idx_est;
  320.   reiniciarAutomato();
  321.   return(est_inicial);
  322.  }
  323.  else return(ERRO_EST_INVALIDO); //Erro!
  324. }
  325. //------------------------------------------------------------------------------------      
  326. void Automato::carregarBuffer(string buf)
  327. {
  328.  buf_entrada=buf;
  329.  reiniciarAutomato();
  330. }
  331. //------------------------------------------------------------------------------------  
  332. int Automato::inserirConfig(string exp,int est_atual,int est_dest)
  333. {
  334.  ExpRegular exp_reg;
  335.  int res,num_est;
  336.  
  337.  //Extrai os elementos da expressão regular que define a condição da configuração
  338.  res=exp_reg.extrairElementos(exp,alfabeto);
  339.  num_est=estados.size();
  340.  
  341.  if(res!=EXP_VALIDA) //Caso a expressão seja inválida
  342.   return(res); //Retorna o índice do caractere onde há o erro
  343.  
  344.  //Verifica se os estados de destino e atual a serem usados sao válidos
  345.  else if(num_est==0 || num_est<est_atual || num_est<est_dest)
  346.   return(ERRO_EST_INVALIDO);
  347.  else
  348.  {
  349.   Config *cfg;
  350.   int flag;
  351.  
  352.   cfg=new Config;
  353.   cfg->exp_reg=exp_reg;
  354.   cfg->est_atual=est_atual;
  355.   cfg->est_dest=est_dest;
  356.  
  357.   //Logo abaixo é uma rotina para a organização das configurações de acordo com o flag da
  358.   //expressao regular das mesmas.
  359.   //Flags %qc são inseridos no inicio da lista, %qo no final e [az:09] no meio
  360.   //Isso permite uma leitura sequencial e coerente caso as flags %qc e %qo estejam presentes na configuração
  361.  
  362.   flag=exp_reg.obterFlag();
  363.   if(flag==FLAG_QC_ALF) //Flag %qc vai para o inicio da lista
  364.    configs.push_front(cfg);
  365.   else if(flag==FLAG_QO_ALF) //Flag %qo vai para o fim da lista
  366.    configs.push_back(cfg);
  367.   else
  368.   {
  369.    deque<Config *>::iterator itr,itr_end;
  370.    
  371.    itr=configs.begin();
  372.    itr_end=configs.end();
  373.  
  374.    //Varre a lista enquanto o flag da exp. reg. do elemento atual é '%qc'  
  375.    while(itr!=itr_end && (*itr)->exp_reg.obterFlag()==FLAG_QC_ALF)
  376.     itr++;
  377.  
  378.    //Insere a configuração
  379.    if(itr!=itr_end)
  380.     configs.insert(itr,cfg);
  381.    else
  382.     configs.push_back(cfg);
  383.   }
  384.  
  385.   reiniciarAutomato();
  386.   return(SEM_ERRO);
  387.  }
  388. }
  389. //------------------------------------------------------------------------------------  
  390. int Automato::inserirConfigs(unsigned num_cfgs,const string *vet_exp,const int *st_atual,const int *st_dest)
  391. {
  392.  int res=SEM_ERRO,num_est;
  393.  vector<ExpRegular> exp_reg;
  394.  ExpRegular exp;
  395.  
  396.  num_est=estados.size();
  397.  
  398.  for(unsigned idx=0;idx<num_cfgs && (res==SEM_ERRO || res==EXP_VALIDA);idx++)
  399.  {
  400.   //Verifica se as expressões regulares sao válidas
  401.   res=exp.extrairElementos(vet_exp[idx],alfabeto);
  402.   if(res==EXP_VALIDA)
  403.   {
  404.    exp_reg.push_back(exp);
  405.    //Verifica se os estados atuais e de destino são válidos
  406.    if(st_atual[idx]>=num_est || st_dest[idx]>=num_est)
  407.     res=ERRO_EST_INVALIDO;
  408.   }
  409.  }
  410.  
  411.  if(res==EXP_VALIDA)
  412.  {
  413.   Config *cfg;
  414.   deque<Config *>::iterator itr,itr_end;
  415.   int flag;
  416.  
  417.   //Logo abaixo é uma rotina para a organização das configurações de acordo com o flag da
  418.   //expressao regular das mesmas.
  419.   //Flags %qc são inseridos no inicio da lista, %qo no final e [az:09] no meio
  420.   //Isso permite uma leitura sequencial e coerente caso as flags %qc e %qo estejam presentes na configuração
  421.  
  422.   for(unsigned idx=0;idx<num_cfgs;idx++)
  423.   {
  424.  
  425.    cfg=new Config;
  426.    cfg->exp_reg=exp_reg[idx];
  427.    cfg->est_atual=st_atual[idx];
  428.    cfg->est_dest=st_dest[idx];
  429.    
  430.    flag=exp_reg[idx].obterFlag();
  431.    if(flag==FLAG_QC_ALF)//Flag %qc vai para o inicio da lista
  432.     configs.push_front(cfg);
  433.    else if(flag==FLAG_QO_ALF) //Flag %qo vai para o fim da lista
  434.     configs.push_back(cfg);
  435.    else
  436.    {
  437.     itr=configs.begin();
  438.     itr_end=configs.end();
  439.    
  440.     //Varre a lista enquanto o flag da exp. reg. do elemento atual é '%qc'  
  441.     while(itr!=itr_end && (*itr)->exp_reg.obterFlag()==FLAG_QC_ALF)
  442.      itr++;
  443.  
  444.     //Insere a configuração
  445.     if(itr!=itr_end)
  446.      configs.insert(itr,cfg);
  447.     else
  448.      configs.push_back(cfg);
  449.    }
  450.   }
  451.   res=SEM_ERRO;
  452.   reiniciarAutomato();
  453.  }
  454.  
  455.  return(res);
  456. }
  457. //------------------------------------------------------------------------------------  
  458. int Automato::obterEstado(int idx_est)
  459. {
  460.  //Vefica se o índice não extrapola os limites do vetor de estados
  461.  if(idx_est>=0 && idx_est<static_cast<int>(estados.size()))
  462.   return(estados[idx_est]);
  463.  else
  464.   return(ERRO_EST_INVALIDO);
  465. }
  466. //------------------------------------------------------------------------------------
  467. int Automato::obterIndiceAtual(void){ return(idx_atual); }
  468. //------------------------------------------------------------------------------------
  469. char Automato::obterCaractereAtual(void)
  470. {
  471.  if(idx_atual>=0)
  472.   return(buf_entrada[idx_atual]);
  473.  else
  474.   return(0);
  475. }
  476. //------------------------------------------------------------------------------------  
  477. int Automato::obterEstadoInicial(void){ return(est_inicial); }
  478. //------------------------------------------------------------------------------------    
  479. int Automato::obterEstadoAtual(void){ return(est_atual); }
  480. //------------------------------------------------------------------------------------      
  481. int Automato::obterStatus(void){ return(status); }
  482. //------------------------------------------------------------------------------------        
  483. const string &Automato::obterBufEntrada(void){ return(buf_entrada); }
  484. //------------------------------------------------------------------------------------        
  485. const string &Automato::obterBufSaida(void){ return(buf_saida); }
  486. //------------------------------------------------------------------------------------        
  487. int Automato::analisarString(void)
  488. {
  489.  int tipo_est;
  490.  
  491.  tipo_est=estados[est_atual];
  492.  if(status==AFD_OCIOSO && (tipo_est==EST_COMUM || tipo_est==EST_COMUM_SAIDA))
  493.  {
  494.   deque<Config *>::iterator itr,itr_end;
  495.   Config *cfg;
  496.   bool chr_valido,chr_alf,est_mudado;
  497.   int flag,tam;//,tam_alf;
  498.   string str_aux;
  499.  
  500.   tam=buf_entrada.length();
  501.   //tam_alf=alfabeto.size();
  502.   str_aux="";
  503.  
  504.   for(idx_atual==0;idx_atual<tam && status==AFD_OCIOSO;idx_atual++)
  505.   {
  506.    str_aux="";
  507.    str_aux+=buf_entrada[idx_atual];
  508.    chr_alf=intervaloValido(str_aux,alfabeto); //Verifica se o caractere atual pertence ao alfabeto
  509.  
  510.    if(chr_alf)
  511.    {
  512.     est_mudado=false;
  513.     itr=configs.begin();
  514.     itr_end=configs.end();
  515.  
  516.     //Efetua uma varredura na lista de configurações
  517.     while(itr!=itr_end && !est_mudado)
  518.     {
  519.      cfg=(*(itr++));
  520.      if(cfg->est_atual==est_atual) //Caso o estado atual da configuração obtida seja o mesmo q o estado atual da máquina
  521.      {
  522.       flag=cfg->exp_reg.obterFlag();
  523.  
  524.       if(flag!=FLAG_QO_ALF) //CAso o flag da exp. reg. da configuração não seja '%qo'
  525.        chr_valido=intervaloValido(str_aux,cfg->exp_reg.obterElementos()); //Verifica se o caractere é valido para aquela configuração
  526.  
  527.       //Condição 0: caractere pertence ao alfabeto
  528.       //Condição 1: caractere válido
  529.       //Condição 2: caractere inválido porém o estado possui a flag para qualquer outro caractere
  530.       //Condição 3: flag da configuração é para qualquer caractere (o caractere atual é simplesmente desprezado)
  531.       if(chr_alf && (chr_valido || (!chr_valido && flag==FLAG_QO_ALF) ||
  532.          flag==FLAG_QC_ALF))
  533.       {
  534.        //Caso o estado seja de saída
  535.        if(estados[est_atual]==EST_FINAL_SAIDA || estados[est_atual]==EST_COMUM_SAIDA)
  536.         buf_saida+=str_aux; //coloca o caractere atual no buffer de saída
  537.  
  538.        est_atual=cfg->est_dest; //Muda o estado atual da máquina
  539.        if(estados[est_atual]==EST_FINAL_ABS)  status=AFD_PARADO; //Para imediatamente o automato
  540.        est_mudado=true; //Indica q o estado foi mudado
  541.       }
  542.      }
  543.     }
  544.     if(!est_mudado) status=AFD_PARADO_ERRO;
  545.    }
  546.    //ERRO: A maq. nao consegue mudar de estado pois leu um caractere q nao está no alfabeto
  547.    //ou não existe uma configuração válida para o caractere lido (apesar de ele pertencer ao alfabeto)
  548.    else status=AFD_PARADO_ERRO;
  549.   }
  550.  }
  551.  //else if(tipo==EST_FINAL || tipo_est==EST_FINAL_SAIDA || tipo_est=EST_FINAL_ABS)
  552.  // status=AFD_PARADO;
  553.  
  554.  //Caso se chegue ao fim da string normalmente , pára o automato
  555.  if(status==AFD_OCIOSO) status=AFD_PARADO;
  556.  
  557.  return(status);
  558. }
  559. //-----------------------------------------------------------------------------------
  560. bool Automato::stringValida(void)
  561. {
  562.  int tipo=estados[est_atual];
  563.  
  564.  //Condição de validade de uma string: automato parado em um estado final
  565.  return((status==AFD_PARADO &&
  566.         (tipo==EST_FINAL || tipo==EST_FINAL_SAIDA || tipo==EST_FINAL_ABS)));
  567. }
  568. //***********************************************************************************
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement