Em analise_lexica.l
é feita a inserção de tokens lidos do arquivo nas tabelas de símbolos e de palavras reservadas.
Expressões Regulares são criadas para reconhecer padrões ou a construção de padrões de tokens, como STRING
ou INT
.
LETRA [a-zA-Z]
DIGITO [0-9]
IDENTIFICADOR (_|{LETRA})(_|{LETRA}|{DIGITO})*
-
Palavras Reservadas de condição, repetição, etc.
IF
ELSE
WHILE
FOR
DO
RETURN
STRUCT
ENUM
DEFINE
INCLUDE
-
Palavras Reservadas para declaração de tipo.
INT
FLOAT
CHAR
VOID
STRING
-
Palavras Reservadas para operações.
SOMA
MULTIPLICAÇÃO
DIVISAO
SUBTRACAO
ATRIBUICAO
-
Símbolos.
IDENTIFICADOR
INTEIRO
REAL
CARACTER
CADEIA
- Erro 1 - reconhecimento de palavras reservadas mal formadas (
if3
,*while
) - Erro 2 - identificadores mal formados, não seguem o padrão solicitado (
90_aliana
) - Erro 3 - operadores mal formados, duplicados ou "compostos" (
++
,+/
)
Ao detectar um token, ele é inserido na tabela de símbolos ou de palavras reservadas, dependendo da sua natureza.
{IF} {inserirReservada(yytext, R);}
{IDENTIFICADOR} {inserirSimbolo(yytext, 0, T);}
Comentários e espaços em branco são ignorados.
No main
, as tabelas são inicializadas e após a varredura do arquivo, são printadas.
Em hash_table.c
e .h
estão as estruturas de dados e métodos para tratar as tabelas hashing.
Um símbolo é armazenado em um "nó", que contém seu identificador, seu valor em string e sua categoria.
typedef struct noS {
int id;
char *lexema;
Categoria categoria;
} Simbolo;
Este nó faz parte de um total da tabela hashing para símbolos.
typedef struct simbolos {
Simbolo **tabela;
} TabelaSimbolo;
Um símbolo pode ter as seguintes categorias:
IDENTIFICADOR
INTEIRO
REAL
CARACTER
STRING
Uma palavra reservada é armazenada em um "nó", que contém seu identificador e seu valor em string.
typedef struct noR {
int id;
char *lexema;
} Reservada;
Este nó faz parte de um total da tabela hashing para palavras reservadas.
typedef struct reservadas {
Reservada **tabela;
} TabelaReservada;
Na fase de Análise Sintática (analise_sintatica.y) do compilador, em geral, é feita a verificação da relação entre tokens por meio das regras de uma gramática.
Ao serem reconhecidas, um valor (nome) associado àquela estrutura é inserido na árvore de derivação, de baixo para cima.
‘@’ = vazio; significa que aquela estrutura não precisa existir obrigatóriamente.
Sentenças Include
#include <nome_arquivo>
#include “nome_arquivo”
@
- Nome Arquivo:
arquivo.extensao
Sentenças Define
-
Sentenças Define
#define identificador expressao @
- Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
-
- Expressão
Funções
-
Funções
tipo_dado identificador ( parametros ) { comandos }
Tipo Dado
int float char void string
Parâmetros
tipo_dado identificador tipo_dado identificador, ... @
Comandos
decl_var atribuicao sentenca_if sentenca_while sentenca_for sentenca_do sentenca_scan sentenca_print chamada_funcao sentenca_return sentenca_switch @
Declaração Variável
tipo_dado identificador tipo_dado atribuicao tipo_dado identificador, ... tipo_dado atribuicao, ...
Tipo Dado
int float char void string
Atribuição
identificador = expressao identificador = atribuicao
Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
Atribuição
identificador = expressao identificador = atribuicao
Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
Sentença If
if ( expressao ) { comandos } if ( expressao ) { comandos } else { comandos }
Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
Sentença While
while ( expressao ) { comandos }
Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
Sentença For
for ( parametros ) { comandos }
Parâmetros
-
Declaração Variável
tipo_dado identificador tipo_dado atribuicao tipo_dado identificador, ... tipo_dado atribuicao, ...
Tipo Dado
int float char void string
Atribuição
identificador = expressao identificador = atribuicao
Expressão
-
Operação
operando op operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
-
-
Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
-
Sentença Do
do { comandos } while ( expressao )
Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
Sentença Scan
scan ( parametros )
Parâmetros
identificador identificador,...
Sentença Print
print ( parametros )
Parâmetros
"string" identificador "string";... identificador;...
Chamada Função
identificador ( argumentos )
Argumentos
expressao expressao,... @
Expressão
-
Operação
operando op operando !operando
-
Op Aritmético
+ - * /
-
Op Relacional
== != > < >= <=
-
Op Lógico
&& || !
-
-
Operando
(operacao) literal
-
Literal
inteiro, real, caractere, cadeia, identificador
-
Sentença Return
return ( valor )
Valor
expressão
Sentença Switch
switch ( valor ) { case x: break }
Valor
expressão
-
Além de reconhecer os erros léxicos, na fase do parser, quando é encontrado um erro sintático, este é exibido na tela. Por exemplo:
- Falta de tipo de retorno em definição de funções
funcao(float a, float b)
- Falta de palavra-chave de fechamento
if (x > 5) {
print(x)
else {
print("Erro")
- Falta de parênteses em chamadas de função
print "Hello"
funcao a, b
É gerado um arquivo de nome “arvore_nomeArquivo.txt” na pasta “arvores_geradas” com a árvore de derivação por onde o parser passou depois que uma “cadeia” (ou código fonte) é aceita.
Caso ocorra um erro, o arquivo não é criado e o erro é exibido.
A estrutura de um nó é dado por um valor (seu nome representativo), seu número de filhos e um ponteiro para a sua lista de filhos:
typedef struct No {
char valor[MAX];
int num_filhos;
struct No** filhos;
} No;
No main, as tabelas de símbolos e de palavras reservadas continuam sendo impressas.
Para compilar, é recomendado estar na pasta de caminho:
cd C:\Users\...\Trabalho\src
Primeiro, é feita a compilação do parser em “analise_sintatica.y”.
Em seguida, do scanner em “analise_lexica.l”.
Por fim, os códigos em .c são compilados e o executável é gerado, em “.\gçç.exe”
yacc -d analise_sintatica.y
lex analise_lexica.l
gcc (Get-ChildItem -Recurse -Path "C:\Users\...\Trabalho" -Filter "*.c").FullName -o gçç
Para testar um arquivo texto contendo um código fonte, basta chamar o executável “.\gçç” seguido do nome do arquivo (redirecionar para a pasta onde ele se encontra, se necessário).
.\gçç ..\codigos_teste\teste_yacc.txt