Trabalho Prático de Processamento de Linguagens I

Universidade do Minho, Braga 10/04/2000

Paulo Alexandre Gonçalves Sousa

E-mail: si24863@ci.uminho.pt

Maria Teresa Gomes Lourenço da Chão

E-mail: si24853@ci.uminho.pt

Resumo:

Este trabalho surge-nos na disciplina de Processamento de Linguagens I, e consiste em "infiltrar-nos" no manuseamento de linguagens geradas por uma determinada gramática, com a utilização das ferramentas Lex & Yacc .


Índice

  1. Introdução
  2. Desenvolvimento
    1. FASE 1
      1. Etapa 1
        1. O que nos e´ pedido
        2. Solução encontrada
          1. Analise Léxica
          2. Análise Sintáctica
      2. Etapa 2
        1. O que nos e' pedido
        2. Solução encontrada
          1. Analise Léxica
          2. Análise Sintáctica
      3. Etapa 3
        1. O que nos é pedido
        2. Solução encontrada
          1. Análise Sintáctica
    2. FASE 2
      1. O que nos é pedido
      2. Solução encontrada
        1. Análise Léxica
        2. Análise Sintáctica
  3. Conclusão
  4. Apêndice
    1. Ficheiros fase 1
      1. analisador.lex
      2. analisador.yacc
      3. groves.h
    2. Ficheiros fase 2
      1. shell.lex
      2. shell.yacc
      3. arvores.h

Introdução

Este trabalho foi feito em três etapas, etapas essas que foram criados pelo nosso prof. José Ramalho para melhor nos orientarmos em relação ao tempo que deveriamos empreender em cada etapa .

A intenção final destas 3 etapas é criar um processador genérico de documentos estruturados (XML) que validará a boa formação dos documentos e que guardará a sua informação num grove .

Este relato´rio tem apenas documentado a parte relativa a´s 3 primeiras etapas desta primeira fase. Vamos ter entaõ o relat´orio dividido nas 3 etapas, em que cada uma delas temos em conta dois aspectos, aspectos esses que são :


Desenvolvimento

FASE 1

Etapa 1

O que nos e´ pedido

Nesta etapa foi-nos pedido para construir um analisador l´exico e um analisador sint´actico, usando as ferramentas Lex + Yacc para uma gram´atica dada.

Solução encontrada

Esta etapa, foi talvez a etapa que obrigasse mais a puxar pela cabeça ja´ que na parte le´xica obrigava-nos a fazer um reconhecimento de um conjunto de expressões que tanto podiam ser texto do utilizador como conjunto de comandos( a que chamamos Tags ), que permitem formatar o texto. Uma Tag é composta por vários símbolos terminais : q <, >, = com ou não identificador e valor.

Exemplo:
Exemplo de uma tag com atributos.

 <Tag_exemplo atr="VALOR" > bla, bla ... </Tag_exemplo>
 

Podemos ver que < ...> e < ...> são exempo de duas Tags.

Analise Léxica

Um problema que tivemos foi o de, na análise léxica , o de como reconhecer o texto do utilizador. Para isso tentamos então encontrar uma expressão regular que contivesse o maior numero possívél de caracteres. Esta exressão regular não podia então fazer matching às tags. Utilizamos então as seguintes expressões (apenas numeramos as mais importantes) :

ID [a-zA-Z][a-zA-Z0-9_\-]*

Esta expressão regular e' a que nos "dá" o identificador, ou seja o que pode estar a seguir ao nome de uma Tag ,de referir ainda que não aceitamos um identificador a começar um um número.

VALOR \"[^\"]+\"

Esta expressão regular dá-nos o valor, ou seja, o que vem depois do identificador numa Tag. De notar que um valor é '"' seguido de um ou mais caracteres excepto '"', seguido outra vez de '"'

TEXTO [^\ <& ]+

Esta expressão é a que nos dá o texto propriamente dito, ou seja um ou mais caracteres, excepto o < e o & .

Nesta primeira etapa, a nivél léxico era apenas necessário então enviar o 'texto', o íd', e o 'valor', não sendo se quer guardar nenhum destes dados como iremos precisar em diante.

Análise Sintáctica

Neste aspecto nesta primeira etapa foi pouco problemática, já que foi quase um Copy Paste da gramática dada, apenas com a alteração da recursividade, passada para a esquerda, para no final, enviar uma mensagem a dizer que reconheceu o documento, caso tenha acontecido esse mesmo reconhecimento.

