• Nenhum resultado encontrado

4 Modelo Proposto

4.3 Transdutor Adaptativo Programável TAP

Estabelecida a sintaxe da linguagem, era necessário denir um dispositivo capaz de reconhecê-la. Para reconhecer tal linguagem, o dispositivo reconhecedor tinha de possuir ao menos uma pilha para analisar expressões, utilizar a tecnologia adaptativa para per- mitir acréscimo de novos símbolos e regras de produção e ainda permitir que sentenças previamente denidas pudessem ser reutilizadas na denição de novas sentenças. Para atender a essas necessidades foi desenvolvido o Transdutor Adaptativo Programável.

O Transdutor Adaptativo Programável é um transdutor com dupla saída, sendo a principal delas uma ta contendo símbolos pertencentes ao alfabeto ∆s. A segunda saída

é um único símbolo δDE associado a cada estado individualmente. A denição formal do

TAP é apresentada a seguir.

Um Transdutor Adaptativo Programável é uma estrutura:

T = (Σ, Q, δ, q0, F, ∆s, M, δMCin, δMCout, ∆DE, δDE)

Onde:

7opcodes ou códigos de operação são comandos de baixo nível que fazem referência à instruções reco-

(Σ, Q, δ, q0, F ) é um AFD

s é o alfabeto de símbolos de saída

M é o conjunto de micro-instruções responsáveis por con- trolar o funcionamento interno de T (será melhor expli- cado a seguir)

δMCin : Q → M é a função que retorna o conjunto de micro-códigos a ser executada ao entrar em um estado

δMCout : Q → M é a função que retorna o conjunto de micro-códigos a ser executada ao sair de um estado

DE é o conjunto de descritores dos estados

δDE : Q → ∆DE é a função que retorna o descritor de cada estado

O TAP possui uma ta de entrada, um conjunto de estados válidos e arestas em forma de grafo, uma unidade de controle para processar símbolos de M, uma lista de micro-instruções de M, uma ta de saída (símbolos de ∆s), um registrador de símbolos

de Σ (tkReg), um registrador contador numérico inteiro (Cntreg), um registrador ponteiro

de micro-instruções (Mip), duas pilhas P1 e P2 capaz de armazenar símbolos de Σ, uma

pilha P3 que armazena estados de Q (reservada para recursão dentro do autômato - será

explicado mais adiante) e um registrador que armazena o estado atual no grafo de estados e arestas (Qatual). O esquema em blocos pode ser visto na Figura 10.

Figura 10: Esquema em Blocos do TAP. Fonte: Autor.

Como pode ser visto na gura e diferentemente da denição dos autômatos com pilhas e dos transdutores pushdown, o TAP não possui um alfabeto de pilha em sua denição

formal. A pilha é gerenciada através de micro-instruções especícas de M. Cada vez que um estado é visitado, a Lista de Micro-instruções é preenchida com micro-comandos a serem executados ao entrar e sair do estado. A Unidade de controle do TAP é responsável por executar cada micro-instruções e assim gerenciar todos os componentes do TAP. De igual modo, os registradores T kreg, Cntreg também são gerenciados através de micro-

instruções próprias. O Mipé um registrador usado internamente pela unidade de controle

e cuja função é apontar para a próxima micro-instrução a ser executada da lista de micro- instruções. Seu valor é incrementado a cada micro-instrução executada. Algumas micro- instruções alteram seu valor de forma não-incremental para realizar saltos e laços de repetição dentro do micro-código de δMCin e δMCout. A cada estado visitado no grafo de

estados, essas funções (δMCin e δMCout) retornam um sub-conjunto de M contendo (ou

não) micro-instruções que serão inseridos na Lista de Micro-instruções e executadas pela unidade de controle. As funções δMCine δMCoutpodem também retornar como subconjunto

de M o conjunto vazio. O que implica dizer que alguns estados poderão não ter nenhuma micro-instrução a ser executada (δMCin = ∅ e/ou δMCout = ∅). A unidade de controle,

por sua vez, de acordo com as instruções de M constantes na lista de micro-instruções, irá manipular as pilhas P1, P2 e P3, os registradores T kreg e Cntreg ou a ta de saída.

A denição formal do TAP também não possui uma função de saída. Micro-instruções próprias são utilizadas para a saída do transdutor. A escrita é sempre incremental - da esquerda para a direita.

