O objetivo deste trabalho é desenvolver, em C, uma função que implementa
um pequeno gerador de código (um "micro-compilador") para uma
linguagem de programação bastante simples, chamada SB.
Essa função C (compila
) deverá ler um arquivo texto contendo o
código fonte de uma função escrita em SB e retornar um
ponteiro para uma função (criada pelo gerador) que contém o código
que executa a função SB.
Os trabalhos podem ser feitos em grupos de até dois alunos. Leia com atenção o enunciado do trabalho e as intruções para a entrega. Em caso de dúvidas, não invente. Pergunte!
A linguagem SB contém apenas três tipos de instruções: atribuição, desvio condicional e retorno.
var '=' varc op varconde
var
pode ser uma variável local ou um parâmetro
da função e varc
uma variável local, um parâmetro
ou uma constante inteira. op
é um dos operadores: + - *
As variáveis locais são da forma vi
, sendo o índice
i utilizado para identificar a variável
(ex. v0, v1
, etc...).
Da mesma forma, os parâmetros são da forma pi
,
sendo p0 o primeiro parâmetro, p1 o segundo, e assim sucessivamente.
A linguagem permite o uso de no máximo 10 variáveis locais e 10
parâmetros.
Na linguagem SB as constantes são escritas na forma
$i
.
Por exemplo, $10
representa o valor 10
e $-10
representa o valor -10.
'if' var n1 n2 n3onde
var
é uma variável local ou um parâmetro.
Essa instrução, baseada no antigo if aritmético de FORTRAN, tem a seguinte semântica:
var
é menor que 0, é realizado um desvio
para a instrução da linha de número n1
var
é igual a 0, é realizado um desvio
para a instrução da linha de número n2
var
é maior que 0, é realizado um desvio
para a instrução da linha de número n3
'ret' varcNeste caso, a função deverá retornar e o valor de retorno é o valor de
var
(uma variável local, um parâmetro ou uma
constante).
A sintaxe da linguagem SB pode ser definida formalmente como abaixo:
func | :: | cmd '\n' | cmd '\n' func |
cmd | :: | att | if | ret |
att | :: | var '=' varc op varc |
if | :: | 'if' var num num num |
ret | :: | 'ret' varc |
var | :: | 'v' digito | 'p' digito |
varc | :: | var | '$' snum |
op | :: | '+' | '-' | '*' |
num | :: | digito | num digito |
snum | :: | [-] num |
digito | :: | 0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'| '8' | '9' |
v0 = p1 + p2 //1: i = b + c v0 = p0 * v0 //2: i = a * i ret v0 //3: retorna i
O próximo exemplo implementa uma função retorna o maior de três parâmetros não negativos:
v0 = p0 - p1 //1: v0 é menor que 0 se p0 < p1 if v0 3 3 7 //2: trata os 3 casos (p0 < p1, p0 = p1, p0 > p1) v0 = p1 - p2 //3: v0 é menor que 0 se p1 < p2 if v0 5 5 6 //4: trata os 3 casos (p1 < p2, p1 = p2, p1 > p2) ret p2 //5: p2 é o maior parâmetro ret p1 //6: p1 é o maior parâmetro v0 = p0 - p2 //7: v0 é menor que 0 se p0 < p2 if v0 5 5 9 //8: trata os 3 casos (p0 < p2, p0 = p2, p0 > p2) ret p0 //9: p0 é o maior parâmetro
compila
que leia
um arquivo de entrada contendo o código fonte de uma função
na linguagem SB, gere o código de máquina IA32 correspondente, e
retorne um ponteiro para função (será um ponteiro para uma área
de memória contendo o código gerado).
O arquivo de entrada terá no máximo 100 linhas, com um comando SB por linha.
O protótipo de compila
é o seguinte:
typedef int (*funcp) (); funcp compila (FILE *f);O único parâmetro de
compila
é o descritor de um arquivo
texto já aberto para leitura, de onde deve ser lido o código fonte SB.
Você deve criar um arquivo contendo a função compila
e outro arquivo com uma função main
para
testá-la.
O arquivo texto contendo a função SB a ser compilada deve ter sempre o nome "SB".
Sua função main deverá abrir o arquivo contendo o código SB e chamar a função compila, passando o arquivo aberto como argumento. Em seguida, sua função main deverá chamar a função retornada por compila, passando os parâmetros apropriados, e imprimir o valor de retorno dessa função. Esse retorno é um valor inteiro, que pode ser exibido com código de formação ("%d\n"). A função criada por compila é a tradução da função SB lida do arquivo de entrada.
Para testar a chamada de uma função SB com diferentes parâmetros, sua
função main deve receber argumentos passados na linha de comando.
Para ter acesso a esses argumentos (representados por strings),
a sua função main
deve ser declarada como
int main(int argc, char *argv[])
sendo argc
o número de argumentos fornecidos na linha de comando
e argv
um array de ponteiros para strings
(os argumentos). Note que o primeiro argumento para main
(argv[0]) é sempre o nome do seu executável!.
Os parâmetros que deverão ser passados para a função
criada por compila
serão o argumento 1 em diante.
A função compila
deve alocar um bloco de memória para
escrever o código gerado.
O valor de retorno de compila
será um ponteiro para essa
área. (Para simplificar, você pode considerar o número máximo de linhas
de arquivo na hora de alocar o bloco de memória para código.)
O código gerado por compila
deverá ser um
código de máquina IA-32, e não um código fonte
assembly. Ou seja, você deverá descobrir o código de máquina que
corresponde às instruções de assembly que implementam cada uma das
instruções de nossa linguagem, por exemplo, usando o
programa objdump
.
Lembre-se que as instruções assembly ocupam um número variável de bytes na memória.
Não é necessário fazer o tratamento de erros do arquivo de entrada, você pode supor que o código nele estará sempre correto. Vale a pena colocar alguns testes (ver programa exemplo abaixo) só para facilitar a própria depuração do seu código, mas as entradas usadas como testes na correção do trabalho sempre estarão corretas.
Para ler e interpretar cada linha da linguagem SB, teste se a linha contém cada um dos formatos possíveis. Veja um esboço de código C para fazer a interpretação de código aqui. (Lembre-se que você terá que fazer adaptações pois, dentre outros detalhes, essa interpretação não deve ser feita na main!!)
IMPORTANTE: Esse programa não é trivial. Implemente sua solução passo a passo, testando separadamente cada passo implementado! Por exemplo:
minhamaquina> gcc -m32 -c code.s(para apenas compilar e não gerar o executável) e depois veja o código de máquina gerado usando:
minhamaquina> objdump -d code.oConstrua um programa inicial que aloque espaço e coloque esse código "colado" do compilador, bem conhecido, para testar um esqueleto inicial.
compila.c
, contendo a
função compila
. Esse arquivo não deve conter a
função main
.
/* Nome_do_Aluno1 Matricula Turma */ /* Nome_do_Aluno2 Matricula Turma */