---
# System prepended metadata

title: Compilador da linguagem c

---

# Compilador da linguagem c

Compilador desenvolvido utilizando a biblioteca ply que segue as fases de analise léxica, analise sintática e analise semântica.

## Instalação 
Para executar o projeto basta clonar o repositório e instalar a dependencia com o código abaixo:
```sh
pip3 install ply
```
Depois de terminar a instalação da biblioteca `ply` será necessário a execução do arquivo `main.py` para ver os tokens gerados e a tabela de símbolos. Esses tokens e a tabela são em relação ao código adicionado no arquivo `main.c`. 

## Descrições das etapas de desenvolvimento
Nessa seção será descrito brevemente como foi feito a implementação de cada etapa do compilador, sendo elas:
 - Análise léxica
 - Análise sintática
 - Análise semântica
### Análise léxica
A análise léxica, ou tokenização, de um compilador consiste na separação de cadeias de caracteres em tokens. Para isso o analisador léxico lê todas as linhas do código de entrada e separa em partes menores de acordo com expreções regulares defnidas. Nesse compilador, existem tokens de palavras reservadas que incluem as estruturas ```if, else, for, while```  e tipos de variáveis como ```int, float```, também existem tokens de símbolos como ```=, +, -, /, >```. Além dos items citados anteriormente, a análise léxica permite atribuir valores aos tokens a partir de funções como:
```python=
def t_ID(t):
    r'[a-zA-Z][a-zA-Z0-9]*'
    t.type = reserved.get(t.value, 'ID')  
    return t
```
Nesta implementação foram ignorados símbolos de `tab`, `space` e fim de linha:
```python=
t_ignore = ' \t\n'
```

### Análise sintática
A análise sintática é uma fase do processo de compilação onde é feita uma verificação da estrutura gramatical segundo uma determinada gramática formal.
Para essa análise, fizemos a declaração de funções de acordo com a biblioteca `lexer`, essas funções são responsáveis pelo que o compilador irá ler e verificar se o que foi lido está dentro do esperado para a linguagem desenvolvida. Como exemplo, podemos verificar a função que faz a verificação de um while: 
```python=
def p_while(p):
    '''while    :  WHILE expr_condition EOL
                |  WHILE expr_condition block'''
    if p[3] != ";":  # block
        p[0] = ("WHILE", p[2], p[3])
    else:  # EOL
        p[0] = ("WHILE", p[2])
```
Nessa função, podemos ver que o que está comentado e em letra minúscula é um símbolo não terminal que é o nome de uma outra função no código, o que faz com que o compilador vá para essa próxima função que, no exemplo acima, tem o nome de  `expr_condition`. Já as palavras em letra maiúscula são os símbolos terminais, que foram declarados como palavras reservadas na parte da análise léxica, ou seja, quando o compilador faz a leitura dessa cadeira de caracteres ele não passa para uma próxima função e encerra a leitura para aquele símbolo terminal.


### Análise semântica
Regras além das definidas pela análise sintática são responsabilidade da análise semântica. Utilizando a biblioteca `ply` é possível criar regras logo após a validação da análise sintática. Um exemplo é apresentado na função ```p_variable()```. 

Para garantir que as variáveis não declaradas sejam utilizadas no código, é validádo se esta existe na tabela de variáveis do compilador. Caso exista, é validado o seu contexto (`if` linha 10).

`stash` :
```python=
def p_variable(p):
    '''variable : ID'''
    '''
    se não tiver na tabela adiciona na stash
    pra verificar a definição "depois"
    '''
    if not p[1] in table['VARIABLE']:
        stash[p[1]] = {'context': context}
    else:
        if table['VARIABLE'][p[1]]['context'] < context:
            raise AssertionError(
                "On p_variable: Variable {} have context {}, but its context {}"
                        .format(p[1], table['VARIABLE'][p[1]]['context'], str(context)))
        # p[0] = table['VARIABLE'][p[1]]
    p[0] = p[1]
```

Caso a variável ainda não exista na tabela de variáveis, ainda é possível que ela seja declarada em um momento posterior da análise semântica (visto que a análise é feita resolvendo os tokens a direita primeiro), por isso é adiciona a uma estrura `stash` para ser posteriormente validado o uso.
```python=
...
if not p[1] in table['VARIABLE']:
        stash[p[1]] = {'context': context}
...
```
Em toda declaração de variável é checado se a variável declarada está na `stash`, de maneira que, caso uma variável seja utilizada e não declarada a `stash` manterá uma variável "solta".
```python=
def p_assign(p):
    '''assign   : ID EQUALS expr_binary EOL
                | type assign'''
...
    if p[2]["left_var"] in stash:
        if stash[p[2]["left_var"]]["context"] <= context:
            del stash[p[2]["left_var"]]
        else:
            raise AssertionError(
            "On p_assign definition: Stash variable {} have context {}, but its context {}"
                .format(p[2], stash[p[2]]['context'], str(context)))
...
```
No final da compilação é verificado se existem variáveis utilizadas e não declaradas.
```python=
if len(stash) > 0:
    for entry in stash:
        print("entry in stash: {}".format(str(entry)))
    raise AssertionError("Stash not empty, variable used and not declared")
```
Outra regra semântica definida pelo compilador é a verificação de tipagem. Caso exista uma expressão do tipo `int a = 3`, será validada a tipagem, comparando o tipo do token `3`, com o tipo armazenado na tabela de variáveis referente a variável `a`.
```python=
def p_assign(p):
    '''assign   : ID EQUALS expr_binary EOL
                | type assign'''
    if len(p) >= 5:
        '''
        Se a direita for um terminal tenta verificar o tipo da variavel a esquerda
        '''
        if "TYPE_" in p[3]:
            right_var = {"name": "TERMINAL", "type": p[3]}

            if p[1] in table['VARIABLE']:
                '''
                se for diferente do terminal sobe erro
                '''
                if table['VARIABLE'][p[1]]['type'] != p[3]:
                    raise AssertionError(
                        "Variable {} is {} and should be {}".
                            format(p[1], table['VARIABLE'][p[1]]['type'], p[3]))
```
Caso a variável ainda não existir na tabela de variáveis, o tipo da variável a direita é adicionado a uma estrutura de verificação (`verify`) que será utilizada na declaração da variável `a`.
```python=
'''
se a variável a esquerda não foi declarada adiciona o tipo do terminal
pra ser checado na declaração da variável a esquerda
'''
if not p[1] in verify:
    verify[p[1]] = []
verify[p[1]].append({"name": "TERMINAL", "type": p[3]})
```

Em toda declaração de variável é verificado se ela foi utilizada a frente no código. Caso tenha sido e com o tipo errado, o compilador dectada o erro e para a compilação.
```python=
...
for key, val in verify.items():
    if key == p[2]["left_var"]:
        for i in range(len(val)):
            if val[i]["type"] != "TYPE_" + str(p[1]).upper():
                raise AssertionError(
                "Variable {} is {} and should be {}".
                    format(key, val[i]["type"], "TYPE_" + str(p[1]).upper(), p[2]))
...
```