Etapa 2

O que nos e' pedido

Nesta etapa foram nos pedidas dois objectivos. Ou seja, acrescentar ao analisador construído na etapa anterior a análise semântica que faça as seguintes validações :

Solução encontrada

Nesta etapa já tivemos mais trabalho e tivemos então de utilizar algumas librarias do C extra, para nos ajudar na melhor resolução de um ou outro problema.

Analise Léxica

Neste etapa foi necessário então algumas ajudas já que tinhas alguns problemas para corrigir, problemas esses, por exemplo:

Como separar o texto de tudo o resto

Tivemos então de recorrer a ajuda de Start Conditions , estas ajudaram-nos, já que funcionam como um meta-autómato, ou seja recebe algo, e passa para um novo estado, foi neste contexto que as usamos, por exemplo, quando nos aparece num texto o caracter '<' , ja sabemos que passamos para um estado , em que estamos dentro de uma Tag.

Como guardar a informação gerada

Neste problema tivemos então que guardar a informação á medida que a iamos trabalhando, para isso guardamos nas variaveis da Union criada no Yacc (falaremos mais á frente) para isso utilizamos a função simples char *strdup(const char *s) .

Análise Sintáctica

Ao nivél do Yacc tivemos também nesta etapa que fazer algumas alterações, criamos uma Union para guardar as variavéis que nos fazem a passagem dos dados relativos a gramática.

Nesta etapa ainda alteramos a gramática de modo que obrigasse um texto a conter no inicio e no fim do mesmo, Tags que o englobassem , da seguinte maneira : <doc> .... <doc> .

Essas mesmas Tags teriam que ter o mesmo ID , ou seja temos que fazer uma comparação dos ID s para ver se são iguais, caso não sejam dá-nos uma mensagem de erro do género :

Erro nas Tags, na linha -> X

Documento Invalido!!

O mesmo acontece no caso das Tags de cada elemento da nossa grámatica, não tenha a mesma descrição quer ao abrir, quer ao fechar .

Ainda nesta etapa decidimos incluir uma variavél global, que nos controla o tipo de erro, ou seja se e' como já vimos anteriormente, erro de Tags, ou se e' outro tipo, erro sintáctico, com a seguinte mensagem de erro:

Erro Sintactico, na linha -> X

Documento Invalido!!

Para terminar quando um documento é válido dá-nos a seguinte mensagem :

Documento Valido!!

Etapa 3

O que nos é pedido

Nesta fase e'-nos pedido o armazenamento da informação, ou seja :

Solução encontrada

Nesta etapa, a única alteração que tivemos de efectuar, foi ao nivél do Yacc , por isso só iremos falar desse nivél , e não no léxico.

Análise Sintáctica

Nesta etapa veio, então talvez, a parte mais dificil deste trabalho, não querendo com isto dizer, que tivessemos muita, ou alguma dificuldade.

Tivemos então de fazer um maior uso do C , essencialmente na construção do grove que exige um tratamento, com o uso de diversos apontadores.

Para criarmos então uma grove, tivemos que criar estruturas válidas para o nosso trabalho, a estrutura que decidimos usar, foi a estrutura aconselhada, na própria etapa, esta estrutura, tem o seguinte código :

Exemplo:
Estrutura

typdedef struct Anodo
 { char *nome;
   char *valor;
   struct Anodo *seg;
 } atributo;

typedef struct Gnodo
 { char *id;
   union c
    {char *texto; /*para nodos texto*/
     struct X /* para os outros nodos */
       { struct Gnodo *conteudo;
         atributo *atribs;
       }atr;
    }corpo;
   struct Gnodo *irmaos;
 }Grove ;

Como podemos ver, a estrutura Anodo é a estrutura que guarda os atributos de uma determinada Tag, como podemos ver tem o nome , e o valor, exemplo :

<REALCADO ESTILO="italico">

Como podemos verificar o ESTILO corresponde ao nome; e o "italico" corresponde ao valor, desse mesmo atributo de notar, que podemos ter uma sequência de atributos, responsav´el por isto é a seguinte linha struct Anodo *seg; que corresponde a um apontador para o próximo atributo.

