Para ajudar-vos nesta tarefa, fornecemos a sua estrutura de base (na forma de um conjunto de ficheiros OCaml e um Makefile) que podem recuperar aqui : mini-python.zip. Uma vez descompactado, este arquivo produz uma pasta mini-python/ com os ficheiros seguintes :
| ast.mli | a sintaxe abstracta de mini-Python (completo) |
| lexer.mll | o analizador léxico (completo) |
| parser.mly | o analizador sintáctico (completo) |
| interp.ml | o interpretador (por completar) |
| main.ml | o programa principal (completo) |
| Makefile | para automatizar a compilação (completo) |
Como para o TD anterior, o código fornecido compila mas está incompleto. Podemos compilar com ajuda do utilitário (e comando) make (o executável resultante chama-se mini-python no directório corrente), ou então recorrer ao utilitário dune com o comando dune build @all (o executável neste caso fica no directório _build e pode-se criar um link simbólico com ln -s _build/default/main.exe mini-python ). O executável aplica-se sobre um ficheiro mini-Python com a extensão .py, assim :
./mini-python fich.pyUm ficheiro mini-Python tem a estrutura seguinte :
# zero, uma ou várias definições de funções no início do ficheiro
def fibaux(a, b, k):
if k == 0:
return a
else:
return fibaux(b, a+b, k-1)
def fib(n):
return fibaux(0, 1, n)
# uma ou várias instruções no fim do ficheiro
print "alguns valores da sequência de Fibonacci :"
for n in [0, 1, 11, 42]:
print fib(n)
Mais geralmente, um ficheiro mini-Python é composto
de uma lista opcional de declarações de funções, seguido de uma
lista de instruções. As instruções são : a atribuição, a
condicional, o ciclo (for), a visualização de uma
expressão com print, a devolução de um valor
com return ou a avaliação de uma expressão. As expressões
inteiras são : uma constante (booleana, inteira ou uma string), o
acesso a uma variável, a construção de uma lista (com a
notação [e1, e2, ..., en]), o acesso a um elemento de
lista (com a notação
e[i]), a chamada de uma função, ou uma das operações
+, -, * e //,
=, <>, <, <=, >,
>=, and, or e not.
Consideramos igualmente as três funções primitivas: list(range(n)) constrói a lista [0, 1, 2, ..., n-1] e len(l) devolve o comprimento da lista l. (Apenas usaremos list e range em conjunto desta forma.)
print 1 + 2*3 print (3*3 + 4*4) // 5 print 10-3-4o resultado deve ser
7 5 3As operações de divisão e de módulo devem assinalar um erro no caso de divisão por zero. Utilizarmos para esse efeito a função error fornecida no ficheiro interp.ml.
Para testar facilmente, podemos editar o ficheiro test.py e invocar o comando make. Este comando compila o interpretador mini-python e executa este último sobre o ficheiro test.py.
Complete em seguida a função expr para interpretar as constantes booleanas, as operações de comparação e as operações and, or e not. Em Python, a comparação é estrutural ; poderemos utilizar directamente a comparação estrutural de OCaml, isto é utilizar operações como < sobre valores de tipo value. (Tal procedimento não é 100% compatível com Python, mas remediaremos esta situação mais adiante.)
Completar finalmente a função stmt para poder interpretar a condicional (construção Sif).
Testar com o programa seguinte :
print not True and 1//0==0
print 1<2
if False or True:
print "ok"
else:
print "oops"
cujo resultado deve ser
False True ok
(string, value) Hashtbl.t
Completar a função expr para que se possa aceder às variáveis. É o caso do padrão (pattern matching) Eident id. Tentar aceder a uma variável que não se encontra ainda na tabela deve provocar um erro. Nesta mesma linha, completar a função stmt para que se possa atribuir uma variável. É o caso do padrão Sassign (id, e1). Desta vez a variável pode estar ou não na tabela. No caso de la estar, o seu valor é modificado.
Finalmente, completar a função expr para que se possa concatenar duas strings com a operação +.
Testar com o programa seguinte :
x = 41 x = x+1 print(x) b = True and False print(b) s = "hello" + " world!" print(s)o resultado deve ser
42 False hello world!
let functions = (Hashtbl.create 17 : (string, ident list * stmt) Hashtbl.t)A cada nome de função está associada um par constituído da lista dos parâmetro e da instrução que define o corpo da função. Completar a função file para que esta preencha esta tabela com as funções contidas na lista fl.
Completar na sequência as funções expr e stmt para interpretar uma chamada de função. Para uma chamada da forma f(e1,...,en) a uma função f da forma def f(x1,...,xn): body deve-se construir um novo ambiente que associe a cada argumento formal xi o valor de ei. Podemos então interpretar a instrução body (o corpo da função) neste novo ambiente. A instrução return sera interpretada utilizando a excepção OCaml Return (já definida).
Testar com o programa seguinte :
def fact(n):
if n <= 1: return 1
return n * fact(n-1)
print fact(10)
cujo resultado deve ser
3628800
Completar em sequência a função stmt para poder interpretar a atribuição de um elemento de lista (caso do padrão Sset (e1, e2, e3)).
Finalmente, completar a função stmt para poder interpretar a construção for. A construção Sfor(x,e,s) atribuí a variável x sucessivamente aos diferentes elementos da lista e e executa de cada vez a instrução s. A expressão e deve ser avaliada uma só vez.
Testar sobre o programa dado no início do enunciado. o resultado deve ser:
0 1 89 267914296
Escrever uma função compare_value: value -> value -> int para comparar dois valores Python. Esta função devolve um inteiro estritamente negativo (resp. nulo, resp. estritamente positivo) quando o primeiro valor é menor (resp. igual, resp. maior) que o segundo. Poder-se-á recorrer ao interpretador de Python como referência em caso de dúvidas. Utilizar esta função para rectificar o que foi feito na questão 2.
Fazer testes suplementares para constatar a correção da solução proposta.