O grafo de estados e arestas é uma estrutura semelhante a um AFD, com um único estado inicial e diversos outros estados nais e intermediários, além das respectivas tran- sições. Algumas micro-instruções de M são capazes de alterar o estado atual e até mesmo fazer com que a unidade de controle inclua ou remova estados e arestas do grafo, modi- cando assim a linguagem suportada pelo conjunto e consequentemente alterando o poder de tradução do transdutor. O grafo de estados e arestas trabalha totalmente integrado à unidade de controle, usando para isto as funções δMCin, δMCout e δDE.

A unidade de controle é responsável por executar as micro-instruções contidas na lista de micro-instruções. Basicamente, ela lê o símbolo armazenado dentro da lista na posição indicada por Mip e executa as operações correspondentes a cada símbolo de M,

modicando assim os demais componentes do TAP. Algumas micro-instruções válidas de M podem ser vistas na Tabela 7.

As sentenças de entrada no TAP podem conter símbolos do alfabeto de entrada Σ e do conjunto de descritores de estados ∆DE. Estes últimos agem como se fossem parâmetros

Micro-instrução Descrição

LABEL Dene rótulo no microcódigo

TKTOREG Põe o símbolo lido na ta no registrador P1ENQTK Empilha o símbolo lido na ta em P1

P2ENQTK Empilha o símbolo lido na ta em P2

P1DEQTOREG Desempilha o topo P1 e põe no regis-

trador tkReg

P2DEQTOREG Desempilha o topo P2 e põe no regis-

trador tkReg

P1ENQREG Empilha o símbolo contido no registra- dor em P1

P2ENQREG Empilha o símbolo contido no registra- dor em P2

JMP <r> Realiza saltos para o rótulo <r> dentro de uma seção do micro-código

JMP_TP1_IS_EMPTY <r> Salta para o rótulo <r> se o P1 estiver

vazia

JMP_TP2_IS_EMPTY <r> Salta para o rótulo <r> se o P1 estiver

vazia

JMP_TK_TYPE_IS_REG_TYPE <r> Salta para o rótulo <r> se o último símbolo lido na ta for o mesmo con- tido no registrador tkReg

JMP_TK_TYPE_IS_NOT_REG_TYPE <r> Salta para o rótulo <r> se o último símbolo lido na ta for diferente do con- tido no registrador tkReg

JMP_PRIO_TK_MENOR_PRIO_TP1 <r> Salta para o rótulo <r> se P1 estiver

vazia ou se a prioridade do último sím- bolo lido na ta for menor que a pri- oridade do símbolo contido no topo de P1

CODVERT_TOREG Põe o código do estado atual no regis- trador tkReg

CREATE_VAR Cria uma vértice para armazenamento de dados. No topo de P1 devem estar o

código do vértice representativo do tipo do dados utilizado, seguido pelo cami- nho da origem até este estado (identi- cador)

P2EMPSYMB Verica se o topo de P2 é uma chave válida para a tabela hash do Vertice- CaminhoSymbolo atual

INC_COUNT Incrementa o contador cntReg

DEC_COUNT Decrementa o contador cntReg

LOAD_REG <s> Carrega o símbolo <s> no registrador tkReg

de funções e são utilizados de forma recursiva no TAP. Existem algumas restrições para a denição de sentenças e elas serão melhor explicadas na Seção 4.4, no nal deste capítulo. O principio de funcionamento do TAP é semelhante ao de um AFD. Diferindo apenas no processo de transição. Para facilitar o entendimento, será adotada a seguinte convenção: a função δMCin de um estado q qualquer será indicada por q.in e a função δMCout do

mesmo estado será indicada por q.out. Supondo realizar uma transição de um estado Vx

para um estado Vy, a sequência de passos é mostrada a seguir:

1. Mip recebe o valor 0 e a lista de micro-instruções é preenchida com o conteúdo de

Vx.out;

2. A unidade de controle executa a sequência de micro-instruções constante na lista de micro-instruções (Vx.out);

3. Altera-se o estado atual (Qatual) de Vx para o novo estado Vy;

4. Mip recebe o valor 0 e a lista de micro-instruções é preenchida com o conteúdo de

Vy.in;

5. A unidade de controle executa a sequência de micro-instruções constante na lista de micro-instruções (Vy.in);

6. Exclui-se o símbolo anteriormente lido na ta antes do início da transição;

As micro-instruções são pequenos códigos representados sob a forma de símbolos de M que manipulam os componentes internos da unidade de controle. Perceba o leitor que essas micro-instruções não fazem parte do alfabeto de saída ∆s. Mas são usadas

internamente pelo dispositivo para controlar o estado interno do TAP. Como transdutor, a principal função do TAP é converter uma linguagem em outra. As micro-instruções de M servem apenas para denir o mecanismo dessa conversão. As micro-instruções de M não têm utilidade fora do TAP. O fato de utilizar um conjunto de micro-instruções confere ao TAP uma camada de abstração que permite isolar Σ de ∆s. Internamente, ao criar