Quanto a estrutura Gnodo que contém em si toda a informaç~ ao relevante de um documento, ou seja, é um Grove própriamente dito, contém um ID que é o identificador da Tag,no mesmo exemplo anterior, seria REALCADO pode ainda ser um texto, ou uma estrutura que contenha dentro de si, outro grove com os seus respectivos atributos. De repara que esta estrutura tem ainda um apontador para outra estrutura, igual a esta, caso exista, caso não, este aponta dor, tal com o anterior, apontam para NULL .

Ainda nesta parte tivemos que utilizar funções auxiliares, que nos ajudassem melhor a inserir a informação no Grove já que quando faziamos, a travessia, devido á recursividade á direita, o documento aparecia-nos como se tivesse sido lido de baixo para cima e, da direita para a esquerda.

As funções são as seguintes Grove *concGrv (Grove *g1, Grove *g2) e atributo *concAtr( atributo *a1 , atributo *a2) cada uma delas recebe duas variaveis, e vai inserir uma como o ultimo elemento da outra (que é uma lista), ou seja percorre a lista ate ao fim e depois insere o elemento na cauda, assim resolve-nos então o problema que tinhamos.Estas funções ambas devolvem as listas já com o elemento inserido .

A outra função que tambem utilizamos foi a função ,

void Percorre_e_GeraESIS(Grove *g)

Esta função o que faz, é percorre um grove e vai imprimindo o nome das Tags , e chama então outras funções auxiliares que se limitam quer os atributos, quer o texto, caso seja essa a sit uação, de notar que essa função chama-se recursivamente, quer para o seu conteudo quer para os seus irmaos .As funções chamadas por esta função :

FASE 2

O que nos é pedido

Pretende-se criar um interpretador para a seguinte lista de comandos:

Foi-nos proposta nas aulas uma determinada gramática, gramática essa que nós decidimos utilizar, que é a seguinte:

Exemplo:
Gramática

         Interp --> ComList
         ComList --> Comando | ComList Comando
	 Comando --> LOAD fich-id id
                    |SHOW id
		    |LIST
		    |EXIT
		    |HELP

Decidimos a ainda acrescentar a esta lista de comandos alguns comandos que achamos serem também uteís a este mini-interpretador de comandos, esses comandos são :

Solução encontrada

Para esta fase tivemos uma maior facilidade em fazê-la, já que era algo muito idêntico ao que tinhamos feito na primeira fase, só que um pouco menos complexa.

Análise Léxica

Neste capitulo (léxico) tivemos que criar algo muito parecido com o que já tinhamos feito na primeira fase.

Criamos duas expressões regulares necessárias para apanhar os identificadores e os ficheiros. Estas expressões são as mesmas utilizadas na primeira fase do trabalho.

De resto foi também utilizado Star Conditions bastante utéis neste tipo de problemas, a única contrariedade que encontramos foi no comando CHANGE id1 id2 que exigia receber dois identificadores logo tivemos que criar uma nova etapa, que s erá iniciada, quando encontrar o primeiro id onde irá aguardar um outro identificador. Esta solução foi necessária, já que ao executarmos o comando da seguinte maneira : CHANGE exemplo_id ele ficaria á espera do segundo id e não envia para o ecrãn uma mensagem de erro, como seria o desejado. E quando fosse executado outro comando aí sim daria a tal mensagem de erro. Foi então a maneira que achamos para resolver este problema .

Tivemos ainda outro problema que foi, a maneira que nós concebemos a nossa solução obrigou-nos a criar um ciclo para resolver o caso em que tinhamos diversos caracteres e apenas dar uma mensagem de erro, senão quando executavamos algo como: ksd daria-nos 3 mensagens de erro, ou seja tantas, quanto o número de caracteres.

Para resolver esse problema fizemos então :

Exemplo:
Solução

   .                 {BEGIN(abr_LX);}
   <abr_LX>.         {BEGIN(abr_LX);}
   <abr_LX>\n        {BEGIN(0);return(*yytext);} 

Análise Sintáctica

Nesta parte tinhamos apenas que usar a gramática aconselhada e identificar qual o comando executado, e fazer propriamente essa execução.

Mas tivemos que fazer algumas alterações, tivemos que introduzir uma nova derivação para sabermos que concluimos um comando, então a gramática, passou a ser

