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