novos estados, a própria unidade de controle gera os micro-códigos necessários - conforme o tipo de vértice que esteja sendo criado. Além de permitir que os estados possam ter comportamentos individuais diferentes, auxilia na hora de expandir o conjunto de estados conhecidos. É necessário ter em mente que o objetivo do TAP é converter a sentença de entrada contendo símbolos de Σ em uma sentença de saída composta por símbolos de ∆s.

Saída Descrição-Estado Descrição

TKEXPRESSAO Indica que a sentença de entrada é uma expressão matemática

TKIDENTIFICADOR Indica que a sentença encontrada é uma identi- cador de variável

TKCOMANDO Indica que a sentença válida é um comando da lin- guagem

AGUARDANDO_OPERANDO Erro: indica que uma expressão matemática não foi corretamente concluída

TIPO_INVÁLIDO Erro: Indica que foi declarado uma instância com um tipo inexistente

Tabela 8: Algumas saídas possíveis para a função δDE do TAP

entrada conforme a lógica denida nas micro-programações de entrada e saída de cada estado. Observando a Tabela 7 pode-se ver que existem micro-instruções especícas para escrever na ta de saída. Por exemplo, a micro-instrução wrcomando escreve um símbolo do alfabeto de saída ∆s na ta. De forma semelhante, a instrução wrregdata escreve o

conteúdo do registrador tkReg diretamente na ta de saída, após convertê-lo nos símbolos

de ∆s correspondentes.

Existem também instruções que manipulam a estrutura interna do TAP. A micro- instrução create_var, por exemplo, cria um novo estado e suas respectivas arestas, fa- zendo com que o autômato altere a sua própria estrutura, reconhecendo novos caminhos e expandindo a linguagem suportada. Este último comando concede ao TAP a caracterís- tica de ser adaptativo - ou seja, ele muda seu conjunto de estados e arestas. Sentenças de entrada que levem até um estado que execute a micro-instrução create_var farão com que novos caminhos sejam criados, alterados ou até mesmo excluídos do TAP, alterando de forma controlada a linguagem reconhecida pelo transdutor e consequentemente a sua capacidade de tradução.

Quanto à segunda saída gerada pelo TAP, cada estado tem associado através da função δDE um símbolo pertencente a ∆DE. De modo que toda sentença aplicada na entrada do TAP irá retornar um símbolo de ∆DE. Esta saída é utilizada internamente pelo TAP como

identicador do tipo de sentença aplicado. Quando uma sentença é concluída, se o estado é não-nal, o símbolo indicará uma condição de erro ou falha na sentença de entrada. Se o estado é nal, o símbolo indicará que a sentença válida e rótula tal sentença com um símbolo de ∆DE que indica o tipo da sentença. A Tabela 8 contém alguns dos tipos de

símbolos possíveis. Esta segunda saída é utilizada para fazer-se recursão dentro do TAP. O mecanismo da recursão será descrito no exemplo a seguir.

Antes de dar prosseguimento ao exemplo, faz-se necessário dizer que existem dois tipos de arestas possíveis no TAP. Aquelas acionadas pelos símbolos de Σ e as acionadas pelo símbolos de ∆DE.

Para melhor compreensão do mecanismo, observe a Figura 11 a seguir.

Figura 11: Conguração do TAP Utilizada para Reconhecer a Sentença Calcule <expres- são>. Fonte: Autor.

Suponha analisar a sentença Calcule 2+3:

1. Inicialmente a sentença é dividida nos símbolos que compõe o alfabeto Σ. Por ser muito simples, a rotina que faz essa divisão será abstraída deste trabalho. Mas tenha o leitor em mente que essa rotina entregará como saída uma lista contendo símbolos de Σ - tais como números, sinais de pontuação, operadores lógicos-aritméticos e palavras da língua portuguesa. No exemplo em questão, será entregue a seguinte lista de símbolos: [calcule, 2, +, 3]. Essa lista será colocada na ta de entrada do TAP.

2. O estado atual é o estado inicial V0. (Considerando que os códigos de entrada e saída

nos estados somente são executados a cada transição, nunca δMCin será executado

para V0. A menos que ele seja visitado durante a avaliação da sentença). Uma vez

lido o primeiro elemento da ta (calcule), observando a Figura 11 percebe-se que será feita a transição para o estado V100.

3. Durante a realização da transição, é executado o micro-código V0.out, muda-se o