Exemplo:
nova gramática

         Interpretador --> ListaComandos
         ListaComandos --> Comando | ListComandos Comando
	 Comando --> LOAD fich id ENTER
                    |SHOW id ENTER
		    |LIST
		    |EXIT
		    |LS
                    |CLEAN
                    |CREATE id ENTER
                    |CHANGE id id ENTER
                    |HELP
                    |ENTER



Desta maneira podemos carregar várias vezes na tecla ENTER sem que dê alguma mensagem de erro.

Nesta parte ainda temos que executar as acções semânticas respectivas a cada comando.

Por exemplo quando executamos o comando LOAD fich id tinhamos que inserir numa estrutura de dados o ficheiro e o respecti vo id, a estrutura usada para guardar a informação são as árvores binárias de procura:

Exemplo:
Estrutura usada:

  typedef struct Arv
   {
    char *id;
    char *fich;
    struct Arv *esq, *dir;
   }AB;

Para executar então esta função (acima referida) usamos a função:

Foram ainda utilizadas as seguintes funções para outros comandos

Todas estas funções, e outras chamam diversas funções, todas estas estão num ficheiro á parte chamado arvores.h quem vem tambem no apêndice deste relatório.


Conclusão

Quanto á nossa conclusão desta fase podemos dizer que foi um trabalho que reuniu algum trabalho e algum gosto pela matéria. Achamos que de facto este método de trabalhar documentos e textos, para além de ser algo u'til, quanto a nos é algo que nos motivou bastante.

Nós neste curso até agora, a linguagem C era a linguagem, mais usada nos nossos trabalhos, se formos ver como faríamos este mesmo trabalho usando apenas a linguagem C era algo um pouco/muito difícil de fazer até mesmo para alguem que trabalhe bem em C .

Se formos então analisar o trabalho de ferramentas, até agora desconhecidas para nós, iremos ver que essas ferramentas simples e utéis facilitam-nos bastante o nosso trabalho, reduzindo o nosso número de linhas do código, á insignificância . As ferramentas que que falamos são o Lex e Yacc , de facto estas duas ajudaram-nos bastante no nosso trabalho.

Verificando agora o facto de ter que guardar uma estrutura bastante complicada , teoricamente, concluimos que foi um trabalho bastante simples. Tivemos que defenir uma estrutura, e depois foi só guardar facilmente nessa mesma estrutura o documento que é dado na entrada, de facto foi algo que fizemos com poucas linhas de código.

Falando na travessia desse grove foi algo também bastante simples, com uma simples funçaõ que imprimia, quer o texto, quer os atributos, depois era só ser chamada recursivamente, tinhamos o nosso problema resolvido .

A maior prova que tivemos do trabalho enorme e exaustivo que o Yacc tem, foi dado pelas últimas aulas que tivemos, onde tinhamos que verificar o funcionamento do parser LR(0) , o que é algo bastante chato e por vezes complexo, só que o Yacc faz também outros parsers , bastante mais complexos quando este não nos permite ainda chegar á próxima produção, que é por exemplo o LR(1) .

Fazendo referência a algo, que já nos íamos esquecendo, o nosso executavél permite, quando invocado c/ -esis imprimir para o ecrãn o documento ESIS, Sem esta mesma opção ele apenas imprime se é válido ou não caso não seja diz que tipo de erro, e em que linha.

-

Na segunda fase do nosso trabalho, foi algo que de facto nos deixou bastante espantados, já que nunca nos tinha passado pela cabeça que poderiamos implementar uma mini-shell com o uso do LEX & YACC . A gramática utilizada, é também algo muito simples e pouco complexa.

Se analisarmos bem a questão podemos ir construíndo uma mini-shell com alguns comandos, e ao longo do tempo podemos acrescentar diversos comandos.

Este segunda fase foi algo que de facto deixou-nos a pensar, iremos com certeza fazer algo muito parecido com este trabalho, mas para nós próprios...


Apêndice

Ficheiros fase 1

analisador.lex

