You are viewing an old revision of this post, from August 7, 2018 @ 08:14:45. See below for differences between this version and the current revision.
Estou amando ler o livro Crafting Interpreters. Nele, Bob Nystrom nos ensina como escrever um interpretador implementando uma pequena linguagem de programação chamada Lox. Há muito tempo não me divertia tanto programando! Além de bem escrito, o livro é engraçado e ensina bem mais coisas do que eu esperava. Mas estou tendo um problema.
Os snipptes no livro são escritos para copiar e colar. Contudo, o livro tem desafios ao final de cada capítulo, estes desafios não têm código-fonte e por vezes nos forçam a alterar muito o interpretador. Eu faço todos estes exercícios, e como resultado meu interpretador é diferente demais do código mostrado no livro. Como consequência, várias vezes quebro alguma parte do interpretador.
Como resolver isso?
Testes unitários seriam frágeis, já que a estrutura do código muda frequentemente. Testes de fim a fim parecem mais práticos nesse caso. Assim, para cada nova funcionalidade da linguagem, escrevi um programinha. Por exemplo, meu interpretador deve criar clausuras, e para garantir isso copiei o programa em Lox abaixo para o arquivo counter.lox
:
fun makeCounter() {
var i = 0;
fun count() {
i = i + 1;
print i;
}
return count;
}
var counter = makeCounter();
counter(); // "1".
counter(); // "2".
O resultado deste programa devem ser os números 1 e 2 impressos em linhas separadas. Então coloquei esses valores em um arquivo chamado counter.lox.out
. O programa também não pode falhar, então criei um arquivo vazio chamado counter.lox.err
. (Em alguns casos é preciso garantir que o programa Lox falhará. Nesses casos, o arquivo .lox.err
deve ter conteúdo.)
Pois bem, escrevi programas e arquivos de saída para vários exemplos. Agora preciso comparar os resultados dos programas com as saídas esperadas. Resolvi então usar a ferramenta que mais me ajuda nos momentos de urgência: shell script. Fiz um script Bash com um laço for
iterando sobre todos os exemplos:
for l in examples/*.lox
do
done
Para cada exemplo, executei o programa Lox, redirecionando as saídas para arquivos temporários:
for l in examples/*.lox
do
out=$(mktemp)
err=$(mktemp)
java -classpath target/classes/ br.com.brandizzi.adam.myjlox.Lox $l > $out 2> $err
done
Agora, comparamos a saída real com a saída esperada com diff
. Quando compara dois arquivos, diff
returna 0 se não há diferença, 1 se existe alguma diferença, ou 2 em caso de erro. Como em Bash o condicional if
considera 0 como verdadeiro, basta checar a negação do retorno de diff
.
Se o programa imprimir algo na saída-padrão diferente do que está no arquivo .lox.out
, temos uma falha:
for l in examples/*.lox
do
out=$(mktemp)
err=$(mktemp)
java -classpath target/classes/ br.com.brandizzi.adam.myjlox.Lox $l > $out 2> $err
if ! diff $l.out $out
then
FAIL=1
fi
done
Também comparamos a saída de erro com o arquivo .lox.err
:
for l in examples/*.lox
do
out=$(mktemp)
err=$(mktemp)
java -classpath target/classes/ br.com.brandizzi.adam.myjlox.Lox $l > $out 2> $err
if ! diff $l.out $out
then
FAIL=1
fi
if ! diff $l.err $err
then
FAIL=1
fi
done
Por fim, verifico se houve alguma falha e reporto o resultado:
for l in examples/*.lox
do
out=$(mktemp)
err=$(mktemp)
java -classpath target/classes/ br.com.brandizzi.adam.myjlox.Lox $l > $out 2> $err
if ! diff $l.out $out
then
FAIL=1
fi
if ! diff $l.err $err
then
FAIL=1
fi
if [ "$FAIL" = "1" ]
then
echo "FAIL" $l
else
echo "PASS" $l
fi
done
Nem todos os meus programas em Lox podem ser checados, porém. Por exemplo, há um programa que cronometra a execução de loops, é impossível prever o valor que ele vai imprimir. Por isso, adicionei a possibilidade de pular alguns programas: basta criar um arquivo com a extensão .lox.skip
:
for l in examples/*.lox
do
if [ -e $l.skip ]
then
echo SKIP $l
continue
fi
out=$(mktemp)
err=$(mktemp)
java -classpath target/classes/ br.com.brandizzi.adam.myjlox.Lox $l > $out 2> $err
if ! diff $l.out $out
then
FAIL=1
fi
if ! diff $l.err $err
then
FAIL=1
fi
if [ "$FAIL" = "1" ]
then
echo "FAIL" $l
else
echo "PASS" $l
fi
done
Se, porém, tenho um exemplo em Lox e ele não possui os arquivos de saída esperada (nem o arquivo .lox.skip
) então tenho um problema e o script inteiro falha:
for l in examples/*.lox
do
if [ -e $l.skip ]
then
echo SKIP $l
continue
elif [ ! -e $l.out ] || [ ! -e $l.err ]
then
echo missing $l.out or $l.err
exit 1
fi
out=$(mktemp)
err=$(mktemp)
java -classpath target/classes/ br.com.brandizzi.adam.myjlox.Lox $l > $out 2> $err
if ! diff $l.out $out
then
FAIL=1
fi
if ! diff $l.err $err
then
FAIL=1
fi
if [ "$FAIL" = "1" ]
then
echo "FAIL" $l
else
echo "PASS" $l
fi
done
Com isto, meu script testador está pronto. Vejamos como se comporta:
$ ./lcheck.sh
PASS examples/attr.lox
PASS examples/bacon.lox
PASS examples/badfun.lox
PASS examples/badret.lox
PASS examples/bagel.lox
PASS examples/bostoncream.lox
PASS examples/cake.lox
PASS examples/checkuse.lox
PASS examples/circle2.lox
PASS examples/circle.lox
1d0
< 3
1c1
<
---
> [line 1] Error at ',': Expect ')' after expression.
FAIL examples/comma.lox
PASS examples/counter.lox
PASS examples/devonshinecream.lox
PASS examples/eclair.lox
PASS examples/fibonacci2.lox
PASS examples/fibonacci.lox
PASS examples/func.lox
PASS examples/funexprstmt.lox
PASS examples/hello2.lox
PASS examples/hello3.lox
PASS examples/hello.lox
PASS examples/math.lox
PASS examples/notaclass.lox
PASS examples/noteveninaclass.lox
PASS examples/point.lox
PASS examples/retthis.lox
PASS examples/scope1.lox
PASS examples/scope.lox
PASS examples/supersuper.lox
PASS examples/thisout.lox
PASS examples/thrice.lox
SKIP examples/timeit.lox
PASS examples/twovars.lox
PASS examples/usethis.lox
PASS examples/varparam.lox
Opa, aparentemente removi o suporte ao operador vírgula por acidente. Ainda bem que fiz este script, não é?
Espero que este post tenha sido minimamente interessante! Agora, vou consertar meu operador vírgula e seguir lendo este livro maravilhoso.
Post Revisions:
- August 7, 2018 @ 08:14:45 [Current Revision] by brandizzi
- August 7, 2018 @ 08:14:45 by brandizzi
Changes:
There are no differences between the August 7, 2018 @ 08:14:45 revision and the current revision. (Maybe only post meta information was changed.)