estado atual para o novo estado V100, executa-se o micro-código V100.in e nalmente

remove-se o elemento mais à esquerda da ta de entrada. Os respectivos códigos V0.out e V100.in estão listados nos Algoritmo 2 e Algoritmo 7 descritos mais adiante. 4. Uma vez concluída a transição para o estado V100, é lido o novo símbolo da ta

(que neste exemplo é o símbolo 2). Observe o leitor que para o estado atual V100

e o elemento lido na ta (2) não existe uma transição denida, uma vez que a única transição possível é uma transição através de um símbolo de ∆DE (ver Figura

11). A unidade de controle do TAP então empilha o estado atual V100 em P3 e

imediatamente salta para o estado V0, dando inicio à recursão.

5. Um vez em V0, é lido o primeiro elemento da ta de entrada - que ainda é o 2. Neste

caso, existe uma regra de transição apontando todas as constantes para o estado V2. Executa-se então os códigos V0.out e V2.in (Algoritmos 2 e 3). O Algoritmo 3 está denido de forma que, caso o símbolo não pertença ao conjunto [')', ';', '{' e então], tal símbolo será empilhado em P2. Assim, o símbolo 2 é empilhado em

P2.

6. O estado atual agora é V2 e o próximo elemento lido na ta é o +. Então será feita

transição para o estado V3. Executando-se os códigos denidos nos algoritmos 4 e 5.

A linha 1 do Algoritmo 4 executa a micro-instrução JMP _T K_IS_OP ERAT OR endwh3, que no caso, força a unidade de controle a simplesmente saltar para a linha 37, onde está denido tal rótulo. O Algoritmo 5, por sua vez possui a micro-instrução JMP _P RIO_T K_MENOR_P RIO_T P 1 endwh1. A tabela de prioridade pode ser vista na Tabela 4. No caso, como P1 está vazia, a unidade de controle

do TAP salta para a linha 6, executando apenas a instrução contida na linha 7, que empilha o símbolo + em P1.

7. Uma vez em V3, o novo símbolo lido na ta de entrada é o símbolo 3. Uma nova

transição será feita, desta vez retornando para o estado V2. Serão executados os

micro-códigos V3.out (Algoritmo 6) e V2.in (Algoritmo 3). V3.out não executa nada.

V2.in por sua vez apenas empilhará o símbolo em P2. P2está com a sequência ['3','2'].

8. Agora em V2, a unidade de controle tentará ler o próximo elemento da ta de entrada.

No entanto, a ta está vazia e nenhuma transição poderá ser feita. A unidade de controle então testa se o estado atual é nal. Caso positivo, encerrará a chamada

recursiva mantendo-se o estado atual em V2. Caso negativo, a sentença analisada

não pertence a linguagem reconhecida pelo agente.

9. Retornando da chamada recursiva, executa-se o código V2.out (Algoritmo 4). ( Caso

o leitor deseje acompanhar o Algoritmo 4, verá que o código seleciona todos os símbolos constantes em P1, desempilhando-os um-a-um e os empilha em P2, acres-

centando assim à P2 os símbolos que estavam em P1. Terminado esse passo, P2

conterá todos os símbolos e P1 estará vazia. No exemplo dado, P2 conterá a sequên-

cia ['+','3','2']. O próximo passo é armazenar em P1 o conteúdo invertido de P2. Para

isto, cada símbolo de P2 é desempilhado e em seguida empilhado em P1. P1 então

passa a ter a sequência ['2','3','+'] e P2 ca vazia. Por m, cada elemento de P1 é

desempilhado e o código restante do algoritmo analisa os símbolos e os escreve na ta de saída sobre a forma de símbolos de ∆s, cando ambas P1 e P2 vazias). Salva-

se então o estado atual V2 em uma variável temporária tmp. Desempilha-se o topo

de P3 e este passa a ser o novo estado atual (no caso, V100). A unidade de controle

verica então se existe uma aresta saindo de V100 acionada pelo símbolo retornado

pela função δDE aplicada ao estado anteriormente salvo na variável temporária tmp

(V2). Olhando para a Figura 11, vê-se que o estado nal V2 retorna como descritor

do estado o símbolo <expressao>. E existe uma transição em V100 acionada por

<expressao> saindo para o estado V101. Neste caso, mais uma transição será feita.

São executados os códigos V100.out (Algoritmo 8) e V101.in (Algoritmo 9).

10. Chegado ao estado V101, a ta está vazia, nenhum transição pode ser feita. Também