Exemplo:
Código analisador.lex

	ID     [_a-zA-Z][a-zA-Z0-9_\-]*
	VALOR  \"[^\"]+\"
	OUTROS  [=\/]
	LIXO [ \t\n\r]+
	TEXTO [^\<&]+

	%option yylineno
	%x abr_ST e_Comercial
	%%

	&                   {BEGIN(e_Comercial);return('&');}

	<e_Comercial>{ID}   {yylval.id=(char *)strdup(yytext);return(ID);}

	<e_Comercial>;      {BEGIN(0);return(';');}

	<e_Comercial>{LIXO} ;

	\<                  {BEGIN(abr_ST);return('<');}
 

	<abr_ST>{ID}        {yylval.id=(char *)strdup(yytext);return(ID);}

	<abr_ST>{VALOR}     {yylval.valor=(char *)strdup(yytext);return(VALOR);}

	<abr_ST>{OUTROS}    return(*yytext);

	<abr_ST>{LIXO}      ;

	<abr_ST>>           {BEGIN(0);return('>');}

	<abr_ST>\<          erro=2;

	<abr_ST>.           return(*yytext);

	{LIXO}              ;

	{TEXTO}             {
        	             yylval.txt=(char *)strdup(yytext);return(TEXTO);
                	    }







analisador.yacc

Exemplo:
Código analisador.yacc

	%{#include "groves.h"
  	Grove *grove;
  	int erro=0;
	%}

	%token TEXTO ID VALOR

	%union {
         	char *txt;
         	char *id;
         	char *valor;
         	Grove *grv;
         	atributo *atrib;
               }

	%type <txt> TEXTO
	%type <id> ID
	%type <valor> VALOR

	%type <grv> Doc ElemList Elem
	%type <atrib> AttrList Attr

	%start Doc

	%%

	Doc : '<' ID AttrList '>'  ElemList '<' '/' ID '>'

    	{
     	 Grove *new=malloc(sizeof(struct Gnodo));
     	 new->id=$2;
     	 new->corpo.atr.conteudo=$5;
     	 new->corpo.atr.atribs=$3;
     	 new->irmaos=NULL;
     	 grove=new;
     	 if( strcmp($2,$8) ) {erro=erroTag;controlo();}
    	};


	ElemList : ElemList Elem {
				$$=concGrv($1,$2);
                         	}
          		|{$$=NULL;};

	Elem : TEXTO {
        	      Grove *new=(Grove *)malloc(sizeof(struct Gnodo));
              	      new->id="Texto";
              	      new->corpo.texto=$1;
              	      $$=new;
             	     }
     		| '&' ID ';' {
                   		Grove *new=malloc(sizeof(struct Gnodo));
                   		new->id=$2;
                   		new->irmaos=NULL;
                   		$$=new;
                  	      }
     		| '<' ID AttrList '>' ElemList '<' '/' ID '>'
       		      {
        		Grove *new=(Grove *)malloc(sizeof(struct Gnodo));
                        new->id=$2;
		        new->corpo.atr.conteudo=$5;
        		new->corpo.atr.atribs=$3;
        		new->irmaos=NULL;
        		$$=new;
        		if(strcmp($2,$8)) {erro=erroTag;controlo();}
       		      }
        		
     		| '<' ID AttrList '/' '>'
       		      {
        		Grove *new=(Grove *)malloc(sizeof(struct Gnodo));
        		new->id=$2;    
		        new->corpo.atr.conteudo=NULL;
        	        new->corpo.atr.atribs=$3;
        		new->irmaos=NULL;
        		$$=new;
       		      };

	AttrList : AttrList Attr {
        	                  $$=concAttr($1,$2);
                	         }
           		|{$$=NULL;};


	Attr : ID '=' VALOR {
        	             atributo *new=malloc(sizeof(struct Anodo));
                	     new->nome=$1;
                     	     new->valor=$3;
                     	     new->seg=NULL;
                    	     $$=new;
                    	    };
	%%
	#include<stdio.h>
	#include<stdlib.h>
	#include "lex.yy.c"

	#define Serro 0
	#define erroTag 1
	#define erroSin 2

	int yyerror(char *s)
	{
 	 erro=erroSin;
 	 return 1;
	}

	
	void controlo()
	{
  	 switch(erro)
  	  {
   	   case Serro:printf("\nDocumento Valido!!\n");break;
   
  	   case erroSin:printf("\nErro Sintatico, na linha -> %d\n",yylineno);
         	        printf("\nDocumento Invalido!!\n");exit(0);

  	   case erroTag:printf("\nErro nas Tags, na linha -> %d\n",yylineno);
           	        printf("\nDocumento Invalido!!\n");exit(0);
   
  	   default:break;
  	  }
	}
	int main(int argc ,char *argv[])
	{
 	 yyparse();
 	 if(argc>1)
   	 {
          controlo();
    	  if(!strcmp(argv[1],"-esis"))
     	   {
      	    printf("\n\n\t\t**** Travessia ESIS do Grove ****\n\n"); 
      	    Percorre_e_GeraESIS(grove);
      	    return 1;
     	   }
   	 }
  	 else controlo();
	}

