Dicas para implementação do Trabalho 6 1 Passo a passo para implementação do trabalho 1 Faça um exemplo que contemple todos os possíveis comandos que sua linguagem aceita. Um possível exemplo, faltando adicionar os comandos se entao senao e enquanto (pois a definição destes comando é pessoal), segue: programa Teste; var a, b, c, d, e: inteiro; f: caracter; g, h, i: string; fim. a := 10; imprima( DigiteUmNumero ); leia (b); c := a * b ; imprima( DigiteOutroNumero ); leia (e); a := max (c, 100, e); imprima( O maximo foi: ); imprima(a); 2 Simplifique sua linguagem (modificando no seu analisador sintático), lembrando sempre de colocar as modificações no arquivo leiame.txt, para aceitar o seu programa que possui todos os testes possíveis. 3 Teste se seu programa exemplo é realmente conhecido pelo seu analisador léxico, pelo sintático e pelo semântico. Na análise semântica você deve apenas criar a tabela de variáveis alocando um espaço para o nome dela, um espaço para o tipo e um espaço que caberia o maior valor correspondente àquele tipo no campo valor, mas com nenhum valor atribuído ou um atribua sempre um valor inicial default. Não faça atribuições de valores às variáveis em tempo de análise. Isso só será feito em tempo de execução! Após criada a tabela de variáveis, basta verificar se as variáveis que aparecm no programa foram todas declaradas e se os tipos atribuídos correspondem aos declarados. As aridades e tipos dos argumentos das funções primitivas também devem ser verificados.
4 Após seu interpretador realizar todas as fases da análise e seu programa que engloba todos os possíveis comandos passar por este sem nenhum problema, desenhe (em um papel mesmo!) a árvore de execução resultante do programa exemplo que, nesta fase, já sabemos que realmente está escrito na linguagem. 5 Com o desenho rascunhado em papel, implemente a estrutura de dados árvore conforme você julgar melhor (conforme você desenhou). Uma possível implementação é o nó que forma a árvore possuir 3 campos (nome, tipo e valor) e 3 ponteiros para nós filhos (esq, centro, dir). Prováveis tipos de comandos presentes em sua linguagem e em sua árvore: atribuição (campo nome preenchid por :=) função (campo nome preenchido por leia, imprima ou max) se entao senao (campo nome preenchido por se) enquanto (campo nome preenchido por enquanto) variavel (campo nome preenchido pelo nome da variável) inteiro (campo nome preenchido pelo valor do inteiro) string (campo nome preenchido pelo valor do caracter) caracter (campo nome preenchido pelo valor da string) operadores aritméticos (campo nome preenchido pelo operador em questão) operadores lógicos (campo nome preenchido pelo operador em questão) 6 Teste exaustivamente se sua estrutura árvore realmente está funcionando em um módulo separado. 7 Implemente uma estrutura de daos Fila que conterá as árvores resultantes de cada comando de seu programa. Esta fila chamaremos de Árvore de Execução do programa. 8 Após possuir estas 2 estruturas implementadas e testadas, utilize o seu analisador sintático juntamente com suas estruturas árvore e fila para criar a Árvore de execução. Para cada comando você criará uma árvore comando e esta será inserida na fila. 9 Após ter sua sintático.y realizando todas as análises, criando a tabela de variáveis e criando a árvore de execução, crie então um executor que pegará este único programa e executará cada nó da Fila (cada árvore comando). Com ao menos esta fase pronta (incluindo a impressão da Árvore de Execução do programa) você obterá 50% da nota do seu trabalho. Conseguir analisar, gerar código (árvore de execução) e depois executá lo contempla todas as fases de um interpretador de um único programa. 10 Após exaustivos testes nesta versão inicial do interpretador, basta criar o ambiente que lhe proporcionará compilar e executar vários programas de uma única vez. Para tal você deve criar uma lista de programas, e cada nó desta lista deve conter o nome do programa compilado, sua tabela de variáveis e sua árvore de execução.
2 Observações de uma possível implementação de um interpretador presentes em um arquivo leiame.txt: A função max só pode ser utilizada em atribuições simples (não pode ser utilizada em expressões). Exemplo: a := max (10, 20, 30); No comando se entao senao só será permitido 1 comando em entao e 1 comando em senao. As expressões foram divididas em 2 tipos: expressões lógicas e expressões aritméticas. Minha linguagem só aceita expressões aritméticas com dois argumentos, e estes aparecem intercalados pelos operadores (logo, nunca há problemas com precedência de operadores). Os operadores aritméticos são representados por: +: soma : subtração *: multiplicação /: divisão ^: elevado %: módulo. Exemplos de atribuições com expressões válidas: a := 10 + 12; a := 12 10; a := 10 * 12; a := 10 / 2; a := 10 % 12; a := 2 ^ 10; Exemplos de atribuições com expressões INVÁLIDAS na minha linguagem: a := 10 + 12 + 30; a := 10 + 12 * 30; Expressões lógicas em minha linguagem devem ser formadas sempre por uma variável (do tipo inteiro) seguida de um operador lógico seguido de um número. Os operadores lógicos são representados por: < : menor > : maior <= : menor ou igual >= : maior ou igual = : igual <>: diferente. Exemplos de expressões lógicas válidas: a < 10 a >= 20
a <> 30 a = 67 Expressões lógicas só serão utilizadas nos comandos se entao senao e enquanto facaincremento_decremento. Exemplo de comando se entao senao com expressão lógica: se a < 10 entao leia(a); senao imprima(a); Para que não aconteçam loops infinitos no comando enquanto faca (ele tambem possuirá apenas 1 linha de instrução no comando faca), será necessário uma linha a mais que incrementará ou decrementará a variável que você está utilizando na expressão lógica. Você pode definir de uma forma diferente. Uma sugestão segue. Exemplo de comando enquanto faca incremento_decremento com expressão lógica: enquanto a < 10 faca imprima (a); incremento_decremento a := a + 1; 3 Lembretes que não devem passar em branco: A função imprima pode imprimir uma string ou uma variável. Os valores só são inseridos nas variáveis em tempo de execução. Logo, retire as linhas de comando do trabalho 5 em que você atribuía ou atualizava valores. A tabela de variável que precisa ser criada e armazenada na compilação é construída apenas com as informações contidas na declaração de variáveis. Você deve alocar um espaço para o nome da variável, um espaço para o tipo e um espaço para que o maior valor possível deste tipo seja armazenado. Não atribua valores em tempo de compilação!
Divida suas expressões em expressões lógicas e aritméticas. No comando se entao senao só será permitido 1 comando em entao e 1 comando em senao. Para que não aconteçam loops infinitos no comando enquanto faca (ele tambem possuirá apenas 1 linha de instrução no comando faca), será necessário uma linha a mais que incrementará ou decrementará a variável que você está utilizando na expressão lógica. Um exemplo de uma possível declaração deste comando (você pode declarar de uma outra forma, desde que não permita loops infinitos): enquanto a < 10 faca imprima (a); incremento_decremento a := a + 1; Para passar como parâmentro o nome do arquivo a ser analisado pelo yylex() e pelo yyparse() basta utilizar a variável yyin (No manual tem exemplos de utilização). Você receberá 50% da nota do seu trabalho implemetando apenas um interpretador de um único programa. Mas terá que me apresentar (imprimir) a árvore de execução do programa a ser interpretado e sua tabela de variáveis a cada comando executado. Uma boa explicação deve ser apresentada no arquivo leiame.txt de como utilizar o programa, quais funcionalidades foram implementadas e como é feito para que eu possa utilizar tais funcionalidade. Sorte!