não há nenhuma recursão pendente. Então a unidade de controle executa o código out do último estado (no caso V101.out, que conforme pode ser visto no Algoritmo

10; escreve na ta de saída o símbolo PWRITE). Como este último estado é nal, pode-se concluir que a sentença foi aceita e o código escrito na ta de saída é válido. Caso contrário, a sentença não foi aceita e o código escrito na ta de saída é descartado. Neste último caso, o valor retornado pela função δDE indicará o tipo

de erro que causou a recusa da sentença analisada.

Como pode ser observado, cada micro-código executado ao entrar e sair em cada estado processa os símbolos lidos naquele estado de forma que o conjunto desses micro- processamentos irá transformar os símbolos de Σ contidos na sentença de entrada em símbolos de ∆s, escrevendo-os na ta de saída. Algoritmo 11 contém o código nal gerado

a partir do exemplo dado. Cada símbolo contido na sentença de saída é uma instrução em linguagem de montagem executável por uma máquina determinada. Para este trabalho,

a máquina utilizada é uma máquina virtual baseada em pilha. Não faz parte do escopo deste trabalho detalhar esta máquina. No entanto, mais informações sobre ela podem ser vista no Apêndice A. Por ora é importante saber que o TAP recebeu a sentença de entrada Calcule 2+3 e a converteu em uma sequência de instruções que, ao ser executada pela máquina virtual, faz com que a mesma empilhe os números 2 (PUSH_CTENUMERO 2) e 3 (PUSH_CTENUMERO 3), desempilhe os dois elementos do topo da pilha, some- os e ponha o resultado novamente na pilha(PADD), empilhe um texto no topo da pilha (PUSH_CTETEXTO A resposta é ), inverta a posição dos dois elementos do topo da pilha (PSWAP), desempilhe os dois elementos do topo da pilha, concatene-os e ponha o resultado novamente na pilha (PADD) e por m exiba o resultado ao usuário (PWRITE). Algoritmo 1 Micro-código retornado em

V0.in

\\ Não executa nada

Algoritmo 2 Micro-código retornado em V0.out

\\ Não executa nada

Algoritmo 3 Micro-código retornado em V2.in 1: jmp_Tk_Is_Null 'if1end' 2: loadReg ')' 3: jmp_Tk_Type_Is_Reg_Type 'if1end' 4: loadReg ';' 5: jmp_Tk_Type_Is_Reg_Type 'if1end' 6: loadReg '{' 7: jmp_Tk_Type_Is_Reg_Type 'if1end' 8: loadReg 'então' 9: jmp_Tk_Type_Is_Reg_Type 'if1end' 10: p2EnqTk 11: if1end:

Algoritmo 4 Micro-código retornado em V2.out

1: Jmp_Tk_Is_Operador 'ENDWH3' 2: loadReg ')'

3: jmp_Tk_Type_Is_Reg_Type

'ENDWH3'

4:

5: WH1: \\ seleciona o que sobrou em p1 e coloca em p2 6: jmp_Tp1_Is_Empty 'ENDWH1' 7: p1DeqToReg 8: p2EnqReg 9: jmp 'WH1' 10: ENDWH1: 11: p2EnqTk 12: if1end:

13: WH2: \\ inverte a sequência de p2 para

escrever no código executável

14: jmp_Tp2_Is_Empty 'ENDWH2' 15: p2DeqToReg 16: p1EnqReg 17: jmp 'WH2' 18: ENDWH2: 19:

20: WH3: \\ escreve o código executável, de

acordo com o tipo de token de p1

21: jmp_Tp1_Is_Empty 'ENDWH3' 22: p1DeqToReg 23: jmp_3L_Type_Reg_Neq TKCTENU- MERO 24: wrcommand PUSH_CTENUMERO 25: wrRegData 26: jmp 'WH3' 27: ... 28: jmp_2L_Type_Reg_Neq TK_OPR_SOMA 29: wrcommand PADD 30: jmp 'WH3' 31: jmp_2L_Type_Reg_Neq TK_OPR_SUB 32: wrcommand PSUB 33: jmp 'WH3' 34: ...

35: Exception 'Operação inválida!' 36: ENDWH3:

Algoritmo 5 Micro-código retornado em V3.in 1: WH1: 2: jmp_Prio_Tk_Menor_Prio_TP1 'ENDWH1' 3: p1DeqToReg 4: p2EnqReg 5: jmp 'WH1' 6: ENDWH1: 7: p1EnqTk

Algoritmo 6 Micro-código retornado em V3.out

\\ Não executa nada

Algoritmo 7 Micro-código retornado em V100.in

Algoritmo 8 Micro-código retornado em V100.out

\\ Não executa nada