groves.h

Exemplo:
Código groves.h

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

	typedef struct Anodo
	{
	 char *nome;
	 char *valor;
	 struct Anodo *seg;
	}atributo;

	typedef struct Gnodo
	{
	 char *id;
 	 union c
	  { char *texto;
   	    /* para nodos texto */
   	    struct X
   	    /* para os outros nodos */
 	      {
     		struct Gnodo *conteudo;
     		atributo *atribs;
    	      }atr;
 	  }corpo;
 	 struct Gnodo *irmaos;
	}Grove;

	void imprimeAttr(Grove *g);
	void imprimeTexto(Grove *g);

	void Percorre_e_GeraESIS(Grove *g)	
	{
 	 if(g!=NULL)
 	 {
  	  if(!strcmp(g->id,"Texto")) imprimeTexto(g);
   	   else {
         	 printf("(%s\n",g->id); /* Abrir Tag */
         	 imprimeAttr(g);
	         Percorre_e_GeraESIS(g->corpo.atr.conteudo);
        	 printf(")%s\n",g->id); /* Fechar Tag */
       	        }
 	 Percorre_e_GeraESIS(g->irmaos);
 	}
       }

       void imprimeAttr(Grove *g)
       {
 	atributo *a=malloc(sizeof(struct Anodo));
 	a=g->corpo.atr.atribs;
 	while(a!=NULL)
  	{
   	 printf(" A %s %s\n",a->nome,a->valor);
  	 a=a->seg;
  	}
       }

       void imprimeTexto(Grove *g)
       {
 	if(!isprint(g->corpo.texto[0]) && !isspace(g->corpo.texto[0]))
   		printf(" -%s\n",(g->corpo.texto)+1);
 	  else  printf(" -%s\n",g->corpo.texto);
       }

       Grove *concGrv ( Grove *g1 , Grove *g2 )
       {
 	while(g1!=NULL)
  	{
   	 g1->irmaos=concGrv(g1->irmaos,g2);
   	 return g1;
  	}
 	g1=g2;
 	return g2;
       }

       atributo *concAttr ( atributo *a1 , atributo *a2 )
	{
 	 while(a1!=NULL)
  	  {
   	   a1->seg=concAttr(a1->seg,a2);
   	   return a1;
          }
 	 a1=a2;
 	 return a2;
	}

Ficheiros fase 2

shell.lex

Exemplo:
Código shell.lex

ID [a-zA-Z][_a-zA-Z0-9\-]*
FICH \"[^\"\n\r]+\"
ESPACOS [ \t]*
%x abr_LD abr_SH abr_CHG abr_LX abr_ID
%%
LOAD 		  {BEGIN(abr_LD);return(LOAD);}
<abr_LD>{FICH}    {yylval.fich=(char *)strdup(yytext);return (FICH);}
<abr_LD>{ID}      {yylval.id=(char *)strdup(yytext);BEGIN(0);return(ID);}
<abr_LD>{ESPACOS} ;
<abr_LD>.         ;
<abr_LD>\n        {BEGIN(0);return(*yytext);}

SHOW              {BEGIN(abr_SH);return(SHOW);}
<abr_SH>{ID}      {yylval.id=(char *)strdup(yytext);BEGIN(0);return(ID);}
<abr_SH>{ESPACOS} ;
<abr_SH>.         ;
<abr_SH>\n        {BEGIN(0);return(*yytext);}

CREATE            {BEGIN(abr_SH);return(CREATE);}

CHANGE            {BEGIN(abr_CHG);return(CHANGE);}
<abr_CHG>{ID}     {yylval.id=(char *)strdup(yytext);BEGIN(abr_ID);return(ID);}
<abr_CHG>{ESPACOS} ;
<abr_CHG>.        ; 
<abr_CHG>\n       {BEGIN(0);return(ENTER);}
<abr_ID>{ID}      {yylval.id=(char *)strdup(yytext);BEGIN(0);return(ID);}
<abr_ID>{ESPACOS} ;
<abr_ID>.         ;
<abr_ID>\n        {BEGIN(0);return(*yytext);}         

LS{ESPACOS}\n     {return(LS);} 
CLEAN{ESPACOS}\n  {return(CLEAN);}
LIST{ESPACOS}\n	  {return (LIST);} 
EXIT{ESPACOS}\n   {return (EXIT);}
HELP{ESPACOS}\n   {return (HELP);}
{ESPACOS}         ;

\n                {return(ENTER);}

.                 {BEGIN(abr_LX);}
<abr_LX>.         {BEGIN(abr_LX);}
<abr_LX>\n        {BEGIN(0);return (*yytext);} 



shell.yacc

Exemplo:
Código shell.yacc

%{
  #include "arvores.h"
  AB *arvB=NULL;
%}

%token LOAD SHOW LIST EXIT HELP FICH ID CREATE CHANGE CLEAN LS ENTER

%union{
       char *fich;
       char *id;
      }

%type <id> ID 
%type <fich> FICH


%start Interpretador
%%
Interpretador : ListaComandos 

ListaComandos : Comando  
	       |ListaComandos Comando
               ;

Comando : LOAD FICH ID ENTER {
                              arvB=insArvB(arvB,$3,$2);
                              return 1;
                             }
         |SHOW ID ENTER   {
                           mostraESIS(arvB,$2,"ecran"); 
                           return 1;
                          } 
	 |LIST     {
                    consArvB(arvB);
                    return 1;
                   }
         |EXIT  {exit(0);} 
         |LS    {
                 system("ls -la *.esis");
                 return 1;
                }
         |CLEAN {
                 arvB=NULL;
                 return 1;
                }
         |CREATE ID ENTER {
                           mostraESIS(arvB,$2,"disco");
                           return 1;
                          }
         |CHANGE ID ID ENTER {
                              troca_ids(arvB,$2,$3);
                              return 1;
                             }
         |HELP {
                TabHELP();
                return 1; 
               }
         |ENTER {return 1;}
         ;

%%
#include <stdio.h>
#include <stdlib.h>
#include "lex.yy.c"

int main(void)
{
 printf("\t\t\n ***** Command's Mini-Interpretor ***** \n\n");
 printf("\nTrabFase2 @ PLI-Shell#> ");
 while(yyparse())
   printf("\nTrabFase2 @ PLI-Shell#> ");
 return 1;
}

int yyerror(char *s)
{
 printf(" Invalid Command, or too few arguments !!\n"); 
 printf(" Print HELP for more information \n");
 return 1;
}


arvores.h

Exemplo:
Código arvores.h

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

typedef struct Arv
 {
  char *id;
  char *fich;
  struct Arv *esq, *dir;
 }AB;

char *tiraAspas(char *file)
{
 int i;
 for(i=1 ; file[i] != '\"' ; i++) *(file + i-1) = *(file + i);
 *(file + i-1)='\0';
 return file;
}

AB *insArvB (AB *arv,char *id,char *fich)
{
 AB *new;
 int comp;

 if ( arv != NULL )
  {
   comp=strcmp(arv->id,id); 
   if ( comp ==0 ) printf("This identifier exist !!\n");
   if ( comp > 0 ) 
               {
                arv->esq=insArvB(arv->esq,id,fich); 
               }
   if ( comp < 0 )
               {
                arv->dir=insArvB(arv->dir,id,fich);
               }
  }   
 else {
       new=(struct Arv *)malloc(sizeof (struct Arv)); 
       new->id=id;
       new->fich=tiraAspas(fich);
       new->esq=NULL;
       new->dir=NULL;
       return new;
      }
 return arv;
}

void da_espacos(int esp)
{
 if(esp > 0)
  while(esp > 0){ printf(" ");esp--; }
  else printf(" ");
}

void escreve(char *id , char *fich)
{
 printf("\t\t¦    %s",id);
 da_espacos(20-strlen(id));
 printf("¦    %s",fich);
 da_espacos(27-strlen(fich));
 printf("¦\n");
}


void consArvB_aux(AB *arv)
{
 if( arv != NULL)
  { 
   escreve(arv->id,arv->fich);
   consArvB_aux(arv->esq);
   consArvB_aux(arv->dir);
  }
}

void consArvB(AB *arv)
{
 printf("\t\t/--------------------------------------------------------\\\n");
 printf("\t\t|    IDENTIFIER          ¦    FILE                       |\n");
 printf("\t\t|--------------------------------------------------------|\n");
 consArvB_aux(arv);
 printf("\t\t\\--------------------------------------------------------/\n");
}

void TabHELP()
{
 printf("\n\n\t\t/---------------------- HELP -------------------------\\\n");
 printf("\t\t| LOAD <fich> <id> -> Loads the file <fich> to        |\n");
 printf("\t\t|                     memory with the <id> identifier |\n"); 
 printf("\t\t| SHOW <id> -> Shows the ESIS ,if possible , for the  |\n");
 printf("\t\t|              file with <id> as identifier           |\n");
 printf("\t\t| LIST -> Shows the table with the files and the      |\n");
 printf("\t\t|         respectives identifiers                     |\n");
 printf("\t\t| HELP -> Displays this help !!                       |\n");
 printf("\t\t| LS -> Shows the files in the current directory,     |\n");
 printf("\t\t|       you can only see .esis files                  |\n");
 printf("\t\t| CLEAN -> Resets from table all files and respective |\n");
 printf("\t\t|          identifier                                 |\n");
 printf("\t\t| CHANGE <id1> <id2> -> Changes the name of the <id1> |\n");
 printf("\t\t|                       to <id2>                      |\n");
 printf("\t\t| CREATE <id> -> Creates a ESIS's file                |\n");
 printf("\t\t| EXIT -> Exits the Mini-Interpretor                  |\n");
 printf("\t\t|-----------------------------------------------------|\n");
 printf("\t\t| <fich> -> Name of file between \"                    |\n");
 printf("\t\t| <id> -> Name of the identifier                      |\n");
 printf("\t\t\\-----------------------------------------------------/\n");
}

int existe_in_AB( AB *a , char *id ) 
{
 int comp=0;
 if(a !=NULL )
  {
   comp=strcmp(a->id,id); 
   if( comp == 0 ) return 1;
   if( comp  > 0 ) return existe_in_AB( a->esq , id );
   if( comp  < 0 ) return existe_in_AB( a->dir , id );
  }
 return 0;
}

void troca_ids(AB *a , char *id , char *id2)
{int comp=0;
  
 if(a !=NULL )
  {
   if(!existe_in_AB(a,id)) 
          {
           printf("The identifier doesn't exist!!");
           return ;
          }
   if(!existe_in_AB(a,id2))
    {
     comp=strcmp(a->id,id); 
     if( comp == 0 )  a->id=id2; 
     if( comp  > 0 )  troca_ids( a->esq , id , id2 );
     if( comp  < 0 )  troca_ids( a->dir , id , id2 );
    }
    else printf(" This identifier belongs to another file!!");
  }
}


char *dafich(AB *a , char *id)
{
 int comp;
 comp=strcmp(a->id,id);
 if( comp == 0 ) return(char *)strdup( a->fich );
 if( comp >  0 ) return dafich( a->esq , id );
 if( comp  <  0 ) return dafich( a->dir , id );
}

void mostraESIS (AB *a , char *id , char *sitio )
{
 if (a != NULL)
  {
   if(existe_in_AB(a,id))
   {
    char *com;
    com=strdup("analisador -esis <");
    strcat(com,dafich(a,id));
    if(!strcmp(sitio,"disco"))
     { 
       strcat(com," >");
       strcat(com,id);
       strcat(com,".esis");
       printf("\nIf everything is ok,file saved as %s.esis\n", id);
       printf("execute LS to verify!!\n");
     }
    system (com);
   }
   else printf(" Identifer not found!!\n");
  }
 else printf(" The table is Empty!!\n");
}


Agradecimentos:

Queremos apenas agradecer aos professores que nos ajudaram nas dúvidas que lhes iámos pondo acerca do nosso trabalho,que por exemplo ao fim das aulas não se importavam de tirar essas dúvidas.

Queremos também agradecer a alguns colegas nossos que em conjunto chegamos á resolução dos nossos problemas .

Não revelamos os nomes, porque não achamos elegante da nossa parte.

Queremos ainda agradecer aos nossos pais por nos terem lembrado, ou não, de nos conceberem, provavelmente não tinham ainda televisão , Obrigado!!

Bibliografia: